Converting multiple rows to columns with heading from the same row - sql

I have a table that holds details for flats - contents of this table is similar to the following:
| flat | description | Amount | Date
--------------------------------------
| flat1 | electricity | 1 |1/1/2016
| flat1 | water | 2 |1/1/2016
| flat1 | levy | 3 |1/1/2016
| flat2 | electricity | 1 |1/1/2016
| flat2 | water | 2 |1/1/2016
| flat2 | levy | 3 |1/1/2016
I need a SQL view that can produce something like the following and any help would be appreciated:
| Flat | electricity | water|levy | next description| and so on |
---------------------------------------------------------------
| flat1 | 1 | 2 | 3 | next amount | next amount |
| flat2 | 1 | 2 | 3 | next amount | next amount |

This is called table pivoting. Here's one option using conditional aggregation assuming you know the number of potential columns:
select flat,
max(case when description = 'electricity' then amount end) electricity,
max(case when description = 'water' then amount end) water,
max(case when description = 'levy' then amount end) levy
from yourtable
group by flat
If you don't know the maximum number of columns, look up dynamic pivot -- there are lots of examples on how to do it:
https://stackoverflow.com/a/10404455/1073631

drop table #temp
create table #temp (flat varchar(5), description varchar(128), amount int, date datetime)
insert into #temp (flat, description, amount, date) values
('flat1','electricity',1,'1/1/2016'),
('flat1','water',2,'1/1/2016'),
('flat1','levy',3,'1/1/2016'),
('flat2','electricity',1,'1/1/2016'),
('flat2','water',2,'1/1/2016'),
('flat2','levy',3,'1/1/2016')
select * from #temp
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(description)
FROM #temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT flat, ' + #cols + ' from
(select flat,amount, description from #temp) x
pivot
(
max(amount)
for description in (' + #cols + ')
) p '
execute(#query)
You can add a date filter by placing a WHERE clause in teh #query portion. i.e. from #temp where date between '1/1/2016' and '3/1/2016'

Related

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.

Get rows of table as columns in second table

I apologize in advance if this question has been asked before.
I have two tables. Table one has three columns CustomerID, SequenceNum, Value, table two has a large amount of columns. I would like to fill in the columns of table two with the values of table one by column, not by row.
An example:
------------------------------------
| CustomerID | SequenceNum | Value |
------------------------------------
| 1 | 1 | A |
------------------------------------
| 1 | 2 | B |
------------------------------------
| 1 | 3 | C |
------------------------------------
| 2 | 1 | Q |
------------------------------------
| 2 | 2 | R |
------------------------------------
| 3 | 1 | X |
------------------------------------
becomes
---------------------------------------------------------------------
| CustomerID | PrimaryVal | OtherVal1 | OtherVal2 | OtherVal3 | ... |
---------------------------------------------------------------------
| 1 | A | B | C | NULL | ... |
---------------------------------------------------------------------
| 2 | Q | R | NULL | NULL | ... |
----------------------------------------------------------------------
| 3 | X | NULL | NULL | NULL | ... |
---------------------------------------------------------------------
In essence. Each unique CustomerID in table one will have a single row in table two. Each SequenceNum of a particular CustomerID will fill in a column in table two under PrimaryVal, OtherVal1, OtherVal2, etc.. A row which has a SequenceNum equal to 1 will fill the PrimaryVal field, and 2-18 (the maximum sequence length is 18) will fill OtherVal#.
The main problem I see is the variable amount of values in a sequence. Some sequences may only contain 1 row, some will fill up all 18 spots, and anything in between.
Any advice on how to solve this problem would be greatly appreciated. Thank you.
Given you know that it is 18 columns max, I would take the normal pivot route.
select customerID, Pivoted.*
from Customer
pivot( Value for sequencenum in (1,2,3,4,5,6, upto 18)) as Pivoted
I've been lazy here and not aliased the columns but you can if you need to.
This can be done with a Dynamic Pivot. The first STUFF Select (or any other GROUP_CONCAT hack) is used to determine the columns needed (based on the values of SequenceNum) before applying this into a dynamic pivot which then assigns the values to these columns.
You'll need to take an opinion on an aggregate during the pivot (I've used Min), although if there aren't duplicate CustomerId, SequenceNum tuples, this is a fairly arbitrary choice:
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(SequenceNum)
FROM Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = N'SELECT CustomerID, ' + #cols + N' from
Table1
pivot
(
min(Value)
for SequenceNum in (' + #cols + N')
) p ';
execute(#query);
SqlFiddle here

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

SQL separate columns by rows value (pivot)

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;

Transpose Column Value to Row in T-SQL

I know this might have been asked before but I really could not find the answer. I have a temporary table named #TEMP which looks like this:
+===============================+=============================+
| NAME | ATTRIBUTE |
+===============================+=============================+
| BadgeType | Permanent |
+-------------------------------+-----------------------------+
| PrimaryLocationInCompany | No |
+-------------------------------+-----------------------------+
| AdminAccessToProductionServer | No |
+-------------------------------+-----------------------------+
| AccessToImportantFIles | No |
+-------------------------------+-----------------------------+
| Waiver_Number | 56987 |
+-------------------------------+-----------------------------+
| Summary | User not much active |
+-------------------------------+-----------------------------+
| TimeStamp | 3/3/2009 |
+-------------------------------+-----------------------------+
| UserID | 86478925 |
+-------------------------------+-----------------------------+
What I want to do is to transpose both the Name and Attribute values to rows. The Attribute values may vary but the Name values are always fixed.
The result should look like this:
+----------+---------------+------------------------------+--------------------------------+-----------------------+--------------------------------------------------------+----------+-----------+
| UserID | BadgeType | PrimaryLocationIntelFacility | adminAccessToProductionServer | AccessToClassifiedData| Info_Sec_Waiver_Number | Summary | TimeStamp |
+----------+---------------+------------------------------+--------------------------------+-----------------------+--------------------------------------------------------+----------+-----------+
| 11313403 | GREEN | No | No | No | This contingent worker is eligible for remote access. | 3/3/2009 | |
+----------+---------------+------------------------------+--------------------------------+-----------------------+--------------------------------------------------------+----------+-----------+
Try this
SELECT UserID,BadgeType,PrimaryLocationInCompany,AdminAccessToProductionServer,AccessToImportantFIles,WaiverNumber,Summary,[TimeStamp]
FROM
(
SELECT * FROM #Temp
) p
PIVOT
(
MIN([ATTRIBUTE]) FOR [NAME] IN(BadgeType,PrimaryLocationInCompany,AdminAccessToProductionServer,AccessToImportantFIles,WaiverNumber,Summary,[TimeStamp],UserID)
) T
Also check the below for dynamic columns
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + [NAME]
from #Temp
group by [NAME]
FOR XML PATH(''))
,1,1,'')
set #query = 'SELECT ' + #cols + ' from
(
select * from #Temp
) x
pivot
(
MAX([ATTRIBUTE])
for [NAME] in (' + #cols + ')
) p '
execute(#query)