Dynamic Pivot Not displaying right output - sql

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

Related

How to transform rows into column in SQL Server?

Using MS SQL Server and I have below table:
+-----------+------------------+------------------+---------------------+-------------------------+
| SrNo | ApprCode | ApprName | ApprStatus | ApprDate |
+-----------+------------------+------------------+---------------------+-------------------------+
| SR_176 | X001 | James | APR | 2019-10-03 |
| SR_176 | X002 | Sam | APR | 2019-10-03 |
+-----------+------------------+------------------+---------------------+-------------------------+
Tried with PIVOT but its showing james and X001 as a column heading:
Expected Result:
+-----------+-------------- +---------------+---------------------+-------------------------+------------------+
SrNo | ApprCode_1 | ApprName_1 | ApprDate_2 ApprCode_2 ApprName_2 ApprDate_2
+-----------+---------------+---------------+---------------------+-------------------------+------------------+
SR_176 X001 James 2019-10-03 X002 Sam 2019-10-03
+-----------+---------------+---------------+---------------------+---- ---------------------+------------------+
Query to generate data:
CREATE TABLE #Temp
(
SrNo NVARCHAR(200),
ApprCode NVARCHAR(200),
ApprName NVARCHAR(200),
ApprDate Date
)
INSERT INTO #Temp VALUES ('SR_176','X001','James', '2019-10-03')
INSERT INTO #Temp VALUES ('SR_176','X002','Sam', '2019-10-03')
Query that I tried:
declare #sql nvarchar(max)
declare #name_concat nvarchar(max)
declare #name1_concat nvarchar(max)
declare #select_aggs nvarchar(max)
select #name_concat = STUFF((select distinct ',' + quotename(ApprCode) from #Temp order by 1 for xml path('')), 1, 1, '')
select #name1_concat = STUFF((select distinct ',' + quotename(ApprName) from #Temp order by 1 for xml path('')), 1, 1, '')
select #sql = '
;with cte2 as
(
SELECT SrNo,' + #name_concat + ',' + #name1_concat + '
FROM #Temp
PIVOT(MAX(ApprCode)
FOR ApprCode IN (' + #name_concat + ')) AS PVTTable PIVOT
(
MAX(ApprName)
FOR ApprName IN (' + #name1_concat + ')) AS PVTTable1
)
select * from cte2
'
exec sp_executesql #sql
In fact, This is not a SQL Pivot situation and the sample data for question is not enough to test it completely but you can find the main idea :
Select
SrNo,
MAX(IIF(ApprCode = 'X001', ApprCode, null)) as ApprCode_1,
MAX(IIF(ApprCode = 'X001', ApprName, null)) as ApprName_1,
MAX(IIF(ApprCode = 'X001', ApprDate, null)) as ApprDate_1,
MAX(IIF(ApprCode = 'X002', ApprCode, null)) as ApprCode_2,
MAX(IIF(ApprCode = 'X002', ApprName, null)) as ApprName_2,
MAX(IIF(ApprCode = 'X002', ApprDate, null)) as ApprDate_2
From #Temp
Group by SrNo
This code works if ApprCode was the key to make two separate columns & SrNo is for the group by between rows.
I solved it by using ROW_NUMBER() and Case Expression:
Here is the query:
SELECT *,
Row_number()
OVER(
partition BY srno
ORDER BY apprdate) AS RN
INTO #temptable
FROM #temp
SELECT srno,
CASE rn
WHEN 1 THEN Max(apprname)
END AS [1 Approver],
CASE rn
WHEN 2 THEN Max(apprname)
END AS [2 Approver],
CASE rn
WHEN 1 THEN Max(apprcode)
END AS [1 ApproverCode],
CASE rn
WHEN 2 THEN Max(apprcode)
END AS [2 ApproverCode],
CASE rn
WHEN 1 THEN Max(apprdate)
END AS [1 Date],
CASE rn
WHEN 2 THEN Max(apprdate)
END AS [2 Date]
INTO #james
FROM #temptable
GROUP BY srno,
rn
SELECT srno,
Max([1 approver]) AS ApproverName_1,
Max([1 approvercode]) AS ApproverCode_1,
Max([1 date]) AS ApproverDate_1,
Max([2 approver]) AS ApproverName_2,
Max([2 approvercode]) AS ApproverCode_2,
Max([2 date]) AS ApproverDate_2
FROM #james
GROUP BY srno

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;

Is this possible with SQL Server

