SQL How to join rows as column headers in a Pivot? - sql-server

I have the following 2 tables:
TagNames:
TagName
TagIndex
Name1
0
Name2
1
Name3
2
TagValues:
DateAndTime
TagIndex
Val
2023-02-08 09:31:51.000
0
0
2023-02-08 09:31:51.000
1
10
2023-02-08 09:31:51.000
2
20
2023-02-08 09:32:01.000
0
1
2023-02-08 09:32:01.000
1
11
2023-02-08 09:32:01.000
2
21
Using this query I managed to fetch the rows as cols
WITH Tags AS (
SELECT
T.[TagIndex],
T.[DateAndTime],
T.[Val]
FROM
[dbo].[TagValues] T
INNER JOIN [dbo].[TagNames] N
ON T.TagIndex = N.TagIndex
)
SELECT *
FROM
Tags
PIVOT (MAX([Val]) FOR [TagIndex] IN ([0], [1], [2])) P
ORDER BY DateAndTime
;
Obtaining as result something like this:
DateAndTime
0
1
2
2023-02-08 09:31:51.000
0
10
20
2023-02-08 09:32:01.000
1
11
21
What I want to do is substitute the column headers with TagName in the first table

You can do this with dynamic SQL, as I describe in this article:
DECLARE #TagNameCols nvarchar(max), #sql nvarchar(max);
SELECT #TagNameCols = STRING_AGG(QUOTENAME(tn.TagName),',')
FROM dbo.TagNames AS tn
WHERE TagIndex IN (SELECT TagIndex FROM dbo.TagValues);
SELECT #sql = N'WITH Tags AS (
SELECT
N.[TagName],
T.[DateAndTime],
T.[Val]
FROM
[dbo].[TagValues] T
INNER JOIN [dbo].[TagNames] N
ON T.TagIndex = N.TagIndex
)
SELECT *
FROM
Tags
PIVOT (MAX([Val]) FOR [TagName] IN (' + #TagNameCols + N')) P
ORDER BY DateAndTime
;';
EXEC sys.sp_executesql #sql;
Working db<>fiddle example

Related

How to get count based on Id

