SQL Pivot multiple columns without forcing aggregate - sql

I need to pivot out some denormalized data but it repeats so I need it to pivot out the columns and then return multiple rows.
I have a table like this
INSERT #TheTable
VALUES
('StockCode' ,'a'),
('Warehouse' ,'b'),
('TrnYear' ,'c'),
('TrnMonth' ,'d'),
('EntryDate' ,'e'),
('TrnTime' ,'f'),
('StockCode' ,'1'),
('Warehouse' ,'2'),
('TrnYear' ,'3'),
('TrnMonth' ,'4'),
('EntryDate' ,'5'),
('TrnTime' ,'6')
But when I pivot it only returns one row:
SELECT StockCode,
Warehouse,
TrnYear,
TrnMonth,
TrnTime,
EntryDate
FROM #TheTable AS src
PIVOT (MAX(column_value)
FOR COLUMN_NAME in ([TrnYear], [TrnMonth], [EntryDate], [TrnTime], [StockCode], [Warehouse])) AS piv
Result:
StockCode Warehouse TrnYear TrnMonth TrnTime EntryDate
-------------------------------------------------------------
a b c d f e
But I need it to return
StockCode Warehouse TrnYear TrnMonth TrnTime EntryDate
-------------------------------------------------------------
a b c d f e
1 2 3 4 5 6

You can use window functions first, then conditional aggregation:
select
max(case when column_name = 'StockCode' then column_value end) StockCode,
max(case when column_name = 'Warehouse' then column_value end) Warehouse,
max(case when column_name = 'TrnYear' then column_value end) TrnYear,
max(case when column_name = 'TrnMonth' then column_value end) TrnMonth,
max(case when column_name = 'TrnTime' then column_value end) TrnTime,
max(case when column_name = 'EntryDate' then column_value end) EntryDate
from (
select t.*,
row_number() over(partition by column_name order by column_value) rn
from #TheTable t
) t
group by rn

