Concatenate a pivot table output - sql

I have the following code that returns a pivoted table of current jobs and their estiamted times:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM JobPhases
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT *
FROM
(
SELECT c.Registration as ''Reg.'', p.Name, [x] = j.EstimatedTime
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
WHERE job.Status = 1 or job.Status = 0
) JobDetails
PIVOT
( SUM(x)
FOR Name IN (' + #cols + ')
) pvt'
execute(#query);
Output:
JobID | Repair & Reshape | Refit Stripped Parts | Polishing
1000 | 2.00 | 1.00 | 1.30
1001 | 2.30 | 0.30 | 2.00
What I need is to concatenate j.ActualTime in the displayed value. Any ideas how I can do this? So, the final output would be - 4.00 / 5.30 (where 4.00 is j.EstimatedTime and 5.30 is j.ActualTime).
Cheers

I would do all of your processing first, outside of the PIVOT. This way you can do aggregations, concatenations, and whatever you want. After all the processing, then do a simple, non-aggregating pivot:
SELECT *
FROM
(
SELECT c.Registration as ''Reg.'', p.Name,
CAST(SUM(j.EstimatedTime) as VARCHAR(MAX)) + '/' +
CAST(SUM(j.ActualTime) as VARCHAR(MAX)) as [x]
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
WHERE job.Status = 1 or job.Status = 0
GROUP BY c.Registration, p.Name
) JobDetails
PIVOT
( MAX(x)
FOR Name IN (' + #cols + ')
) pvt

Related

Dynamic Pivot Sql Query display all from one table

TABLE-A:-
Custno
Name
Route
Phone
1
C1
1
12345
2
C2
1
23456
3
C3
2
34567
4
C4
1
45678
5
C5
1
56789
TABLE-B:-
ODate
Custno
Route
ProductId
qty
2021-04-22
1
1
1
100
2021-04-22
1
1
3
200
2021-04-22
2
1
1
120
Table-C
ProductId
BrandName
1
Brand-1
2
Brand-2
3
Brand-3
EXPECTED RESULT
Phone
CustNo
Name
Brand-1
Brand-2
Brand-3
12345
1
C1
100
200
23456
2
C2
120
45678
4
C4
56789
5
C5
What I tried Using Dynamic Pivot
DECLARE #query AS VARCHAR(MAX)
, #cols_ AS vARCHAR(MAX)
--Making the column list dynamically
select #cols_ = STUFF((SELECT ',' + QUOTENAME(brandname) from [Table-C] order by productid FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
print #cols_
--preparing PIVOT query dynamically.
SET #query = ' SELECT
pivoted.*
into #Temp_data
FROM
(
select a.phone,a.custno,a.[name],d.BrandName,c.qty from [Table-A] a inner join [Table-B] c on a.custno = c.custno inner join [Table-C] d on c.productid = d.Productid and a.Route='1' and c.odate='2021-04-22'
) AS [p]
PIVOT
(
MIN([P].[qty])
FOR [P].[BrandName] IN (' + #cols_ + ')
) AS pivoted
order by custno;
select *
from #Temp_data [B]
-- GROUP BY [B].[ODate]
drop table #Temp_data
';
EXEC (#query)
You can reconstruct the query
SELECT *
FROM
(
SELECT A.[Phone], A.[CustNo], A.[Name], C.[BrandName], B.[qty]
FROM [Table-A] AS A
LEFT JOIN [Table-B] AS B
ON A.[CustNo] = B.[CustNo]
AND B.[odate] = '2021-04-22'
LEFT JOIN [Table-C] AS C on C.productid = B.Productid
WHERE A.[Route] = 1
) t
PIVOT
(
MIN([qty]) FOR [BrandName] IN ([Brand-1],[Brand-2],[Brand-3])
) AS piv
which contains LEFT JOIN rather than INNER JOIN, and STRING_AGG() function in order to generate the pivoted columns dynamically as in the following code block
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols = ( SELECT STRING_AGG(QUOTENAME([BrandName]),',')
FROM (SELECT DISTINCT [BrandName]
FROM [Table-C] ) C );
SET #query =
N'SELECT *
FROM
(
SELECT A.[Phone], A.[CustNo], A.[Name], C.[BrandName], B.[qty]
FROM [Table-A] AS A
LEFT JOIN [Table-B] AS B
ON A.[CustNo] = B.[CustNo]
AND B.[odate] = ''2021-04-22''
LEFT JOIN [Table-C] AS C on C.productid = B.Productid
WHERE A.[Route] = 1
) t
PIVOT
(
MIN([qty]) FOR [BrandName] IN (' + #cols + N')
) AS piv'
EXEC sp_executesql #query;
Demo

SQL How to pivot two columns of data into different columns?

This is the table I have:
| Scheme Code | MonthYear | Revenue | Revenue2 |
|-------------|-----------|---------|----------|
| 18VDA | 2018.1 | 100 | 50 |
| 18VDA | 2018.2 | 200 | 100 |
| 18VDA | 2018.3 | 200 | 150 |
and I want to pivot it to like this:
| Scheme Code | 2018.1 A | 2018.2 A | 2018.3 A | 2018.1 B | 2018.2 B | 2018.3 B |
|-------------|----------|----------|----------|----------|----------|----------|
| 18VDA | 100 | 200 | 200 | 50 | 100 | 150 |
How do I do it so that it pivots in MonthYear, but it duplicates it for both Revenue and Revenue2?
Thanks
EDIT: Messed up the output table I was hoping for! I've edited the actual output table I want to see!
EDIT 2:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME([MonthYear])
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT *
FROM ( SELECT [Scheme Code], MonthYear ,[Revenue]
FROM TableA
) a
PIVOT(sum(Revenue) for MonthYear in (' + #cols + ')
) as RevenueMonth
ORDER BY [Scheme Code]'
execute(#query);
This code I wrote will do it for just one column, and I get the output like this:
| Scheme Code | 2018.1 | 2018.2 | 2018.3 |
|-------------|--------|--------|--------|
| 18VDA | 100 | 200 | 200 |
My suggestion always is to try to write your query as a hard-coded or static version first before diving into dynamic SQL. This let's you get the final result you want with a smaller subset of data and you can verify that you have the logic correct.
I would tackle this by performing an UNPIVOT of the two Revenue columns first, then look at applying the PIVOT function. To UNPIVOT you can use either the UNPIVOT function or you can use CROSS APPLY with a UNION ALL to convert your two Revenue columns into a single column. A static version of the query would be similar to this:
select *
from
(
select
t.[Scheme Code],
new_colname = concat(t.[MonthYear], ' ', r.colname),
r.colvalue
from yourtable t
cross apply
(
select 'A', Revenue union all
select 'B', Revenue2
) r (colname, colvalue)
) d
pivot
(
sum(colvalue)
for new_colname in ([2018.1 A], [2018.2 A], [2018.3 A], [2018.1 B], [2018.2 B], [2018.3 B])
) p;
You'll notice that in the CROSS APPLY I added a column with the A or B that I use to identify either the Revenue or Revenue2 columns. This is then used to create the new column names for the PIVOT.
This should generate the result you want. Now to do this dynamically, you just need to convert the SQL to dynamic code. You can use the following to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME(concat([MonthYear], x.col))
from yourtable
cross join (select col = ' A' union all select ' B') x
group by [MonthYear], x.col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT *
FROM
(
select
t.[Scheme Code],
new_colname = concat(t.[MonthYear], '' '', r.colname),
r.colvalue
from yourtable t
cross apply
(
select ''A'', Revenue union all
select ''B'', Revenue2
) r (colname, colvalue)
) a
PIVOT
(
sum(colvalue) for new_colname in (' + #cols + ')
) as x
ORDER BY [Scheme Code]';
exec sp_executesql #query;
Both of these should generate the same results (dbfiddle demo)
Do it with CASE and dynamic sql.
DECLARE #colsA AS NVARCHAR(MAX),
#colsB AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsA = (SELECT ', sum(case [MonthYear] when ''' + [MonthYear] + ''' then Revenue end)' + QUOTENAME([MonthYear] + ' A')
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),
#colsB = (SELECT ', sum(case [MonthYear] when ''' + [MonthYear] + ''' then Revenue2 end)' + QUOTENAME([MonthYear] + ' B')
from tableA
group by [MonthYear]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)');
Set #query = 'select [Scheme Code]' + #colsA + #colsB + ' from TableA group by [Scheme Code] order by [Scheme Code];';
print #query;

Dynamic Pivot Not displaying right output

I have this table:
Year Month| User| Player | Manager
1996-06 | 1256| 2 | 1
1997-07 | 1243| 5 | 2
and was trying to pivot it so it returns:
| 1997-06|1996-07
User | 1256 | 1243
Player | 2 | 5
Manager| 1 | 2
however I'm not to sure why my script below is returning something different:
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME([User])
FROM (SELECT p.[User] FROM dbo.practise AS p
GROUP BY p.[User]) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT [year month], [User]
FROM dbo.practise AS p
) AS j
PIVOT
(
SUM([User]) FOR [year month] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
It returns:
1256|1243
1 Null|Null
Not sure what I'm doing wrong :/
Your could simplify this a bit
Example
Declare #SQL varchar(max) = '
Select *
From (
Select [Year Month]
,B.*
From practise
Cross Apply (values (''User'' ,cast([User] as varchar(max)))
,(''Player'' ,cast(Player as varchar(max)))
,(''Manager'',cast(Manager as varchar(max)))
) B (Item,Value)
) A
Pivot (max([Value]) For [year month] in (' + Stuff((Select Distinct ','+QuoteName([year month])
From practise
Order By 1
For XML Path('')),1,1,'') + ') ) p
Order by 1 Desc'
Exec(#SQL);
--Print #SQL
Returns
Item 1996-06 1997-07
User 1256 1243
Player 2 5
Manager 1 2
The Generated SQL Looks Like This
Select *
From (
Select [Year Month]
,B.*
From practise
Cross Apply (values ('User' ,cast([User] as varchar(max)))
,('Player' ,cast(Player as varchar(max)))
,('Manager',cast(Manager as varchar(max)))
) B (Item,Value)
) A
Pivot (max([Value]) For [year month] in ([1996-06],[1997-07]) ) p
Order by 1 Desc

SQL: Display the table and its job by yes and no

This is my table:
User Table
Id | Username |
---------------
1 | jdoe |
Job Table
Id | Job |
----------------
1 | Waiter |
2 | Office |
3 | Freelance |
User Job Table
Id |UserId | JobId |
--------------------
1 | 1 | 2 |
2 | 1 | 3 |
How to select this table and display it to look like this below:
Id | Username | Waiter| Office | Freelance|
-------------------------------------------
1 | jdoe | No | Yes | Yes |
This is a pretty standard pivot query question, with an additional slight twist. In case a given user does not have a certain type of job assigned, you want to display 'No'. One way to do this is to make use of COALESCE and replace NULL job aggregates.
SELECT u.Id,
u.Username,
COALESCE(MAX(CASE WHEN j.Job = 'Waiter' THEN 'Yes' END), 'No') AS Waiter,
COALESCE(MAX(CASE WHEN j.Job = 'Office' THEN 'Yes' END), 'No') AS Office,
COALESCE(MAX(CASE WHEN j.Job = 'Freelance' THEN 'Yes' END), 'No') AS Freelance
FROM User u
LEFT JOIN User_Job uj
ON u.Id = uj.UserId
LEFT JOIN Job j
ON uj.JobId = j.Id
GROUP BY u.Id,
u.Username
Try this..
WITH cte AS
(
SELECT u.username,job,CASE WHEN uj.jobid IS NULL THEN 'No' ELSE 'yes' END AS jobid
FROM USER u INNER JOIN UserJob uj on u.id = uj.Userid
RIGHT JOIN Job j on j.id = uj.jobid
)
SELECT username,ISNULL(waiter,'no') waiter,
ISNULL(Office,'no') Office,
ISNULL(Freelance,'no') Freelance
FROM cte
PIVOT
(
MAX(jobid) FOR job IN (Waiter, Office,Freelance)
) piv
WHERE username IS NOT NULL
DECLARE #cols AS NVARCHAR(MAX),#Listcols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + 'Isnull('+QUOTENAME(t.Job) +',''No'') as ' + t.Job
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #Listcols = STUFF((SELECT ',' + QUOTENAME(t.Job)
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT Id, Username,' + #cols + N' from
(
select D1.Id, D1.Username,
COALESCE(CASE WHEN isnull(D2.UserId,0) > 0 THEN ''Yes''
Else ''No''
End, ''No'') as UserId,
D3.Job
from [User] D1
Inner Join User_Job D2
On D1.Id = D2.UserId
Inner Join Job D3
On D2.JobId = D3.Id
) x
pivot
(
max(UserId)
for Job in (' + #Listcols + N')
) p '
exec sp_executesql #query;
If the job items are come from a result.
CREATE TABLE #T1(id INT ,Username VARCHAR(100))
INSERT INTO #T1 SELECT 1,'jdoe'
CREATE TABLE #T2(id INT ,Job VARCHAR(100))
INSERT INTO #T2 VALUES(1,'Waiter'),(2,'Office'),(3,'Freelance')
CREATE TABLE #T3(id INT ,UserId INT ,JobId INT )
INSERT INTO #T3 VALUES(1,1,2),(2,1,3)
DECLARE #col NVARCHAR(max),#sql NVARCHAR(max)
SELECT #col=ISNULL(#col+',[','[')+Job+']' FROM #t2
PRINT #col
SET #sql='
SELECT * FROM (
SELECT uj.Job,uj.Username,CASE WHEN t3.UserId IS NULL THEN ''No'' ELSE ''Yes'' END AS f FROM (
SELECT t2.id AS JobId, t2.Job,t1.id AS UserId, t1.Username
FROM #T1 AS t1,#t2 AS t2
) uj LEFT JOIN #T3 AS t3 ON t3.JobId = uj.JobId AND t3.UserId = uj.UserId
) AS t PIVOT(MAX(f) FOR job IN ('+#col+')) p'
EXEC(#sql)

Dynamically select distinct current and previous columns from a sql table

I have a temporary table like so
Id |Name |Status | Rate | Method |ModifiedTime |ModifiedBy
-----------------------------------------------------------------------------
1 |Recipe1 | 0 | 30 | xyz | 2016-07-26 14:55:57.977 | A
-------------------------------------------------------------------------------
2 |Recipe1 | 0 | 30 | abc | 2016-07-26 14:56:18.123 | A
--------------------------------------------------------------------------------
3 |Recipe1 | 1 | 30 | xyz | 2016-07-26 14:57:50.180 | b
I would like to select only the changes and wanted to show what the value was previously and what it is currently accompanied by who changed it. The final outcome will be as follows. I am using SQL Server 2014.
Item | Before | After |ModifiedTime | ModifiedBy
-----------------------------------------------------------------------------
Method | xyz | Abc | 2016-07-26 14:56:18.123 | A
-------------------------------------------------------------------------------
Status | 0 | 1 | 2016-07-26 14:57:50.180 | b
--------------------------------------------------------------------------------
Method | Abc | xyz | 2016-07-26 14:57:50.180 | b
I would like to do this dynamically instead of specifying each column name individually as shown in this link
Link
Assuming NAME (Recipe1) is a key
Declare #Table table (Id int,Name varchar(50),Status int,Rate int,Method varchar(50),ModifiedTime DateTime,ModifiedBy varchar(50))
Insert Into #Table values
(1,'Recipe1',0,30,'xyz','2016-07-26 14:55:57.977','A'),
(2,'Recipe1',0,30,'abc','2016-07-26 14:56:18.123','A'),
(3,'Recipe1',1,30,'xyz','2016-07-26 14:57:50.180','b')
Declare #XML xml
Set #XML = (Select * from #Table for XML RAW)
;with cteBase as (
Select ID = r.value('#Id','int')
,Name = r.value('#Name','varchar(150)')
,ModifiedTime = r.value('#ModifiedTime','varchar(150)')
,ModifiedBy = r.value('#ModifiedBy','varchar(150)')
,Item = Attr.value('local-name(.)','varchar(max)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') AS A(r)
Cross Apply A.r.nodes('./#*[local-name(.)!="Id"]') AS B(Attr)
)
,cteExt as (Select *,LastValue =Lag(Value) over (Partition By Name,Item Order by ModifiedTime) From cteBase)
Select Name
,Item
,Before=LastValue
,After =Value
,ModifiedTime
,ModifiedBy
From cteExt
Where Value<>LastValue and LastValue is not null
and Item not in ('ModifiedTime','ModifiedBy')
Order By Name,ModifiedTime
Returns
Name Item Before After ModifiedTime ModifiedBy
Recipe1 Method xyz abc 2016-07-26T14:56:18.123 A
Recipe1 Method abc xyz 2016-07-26T14:57:50.180 b
Recipe1 Status 0 1 2016-07-26T14:57:50.180 b
Ok, I adapted my previous answer but on Dynamic SQL. It's a little crazy but it works (using testTable as table name you can change that just replace 'testTable'):
DECLARE #query NVARCHAR(max)
SET #query = 'select item, case item';
SELECT #query = #query + Stuff(( SELECT
' when '''+a.NAME+''' then cast(prev'+a.NAME+' as varchar) '
FROM sys.all_columns a JOIN sys.tables t ON
t.object_id = a.object_id AND t.NAME = 'testTable' AND a.NAME
NOT IN ('id',
'Name', 'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 0,
'');
SET #query = #query + ' end as Before, case item ';
SELECT #query = #query + Stuff(( SELECT
' when '''+a.NAME+''' then cast('+a.NAME+' as varchar) '
FROM sys.all_columns a JOIN sys.tables t ON t.object_id =
a.object_id AND t.NAME = 'testTable' AND a.NAME NOT IN (
'id',
'Name',
'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 1,
'');
SET #query = #query
+ ' end as After, ModifiedTime, ModifiedBy from ( select ';
SELECT #query = #query + Stuff(( SELECT a.NAME +', lag('+ a.NAME +
') over (partition by Name order by id) prev'+a.NAME+', '
FROM
sys.all_columns a JOIN sys.tables t ON t.object_id =
a.object_id
AND t.NAME = 'testTable' AND a.NAME NOT IN ('id', 'Name',
'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 0,
'');
SET #query = #query
+ ' ModifiedBy, ModifiedTime from testTable ) as t1 cross join ('
;
SELECT #query = #query + Stuff(( SELECT ' select '''+ a.NAME +
'''as item union all '
FROM
sys.all_columns a JOIN sys.tables t ON t.object_id =
a.object_id
AND t.NAME =
'testTable' AND a.NAME NOT IN ('id', 'Name',
'ModifiedTime',
'ModifiedBy') FOR xml path('') ), 1, 1, '');
SET #query = LEFT(#query, Len(#query) - 10); --get rid of last union all
SET #query = #query + ' ) items where ';
SELECT #query = #query + Stuff(( SELECT ' or (item = '''+ a.NAME +''' and '+
a.NAME +
' != prev'+ a.NAME +')' FROM sys.all_columns a JOIN
sys.tables
t ON t.object_id = a.object_id AND t.NAME = 'testTable'
AND
a.NAME NOT IN ('id', 'Name', 'ModifiedTime', 'ModifiedBy'
) FOR
xml
path('') ), 1, 3, '');
SET #query = #query + ' order by ModifiedTime';
EXECUTE Sp_executesql
#query