In my stored procedure, I have a tempory table #Branches. I am inserting values into #Branches and table structure is as follows:
declare #Branches table( BranchId int, BranchName varchar(60))
BranchID BranchName IsActive
--------------------------------
16 New Delhi 1
17 Panjab 0
In my database have a table called lobby and its data as follows,
QueueID FkBranch IsActive Status AddedLocalTime FkAssistTypeID
553279 16 1 5 7/12/2019 2
553278 16 1 5 7/12/2019 1
553277 16 1 5 7/12/2019 1
553276 16 1 5 7/12/2019 1
553275 16 1 5 7/12/2019 2
553274 16 1 5 7/9/2019 2
I need to get count of the FkAssistTypeID based on its value, I retied this Script
declare #BranchDetail table (Id int, Name varchar(60), TotalInteraction float, AssistCount float)
insert into #BranchDetail
select b.BranchId as Id, b.BranchName as Name,
count(lo.LobbyId) TotalInteraction,
count(case WHEN lo.FkAssistTypeID = 1 then 1 end) as AssistCount
from
#Branches b
left outer join
(select br.BranchId, l.LobbyId, l.FkAssistTypeID
from lobby l
left outer join #Branches br on l.FkBranchId = br.BranchId
where l.AddedLocalTime >= #startDate
and l.AddedLocalTime <= CONVERT(VARCHAR, #endDate, 101) + ' 23:59:59'
and l.IsActive = 1
group by br.BranchId, l.LobbyId) lo on lo.BranchId = b.BranchId
group by
b.BranchId, b.BranchName
order by
b.BranchName
select #AvgInteractions= COALESCE( Convert(decimal(18,2), AVG(TotalInteraction)),0) from #BranchDetail
update #BranchDetail
SET AverageInteractions =#AvgInteractions from #BranchDetail
select * from #BranchDetail
I'm getting this error
Msg 8120, Level 16, State 1, Procedure spGetActComDetails, Line 228 [Batch Start Line 7]
Column 'lobby.FkAssistTypeId' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
But If I remove FkAssistTypeID everywhere its used in the above query, query works fine and retreive this output.
But I need this
How to solve this?
Try:
insert into #BranchDetail
select b.BranchId as Id,b.BranchName as Name,count(lo.LobbyId) TotalInteraction,
SUM(case WHEN lo.FkAssistTypeID = 1 THEN 1 ELSE 0 END) as AssistCount
from #Branches b
left outer join
(
select br.BranchId,l.LobbyId,l.FkAssistTypeID
from lobby l
left outer join #Branches br on l.FkBranchId=br.BranchId
where l.AddedLocalTime >=#startDate and ( l.AddedLocalTime ) <= CONVERT(VARCHAR, #endDate, 101)+ ' 23:59:59'and l.IsActive=1
group by br.BranchId,l.LobbyId,l.FkAssistTypeID) lo on lo.BranchId=b.BranchId
group by b.BranchId,b.BranchName
order by b.BranchName
You need to use SUM instead COUNT, because COUNT:
COUNT(*) returns the number of items in a group. This includes NULL
values and duplicates.

How to keep column order same in dynamic pivot

I have below mentioned table :
drn RecNum Name Value
----------------------------------------------
1 1 ad1_pk 1
2 1 ad1_address1 P.O. Box 5036
3 1 ad1_address2 NULL
4 1 ad1_address3 NULL
5 1 ad1_ctyfk 56
6 1 ad1_postalcode 80155-5036
7 1 ad1_active Y
8 1 ad1_irstat A
9 1 ad1_irdata NULL
10 1 ad1_at1fk 1
1 2 ad1_pk 2
2 2 ad1_address1 1871 S. Broadway
3 2 ad1_address2 NULL
4 2 ad1_address3 NULL
5 2 ad1_ctyfk 1
6 2 ad1_postalcode 80210
7 2 ad1_active Y
8 2 ad1_irstat A
9 2 ad1_irdata NULL
10 2 ad1_at1fk 1
I am creating the pivot using the below mentioned query:
declare #var nvarchar(max)
declare #sql nvarchar(max)
set #var = stuff((select distinct ',' + name from temp
for xml path('')),1,1,'') -- **this is giving distinct column list but the order of columns get changed..**
set #sql = 'select * from temp
pivot(max(value) for name in (' + #var + ')) as pvt'
exec sp_executesql #sql
Is there a way to keep the order of the columns unchanged? I want the order of columns listed in #var to be same as in the table.
Add a GROUP BY and an ORDER BY clause (to replace the DISTINCT) where you build your column list as follows:
set #var = stuff((select ',' + min(name) from temp GROUP BY drn ORDER BY drn
for xml path('')),1,1,'')
And don't forget the the necessary aggregation (I've used MIN()). Thanks #Ionic.
This is because you're using a DISTINCT in your SELECT query. If you look at the execution plan, you can see DISTINCT SORT operation. This sorts your result based on the DISTINCT columns you specify, in this case it's Name:
To retain the order, you can try this:
set #var = stuff((
select ',' + name
from(
select
name,
drn,
rn = row_number() over(partition by name order by drn)
from temp
)t
where rn = 1
order by drn
for xml path('')),
1,1,'')

T-SQL - Incremental update using subquery

