input table
country tag short
UK F1 Units
UK F2 Volume
UK F3 Value
FR T3 Units
FR T2 Volume
FR T1 Value
result output i want :
country Units Volume Value
uk f1 f2 f3
fr t1 t2 t3
If there are a fixed number of different short values, simply use case expressions to do conditional aggregation:
select country,
max(case when short = 'Units' then tag end) as Units,
max(case when short = 'Volume' then tag end) as Volume,
max(case when short = 'Value' then tag end) as val
from tablename
group by country
For solution you have to use dynamic pivoting.
create table #temp
(
country varchar(30),tag varchar(20),short varchar(300)
)
insert into #temp values ('UK', 'F1', 'Units')
insert into #temp values ('UK', 'F2' , 'Volume')
insert into #temp values ('UK' ,'F3', 'Value')
insert into #temp values ('FR', 'T3' , 'Units')
insert into #temp values ('FR' , 'T2', 'Volume')
insert into #temp values ('FR', 'T1' , 'Value')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.short)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT country, ' + #cols + ' from
(
select country
, tag
, short
from #temp
) x
pivot
(
max(tag)
for short in (' + #cols + ')
) p '
execute(#query)
drop table #temp
Table Structure
CREATE TABLE tablename
(
[country] [NVARCHAR](10) NULL,
[tag] [NVARCHAR](10) NULL,
[short] [NVARCHAR](10) NULL
)
INSERT INTO tablename
VALUES
('UK','F1','Units'),
('UK','F2','Volume'),
('UK','F3','Value'),
('FR','T3','Units'),
('FR','T2','Volume'),
('FR','T1','Value');
Using Pivot function
SELECT *
FROM tablename
PIVOT ( Max(tag)
FOR short IN ([Units], [volume], [Value]) ) piv;
Online Demo: Link
Using Dynamic SQL PIVOT
DECLARE #cols AS NVARCHAR(max),
#query AS NVARCHAR(max)
SELECT #cols = Stuff((SELECT distinct ',' + Quotename(short)
FROM tablename
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1,'');
SET #query = 'SELECT *
FROM tablename
PIVOT ( Max(tag)
FOR short IN (' + #cols + ') ) piv;';
EXECUTE(#query);
Online Demo: Link
Result
+---------+-------+--------+-------+
| country | Units | volume | Value |
+---------+-------+--------+-------+
| FR | T3 | T2 | T1 |
| UK | F1 | F2 | F3 |
+---------+-------+--------+-------+
Related
I have a table,
Output before pivoting:
keli | onoma
-----+---------------
name | Step1
a1 | DSP
a2 | Tekmiriosi
Expected output:
Step1 | DSP | Tekmiriosi
How to do that for three or more rows but always with to one-row column?
You can use conditional aggregation:
select max(onoma) filter (where keli = 'name'),
max(onoma) filter (where keli = 'a1'),
max(onoma) filter (where keli = 'a2')
from t;
However, I might suggest that you if you want all values in a single row that you just use arrays:
select array_agg(keli)
from t;
you can use following query
create table temp
(
keli varchar(100),
onoma varchar(100)
)
insert into temp values ('name', 'Step1')
insert into temp values ('a1', 'DSP')
insert into temp values ('a2', 'Tekmiriosi')
DECLARE #column AS NVARCHAR(MAX)
DECLARE #qry AS NVARCHAR(MAX)
SET #column = STUFF((SELECT distinct ',' + QUOTENAME(c.onoma)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #qry = 'SELECT ' + #column + ' from
(
select onoma
from temp
) x
pivot
(
max(onoma)
for onoma in (' + #column + ')
) p '
execute(#qry)
drop table temp
Dynamic pivot combined combined with static aggregates
I have a table that looks something like this:
Place State Category CategCount MCount Buys Cost
London UK Old 3 NULL 22 4.50
London UK Old 6 5 3 22.00
Brussels BE Young 2 NULL 4 3.50
Brussels BE M NULL 5 12 1.20
Brussels BE M NULL 2 1 1.20
I basically need to:
Group by a number of fields (Place, State, Category in the example)
Count per such group
Sum MCount, Cost (and others, not in example) per group, these columns are static
Pivot over Category and sum CategCount for each such grouped category (here Old, Young). This is the dynamic part
Result should look like:
Count Place State Category SumMCount SumOld SumYoung SumCost SumBuys
2 London UK Old 5 9 0 26.50 25
1 Brussels BE Young 0 0 2 3.50 4
2 Brussels BE NULL 7 0 0 2.40 13
I know how to get a dynamic pivot query (as per https://stackoverflow.com/a/38505375/111575) and I know how to do the static part. But I don't know how to combine the two. Anybody any ideas? Maybe I go about it all wrong?
What I've got so far:
The following gives me the proper dynamic pivot results for Old and Young, but not sure how to add the count and the the 'regular' sums/aggregates to it:
create table #temp
(
Place nvarchar(20),
State nvarchar(20),
Category nvarchar(20) null,
CategCount int null,
MCount int null,
Buys int,
Cost int
)
insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
--select (#cols) as bm
set #query =
'SELECT * from
(
select
sum(CategCount) as totalCatCount,
Category
from #temp
group by Category
) src
pivot
(
max(totalCatCount) for Category in (' + #cols + ')
) piv'
execute(#query)
drop table #temp
Returning:
And the following is the 'regular' query without the pivoting:
select count(*) as count, place, state, category,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(buys, 0)) as SumBuys,
sum(Cost) as SumCost
from #temp
group by place, state, category
Returning:
But it should look something like this:
I have used your static pivot part of the query as the source of dynamic pivot. Create two sets of dynamic pivot column list. One for pivoting and the another with Coalesce() to select pivoted columns (to convert null into 0). If there is no categcount for any category then that category has been replaced with null (case when). Two more aliases for Category and SumCatCount have been created since those were used in pivot condition.
Here goes your answer:
create table #temp
(
Place nvarchar(20),
State nvarchar(20),
Category nvarchar(20) null,
CategCount int null,
MCount int null,
Buys int,
Cost int
)
insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
DECLARE #colsForSelect AS NVARCHAR(MAX)='';
SET #cols = STUFF((SELECT distinct ',' + quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #colsForSelect = STUFF((SELECT distinct ',' + ' Coalesce('+quotename(category)+',0) '+ quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
--select (#cols) as bm
set #query =
'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + #colsForSelect + ',SumCost,SumBuys from
(
select count(*) as count, place, state,category OldCategory, category,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(CategCount, 0)) as OldSumCatCount,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(Cost) as SumCost,
sum(ISNULL(buys, 0)) as SumBuys
from #temp
group by place , state, category
) src
pivot
(
max(SumCatCount) for Category in (' + #cols + ')
) piv
order by place desc,count'
execute(#query)
GO
count
place
state
Category
SumMCount
Old
Young
SumCost
SumBuys
2
London
UK
Old
5
9
0
26
25
1
Brussels
BE
Young
0
0
2
3
4
2
Brussels
BE
null
7
0
0
2
13
db<>fiddle here
Thanks to #Larnu in the comments for pointing me in the right direction. His/her statement on "you cannot JOIN a static to a dynamic query" and that either all or nothing has to be dynamic, prompted me to build onto the dynamic part and simply extend it.
I thought I needed to repeat the columns somehow in the PIVOT section, but that appears to not be the case. Only the column you want to pivot, apparently (logically so, once you think about it).
The only part I haven't figured out yet is how to get rid of NULL in the resulting set, hopefully someone answers with that in mind ;).
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
--select (#cols) as bm
set #query =
'SELECT * from
(
select
count(*) as count,
Place,
State,
Category,
Category as CatPivot,
sum(ISNULL(CategCount, 0)) as TotalCatCount,
sum(ISNULL(Buys, 0)) as SumBuys,
sum(ISNULL(Cost, 0)) as SumCost,
sum(ISNULL(MCount, 0)) as SumMCount
from #temp
group by Category, Place, State
) src
pivot
(
max(TotalCatCount) for CatPivot in (' + #cols + ')
) piv'
execute(#query)
Here I am sharing another answer which is same but as suggested by #Anthony Hancock dynamic column names for pivot have been created with string_agg() instead of stuff() and xml path for(). It's way too faster and more readable (for SQL Server 2017 and onward)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
DECLARE #colsForSelect AS NVARCHAR(MAX)='';
select #cols =string_agg(category,',') from (
select distinct category FROM #temp where CategCount is not null )t
select #colsForSelect= STRING_AGG(category,',') from
(select distinct 'coalesce('+category+',0) '+category category FROM #temp where CategCount is not null )t
set #query =
'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + #colsForSelect + ',SumCost,SumBuys from
(
select count(*) as count, place, state,category OldCategory, category,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(CategCount, 0)) as OldSumCatCount,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(Cost) as SumCost,
sum(ISNULL(buys, 0)) as SumBuys
from #temp
group by place , state, category
) src
pivot
(
max(SumCatCount) for Category in (' + #cols + ')
) piv
order by place desc,count'
execute(#query)
I have a table and values as shown here
create table #example(id int primary key identity, cols varchar(255))
insert into #example(cols) values('HI,HELLO,BYE,TC')
insert into #example(cols) values('WHAT,ARE,YOU,DOING,HERE')
I need the resultant output as shown in the picture
Note : There is no limit for values i.e dynamic
This is what you are expecting
Schema:
CREATE TABLE #EXAMPLE(ID INT PRIMARY KEY IDENTITY, COLS VARCHAR(255))
INSERT INTO #EXAMPLE(COLS) VALUES('HI,HELLO,BYE,TC')
INSERT INTO #EXAMPLE(COLS) VALUES('WHAT,ARE,YOU,DOING,HERE')
Do split those comma separated values into Rows and Apply pivot
SELECT * FROM (
SELECT id
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2
, SPLT.CLMS.value('.','VARCHAR(MAX)') AS LIST FROM (
select id
, CAST( '<M>'+REPLACE(cols,',','</M><M>')+'</M>' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes('/M') AS SPLT(CLMS)
)A
PIVOT
(
MAX(LIST) FOR ID2 IN ([1],[2],[3],[4],[5])
)PV
You will get result as
+----+------+-------+-----+-------+------+
| id | 1 | 2 | 3 | 4 | 5 |
+----+------+-------+-----+-------+------+
| 1 | HI | HELLO | BYE | TC | NULL |
| 2 | WHAT | ARE | YOU | DOING | HERE |
+----+------+-------+-----+-------+------+
Edit:
And now you need to go for dynamic pivot,since There is no limit for values.
DECLARE #COLS VARCHAR(MAX)='', #QRY VARCHAR(MAX)='';
SELECT #COLS =#COLS+'['+CAST( ID2 AS VARCHAR(10))+'],' FROM (
SELECT DISTINCT ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2 FROM (
select id, CAST( '<M>'+REPLACE(cols,',','</M><M>')+'</M>' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes('/M') AS SPLT(CLMS)
)A
SELECT #COLS = LEFT(#COLS,LEN(#COLS)-1)
SELECT #QRY =
'
SELECT * FROM (
SELECT id
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2
, SPLT.CLMS.value(''.'',''VARCHAR(MAX)'') AS LIST FROM (
select id, CAST( ''<M>''+REPLACE(cols,'','',''</M><M>'')+''</M>'' AS XML) AS XML_COL from #example E
)E
CROSS APPLY E.XML_COL.nodes(''/M'') AS SPLT(CLMS)
)A
PIVOT
(
MAX(LIST) FOR ID2 IN ('+#COLS+ ')
)PV'
EXEC( #QRY)
Dynamic version base on split string then pivot table
Schema:
create table #example(id int primary key identity, cols varchar(255))
insert into #example(cols) values('HI,HELLO,BYE,TC')
insert into #example(cols) values('WHAT,ARE,YOU,DOING,HERE')
Calculate columns pivot
-- Calculate dynamic columns
;WITH temps AS
(
SELECT sd.* ,CAST('<x>' + replace(sd.cols, ',', '</x><x>') + '</x>' as xml) AS xmlText
FROM #example sd
),
temps1 AS
(
SELECT t.Id, t.cols, v.x.value('.','varchar(50)') AS Value
FROM temps t
CROSS APPLY
t.xmlText.nodes('/x') AS v(x)
)
SELECT #ColumnPivot = STUFF((SELECT DISTINCT CONCAT(',COL',row_number() OVER(PARTITION BY t.id ORDER BY t.Value)) FROM temps1 t FOR XML PATH('')), 1,1,'')
PRINT #ColumnPivot
Then PIVOT table
DECLARE #query nvarchar(max) =
CONCAT(N';WITH temps AS
(
SELECT sd.* ,CAST(''<x>'' + replace(sd.cols, '','', ''</x><x>'') + ''</x>'' as xml) AS xmlText
FROM #example sd
),
temps1 AS
(
SELECT t.Id, t.cols, v.x.value(''.'' , ''varchar(50)'') AS Value
FROM temps t
CROSS APPLY
t.xmlText.nodes(''/x'') AS v(x)
),
temps2 AS
(
SELECT t.* , CONCAT(''COL'',row_number() OVER(PARTITION BY t.id ORDER BY (select 1))) AS ColGroup
FROM temps1 t
)',
N'SELECT Id, cols, ' , #ColumnPiVot,
' FROM
(
select * from temps2
) AS src
PIVOT
(MAX(Value) FOR ColGroup IN (',#ColumnPiVot,')) AS pvt')
PRINT #query
exec sp_executesql #query
DROP TABLE #example
Demo link: Rextester
DECLARE #COLS AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX);
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(C.RN)
FROM (SELECT ID,TOKEN,COLS
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY (
SELECT NULL
)
) AS RN
FROM #EXAMPLE
CROSS APPLY (
SELECT *
FROM UDF_SPLITSTRING(COLS, ',')
) A) C
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #QUERY = 'SELECT ID,cols, ' + #COLS + ' FROM
(
SELECT ID,TOKEN,cols
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY (
SELECT NULL
)
) AS RN
FROM #EXAMPLE
CROSS APPLY (
SELECT *
FROM UDF_SPLITSTRING(COLS, '','')
) A
) X
PIVOT
(
MAX(TOKEN)
FOR rn IN (' + #COLS + ')
) P '
exec(#QUERY)
output
ID cols 1 2 3 4 5
1 HI,HELLO,BYE,TC HI HELLO BYE TC NULL
2 WHAT,ARE,YOU,DOING,HERE WHAT ARE YOU DOING HERE
I have some data stored in a table in the following format
BatchID EntityChanged ChangeValue
EERS ABC DEF
EERS ABCD XYZ
EERS Something SomeValue
New Batch SomethingMore GHI
The list of values that can appear in the "EntityChanged" columns is finite, known beforehand and has no spaces etc in it. For the sake of argument let us say that this list is - ABC, ABCD, Something, SomethingMore
Then for the above dataset I would like an output of
BatchID ABC ABCD Something SomethingMore
EERS DEF XYZ SomeValue NULL
New Batch NULL NULL NULL GHI
Using Pivot I could only go so far.
Can someone please help me slice this data in the desired way?
You can use a PIVOT for this, either via Static PIVOT or a Dynamic PIVOT which might be good if you have a unknown number of columns.
Static (See SQL Fiddle with Demo):
create table t2
(
batchid varchar(10),
entitychanged varchar(20),
changevalue varchar(10)
)
insert into t2 values ('EERS', 'ABC', 'DEF')
insert into t2 values ('EERS', 'ABCD', 'XYZ')
insert into t2 values ('EERS', 'Something', 'SomeValue')
insert into t2 values ('New Batch', 'SomethingMore', 'GHI')
select *
from
(
select batchid, entitychanged, changevalue
from t2
) x
pivot
(
min(changevalue)
for entitychanged in ([ABC], [ABCD], [Something], [SomethingMore])
) p
Dynamic (see SQL Fiddle with Demo):
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(entitychanged)
from t2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT batchid, ' + #cols + ' from
(
select batchid, entitychanged, changevalue
FROM t2
) x
pivot
(
min(changevalue)
for entitychanged in (' + #cols + ')
) p '
execute(#query)
Both will give you the same result.
Try this:
SELECT BatchID,
MAX(CASE WHEN EntityChanged = 'ABC' THEN ChangeValue END) 'ABC',
MAX(CASE WHEN EntityChanged = 'ABCD' THEN ChangeValue END) 'ABCD',
MAX(CASE WHEN EntityChanged = 'Something' THEN ChangeValue END) 'Something',
MAX(CASE WHEN EntityChanged = 'SomethingMore' THEN ChangeValue END) 'SomethingMore'
FROM YourTable t
GROUP BY BatchID
DEMO
How to transform this:
ID Name Description
1 Test1a TestDesc1a
1 Test1b TestDesc1b
2 Test2a TestDesc2a
2 Test2b TestDesc2b
into this:
ID Column 1 2
1 Name test1a test1b
1 Description testDesc1a testDesc1b
You ask for complex pivoting table. Read about this here: http://msdn.microsoft.com/en-us/library/ms177410.aspx
You need to use a PIVOT query.
A basic discussion of PIVOT queries can be found at [MSDN][1].
This is a tricky question to solve, however the output specified is not proper/ conflicting. Below is similar solution which you can try
Sample Table creation:
CREATE TABLE [dbo].[TestTable](
[Id] [int] NULL,
[Name] [nvarchar](50) NULL,
[Description] [nvarchar](50) NULL)
Inserting sample values:
INSERT INTO TestTable VALUES (1,'Test1a','TestDesc1a')
INSERT INTO TestTable VALUES (2,'Test1b','TestDesc1b')
INSERT INTO TestTable VALUES (3,'Test2a','TestDesc2a')
INSERT INTO TestTable VALUES (4,'Test2b','TestDesc2b')
Query to get the desired output using Pivot:
SELECT 'Name' AS [Column], [1], [2],[3],[4]
FROM
(SELECT Name, id from TestTable) AS ST
PIVOT
(Max(Name) FOR ID IN ([1], [2],[3],[4])) AS PT
UNION
SELECT 'Description' AS [Column], [1], [2],[3],[4]
FROM
(SELECT id,[Description] from TestTable) AS ST
PIVOT
(Max([Description]) FOR ID IN ([1], [2],[3],[4])) AS PT
ORDER BY [Column] DESC
OutPut:
Column 1 2 3 4
Name Test1a Test1b Test2a Test2b
Description TestDesc1a TestDesc1b TestDesc2a TestDesc2b
Hope this helps to solve your question.
You can use a static PIVOT if you only are going to have a few columns but I am guessing that you will have more than 2 IDs so you will probably want to use a Dynamic PIVOT for this query. Using a dynamic pivot will allow you to have more than the two ids you provided, it will grab all of the Ids from the table and then PIVOT:
create table temp
(
id int,
name varchar(10),
description varchar(20)
)
insert into temp values (1, 'Test1a', 'TestDesc1a')
insert into temp values (1, 'Test1b', 'TestDesc1b')
insert into temp values (2, 'Test2a', 'TestDesc2a')
insert into temp values (2, 'Test2b', 'TestDesc2b')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.id)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ''Name'' as [Column], ' + #cols + ' from
(
select id
, name
from temp
) x
pivot
(
max(name)
for id in (' + #cols + ')
) p
UNION
SELECT ''Description'' as [Column], ' + #cols + ' from
(
select id
, description
from temp
) x
pivot
(
max(description)
for id in (' + #cols + ')
) p '
execute(#query)
drop table temp
Results:
Column 1 2
Description TestDesc1b TestDesc2b
Name Test1b Test2b