MS SQL Pivot dat from long with multiple columns - sql

I want to pivot a table from long to wide that has multiple id columns.
I found solutions for one column but not really for multiple columns.
The closest solution that I could adapt for one column was this one
T-SQL PIVOT data from long form to wide by a date
My table looks more or less like this,
create table t (id int, date date, varA_id int, VarB_id int, value int)
insert into t values
(1,'2005-01-20',1, 1,197)
,(2,'2005-01-20',1,2,58)
,(3,'2005-01-20',1,3,90)
,(4,'2005-01-20',2,1,210)
,(5,'2005-01-20',2,2,133)
,(6,'2005-01-20',2,3,67)
,(7,'2005-01-20',3,1,87)
,(8,'2005-01-20',3,2,87)
,(9,'2005-01-20',3,3,87)
Actually without the date, but that's fine. I want to spread in a way that I get columns for each permutation of VarA_id and VarB_id
So my expected result would look like this
My actual table has three _id columns and more permutations, so I really need a generic solution.
Based on the other solution in my link I was hoping something like this would work. I adjust the top part that creates the column names and this would work. I dont know how to realy adjust the bottom part that fetches the values.
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + 'VarA_'+convert(varchar(10),varA_id) + '_VarB_'+convert(varchar(10),varB_id)
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select Id, date, ' + #cols + '
from (
select Id, date, varA_id = ''v''+convert(varchar(10),varA_id), value
from t
) as t
pivot (sum([value]) for [varA_id] in (' + #cols + ') ) p'
select #sql
exec(#sql);

The main problem with your dynamic sql?
It was that the name constructed in the source query didn't match the generated column names.
Here's a fix :
declare #cols varchar(max) = null;
declare #sql nvarchar(max);
select #cols = concat(#cols+', '+char(10), quotename(concat('VarA_', varA_id, '_VarB_', varB_id)))
from test
group by varA_id, varB_id
order by varA_id, varB_id;
-- select #cols as cols;
set #sql = 'select * '+char(10)+
'from ( ' +char(10)+
' select [date], [value], ' +char(10)+
' concat(''VarA_'',varA_id,''_VarB_'',varB_id) as Col ' +char(10)+
' from test ' +char(10)+
') as src ' +char(10)+
'pivot (sum([value]) for Col in ('+char(10)+ #cols +char(10)+')) pvt';
-- select #sql as sql;
exec(#sql);
date
VarA_1_VarB_1
VarA_1_VarB_2
VarA_1_VarB_3
VarA_2_VarB_1
VarA_2_VarB_2
VarA_2_VarB_3
VarA_3_VarB_1
VarA_3_VarB_2
VarA_3_VarB_3
2005-01-20
197
58
90
210
133
67
87
87
87
db<>fiddle here

Ok my own solution so far is to add a help column and basically just do what the other questions does. I need to improve on this, so I dont add a column and I would like better names, but at least this shows what I want.
alter table t add help_col nvarchar(10)
Update t
set help_col=convert(varchar(10),varA_id)+convert(varchar(10),varB_id)
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + 'v'+convert(varchar(10),help_col)
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select date, ' + #cols + '
from (
select date, help_col = ''v''+convert(varchar(10),help_col), value
from t
) as t
pivot (sum([value]) for [help_col] in (' + #cols + ') ) p'
select #sql
exec(#sql);
Which results in
select date, v11, v12, v13, v21, v22, v23, v31, v32, v33 from ( select date, help_col = 'v'+convert(varchar(10),help_col), value from t ) as t pivot (sum([value]) for [help_col] in (v11, v12, v13, v21, v22, v23, v31, v32, v33) ) p
which yields
date v11 v12 v13 v21 v22 v23 v31 v32 v33
2005-01-20 197 58 90 210 133 67 87 87 87

Related

Syntax Error when trying to use Dynamic Pivot Query

I've been playing around with the PIVOT function for a while. I have a table that looks like this
IdPersona IdEstadoSocio Periodo
-------------------------------
1044659 6 2021-06
721396 5 2021-06
219886 6 2021-06
1906611 7 2021-06
1027906 2 2021-06
Every ClientID is repeated once for each month. Every month new rows are added to the table with a new period associated to the row. It's an incrementing table.
What I need to do is to PIVOT the table, so that it basically ends up like this:
IdPersona 2021-01 2021-02 2021-03 2021-04
----------------------------------------
1044659 6 3 1 4
721396 5 5 2 6
219886 6 6 4 1
1906611 7 7 9 2
1027906 2 1 1 1
Naturally, I want my code to be dynamic. I don't want to harcode every month like:
SELECT *
FROM [fid].[FACT_Socios_Resumen_Periodo]
PIVOT(MAX(IdEstadoSocio)
FOR [Periodo] IN ([2021-01], [2021-02], [2021-03], [2021-04], [2021-05], [2021-06])) AS PVTTable
So I've been trying several dynamic solutions found in here, but none of them are working for me, and I don't know why. And it's driving me crazy.
This solutions gives me
Incorrect Syntax near 'FOR'
My code below:
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(periodo)
FROM [fid].[FACT_Socios_Resumen_Periodo]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT IdPersona, ' + #cols + ' from
(
select IdPersona
, IdEstadoSocio
, periodo
from [fid].[FACT_Socios_Resumen_Periodo]
) x
pivot
(
max(IdEstadoSocio)
for periodo in (' + #cols + ')
) p '
execute(#query)
The second solution provided in the same link gives me
A variable that has been assigned in a SELECT statement cannot be
included in a expression or assignment when used in conjunction with a
from clause.
Which is kinda understandable, as the query tries to solve getting the DISTINCT values of [Period] in a recursive way, as far as I understood. However, everybody accepted that as a viable answer too.
My code below:
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(periodo) + ',' FROM (select distinct periodo from [fid].[FACT_Socios_Resumen_Periodo] ) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
set #query =
'SELECT * from
(
select idpersona, idestadosocio, periodo from [fid].[FACT_Socios_Resumen_Periodo]
) src
pivot
(
max(idestadosocio) for periodo in (' + #cols + ')
) piv'
execute(#query)
The third solution I tried gives me the same error noted above, with a similar syntax but not exactly the same. It tries to solve the DISTINCT [Period]s recursively.
My code below:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName = ISNULL(#ColumnName + ',', '') + QUOTENAME(periodo)
FROM (SELECT DISTINCT periodo FROM [fid].[FACT_Socios_Resumen_Periodo]) AS Periodos
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT IdPersona, ' + #ColumnName + '
FROM [fid].[FACT_Socios_Resumen_Periodo]
PIVOT(MAX(IdEstadoSocio)
FOR periodo IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
So, what exactly am I doing wrong?? Can anybody throw me a hint? I'm failing over solutions approved by the whole community, and I can't understand why. I basically tried to understand the code, copied it, and replaced columns and tables. Just that. I didn't change anything more. Thanks in advance!
Looking at your third solution.
First thing I notice is that you are not coalescing the initial null value of #ColumnName when you do your concatenation.
SELECT #ColumnName = Isnull(#ColumnName + ',', '') + QUOTENAME(periodo)
FROM (SELECT DISTINCT periodo FROM [fid].[FACT_Socios_Resumen_Periodo]) AS Periodos
That should solve your problem.
If you would PRINT the result #ColumnName and #DynamicPivotQuery, before you execute it, it will usually show you where the problems are.
For versions of sql that do not support SELECT #Variable
SQL Azure and SQL DW only support using SET when setting variable values.
You can use STRING_AGG() on these servers
set #ColumnName = (select string_agg(QUOTENAME(periodo),',') from (select distinct periodo from FACT_Socios_Resumen_Periodo) t)

How to display columns as rows in a SQL query?

I'm trying to display exam results form simple database containing two tables tblStudents and tblExamResults.
tblstudents contains student ID and Full_Name columns
In tblexamResults columns are Student_id, Subject and Marks.
as in below Picture
1- Currently I am displaying Student results using this query
SELECT tblStudents.Full_Name, tblExamResults.Subject, tblExamResults.Marks
FROM tblExamResults INNER JOIN
tblStudents ON tblExamResults.Student_id = tblStudents.Student_ID
order by tblStudents.Full_Name
2 - And results looks like in the following picture:
3 - But what I want is to display each subject as row and get result of each subject below it
So that each student's result is displayed in the same row:
Student_Name sub1_result sub2_Result sub3_Result
Like in the following picture (Excel screenshot)
So:
How I can display data in that format?
Is that possible in SQL Server?
select fullname,[english] english, [history] history, [physics] physics
from
(
select fullname,subject,marks
from (yourquery)
) src
pivot
(
max(marks)
for subject in ([english], [history], [physics])
) piv;
or
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.subject)
FROM (yourquery) c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT fullname, ' + #cols + ' from
(
select fullname,subject,marks
from (your query)
) x
pivot
(
max(marks)
for subject in (' + #cols + ')
) p '
execute(#query)
fullname english history physics
a 85 70 60
i 60 100 89
s 90 90 99
Finally I've used next part of #Chanukya Answer with little Changes
in that Answer i was getting error at line 5 becouse of parentheses in FROM (yourquery) c
Declare #query nvarchar(max);
DECLARE #cols AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.subject)
FROM tblExamResults c ' parentheses Removed from (tblExamResults) c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
print(#cols)
set #query = '
SELECT *
FROM (
SELECT tblStudents.Full_Name, tblExamResults.Subject, tblExamResults.Marks
FROM tblExamResults INNER JOIN
tblStudents ON tblExamResults.Student_id = tblStudents.Student_ID
) as s
PIVOT
(
sum(marks) FOR subject IN ('+ #cols +')
)AS pvt'
;
execute(#query)

SQL query unknown rows into columns

I asked this question and it was marked as a duplicate of How to pivot unknown number of columns & no aggregate in SQL Server?, but that answer doesn't help me.
I have a table of data that looks like this, with an unknown number of rows and values.
RecID Name Value
1 Color Red
2 Size Small
3 Weight 20lbs
4 Shape Square
I need a query that will return the data like this, building out a column for each row. I cannot hard code anything except the column headers 'Name' and 'Value'.
Color Size Weight Shape
Red Small 20lbs Square
Here is what I have so far that is partly working:
INSERT INTO #Table VALUES
(1,'Color' ,'Red'),
(2,'Size' ,'Small'),
(3,'Weight','20lbs'),
(4,'Shape' ,'Square')
;with mycte
as
(
SELECT rn,cols,val
FROM (SELECT row_number() over(order by Name) rn, Name, Value
FROM #Table) AS src1
UNPIVOT (val FOR cols
IN ( [Name], [Value])) AS unpvt
)
SELECT *
FROM (SELECT rn,cols,val
FROM mycte) AS src2 PIVOT
( Max(val) FOR rn IN ([1], [2], [3])) AS pvt
Which returns:
cols 1 2 3
Name Color Shape Size
Value Red Square Small
Two problems with this that I can't seem to resolve.
I don't need the column headers and the first column that has cols, Name, Value in it.
Can't figure out how to have it build a column for each row without specifying the [x] identifiers.
Any guidance would be great I've been stuck on this a while now.
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(name)
FROM #t -- your table here
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
declare #q nvarchar(max)
set #q = '
select *
from (
select rn, name, Value
from (
select *, row_number() over (partition by name order by RecID desc) as rn
from #t -- your table here
) as x
) as source
pivot (
max(Value)
for name in (' + #collist + ')
) as pvt
'
exec (#q)
Until now, I have reached to following code, hope it helps you,
Current outpu comes as
Color Shape Size Weight
Red NULL NULL NULL
NULL NULL Small NULL
NULL NULL NULL 20lbs
NULL Square NULL NULL
Create table DyTable
(
tid int,
Name varchar(20),
Value varchar(20)
)
INSERT INTO DyTable VALUES
(1,'Color' ,'Red'),
(2,'Size' ,'Small'),
(3,'Weight','20lbs'),
(4,'Shape' ,'Square')
DECLARE #Cols NVARCHAR(MAX);
DECLARE #Cols1 NVARCHAR(MAX);
SELECT #Cols = STUFF((
SELECT DISTINCT ', ' + QUOTENAME(Name)
FROM DyTable
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,'')
,#Cols1 = STUFF((
SELECT DISTINCT ', max(' + QUOTENAME(Name) + ') as ' + Name
FROM DyTable
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,'')
DECLARE #Sql NVARCHAR(MAX)
Select #Cols
SET #Sql = 'Select '+ #Cols1 +'
from (SELECT ' + #Cols + '
FROM DyTable t
PIVOT (MAX(Value)
FOR Name
IN (' + #Cols + ')
)P)a'
EXECUTE sp_executesql #Sql

Dynamic pivot table with multiple columns in sql server

I am trying to pivot table DYNAMICALLY but couldn't get the desired result.
Here is the code to create a table
create table Report
(
deck char(3),
Jib_in float,
rev int,
rev_insight int,
jib_out float,
creation int
)
insert into Report values
('A_1',0.345,0,0,1.23,20140212),
('B_2',0.456,0,4,2.34,20140215),
('C_3',0.554,0,6,0.45,20140217),
('D_4',0.231,0,8,7.98,20140222),
('E_5',0.453,0,0,5.67,20140219),
('F_6',0.344,0,3,7.23,20140223)'
Code written so far.... this pivots the column deck and jib_in into rows but thats it only TWO ROWS i.e the one i put inside aggregate function under PIVOT function and one i put inside QUOTENAME()
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(deck)
FROM (SELECT p.deck FROM dbo.report AS p
GROUP BY p.deck) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT p.deck, p.jib_in
FROM dbo.report AS p
) AS j
PIVOT
(
SUM(jib_in) FOR deck IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
I need all the columns to be pivoted and show on the pivoted table. any help would be appreciated. I am very new at dynamic pivot. I tried so many ways to add other columns but no avail!!
I know there are other ways please feel free to mention if there is any other way to get this right.
Please use this (If you are getting Collation issue, please change all the 3 INT datatypes):
STATIC code:
SELECT HEADER, [A_1],[B_2],[C_3],[D_4],[E_5],[F_6]
FROM
(SELECT DECK,HEADER, VALUE FROM REPORT
UNPIVOT
(
VALUE FOR HEADER IN ([JIB_IN],[REV],[REV_INSIGHT],[JIB_OUT],[CREATION])
) UNPIV
) SRC
PIVOT
(
SUM(VALUE)
FOR DECK IN ([A_1],[B_2],[C_3],[D_4],[E_5],[F_6])
) PIV
Using Dynamic SQL:
DECLARE #COLSUNPIVOT AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX),
#COLSPIVOT AS NVARCHAR(MAX)
SELECT #COLSUNPIVOT = STUFF((SELECT ','+QUOTENAME(C.NAME)
FROM SYS.COLUMNS AS C
WHERE C.OBJECT_ID = OBJECT_ID('REPORT') AND C.NAME <> 'DECK'
FOR XML PATH('')), 1, 1, '')
SELECT #COLSPIVOT = STUFF((SELECT ',' + QUOTENAME(DECK)
FROM REPORT T FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
SET #QUERY
= 'SELECT HEADER, '+#COLSPIVOT+'
FROM
(
SELECT DECK,HEADER,VALUE FROM REPORT
UNPIVOT
(
VALUE FOR HEADER IN ('+#COLSUNPIVOT+')
) UNPIV
) SRC
PIVOT
(
SUM(VALUE)
FOR DECK IN ('+#COLSPIVOT+')
) PIV'
EXEC(#QUERY)

select from the result of execute(#query)

I am using this SQL query
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Animal2)
from animals
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Animal1, ' + #cols + ' from
(
select animal1, animal2, Corelation
from animals
) x
pivot
(
min(Corelation)
for animal2 in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with demo
When I execute the query I get a table as a return.
How can I select from that table? I tried to use SELECT * FROM (*past here the script*) but it did not work. I just need to use the result of the execute(#query) as a table and select from it (to put it in a new table). How can I do it?
Thanks
NOTE: that query was an answer of this SO question
Use the Insert into ... exec format, like this:
CREATE TABLE #tmp1 (
[Animal1] varchar(5),
[Cat] decimal(10, 5),
[Dog] decimal(10, 5),
[Mouse] decimal(10, 5)
)
Insert Into #Tmp1
execute(#query)
select * from #tmp1
where cat = 1
Of course, since the column names are dynamic, you'll need to shift the create statement to dynamic sql too.
SQL Fiddle with the fixed version
SQL Fiddle with the dynamic version
Use into and a global temporary table - then you don't have to define the table columns in advance.
set #query = 'SELECT Animal1, ' + #cols +
+' into ##temp '
+' from
(
select animal1, animal2, Corelation
from animals
) x
pivot
(
min(Corelation)
for animal2 in (' + #cols + ')
) p '
select * from ##temp