I'm trying to write a incremental update statement using SQL Server 2012.
Current Data:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx NULL
3 16 xxx NULL
12 19 yyy 3
13 19 yyy NULL
14 19 yyy NULL
15 19 yyy NULL
Expected result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 0
12 19 yyy 3
13 19 yyy 2
14 19 yyy 1
15 19 yyy 0
However with following approach, I ended up with the result set as below.
UPDATE a
SET a.Revision = (SELECT MIN(b.Revision)
FROM [dbo].[foo] b
WHERE b.item_code = a.item_code
AND b.budget_id = a.budget_id
GROUP BY b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
Result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 1
12 19 yyy 3
13 19 yyy 2
14 19 yyy 2
15 19 yyy 2
Can anyone help me to get this right?
Thanks in advance!
Try this:
;with cte as
(select *, row_number() over (partition by budget_id order by rec_no desc) rn from dbo.foo)
update cte
set revision = rn - 1
Basically, since the revision value seems to be decreasing with increase in rec_no, we simply use the row_number() function to get row number of each record within the subset of all records with a particular budget_id, sorted in descending order of rec_no. Since the least possible value of row_number() will be 1, we subtract 1 so that the last record in the partition will have revision set to 0 instead 1.
You may test the code here
I found this example from this link https://stackoverflow.com/a/13629639/1692632
First you select MIN value to some variable and then you can update table by decreasing variable at same time.
DECLARE #table TABLE (ID INT, SomeData VARCHAR(10))
INSERT INTO #table (SomeData, ID) SELECT 'abc', 6 ;
INSERT INTO #table (SomeData) SELECT 'def' ;
INSERT INTO #table (SomeData) SELECT 'ghi' ;
INSERT INTO #table (SomeData) SELECT 'jkl' ;
INSERT INTO #table (SomeData) SELECT 'mno' ;
INSERT INTO #table (SomeData) SELECT 'prs' ;
DECLARE #i INT = (SELECT ISNULL(MIN(ID),0) FROM #table)
UPDATE #table
SET ID = #i, #i = #i - 1
WHERE ID IS NULL
SELECT *
FROM #table
I'm not sure if this will do the trick but you can try with
Update top(1) a
SET a.Revision = (Select MIN(b.Revision)
FROM [dbo].[foo] b where b.item_code = a.item_code and b.budget_id = a.budget_id
group by b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
and repeat until there's no changes left
Update Data
set Revision = x.Revision
from
(select RecNo, Budget_ID, Item_Code, case when Revision is null then ROW_NUMBER() over(partition by Budget_ID order by RecNo desc) - 1 else Revision end Revision
from Data
) x
where x.RecNo = data.RecNo
You basically use ROW_NUMBER() to count backwards for each Budget_ID, and use that row number minus 1 where Revision is null. This is basically the same as Shree's answer, just without the CTE.

Using PIVOT over a group by query

I've been trying to create a pivot for following query :
select mainstate, customertypeid, count(1) as [counter] from customers group by customertypeid, mainstate
This query should display as many customers types per state, it looks like this (order by doesn't matter) :
State|customertypeid|counter
UT 3 200
CA 3 500
NY 3 300
UT 2 100
CA 2 200
NY 2 120
UT 1 20
CA 1 50
NY 1 30
I've tried to use PIVOT as follow (I'm sure I'm wrong) :
SELECT *
FROM ( select mainstate, customertypeid, count(1) as [counter] from customers where customertypeid in (1,2,3) and mainstate != '' group by customertypeid, mainstate) as NonPivotedDataForReport2
PIVOT
(
COUNT([counter])
FOR mainstate IN ([# of Amb],[# Whole Sale Customers],[# Retail Customers])
) AS PivotedDataForReport2
I'm getting this :
customertypeid|type1|type2|type3
1 0 0 0
2 0 0 0
3 0 0 0
and the report should look like this :
State|type1|type2|type3
UT 20 100 200
CA 50 200 500
NY 30 120 300
*Ps : I don't really want to go back to CASE + SUM Statement,
Thanks a lot!
This will do:
SELECT mainstate [State],
[1] type1,
[2] type2,
[3] type3
FROM ( SELECT mainstate, customertypeid, COUNT(1) [counter]
FROM customers
WHERE customertypeid in (1,2,3)
AND mainstate != ''
GROUP BY customertypeid, mainstate) as NonPivotedDataForReport2
PIVOT(SUM([counter]) FOR customertypeid IN ([1],[2],[3])) AS PivotedDataReport2
This (perhaps slightly edited) should do the job for you without case/sum/pivot. Create a temp table, insert starting data and then dynamically add columns depending on how many customer type ids there is.
declare #s varchar(10), #xx1 varchar(500)
select distinct state into #temp from customers
DECLARE myCursor CURSOR FOR SELECT distinct customertypeid from customers
open MyCursor
FETCH NEXT FROM myCursor into #S
WHILE ##FETCH_STATUS = 0
begin
set #xx1 = 'alter table #temp add ['+#s+'] varchar(5)'
execute sp_executesql #xx1
set #xx1 = 'update a set a.['+#s+'] = coalesce(b.counter,0) from #temp a, customers b where b.customertypeid = '+#s+' and a.state = b.state'
execute sp_executesql #xx1
FETCH NEXT FROM myCursor into #S
End
Close myCursor
DEALLOCATE myCursor
select * from #temp

SQL Server Pivots: Displaying row values to column headers

I have a table (items) which is in the following format:
ITEMNO | WEEKNO | VALUE
A1234 | 1 | 805
A2345 | 2 | 14.50
A3547 | 2 | 1396.70
A2208 | 1 | 17.65
A4326 | 6 | 19.99
It's a table which shows the value of sales for items in a given week.
The results or what I want to display in a table format is the item number in a row followed by columns for each week containing the values, e.g.
ITEMNO | WK1 | WK2 | WK3 | WK4 | WK5 ...etc up to 52
A1234 | 805 | 345 | 234 | 12 | 10 ...etc up to 52
A2345 | 23 | 12 | 456 | 34 | 99 ...etc up to 52
A3456 | 234 | 123 | 34 | 25 | 190 ...etc up to 52
Although I've 52...so I've only data for up to week9 but that will increase with time.
So basically what it is I'm looking to display is the week number value as a column header.
Is this possible...although I'm tempted to just grab the data and display properly through code/(asp.net) but I was wondering if there was away to display it like this in SQL?
Does anyone know or think that that this might be the best way?
There are two ways of doing this with static SQL and dynamic SQL:
Static Pivot:
SELECT P.ItemNo, IsNull(P.[1], 0) as Wk1, IsNull(P.[2], 0) as Wk2
, IsNull(P.[3], 0) as Wk3, IsNull(P.[4], 0) as Wk4
, IsNull(P.[5], 0) as Wk5, IsNull(P.[6], 0) as Wk6
, IsNull(P.[7], 0) as Wk7, IsNull(P.[8], 0) as Wk8
, IsNull(P.[9], 0) as Wk9
FROM
(
SELECT ItemNo, WeekNo, [Value]
FROM dbo.Items
) I
PIVOT
(
SUM([Value])
FOR WeekNo IN ([1], [2], [3], [4], [5], [6], [7], [8], [9])
) as P
Dynamic Pivot:
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX);
-- Construct the column list for the IN clause
SET #cols = STUFF(
(SELECT N',' + QUOTENAME(w) AS [text()]
FROM (SELECT DISTINCT WeekNo AS W FROM dbo.Items) AS W
ORDER BY W
FOR XML PATH('')),
1, 1, N'');
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT ItemNo, WeekNo, Value
FROM dbo.Items) AS I
PIVOT(SUM(Value) FOR WeekNo IN(' + #cols + N')) AS P;';
EXEC sp_executesql #sql;
GO
Maybe something like this:
Test data
CREATE TABLE #tbl
(
ITEMNO VARCHAR(100),
WEEKNO INT,
VALUE FLOAT
)
INSERT INTO #tbl
VALUES
('A1234',1,805),
('A2345',2,14.50),
('A3547',2,1396.70),
('A2208',1,17.65),
('A4326',6,19.99)
Week columns
DECLARE #cols VARCHAR(MAX)
;WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 52 )
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(n AS VARCHAR(2))),
QUOTENAME('WK'+CAST(n AS VARCHAR(2))))
FROM
Nbrs
Just the included weeks
DECLARE #cols VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY WEEKNO ORDER BY WEEKNO) AS RowNbr,
WEEKNO
FROM
#tbl
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))),
QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))))
FROM
CTE
WHERE
CTE.RowNbr=1
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
tbl.ITEMNO,
''WK''+CAST(tbl.WEEKNO AS VARCHAR(2)) AS WEEKNO,
tbl.VALUE
FROM
#tbl as tbl
) AS p
PIVOT
(
SUM(VALUE)
FOR WEEKNO IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
Drop the temp table
DROP TABLE #tbl
Use Pivot, although quite a bit of code..
If you create report in reporting services, can use matrix..
Follow the below walkthrogh which explains it clearly
http://www.tsqltutorials.com/pivot.php
You can use PIVOT if you want to do this in sql directly.
It can be more efficient to use the SQL Server to do this as opposed to the client depending upon the size of the data and the aggregation.

Resources