Unpivot table with multiple columns and dynamic column names - sql

I am trying to unpivot a table with multiple rows and columns. Each row needs to be extratced to 2 rows with specific columns and the column names need to be renamed and a new column needs to added based on the columns selected!
I am including before and after sample data and a script to setup the data.
CREATE TABLE #tmpProducts (
ProductId INT,
ProductName nVARCHAR(100),
B2B_GrossRevenue DECIMAL(10,2),
B2B_DirectCost DECIMAL(10,2),
B2B_NetRevenue DECIMAL(10,2),
B2C_GrossRevenue DECIMAL(10,2),
B2C_DirectCost DECIMAL(10,2),
B2C_NetRevenue DECIMAL(10,2)
)
INSERT INTO #tmpProducts SELECT 1, 'Product1',1545.00,406.25,1138.75,195.00,35.10,159.90
INSERT INTO #tmpProducts SELECT 2, 'Product2',902.00,189.00,713.00,3280.00,590.40,2689.60
INSERT INTO #tmpProducts SELECT 3, 'Product3',15665.00,3988.39,11676.61,6247.00,1124.46,5122.54
INSERT INTO #tmpProducts SELECT 4, 'Product4',736.00,196.16,539.84,2395.00,431.10,1963.90
SELECT * FROM #tmpProducts
DROP TABLE #tmpProducts
CREATE TABLE #tmpProducts2 (
ProductId INT,
ProductName nVARCHAR(100),
[Type] nVARCHAR(3),
GrossRevenue DECIMAL(10,2),
DirectCost DECIMAL(10,2),
NetRevenue DECIMAL(10,2)
)
INSERT INTO #tmpProducts2 SELECT 1, 'Product1','B2B',1545.00,406.25,1138.75
INSERT INTO #tmpProducts2 SELECT 1, 'Product1','B2C',195.00,35.10,159.90
INSERT INTO #tmpProducts2 SELECT 2, 'Product2','B2B',902.00,189.00,713.00
INSERT INTO #tmpProducts2 SELECT 2, 'Product2','B2C',3280.00,590.40,2689.60
INSERT INTO #tmpProducts2 SELECT 3, 'Product3','B2B',15665.00,3988.39,11676.61
INSERT INTO #tmpProducts2 SELECT 3, 'Product3','B2C',6247.00,1124.46,5122.54
INSERT INTO #tmpProducts2 SELECT 4, 'Product4','B2B',736.00,196.16,539.84
INSERT INTO #tmpProducts2 SELECT 4, 'Product4','B2C',2395.00,431.10,1963.90
SELECT * FROM #tmpProducts2
DROP TABLE #tmpProducts2
I have attempted this but i can't get past the second column and im not sure how at add a new column with specific text, (probably dynamic sql but trying to avoid this if possible)
Here is the start of my attempt, any help would be much appreciated.
SELECT ProductId, ProductName,GrossRevenue
FROM (
SELECT ProductId, ProductName, B2B_GrossRevenue,B2C_GrossRevenue FROM #tmpProducts
) as t
UNPIVOT ( GrossRevenue for test IN (B2B_GrossRevenue,B2C_GrossRevenue)) AS unpvt

You just keep unpivoting
SELECT ProductId,ProductName, Substring(col1,1,3) as type, GrossRevenue, DirectCost, NetRevenue
FROM (
SELECT * FROM #tmpProducts
) as t
UNPIVOT ( GrossRevenue for col1 IN (B2B_GrossRevenue,B2C_GrossRevenue)) AS unpvt
unpivot ( DirectCost for col2 in (B2b_DirectCost, B2c_DirectCost)) up2
unpivot ( NetRevenue for col3 in (B2b_NetRevenue, B2c_NetRevenue)) up3
where SUBSTRING(col1,1,3)=SUBSTRING(col2,1,3)
and SUBSTRING(col1,1,3)=SUBSTRING(col3,1,3)
and join on the col columns to filter out mismatches

