How to split group result into unlimited individual columns - sql

If I have a table:
id tstamp x y
-----------------
1 111 1 A
2 111 2 B
3 111 3 C
4 222 1 D
5 222 2 E
6 222 3 F
7 333 1 G
8 333 2 H
9 333 3 I
... nnn ... ...
How can I split it into the following, where the number of columns is dependent on a range of tstamp?
x y111 y222 y333 ynnn
----------------------------------
1 A D G ...
2 B E H ...
3 C F I ...
It seems fundamentally simple, but I can't find any relevant example or API? There's a good 10 or so similar questions but they all deal with string handling, csv, xml???
I imagine it would be pretty straight forward to do in c# or another scripting language, but I would expect it should be an easy thing in sql?

you need to construct column values of tstamp for PIVOT and use dynamic query
DECLARE #cols NVARCHAR(MAX)
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + tstamp
FROM Table1
ORDER BY '],[' + tstamp
FOR XML PATH('')
), 1, 2, '') + ']'
DECLARE #query NVARCHAR(4000)
SET #query = N'SELECT x, '+
#cols +'
FROM
(SELECT x, y, tstamp
FROM Table1
) p
PIVOT
(
MAX(y)
FOR tstamp IN
( '+
#cols +' )
) AS pvt
'
exec sp_executesql #query

Related

Create a sparse matrix from observations

I am new to SQL server, I am a R user but R can't be used with my huge dataset (not enough memory).
What I want to do:
I want to create a sparse matrix from a table with only 2 columns, I dont have any value column, it seems easy but I didn't find the right way to do it.
My data:
ID_patient | ID_product
-----------------------
123 A
123 B
111 C
222 A
333 D
333 E
Ouput wanted:
ID_patient | A | B | C | D | E |
----------------------------------------------------
123 1 1
111 1
222 1
333 1 1
I have read that I can use the GROUP BY function or Pivot feature but what I have tried so far failed.
Edit
I don't know all the products, so the right way to do that is by using dynamic pivot ?
You can try something like PIVOT
See demo
Select * from
(select *, copy=Id_product from t)t
pivot
(
count(copy) for ID_product in ([A],[B],[C],[D],[E]))p
If you don't know A, B, C, D, .. before hand then you should go for dynamic pivot
update:
updated dynamic piv demo
declare #cols nvarchar(max)
declare #query nvarchar(max)
select #cols= Stuff((select ','+ quotename( ID_product) from
(select distinct id_product from t) t for xml path ('')),1,1,'')
select #query='Select * from
( select *, copy=Id_product from t ) t
pivot
(count(copy) for ID_product in ( '+#cols+' ))p '
exec(#query)
Try this
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
DECLARE #temp AS TABLE (ID_patient INT, ID_product varchar(10))
INSERT INTO #temp
SELECT 123,'A' UNION ALL
SELECT 123,'B' UNION ALL
SELECT 111,'C' UNION ALL
SELECT 222,'A' UNION ALL
SELECT 333,'D' UNION ALL
SELECT 333,'E'
SELECT * INTO #Temp
FROM #temp
DECLARE #Sql nvarchar(max),
#Col nvarchar(max),
#Col2 nvarchar(max)
SELECT #Col=STUFF((SELECT DISTINCT ', '+QUOTENAME(ID_product) FROM #Temp FOR XML PATH ('')),1,1,'')
SELECT #Col2=STUFF((SELECT DISTINCT ', '+'ISNULL('+QUOTENAME(ID_product)+','' '') AS '+QUOTENAME(ID_product) FROM #Temp FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT ID_patient,'+#Col2+'
FROM
(
SELECT *, DENSE_RANK()OVER( PARTITION BY ID_patient ORDER By ID_patient) AS [Val] FROM #Temp
)AS Src
PIVOT
(MAX(Val) FOR ID_product IN ('+#Col+')
)AS PVT '
PRINT #Sql
EXEC (#Sql)
Result
ID_patient A B C D E
------------------------------
111 0 0 1 0 0
123 1 1 0 0 0
222 1 0 0 0 0
333 0 0 0 1 1

SQL Server 2016 Pivot

I have one question regarding sql (MS SQL 2016) and pivot functionality.
First let me explain about the data structure.
Examples of tbl_Preise. There are several prices (Preis) for each area (Gebiet_von, Gebiet_bis) in relays (StaffelNr). All connected to the same freight (Fracht_id). There can be a different number of relays for each freight. All of these relays repeat for each area, so i.e. there is one price for relay 1 in area 1800 - 1899, but there is another price for relay 1 for area 1900 - 1999.
This is how the table tbl_Preise looks:
autoID Fracht_id Gebiet_von Gebiet_bis Zielland_Nr StaffelNr Preis Mindestpreis Mautkosten
16933 4 1800 1899 4 1 22,6481 0,00 0,00
16934 4 1800 1899 4 2 37,0843 0,00 0,00
16935 4 1800 1899 4 3 54,9713 0,00 0,00
16936 4 1900 1999 4 1 23,4062 0,00 0,00
16937 4 1900 1999 4 2 84,4444 0,00 0,00
Now I have another table tbl_Fracht_Staffeln where the quantity of the relay is saved.
This table looks like:
id fracht_id staffelNr menge
18 4 1 50
19 4 2 100
20 4 3 150
21 4 4 200
Now I want to combine these data, which can vary through different number of relays to each freight.
I have done this via this query:
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(staffelNr)
from tbl_Preise (nolock)
where fracht_id = #freightId
group by staffelNr
order by StaffelNr
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'
SELECT
Bezeichnung,
fracht_id,
gebiet_von,
gebiet_bis,
' + #cols + N'
from
(
select
l.Bezeichnung as Bezeichnung,
Zielland_Nr,
tbl_Preise.fracht_id,
gebiet_von,
gebiet_bis,
preis,
tbl_Preise.staffelNr as staffelNr
from
tbl_Preise (nolock)
left join
[dbo].[vw_Laender] l on tbl_Preise.Zielland_Nr = l.[Nummer]
where
tbl_Preise.Fracht_id = ' + cast(#freightId as nvarchar(100)) + '
) x
pivot
(
max(preis)
for staffelNr in (' + #cols + N')
) p
order by
gebiet_von, gebiet_bis'
exec sp_executesql #query;
This query gives me this result:
Bezeichnung fracht_id gebiet_von gebiet_bis 1 2 3 4 5 6
Germany 4 01800 01899 NULL NULL NULL NULL NULL NULL
Germany 4 06400 06499 NULL NULL NULL NULL NULL NULL
Germany 4 1800 1899 22,6481 37,0843 54,9713 64,4062 84,4444 94,6546
Germany 4 20500 20599 17,9088 27,3983 40,8845 46,7485 61,4905 67,835
Germany 4 21200 21299 17,9088 27,3983 40,8845 46,7485 61,4905 67,835
Germany 4 21500 21599 17,9088 27,3983 40,8845 46,7485 61,4905 67,835
Don't look exactly on the prices and the area codes. I've changed some in my example of tbl_Preise to make the relation and sense more clear.
So far so good. But now, as you can see, I have the staffelNr (1,2,3,4,...) as Header in my table.
I need there the column menge of table tbl_Fracht_Staffeln instead.
I tried already some joins and other stuff, but all did not work, because I have found no way to connect the column names (1,2,3,4...) to the table tbl_Fracht_Staffeln. Is there any way to achieve this?
Thank you very much in advance for help!
To do this you need to play with column header 2 times -
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX) , #freightId as int , #cols1 AS NVARCHAR(MAX)
select #freightId = 4
select #cols = STUFF((SELECT ',' + QUOTENAME(t1.staffelNr) + ' as ' + QUOTENAME(t2.menge )
from tbl_Preise t1 (nolock)
join tbl_Fracht_Staffeln t2(nolock)
on t1.fracht_id = t2.fracht_id and t1.staffelNr = t2.staffelNr
where t1.fracht_id = #freightId
group by t1.staffelNr , t2.menge
order by t1.StaffelNr
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols1 = STUFF((SELECT ',' + QUOTENAME(staffelNr)
from tbl_Preise (nolock)
where fracht_id = #freightId
group by staffelNr
order by StaffelNr
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'
SELECT
fracht_id,
gebiet_von,
gebiet_bis,
' + #cols + N'
from
(
select
Zielland_Nr,
tbl_Preise.fracht_id,
gebiet_von,
gebiet_bis,
preis,
tbl_Preise.staffelNr as staffelNr
from
tbl_Preise (nolock)
where
tbl_Preise.Fracht_id = ' + cast(#freightId as nvarchar(100)) + '
) x
pivot
(
max(preis)
for staffelNr in (' + #cols1 + N')
) p
order by
gebiet_von, gebiet_bis'
print #query
exec sp_executesql #query;

how to make a dynamic sql pivot

I have a select statement with the following
select country,city,school,class,quantity from tableA
I want the pivot to be based on class like in the below table:
country City School ClassA ClassB ClassC ClassD
XXX AAA SCH01 37 37 39 37
XXX BBB SCH02 12 12 1 12
XXX BBB SCH03 6 6 9 6
XXX DDD SCH04 1 1 1 1
YYY ABC SCH05 1 1 1 1
YYY CDE SCH06 1 1 1 1
YYY EDY SCH07 1 1 1 1
YYY ZER SCH08 1 1 1 1
SSS GFY SCH09 1 1 1 1
SSS AHY SCH10 1 1 1 1
MS SQL SERVER:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([class])
from [table-name]
group by [class]
order by [class]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [country], [city], [school],' + #cols + ' from
(
select [country], [city], [school],[class],[quantity]
from [table-name]
) x
pivot
(
sum([quantity])
for [class] in (' + #cols + ')
) p '
execute(#query);

Aggregate function within inner select in Pivot query using SQL Server

I have the following table:
select * from product;
slno item
---------------
1 HDD
2 PenDrive
3 RAM
4 DVD
5 RAM
6 HDD
7 RAM
7 RAM
7 RAM
Now I need to do pivoting for this table for which i am using following query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(item)
from product
group by item
order by item
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT slno,TotalProduct ,' + #cols + '
from
(
select slno,Count(*) as TotalProduct,item
from product
group by slno,item
) x
pivot
(
count(item)
for item in (' + #cols + ')
) p '
exec(#query)
Result:
slno TotalProducts DVD HDD PenDrive RAM
---------------------------------------------
1 1 0 1 0 0
2 1 0 0 1 0
3 1 0 0 0 1
4 1 1 0 0 0
5 1 0 0 0 1
6 1 0 1 0 0
7 3 0 0 0 1
Note The total of product RAM is 3 but in Column RAM showing only 1. I have used COUNT(*) aggregate function within the inner select statement in #query. How can i show actual count?
You only need to group by slno, not by the combination of slno and item. Therefore, you need to change the query which provides a source for your pivot as follows:
set #query = 'SELECT slno,totalproduct,' + #cols + '
from
(
select p.slno slno, c.count as totalproduct, p.item
from product p
inner join
(select slno, count(item) count
from product
group by slno) c on p.slno = c.slno
) x
pivot
(
count(item)
for item in (' + #cols + ')
) p '
Demo
Use following sub query instead of your sub query:
select slno,Count(*) OVER (PARTITION BY slno) as TotalProduct,item
from product
Edit: Count(*) Over(Partition by ...) supported in SQL Server 2012 and above versions.

Dynamic Pivot (row to columns)

I have a Table1:
ID Instance Name Size Tech
1 0 D1 123 ABC
1 1 D2 234 CDV
2 2 D3 234 CDV
2 3 D4 345 SDF
I need the resultset using Dynamic PIVOT to look like along with the headers:
ID | Instance0_Name | Instance0_Size | Instance0_Tech | Instance1_Name | Instance1_Size | Instance1_tech
1 | D1 | 123 | ABC | D2 | 234 | CDV
Any help would be appreciated. using Sql Server 2008.
Sorry for the earlier post.
Your desired output is not exactly clear, but you can use the both the UNPIVOT and PIVOT function to get the result
If you know the number of columns, then you can hard code the values:
select *
from
(
select id,
'Instance'+cast(instance as varchar(10))+'_'+col col,
value
from
(
select id,
Instance,
Name,
cast(Size as varchar(50)) Size,
Tech
from yourtable
) x
unpivot
(
value
for col in (Name, Size, Tech)
) u
) x1
pivot
(
max(value)
for col in
([Instance0_Name], [Instance0_Size], [Instance0_Tech],
[Instance1_Name], [Instance1_Size], [Instance1_Tech],
[Instance2_Name], [Instance2_Size], [Instance2_Tech],
[Instance3_Name], [Instance3_Size], [Instance3_Tech])
) p
See SQL Fiddle with Demo
Then if you have an unknown number of values, you can use dynamic sql:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ','
+ quotename('Instance'+ cast(instance as varchar(10))+'_'+c.name)
from yourtable t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('id', 'instance')
group by t.instance, c.name
order by t.instance
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select id,
''Instance''+cast(instance as varchar(10))+''_''+col col,
value
from
(
select id,
Instance,
Name,
cast(Size as varchar(50)) Size,
Tech
from yourtable
) x
unpivot
(
value
for col in (Name, Size, Tech)
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
If the result is not correct, then please edit your OP and post the result that you expect from both of the Ids you provided.