I have a table which has events and the day they occurred:
Table 'Events':
Name Day
-----------
A 1
B 2
A 2
B 3
I need output columns to be a date range based on query input with rows being which event happened on the day so:
Desired output:
Day-1 Day-2 Day-3
-----------------
A A -
- B B
If this is possible can anyone give me a sample query that could generate this output based on a date range. There are all sorts of I have no clue how to even approach this issues here like an unknown number of columns.
You can use conditional aggregation:
select max(case when day = 1 then name end) as day_1,
max(case when day = 2 then name end) as day_2,
max(case when day = 3 then name end) as day_3
from t
group by name;
Note: This returns NULL rather than -. I think NULL makes more sense.
Try this one...
Table Script and Sample data
CREATE TABLE [TableName](
[Name] [nvarchar](50) NULL,
[Day] [int] NULL
)
INSERT [TableName] ([Name], [Day]) VALUES (N'A', 1)
INSERT [TableName] ([Name], [Day]) VALUES (N'B', 2)
INSERT [TableName] ([Name], [Day]) VALUES (N'A', 2)
INSERT [TableName] ([Name], [Day]) VALUES (N'B', 3)
Query (dynamic PIVOT)
DECLARE #startDay AS INT;
DECLARE #endDay AS INT;
SET #startDay = 1;
SET #endDay = 3;
DECLARE #cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #query AS NVARCHAR(max) = 'SELECT '+ #cols +'
FROM (SELECT *,
Dense_rank() OVER (ORDER BY NAME) AS dr
FROM TableName) sq
PIVOT(Max([name])
FOR [day] IN ('+ #cols +') ) pvt ';
EXECUTE(#query)
Output
+--------+---+--------+
| 1 | 2 | 3 |
+--------+---+--------+
| A | A | (null) |
| (null) | B | B |
+--------+---+--------+
Online Demo: http://www.sqlfiddle.com/#!18/c688b/8/0
If you also want to use custom column name, try this...
DECLARE #startDay AS INT;
DECLARE #endDay AS INT;
SET #startDay = 1;
SET #endDay = 3;
DECLARE #cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #colNames AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day]) + ' AS Days' + CONVERT(NVARCHAR(MAX), [day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #query AS NVARCHAR(max) = 'SELECT '+ #colNames +'
FROM (SELECT *,
Dense_rank() OVER (ORDER BY NAME) AS dr
FROM TableName) sq
PIVOT(Max([name])
FOR [day] IN ('+ #cols +') ) pvt ';
EXECUTE(#query)
Output
+-------+-------+-------+
| Days1 | Days2 | Days3 |
+-------+-------+-------+
| A | A | NULL |
| NULL | B | B |
+-------+-------+-------+
Online Demo: http://www.sqlfiddle.com/#!18/c688b/9/0

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

How to pivot the given structure to expected one?

I have queries results as in the image, similarly for 90 date's, so how to group object and make date's as columns and count to respective date's.
Thanks in advance!!
You can do it using a dynamic crosstab:
SQL Fiddle
DECLARE #sql1 VARCHAR(4000) = ''
DECLARE #sql2 VARCHAR(4000) = ''
DECLARE #sql3 VARCHAR(4000) = ''
SELECT #sql1 =
'SELECT
[Object]' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(8), [Date], 112) + ''' AS DATE) THEN [count] END) AS ' + QUOTENAME([Date]) + CHAR(10)
FROM(
SELECT DISTINCT [Date] FROM tbl
)t
ORDER BY [Date]
SELECT #sql3 =
'FROM tbl
GROUP BY [Object]
ORDER BY [Object]'
PRINT(#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)
RESULT
| Object | 2015-01-01 | 2015-01-02 |
|--------|------------|------------|
| 1 | 10 | 34 |
| 2 | 20 | 46 |
| 3 | 130 | 78 |
| 4 | 40 | 89 |
| 5 | 55 | 45 |
This is the output of the PRINT command:
SELECT
[Object]
, MAX(CASE WHEN [Date] = CAST('20150101' AS DATE) THEN [count] END) AS [2015-01-01]
, MAX(CASE WHEN [Date] = CAST('20150102' AS DATE) THEN [count] END) AS [2015-01-02]
FROM tbl
GROUP BY [Object]
ORDER BY [Object]
You can use SQL Server PIVOT relational operator
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
--Get unique values of pivot column
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME([Date])
FROM (SELECT DISTINCT [Date] FROM [dbo].[PivotExample]) AS PivotExample
--Create the dynamic query with all the values for
--pivot column at runtime
SET #SQLQuery =
N'SELECT ObjectId, ' + #PivotColumns + '
FROM [dbo].[PivotExample]
PIVOT( SUM(COUNT)
FOR [Date] IN (' + #PivotColumns + ')) AS P'
EXEC sp_executesql #SQLQuery