Here is a way to do this using Dynamic SQL. This will allow you to get all of the columns upon execution. Then you will not have to alter the query if you get any data changes. This does both an UNPIVOT and PIVOT:
DECLARE #colsUnPivot AS NVARCHAR(MAX),
#colsPivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #colsUnPivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('tmpProducts') and
C.name like 'B2%'
for xml path('')), 1, 1, '')
SET #colsPivot = stuff((select DISTINCT ','+quotename(right((C.name), len(C.name)-4))
from sys.columns as C
where C.object_id = object_id('tmpProducts') and
C.name like 'B2%'
for xml path('')), 1, 1, '')
set #query
= ' SELECT ProductId, ProductName, [type], ' + #colsPivot +'
FROM
(
SELECT ProductId, ProductName, substring(field, 1, 3) [type]
, value, right((field), len(field)-4) as col
from tmpProducts
unpivot
(
value
for field in (' + #colsUnPivot + ')
) unpvt
) x
PIVOT
(
sum(value)
FOR col IN (' + #colsPivot +')
)p'
execute(#query)
See SQL Fiddle with Demo

If you have multiple groups that you're looking to pivot around you can also do something like this:
Select *
from table
unpivot include nulls ( (test_name, test_date) for col_nm in ( (test1, date1) as 'test_val1'
,(test2, date2) as 'test_val2'
,(test3, date3) as 'test_val3'
)
)

Related

SQL Transpose Data with Column Name

I am trying to transpose data in a SQL Server table with one row of data but with several columns, all into one column along with their respective column headers.
Original Data Table:
**TABLE Column Names:** Id, ColumnA , ColumnB , ColumnC , StartDate
**Data:** 1, 'aa' , 'bb' , 'cc', 2016-10-10
Required Format of Data:
**ColumnName Values**
Id 1
ColumnA aa
ColumnB bb
ColumnC cc
StartDate 2016-10-10
CREATE DATABASE ToDelete
GO
USE [ToDelete]
GO
CREATE TABLE [dbo].[sourceData](
[id] [int] NULL,
[ColumnA] [varchar](50) NULL,
[ColumnB] [varchar](50) NULL,
[ColumnC] [varchar](50) NULL,
[StartDate] [datetime] NULL
)
GO
INSERT [dbo].[sourceData] ([id], [ColumnA], [ColumnB], [ColumnC], [StartDate], [EndDate]) VALUES (1, 'aa', N'bb', N'cc', GETDATE())
GO
The query I am using to pull the table column names is:
SELECT c.name
FROM sys.tables t
JOIN sys.columns c
ON t.object_id = c.object_id
WHERE t.name = 'sourceData'
Your help would be appreciated.
Thank you
Here is an option that may help you create something more like an EAV structure
Example
Declare #YourTable Table ([Id] varchar(50),[ColumnA] varchar(50),[ColumnB] varchar(50),[ColumnC] varchar(50),[StartDate] date)
Insert Into #YourTable Values
(1,'aa','bb','cc','2016-10-10')
Select A.ID
,C.*
From #YourTable A
Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)
Cross Apply (
Select Field = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From B.XMLData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('local-name(.)','varchar(100)') not in ('ID','OtherColumnsTo','Exclude')
) C
Returns
ID Field Value
1 ColumnA aa
1 ColumnB bb
1 ColumnC cc
1 StartDate 2016-10-10
Try the Following Solution, I have Referred from the following article to write this query:
https://www.red-gate.com/simple-talk/sql/t-sql-programming/questions-about-pivoting-data-in-sql-server-you-were-too-shy-to-ask/
USE [ToDelete]
GO
DECLARE #sql AS NVARCHAR(2000);DECLARE #col AS NVARCHAR(2000);
DECLARE #col1 AS NVARCHAR(2000);
SELECT #col = ISNULL(#col + ', ', '') +
concat('cast(',QUOTENAME(column_name),'as nvarchar(max))',' ',
QUOTENAME(column_name) ),#col1=ISNULL(#col1 + ', ', '') +QUOTENAME(column_name)
FROM (SELECT DISTINCT column_name FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='sourceData') AS Colname;
SET #sql =N'select columnname,[values] from (select '+#col+ ' from dbo.sourceData) as D Unpivot
([values] for columnname in (' + #col1 + '))
as unpiv'
EXEC sp_executesql #sql;
Simply use Union all like this:
select Id,'ColumnA' columnName, ColumnA [Values]
from t
union all
select Id,'ColumnB' , ColumnB
from t
union all
select Id,'ColumnC' , ColumnC
from t
union all
select Id,'StartDate' , cast(StartDate as nvarchar(max))
from t;
SQL Fiddle Demo
Additional variant using unpivot:
dataset
DECLARE #YourTable TABLE
([Id] VARCHAR(10),
[ColumnA] VARCHAR(10),
[ColumnB] VARCHAR(10),
[ColumnC] VARCHAR(10),
[StartDate] DATE
);
INSERT INTO #YourTable
VALUES
(1, 'aa', 'bb', 'cc', '2016-10-10'),
(2, 'cc', 'dd', 'zz', '2016-10-11');
query
SELECT Id,
Field,
[Value]
FROM
( SELECT Id,
ColumnA,
ColumnB,
ColumnC,
CONVERT(VARCHAR(10), StartDate) AS StartDate
FROM #YourTable
) AS t UNPIVOT([Value] FOR [Field] IN ( ColumnA,
ColumnB,
ColumnC,
StartDate)) up;