You can use ROW_NUMBER() Analytic function :
SELECT *
FROM
(
SELECT column_name, column_value,
ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY column_value) AS rn
FROM #TheTable
) q
PIVOT
( MAX(column_value)
FOR column_name in ([TrnYear], [TrnMonth], [EntryDate], [TrnTime], [StockCode], [Warehouse])
) AS piv
or more dynamically, use :
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols = ( SELECT STRING_AGG(column_name,',')
FROM (SELECT DISTINCT column_name
FROM [#TheTable] ) q );
SET #query =
N'SELECT *
FROM
(
SELECT column_name, column_value,
ROW_NUMBER() OVER
(PARTITION BY [column_name] ORDER BY [column_value]) AS rn
FROM [#TheTable]
) f
PIVOT
(
MAX([column_value]) FOR [column_name] IN (' + #cols + N')
) AS piv '
EXEC sp_executesql #query;
Demo

The problem was that I needed a row_number per every group of column names. So the below worked
SELECT DISTINCT TrnYear,
TrnMonth,
EntryDate,
TrnTime,
StockCode,
Warehouse
FROM
(SELECT (ROW_NUMBER() OVER (ORDER BY dw_view_change_event_nr) - 1) / 6 + 1 AS rn,
COLUMN_NAME ,
column_value
FROM
#TheTable AS tmp) AS src PIVOT (MAX(column_value)
FOR COLUMN_NAME in ([TrnYear], [TrnMonth], [EntryDate], [TrnTime], [StockCode], [Warehouse])) AS piv

Related

convert multiple rows to columns

Source:
ItemId
ItemName
Nutrient
GAV
A
Beef
Vit A
1
A
Beef
Vit B
2
A
Beef
Vit C
3
target:
Id
Name
Nut1
GAV1
Nut2
GAV2
Nut3
GAV3
A
Beef
VitA
1
VitB
2
VitC
3
How can we achieve this with ms-sql query?
Assuming GAV is NOT sequential as presented, we'll have to use the window function row_number() and some Dynamic SQL
Example or dbFiddle
Declare #SQL varchar(max)
Select #SQL = string_agg( concat('[',ColName,ColNr,']','=','max(case when ColNr =',ColNr,' then ',ColName,' end)') , ',') within group (ORDER BY ColNr,ColName Desc)
From (values ('Nutrient'),('GAV') ) A(ColName)
Cross Join ( Select Distinct ColNr = row_number() over( partition by ItemID order by GAV) from YourTable ) B
Set #SQL = '
Select ItemID
,ItemName
,' + #SQL + '
From ( Select *
,ColNr = row_number() over( partition by ItemID order by GAV )
From YourTable
) A
Group By ItemID
,ItemName
'
Exec(#SQL)
Results
UPDATE 2016 Version
Declare #SQL varchar(max) = ''
Select #SQL = #SQL + concat(',','[',ColName,ColNr,']','=','max(case when ColNr =',ColNr,' then ',ColName,' end)')
From (values ('Nutrient'),('GAV') ) A(ColName)
Cross Join ( Select Distinct ColNr = row_number() over( partition by ItemID order by GAV) from YourTable ) B
Order By ColNr,ColName Desc
Set #SQL = '
Select ItemID
,ItemName
' + #SQL + '
From ( Select *
,ColNr = row_number() over( partition by ItemID order by GAV )
From YourTable
) A
Group By ItemID
,ItemName
'
Exec(#SQL)

Convert three rows values into columns, NOT as comma separated value

I have table structure like
select catalog_item_id,metal_type,metal_color
from catalog_item_castings
where catalog_Item_Id =465173
It returns output as:
And I want output as:
And I want to insert this data into new temp table in SQL Server.
Thanks in advance.
Conditional aggregation is an option:
SELECT
catalog_item_id,
MAX(CASE WHEN rn % 3 = 1 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_1,
MAX(CASE WHEN rn % 3 = 2 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_2,
MAX(CASE WHEN rn % 3 = 0 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_3
FROM (
SELECT
catalog_item_id, metal_type, metal_color, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn
FROM (VALUES
(465173, 'na', 'METALCOLOR'),
(465173, 'na', 'METAL-001'),
(465173, 'na', 'na')
) catalog_item_castings (catalog_item_id, metal_type, metal_color)
WHERE catalog_Item_Id = 465173
) t
GROUP BY catalog_item_id
-- or if you have more than three rows per [catalog_item_id]
-- GROUP BY catalog_item_id, (rn - 1) / 3
Result:
catalog_item_id Casting_1 Casting_2 Casting_3
-------------------------------------------------
465173 na/METALCOLOR na/METAL-001 na/na
You can use Conditional Aggregation within a Dynamic Pivot Statement in order to include all distinct combinations of the columns [metal_type] and [metal_color], even different values for combinations are inserted in the future :
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = (SELECT STRING_AGG(CONCAT('MAX(CASE WHEN [dr]=',dr,
' THEN CONCAT([metal_type],''/'',[metal_color]) END) AS [Casting_',dr,']'),',')
WITHIN GROUP ( ORDER BY dr )
FROM
(
SELECT DISTINCT
DENSE_RANK() OVER
(PARTITION BY [catalog_item_id]
ORDER BY CONCAT([metal_type],[metal_color])) AS dr
FROM [catalog_item_castings] ) c);
SET #query =
'SELECT [catalog_item_id],'+ #cols +
' FROM
(
SELECT *, DENSE_RANK() OVER
( PARTITION BY [catalog_item_id]
ORDER BY CONCAT([metal_type], [metal_color]) ) AS dr
FROM [catalog_item_castings]
) c
GROUP BY [catalog_item_id]';
EXEC sp_executesql #query;
Demo

How to create dynamic pivot query in SQL

I am trying to create dynamic pivot query in SQL but my issue is that the contract id and the tier desc columns are both dynamic and I could not figure out how to solve this issue. I have something like this:
and this is the output I would like to see
This can be done with repeating column names, however, I can't imagine why one would want this.
The #Col is where we apply the Alias ...[#] as [Tier Value]...
Example
Declare #Col varchar(max) = Stuff((Select Distinct ',' + concat(QuoteName(row_number() over (Partition By ContractID Order by TierDesc)),' as [Tier Value]') From Yourtable Order by 1 For XML Path('')),1,1,'')
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(row_number() over (Partition By ContractID Order by TierDesc)) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select ContractID,'+#Col+'
From (
Select ContractID
,TierDesc
,ColNr = row_number() over (Partition By ContractID Order by TierDesc)
From YourTable
) Src
Pivot (max(TierDesc) for ColNr in ('+#SQL+') ) pvt
'
Exec(#SQL)
Returns
ContractID Tier Value Tier Value Tier Value
123 tier1 tier2 NULL
555 tier4 tier5 tier6
652 tier0 tier4 NULL
EDIT - Then generated SQL Looks like this
Select ContractID
,[1] as [Tier Value]
,[2] as [Tier Value]
,[3] as [Tier Value]
From (
Select ContractID
,TierDesc
,ColNr = row_number() over (Partition By ContractID Order by TierDesc)
From YourTable
) Src
Pivot (max(TierDesc) for ColNr in ([1],[2],[3]) ) pvt
EDIT 2
Select Distinct
ColNr = row_number() over (partition by ContractID Order By TierDesc)
From Yourtable

Split/separate column into multiple columns

I'm completely stuck and I cannot find any answers for this problem even though problem seems to be quite simple. Can I separate that 'description' column without making a new table?
For now I just wrote this simplest code.
select item_id, description
from data
where item_id = '123'
With that code it looks like this:
item_id description
123 A
123 B
123 C
But I'd like to make it look like this:
item_id desc_1 desc_1 desc_2
123 A B C
Use conditional aggregation with the help of case expression
select item_id,
max(case when description= 'A' then description end) [desc_1],
max(case when description= 'B' then description end) [desc_2],
max(case when description= 'C' then description end) [desc_3],
from table
group by item_id
EDIT : So, the dynamic pivot way will look like as for SQL Server
declare #col varchar(max), #q varchar(max)
set #col = stuff(
(select distinct ','+quotename('desc_'+cast(row_number() over(partition by Item_id order by description) as varchar))
from table for xml path('')),
1,1,'')
set #q = 'select * from
(
select *,
''desc_''+cast(row_number() over(partition by Item_id order by description) as varchar) rn
from table
)a
PIVOT
(
max(description) for rn in ('+#col+')
)p'
EXEC (#Q)
Result :
item_id desc_1 desc_2 desc_3
123 A B C
234 B C d
first Declare Distinct column names
Like ABC, DEF,GHI
and values
then write dynamic pivot
DECLARE #COLS AS NVARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
SET #COLS=STUFF((select ',' + QUOTENAME(Course) from cst_coursedetails where programid=1 FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
SET #query =' SELECT * FROM(SELECT B.COLLCODE, B.COLLNAME,C.Course AS COURSE,D.ROLLNAME, E.ExamType, COUNT (A.HTNO) AS PRECOUNT FROM TableOne AS A
INNER JOIN TableTwo AS B ON A.COLLCODE=B.COLLCODE AND A.PROGRAMID=B.PROGRAMID
INNER JOIN TableThreee AS C ON C.CourseId=A.CourseId
INNER JOIN TableFour AS D ON D.ROLLID=A.ROLLID
INNER JOIN CST_EXAMTYPE AS E ON E.ExamTypeId=A.ExamTypeId
WHERE A.STATUSID !=17 AND a.ProgramId=1 AND A.ROLLID IN(1,2,3) AND A.EXAMTYPEID IN(1) GROUP BY B.COLLNAME,B.COLLCODE,C.Course,d.ROLLNAME ,E.ExamType
)SRC
PIVOT(
SUM(PRECOUNT) FOR COURSE IN('+#COLS+')
)AS PIV'
afs -- with clause name
giga -- alias name for listagg
with afs as
(
select item_id,LISTAGG(description, ',') WITHIN GROUP (ORDER BY item_id) AS
giga from test_jk group by item_id
)
select item_id,REGEXP_SUBSTR (giga, '[^,]+', 1, 1) AS
desc_1,REGEXP_SUBSTR (giga, '[^,]+', 1, 2) as desc_2 from afs;
output

How to split data of 1 column in 3 columns?

Data is in given format-
Id Date Location
a123 6/6/2016 mmp
a123 6/7/2016 jpr
a123 6/8/2016 hjl
a123 6/9/2016 jhag
a678 6/10/2016 hjlwe
a678 6/11/2016 mkass
a980 6/7/2016 asdadf
a980 6/7/2016 lasdj
a980 6/7/2016 xswd
I want the same in given format-:
Id Date 1 Location1 Date 2 Location 2 Date 3 Location 3
a123 6/6/2016 mmp 6/7/2016 jpr 6/8/2016 hjl
a678 6/10/2016 hjlwe 6/11/2016 mkass
a980 6/7/2016 asdadf 6/7/2016 lasdj 6/7/2016
How to do that in SQL?
You can use ROW_NUMBER() with conditional aggregation :
SELECT s.id,
MAX(CASE WHEN s.rnk = 1 THEN s.date END) as date_1,
MAX(CASE WHEN s.rnk = 1 THEN s.location END) as location_1,
MAX(CASE WHEN s.rnk = 2 THEN s.date END) as date_2,
MAX(CASE WHEN s.rnk = 2 THEN s.location END) as location_2,
MAX(CASE WHEN s.rnk = 3 THEN s.date END) as date_3,
MAX(CASE WHEN s.rnk = 3 THEN s.location END) as location_3
FROM(
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.id ORDER BY t.Date) as rnk
FROM YourTable t) s
GROUP BY s.id
This can also be solved with PIVOT but I prefer to use conditional aggregation as long as the amount of the columns is limited .
If you need to add more levels, just follow the logic and replace 3 with 4 and so on..
Also you can make it with PIVOT (if number of columns can change dynamically you must use dynamic SQL):
;WITH cte AS (
SELECT *
FROM (VALUES
('a123', '6/6/2016', 'mmp'),
('a123', '6/7/2016', 'jpr'),
('a123', '6/8/2016', 'hjl'),
('a123', '6/9/2016', 'jhag'),
('a678', '6/10/2016', 'hjlwe'),
('a678', '6/11/2016', 'mkass'),
('a980', '6/7/2016', 'asdadf'),
('a980', '6/7/2016', 'lasdj'),
('a980', '6/7/2016', 'xswd')
) as t(Id, [Date], [Location])
)
SELECT p1.Id,
p1.[Date1],
p2.[Location1],
p1.[Date2],
p2.[Location2],
p1.[Date3],
p2.[Location3],
p1.[Date4],
p2.[Location4]
FROM
(SELECT *
FROM (
SELECT Id,
[Date],
'Date' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)) as rn
fROM cte
) AS D
PIVOT (
MAX([Date]) for RN in ([Date1],[Date2],[Date3],[Date4])
) as pvt
) as p1
LEFT JOIN
(SELECT *
FROM (
SELECT Id,
[Location],
'Location' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)) as rn
fROM cte
) AS D
PIVOT (
MAX([Location]) for RN in ([Location1],[Location2],[Location3],[Location4])
) as pvt
) as p2
ON p1.Id = p2.Id
Output:
Id Date1 Location1 Date2 Location2 Date3 Location3 Date4 Location4
---- --------- --------- --------- --------- --------- --------- --------- ---------
a123 6/6/2016 mmp 6/7/2016 jpr 6/8/2016 hjl 6/9/2016 jhag
a678 6/10/2016 hjlwe 6/11/2016 mkass NULL NULL NULL NULL
a980 6/7/2016 lasdj 6/7/2016 xswd 6/7/2016 asdadf NULL NULL
EDIT
With dynamic SQL (same output):
CREATE TABLE #temp (
Id nvarchar(10),
[Date] date,
[Location] nvarchar(10)
)
INSERT INTO #temp VALUES
('a123', '6/6/2016', 'mmp'),
('a123', '6/7/2016', 'jpr'),
('a123', '6/8/2016', 'hjl'),
('a123', '6/9/2016', 'jhag'),
('a678', '6/10/2016', 'hjlwe'),
('a678', '6/11/2016', 'mkass'),
('a980', '6/7/2016', 'asdadf'),
('a980', '6/7/2016', 'lasdj'),
('a980', '6/7/2016', 'xswd')
DECLARE #locs nvarchar(max),
#dates nvarchar(max),
#cols nvarchar(max),
#sql nvarchar(max)
SELECT #locs = STUFF((
SELECT DISTINCT ',' + QUOTENAME('Location' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)))
FROM #temp
FOR XML PATH('')
),1,1,'')
SELECT #dates = STUFF((
SELECT DISTINCT ',' + QUOTENAME('Date' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)))
FROM #temp
FOR XML PATH('')
),1,1,'')
SELECT #cols = STUFF((
SELECT DISTINCT ',' + QUOTENAME('Date' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10))) +
',' + QUOTENAME('Location' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)))
FROM #temp
FOR XML PATH('')
),1,1,'')
SELECT #sql ='
SELECT p1.Id,
'+#cols+'
FROM
(SELECT *
FROM (
SELECT Id,
[Date],
''Date'' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)) as rn
fROM #temp
) AS D
PIVOT (
MAX([Date]) for RN in ('+#dates+')
) as pvt
) as p1
LEFT JOIN
(SELECT *
FROM (
SELECT Id,
[Location],
''Location'' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Date]) AS NVARCHAR(10)) as rn
fROM #temp
) AS D
PIVOT (
MAX([Location]) for RN in ('+#locs+')
) as pvt
) as p2
ON p1.Id = p2.Id'
EXECUTE sp_executesql #sql
DROP TABLE #temp
If you don't know how many columns you have, you can use a dynamic version of sagi's answer. Here is one using a dynamic crosstab:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
t.id' + CHAR(10);
SELECT #sql = #sql +
(SELECT
' , MAX(CASE WHEN rn = ' + CAST(rn AS VARCHAR(10)) + ' THEN t.Date END) AS ' + QUOTENAME('Date' + CAST(rn AS VARCHAR(10))) + CHAR(10) +
' , MAX(CASE WHEN rn = ' + CAST(rn AS VARCHAR(10)) + ' THEN t.Location END) AS ' + QUOTENAME('Location' + CAST(rn AS VARCHAR(10))) + CHAR(10)
FROM (
SELECT DISTINCT
ROW_NUMBER() OVER (PARTITION BY Id ORDER BY(SELECT NULL)) AS rn
FROM tbl
) t
ORDER BY rn
FOR XML PATH(''));
SELECT #sql = #sql +
'FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Date) AS rn
FROM tbl
) t
GROUP BY t.Id;';
PRINT (#sql);
EXEC (#sql);
ONLINE DEMO