SQL separate columns by rows value (pivot) - sql

Table1
| MODULE | COUNT | YEAR |
-------------------------
| M1 | 12 | 2011 |
| M1 | 43 | 2012 |
| M2 | 5 | 2011 |
| M3 | 24 | 2011 |
| M4 | 22 | 2011 |
| M4 | 11 | 2012 |
| M5 | 10 | 2012 |
I want to display like this
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | - |
| M3 | 24 | - |
| M4 | 22 | 11 |
| M5 | - | 10 |

This can be done using PIVOT query. Or the following:
select Module,
SUM(CASE WHEN Year='2011' then Count ELSE 0 END) as [2011],
SUM(CASE WHEN Year='2012' then Count ELSE 0 END) as [2012]
FROM T
GROUP BY Module
SQL Fiddle demo

You can use PIVOT for that:
SELECT
Module,
[2011], [2012]
FROM
(
SELECT
*
FROM Table1
) AS SourceTable
PIVOT
(
SUM([Count])
FOR [Year] IN ([2011], [2012])
) AS PivotTable;
You can also use this dynamic query if you don't have limited Year
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ',' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module,' + #cols + '
FROM
(
Select *
FROM Table1
) dta
PIVOT
(
SUM([Count])
FOR [Year] IN (' + #cols + ')
) pvt '
EXECUTE(#query);
Result:
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | (null) |
| M3 | 24 | (null) |
| M4 | 22 | 11 |
| M5 | (null) | 10 |
See this SQLFiddle
Update
You can also use this alternative dynamic method: (Dynamic of the query given by #valex)
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ','
+ ' SUM(CASE WHEN YEAR= ''' + CAST(Year AS varchar(50))
+ ''' THEN [COUNT] ELSE ''-'' END) AS ' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module, ' + #cols + '
FROM Table1 GROUP BY Module'
EXECUTE(#query);
See this SQLFiddle

SELECT *
FROM Tablename
PIVOT (
AVG([Count] FOR [Year] IN (2011, 2012, 2013)) AS AvgCount
)

I think you need to look into the relational operator PIVOT.
Using PIVOT and UNPIVOT
SELECT Module,
[2011], [2012]
FROM T
PIVOT (SUM(Count) FOR Year IN ([2011], [2012])) AS PivotTable;

Related

How to convert unknown values in column to row in SQL Server [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 3 years ago.
How to convert unknown values in column to row with fixed column and sum value in another column in SQL Server?
This example
| Id | Date | Amount1 | Amount2
+----+------+---------+--------
| 1 | 2018 | 1000 | 100
| 2 | 2018 | 2000 | 200
| 2 | 2018 | 3000 | 300
| 1 | 2019 | 4000 | 400
| 1 | 2019 | 5000 | 500
| .. | .... | .... | ...
I want this
| Id | 2018Amount1 | 2018Amount2 | 2019Amount1 | 2019Amount2 | ...
+----+-------------+-------------+-------------+-------------+-------
| 1 | 1000 | 100 | 9000 | 900 | ...
| 2 | 5000 | 500 | 0 | 0 | ...
You can use dynamic pivot
DECLARE #ColName NVARCHAR(MAX) =''
SELECT #ColName = #ColName + ', '+ ColName
FROM ( SELECT DISTINCT QUOTENAME( CAST([Date] AS VARCHAR(4)) + 'Amount1')
+ ', ' + QUOTENAME(CAST([Date] AS VARCHAR(4)) + 'Amount2') ColName FROM TestTable )T
SET #ColName = STUFF (#ColName,1,1,'')
DECLARE #SqlText NVARCHAR(MAX) =
'SELECT * FROM (
SELECT Id, CAST([Date] AS VARCHAR(4)) + ''Amount1'' AS [Date], Amount1 AS Amount FROM TestTable
UNION
SELECT Id, CAST([Date] AS VARCHAR(4)) + ''Amount2'' [Date], Amount2 AS Amount FROM TestTable
) SRC
PIVOT(SUM(Amount) FOR Date IN (' + #ColName+ ') ) AS PVT'
EXECUTE sp_executesql #SqlText
Use conditional aggregation :
select Id,
sum( case when Date = 2018 then Amount1 end ) as "2018Amount1",
sum( case when Date = 2018 then Amount2 end ) as "2018Amount2",
sum( case when Date = 2019 then Amount1 end ) as "2019Amount1",
sum( case when Date = 2019 then Amount2 end ) as "2019Amount2"
from tab
group by Id

SQL Server group pivot table according to id

I have a table called 'info':
|InfoId | OtherId | Year | InfoNo |
-------------------------------------
|1 | 1 | 2012 | abc |
|2 | 1 | 2013 | def |
|3 | 1 | 2014 | ghi |
I want to get this result:
| OtherId | 2012 | 2013 | 2014 |
---------------------------------
| 1 | abc | def | ghi |
i tried using:
SELECT *
FROM info
PIVOT (MAX(InfoNo)
FOR Year in ([2012],[2013],[2014])) AS pvt
where OtherId= '1'
But instead, I get this result:
| OtherId | 2012 | 2013 | 2014 |
---------------------------------
| 1 | abc | | |
| 1 | | def | |
| 1 | | | ghi |
How do I make the three rows group as one row according to 'OtherId'?
EDIT
I updated my SQL to the following and manage to get the result I wanted as well:
SELECT
OtherId,
MAX(case WHEN Year = '2012' THEN InvoiceNo ELSE NULL end) AS [2012],
MAX(case WHEN Year = '2013' THEN InvoiceNo ELSE NULL end) AS [2013],
MAX(case WHEN Year = '2014' THEN InvoiceNo ELSE NULL end) AS [2014]
FROM info
Thanks all for helping me.
You need to group your result by OtherId, I did a quick test and here's the result:
Select * FROM info
InfoId OtherId Year InfoNo
----------- ----------- ----------- ----------
1 1 2012 abc
2 1 2013 edf
3 1 2014 ghk
SELECT
pvt.OtherId,
MAX([2012]) AS '2012',
MAX([2013]) AS '2013',
MAX([2014]) AS '2014'
FROM info AS src
PIVOT (
MAX(InfoNo)
FOR [Year] IN ([2012],[2013],[2014]))
AS pvt
GROUP BY pvt.OtherId
Result:
OtherId 2012 2013 2014
----------- ---------- ---------- ----------
1 abc edf ghk
Hope that helps your cause.
IF OBJECT_ID('Tempdb..#Temp') IS NOt NUll
Drop Table #Temp
;With cte(InfoId ,OtherId ,[Year],InfoNo)
AS
(
SELECT 1,1,'2012' , 'abc' Union all
SELECT 2,1,'2013' , 'def' Union all
SELECT 3,1,'2014' , 'ghi'
)
SELECT * INTO #Temp FROM cte
DECLARE #dynamicCol nvarchar(max),#dynamicCol2 nvarchar(max),
#Sql nvarchar(max)
SELECT #dynamicCol=STUFF((SELECT DISTINCT ', ' + QUOTENAME(Year) FROM #Temp
FOR XML PATH('')),1,1,'')
SELECT #dynamicCol2=STUFF((SELECT DISTINCT ', ' + 'MAX('+ QUOTENAME(Year) +' )' +' AS '+ QUOTENAME(Year) FROM #Temp
FOR XML PATH('')),1,1,'')
SET #Sql= N' SELECT [OtherId] , '+ #dynamicCol2 +' From
(
SELECT InfoId ,OtherId ,[Year],InfoNo From
#temp
)AS Src
PIVOT
(
MAX([InfoNo]) For [Year] IN ('+#dynamicCol+')
)
AS Pvt
GROUP BY Pvt.OtherId
'
PRINT #Sql
EXEC(#Sql)
OutPut
| OtherId | 2012 | 2013 | 2014 |
---------------------------------
| 1 | abc | def | ghi |

String concatenation in Dynamic SQL using PIVOT table

I have to work on a mapping from an ERP system to a MySQL database. The structure that is present in the ERP system is:
_____________________________________
| Article | Feature | Criterion |
|---------|---------------|-----------|
| Art1 | size | 4*10 |
| Art1 | color | red |
| Art1 | functionality | doesA |
| Art1 | ... | ... |
| Art2 | size | 2*5 |
| Art2 | color | green |
| Art2 | functionality | doesB |
| Art2 | ... | ... |
-------------------------------------
What i need to do is map it like this:
________________________________________________
| Article | size | color | functionality | ... |
|---------|------|-------|---------------|-------|
| Art1 | 4*10 | red | doesA | ... |
| Art2 | 2*5 | green | doesB | ... |
------------------------------------------------
I can access the ERP system via T-SQL and can perform a working dynamic query, that provides me a table and looks like:
DECLARE #cols AS nvarchar(MAX),
#query AS nvarchar(MAX)
SELECT #cols = stuff((SELECT DISTINCT ', ' + quotename(f.Feature) + ''
FROM CRITERION c, FEATURE f
WHERE --necessary joins
FOR xml PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = N'SELECT Article, ' + #cols + N'
FROM (
SELECT Article, Feature, Criterion
FROM --necessary tables
WHERE --necessary joins
) x
pivot
(
max(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
EXEC sp_executesql #query;
The problem that is coming up now is, that the system features multiple selection for some of the features:
_____________________________________
| Article | Feature | Criterion |
|---------|---------------|-----------|
| Art3 | color | red |
| Art3 | color | green |
-------------------------------------
and the query just gives me the first result in the table.
________________________________________
| Article | size | color | functionality |
|---------|------|-------|---------------|
| Art3 | ... | red | ... |
----------------------------------------
So my question is, if there is any way to add a string concatenation either in the subquery 'x' or in the pivot table 'p', so the result becomes following:
_____________________________________________
| Article | size | color | functionality |
|---------|------|------------|---------------|
| Art3 | ... | red, green | ... |
---------------------------------------------
#Serg has the right idea but the fields seem to be off. This should be closer.
SET #query = N'
SELECT Article, ' + #cols + N'
FROM (
SELECT Article,
Feature,
Criterion = STUFF(
(SELECT '', '' + t2.Criterion
FROM t1 as t2
WHERE t2.Article = t1.Article
AND t2.[Feature] = t1.[Feature]
FOR XML PATH('''')), 1, 2,'''')
FROM (SELECT Article, Feature, Criterion
FROM --necessary tables
WHERE --necessary joins) t1
) x
pivot
(
MAX(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
GROUP BY features first using the same FOR XML trick. Kind of
SET #query = N'SELECT Article, ' + #cols + N'
FROM (
SELECT Article, Criterion,
Feature = stuff(
(SELECT '',''+ t2.Feature
FROM ttt as t2
WHERE t2.Article = t1.Article AND
t2.Criterion = t1.Criterion
FOR XML PATH(''))
,1,1,'''')
FROM ttt t1
GROUP BY Article, Criterion
) x
pivot
(
max(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
Replace ttt with real data sources.

Dynamic field content as Row Sql

I have the following dataset on a sql database
----------------------------------
| ID | NAME | AGE | STATUS |
-----------------------------------
| 1ASDF | Brenda | 21 | Single |
-----------------------------------
| 2FDSH | Ging | 24 | Married|
-----------------------------------
| 3SDFD | Judie | 18 | Widow |
-----------------------------------
| 4GWWX | Sophie | 21 | Married|
-----------------------------------
| 5JDSI | Mylene | 24 | Singe |
-----------------------------------
I want to query that dataset so that i can have this structure in my result
--------------------------------------
| AGE | SINGLE | MARRIED | WIDOW |
--------------------------------------
| 21 | 1 | 1 | 0 |
--------------------------------------
| 24 | 1 | 1 | 0 |
--------------------------------------
| 18 | 0 | 0 | 1 |
--------------------------------------
And the status column can be dynamic so there will be more columns to come.
Is this possible?
Since you are using SQL Server, you can use the PIVOT table operator like this:
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN(Single, Married, Widow)
) AS p;
SQL Fiddle Demo
To do it dynamically you have to use dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(status)
FROM tablename
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = '
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN( ' +#cols + ')
) AS p;';
execute(#query);
Updated SQL Fiddle Demo

grouping and switching the columns and rows

I don't know if this would officially be called a pivot, but the result that I would like is this:
+------+---------+------+
| Alex | Charley | Liza |
+------+---------+------+
| 213 | 345 | 1 |
| 23 | 111 | 5 |
| 42 | 52 | 2 |
| 323 | | 5 |
| 23 | | 1 |
| 324 | | 5 |
+------+---------+------+
my input data is in this form:
+-----+---------+
| Apt | Name |
+-----+---------+
| 213 | Alex |
| 23 | Alex |
| 42 | Alex |
| 323 | Alex |
| 23 | Alex |
| 324 | Alex |
| 345 | Charley |
| 111 | Charley |
| 52 | Charley |
| 1 | Liza |
| 5 | Liza |
| 2 | Liza |
| 5 | Liza |
| 1 | Liza |
| 5 | Liza |
+-----+---------+
because I have approximately 100 names, I don't want to have to do a ton of sub queries lik this
select null, null, thirdcolumn from...
select null, seconcolumn from...
select firstcolumn from...
Is there a way to do this with PIVOT or otherwise?
You can do this with dynamic PIVOT and the ROW_NUMBER() function:
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT DISTINCT *
FROM #test)
,cte2 AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Apt)RowRank
FROM cte)
SELECT *
FROM cte2
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Distinct List, Specific Order
Edit: If you don't want the list to be distinct, eliminate the first cte above, and if you want to keep arbitrary ordering change the ORDER BY to (SELECT 1):
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT *
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Full List, Arbitrary Order
And finally, if you didn't want the RowRank field in your results, just re-use the #cols variable in your SELECT:
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT '+#cols+'
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
Oh, this is something of a pain, but you can do it with SQL. You are trying to concatenate the columns.
select seqnum,
max(case when name = 'Alex' then apt end) as Alex,
max(case when name = 'Charley' then apt end) as Charley,
max(case when name = 'Liza' then apt end) as Liza
from (select t.*, row_number() over (partition by name order by (select NULL)) as seqnum
from t
) t
group by seqnum
order by seqnum;
As a note: there is no guarantee that the original ordering will be the same within each column. As you know, SQL tables are inherently unordered, so you would need a column to specify the ordering.
To handle multiple names, I'd just get the list using a query such as:
select distinct 'max(case when name = '''+name+''' then apt end) as '+name+','
from t;
And copy the results into the query.