How to use pivot for columns of type varchar

My table is:
SBType|SBName|Qty
===================
SMDB SB01 1
SMDB SB01 4
SMDB SB02 2
SMDB SB02 5
SMDB SB03 3
SMDB SB03 6
My desired output is:
SB01 | SB02 | SB03
==================
1 2 3
4 5 6
This is what my code looks like:
SELECT *
FROM (
SELECT
SM.SBName,ISNULL(ES.Qty,0)Qty
FROM RE_ES_SwitchBoard_Mast SM
left outer join RE_ES_Estimations ES on SM.PrCode=ES.PrCode and
Sm.SBType=ES.SBType and SM.SBName=ES.SBName
Where SM.PrCode='PR004' and SM.SBType='SMDB'
) as s
PIVOT
(
Max(Qty)
FOR [SBName] IN (SB01, SB02, SB03)
)AS pvthere
and the result of my attempt looks like:
SB01 SB02 SB03
1 2 3
I have tried with MAX(Qty) but it is not working.
Thanks in advance.
You are almost there.
By adding ROW_NUMBER() OVER (PARTITION BY SBName ORDER BY Qty) rn to the source of PIVOT clause you get multiple rows for different SBName instead of one grouped row. Your query should look like:
SELECT SB01, SB02, SB03
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY SB.SBName ORDER BY Qty) rn,
SB.SBName,ISNULL(ES.Qty,0) Qty
FROM RE_ES_SwitchBoard_Mast SM
left outer join RE_ES_Estimations ES on SM.PrCode=ES.PrCode and
Sm.SBType=ES.SBType and SM.SBName=ES.SBName
Where SM.PrCode='PR004' and SM.SBType='SMDB'
) as s
PIVOT
(
Max(Qty)
FOR [SBName] IN (SB01, SB02, SB03)
)AS pvthere
A verifiable example here:
CREATE TABLE #sample
(
SBType varchar(MAX),
SBName varchar(MAX),
Qty int
)
INSERT INTO #sample VALUES ('SMDB','SB01',1)
INSERT INTO #sample VALUES ('SMDB','SB01',4)
INSERT INTO #sample VALUES ('SMDB','SB02',2)
INSERT INTO #sample VALUES ('SMDB','SB02',5)
INSERT INTO #sample VALUES ('SMDB','SB03',3)
INSERT INTO #sample VALUES ('SMDB','SB03',6)
SELECT SB01, SB02, SB03
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY SBName ORDER BY Qty) rn, SBName,ISNULL(Qty,0) Qty
FROM #sample
) as s
PIVOT
(
Max(Qty)
FOR [SBName] IN (SB01, SB02, SB03)
) AS pvthere
DROP TABLE #sample
Dynamic query is the only way to use varchar columns in pivot. Have a look at below code to get idea.
First step is to generate comma separated list of items for column you need to use in pivot.
Then you can use this generated list in dynamic query for pivot columns.
Note: For example purpose I have used temp table. Replace it with your actual table.
CREATE TABLE #temptable
(
SBType VARCHAR(20),
SBName VARCHAR(20),
Qty INT
)
INSERT INTO #temptable SELECT 'SMDB','SB01',1
INSERT INTO #temptable SELECT 'SMDB','SB01',4
INSERT INTO #temptable SELECT 'SMDB','SB02',2
INSERT INTO #temptable SELECT 'SMDB','SB02',5
INSERT INTO #temptable SELECT 'SMDB','SB03',3
INSERT INTO #temptable SELECT 'SMDB','SB03',6
SELECT * FROM #temptable
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(SBName)
from #temptable
group by SBName
order by SBName
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #cols
DECLARE #query NVARCHAR(MAX)
SET #query = '
SELECT *
FROM
(
SELECT SBType,SBName,Qty,
row_number() over (partition by SBName order by Qty) as rn
FROM #temptable
) src
PIVOT
(
MIN(Qty)
FOR SBName IN (' + #cols + ')
) piv;'
EXEC(#query)
DROP TABLE #temptable

Rows to single cell

I would like to get the desired output marked in green
the data points for each id get put into a single cell
Basically take all the events that have happened with A and attach it in the same order
Use Stuff Function:
DECLARE #tblTest AS Table(
ID INT,
EVENT VARCHAR(5)
)
INSERT INTO #tblTest VALUES
(1,'A'),
(1,'A'),
(1,'C'),
(2,'A'),
(2,'B'),
(2,'C')
SELECT DISTINCT
T1.ID,
STUFF
(
(SELECT '' + convert(varchar(10), T2.EVENT, 120)
FROM #tblTest T2
where T1.ID = T2.ID
FOR XML PATH (''))
, 1, 0, '') AS EVENT
FROM #tblTest T1
You can use FOR XML:
SELECT DISTINCT
ID,
(SELECT [EVENT] +''
FROM YourTable
WHERE ID = y.ID
FOR XML PATH('')
) as [EVENT]
FROM YourTable y
Output:
ID EVENT
1 AABCD
2 AABBCC
You can use UDF to do so as follows:
CREATE TABLE t(
id INT,
col CHAR(1)
);
INSERT INTO t VALUES (1,'a');
INSERT INTO t VALUES (1,'b');
INSERT INTO t VALUES (1,'c');
INSERT INTO t VALUES (1,'d');
INSERT INTO t VALUES (2,'e');
INSERT INTO t VALUES (2,'f');
INSERT INTO t VALUES (3,'g');
INSERT INTO t VALUES (4,'h');
The UDF (User defined function) -
USE [t]
GO
CREATE FUNCTION dbo.ConcatenateCols(#Id INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #RtnStr VARCHAR(MAX)
SELECT #RtnStr = COALESCE(#RtnStr + '','') + col
FROM dbo.t
WHERE id = #Id AND col > ''
RETURN #RtnStr
END
GO
Finally the query and result:
SELECT id, dbo.ConcatenateCols(id) AS Cols -- UDF - ConcatenateCols(id)
FROM t GROUP BY Id
CREATE TABLE #temp(Id INt,Event Nvarchar(25))
INSERT INTO #temp
SELECT 1,
'A'
UNION ALL
SELECT 1,
'A'
UNION ALL
SELECT 1,
'B'
UNION ALL
SELECT 1,
'C'
UNION ALL
SELECT 1,
'D'
UNION ALL
SELECT 2,
'A'
UNION ALL
SELECT 2,
'A'
UNION ALL
SELECT 2,
'B'
UNION ALL
SELECT 2,
'B'
UNION ALL
SELECT 2,
'C'
UNION ALL
SELECT 2,
'C'
SELECT DISTINCT ID,
(SELECT [EVENT] +''
FROM #temp
WHERE ID = y.ID
FOR XML PATH('') ) AS [EVENT]
FROM #temp y

Iterating through a SQL Server 2008 R2 table variable and concatenate values

I have a table variable #Holding two columns: an id (not unique) and a message:
id message
---- -------
2 give
2 me
2 help
3 Need
3 help
1 help!
The result should be
2 give me help
3 Need help
1 help!
This it very much simplified, but shows that there are id which may exist more than once, and some kind of text which should be concatenated into a string.
I cannot manage it to loop through this table variable (but not through a table too!).
I tried a cursor (which I did not understand correctly) but it failed of course.
The number of records are not that much, not even 100 in that table variable.
Thanks yr. help
Michael
Original question
No CURSOR, WHILE loop, or User-Defined Function needed.
Just need to be creative with FOR XML and PATH.
[Note: This solution only works on SQL 2005 and later. Original question didn't specify the version in use.]
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
This should work if you are using SQL Server.
select T1.id,
stuff((select ' '+T2.[message]
from #A as T2
where T1.id = T2.id
for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '') as [message]
from #A as T1
group by T1.id
Pretty the same, just less letters in the code:
DECLARE #t AS TABLE ( id INT, msg VARCHAR(100) );
INSERT INTO #t
VALUES ( 2, 'give' );
INSERT INTO #t
VALUES ( 2, 'me' );
INSERT INTO #t
VALUES ( 2, 'help' );
INSERT INTO #t
VALUES ( 3, 'Need' );
INSERT INTO #t
VALUES ( 3, 'help' );
INSERT INTO #t
VALUES ( 1, 'help!' );
SELECT DISTINCT
id ,
(
SELECT ST1.msg + ' '
FROM #t ST1
WHERE ST1.id = ST2.id
FOR XML PATH('')
) t
FROM
#t ST2;

How to pivot column values of the below table?

TableA:
Brand Product
------------------
A X
A XX
A XXX
B Y
B YY
C Z
I need data as shown in Table below:
A B C
-------------------
X Y Z
XX YY NULL
XXX NULL NULL
How to do that in Sql Server 2008 ?
I dont beleive a PIVOT is what you are looking for here.
From what I can see you are looking at using the entries in order to generate the rows?
Also, PIVOTs make use of aggregate functions, so I cant see this happening.
What you can try, is something like
DECLARE #Table TABLE(
Brand VARCHAR(10),
Product VARCHAR(10)
)
INSERT INTO #Table SELECT 'A','X '
INSERT INTO #Table SELECT 'A','XX'
INSERT INTO #Table SELECT 'A','XXX'
INSERT INTO #Table SELECT 'B','Y'
INSERT INTO #Table SELECT 'B','YY'
INSERT INTO #Table SELECT 'C','Z'
;WITH Vals AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Brand ORDER BY (SELECT NULL)) RID
FROM #Table
)
, RIDs AS (
SELECT DISTINCT
RID
FROM Vals
)
SELECT vA.Product [A],
vB.Product [B],
vC.Product [C]
FROM RIDs r LEFT JOIN
Vals vA ON r.RID = vA.RID
AND vA.Brand = 'A' LEFT JOIN
Vals vB ON r.RID = vB.RID
AND vB.Brand = 'B' LEFT JOIN
Vals vC ON r.RID = vC.RID
AND vC.Brand = 'C'
I know it is a late entry, but here is a different approach to solve it:
DECLARE #Table TABLE(Brand VARCHAR(10), Product VARCHAR(10))
INSERT INTO #Table SELECT 'A','X '
INSERT INTO #Table SELECT 'A','XX'
INSERT INTO #Table SELECT 'A','XXX'
INSERT INTO #Table SELECT 'B','Y'
INSERT INTO #Table SELECT 'B','YY'
INSERT INTO #Table SELECT 'C','Z'
SELECT [A],[B],[C] FROM (
SELECT row_number() over (partition by brand order by product) rn,
Product, brand FROM #table
) as p
PIVOT(
MAX(product) for Brand in ([A],[B],[C])
)as pvt