SQL Server Pivot Multiple Values - sql-server

Using SQL Server 2012.
I have the following table. The Style and colour are passed as a parameter:
Style Colour Size Whse Stock Sales 4WeekSales ATP
ABC123 AS12 10 London 2 3 6 7
ABC123 AS12 12 London 4 6 8 10
ABC123 AS12 14 New York 6 8 9 12
ABC123 AS12 10 New York 7 5 7 5
But need the data to look like this with the sizes along the top:
Whse 10 12 14
Lon
Stock 2 4 6
Sales 3 6 8
4WeekSales 6 8 9
ATP 7 10 12
New York
Stock 7 6
Sales 5 8
4WeekSales 7 9
ATP 5 12
Points to note - the size field needs to be dynamic - sometimes it can be 6 /8/10/12, sometimes it can be XS/S/M/L etc
Also their are more than two whse's - this is just an example.
I did make a start in T-SQL:
use SafetyStock
go
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(Size)
FROM (SELECT p.Size FROM dbo.vw_optimums AS p
GROUP BY p.Size) AS x;
SET #sql = N'
SELECT SKU, Style,' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT SKU, Style, p.Size, p.SAFETYSTOCK
FROM dbo.vw_optimums AS p
) AS j
PIVOT
(
SUM(SAFETYSTOCK) FOR Size IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
However, this works but only pivots on the stock - how do I also pivot by Sales\4WeekSales\ATP and also groupb by the whse?
Thank you in advance.
This is my latest code. If I take the SEQNO out it works, but I need this so the sizes appear along the top correctly e.g. S / M / L / XL / XXL etc or 6/ 8 / 10 / 12
DECLARE #SizeColums VARCHAR(MAX)
DECLARE #Seq Integer
SELECT
#SizeColums = COALESCE(#SizeColums + ',','') + QUOTENAME([Size]),
#Seq = SEQNO
FROM vw_optimums1
GROUP BY [Size],[SEQNO]
ORDER BY [SEQNO]
DECLARE #Sql NVARCHAR(MAX) = N'
SELECT Whse,
[Types],' +
#SizeColums + '
FROM (SELECT * FROM vw_optimums1) t
UNPIVOT (
[Type]
FOR [Types] IN ([Stock],[LWSALES],[L4WSALES],[ATP]) ) up
PIVOT (
MAX([Type])
FOR [Size] IN (' + #SizeColums + ')
) p
'
EXEC sp_executesql #sql;

About the closest I can get you is this.
DECLARE #SizeColums VARCHAR(MAX)
SELECT #SizeColums = COALESCE(#SizeColums + ',','') + QUOTENAME([Size])
FROM vw_optimums
GROUP BY [Size]
DECLARE #Sql NVARCHAR(MAX) = N'
SELECT Whse,
[Types],' +
#SizeColums + '
FROM (SELECT * FROM vw_optimums
) t
UNPIVOT (
[Type]
FOR [Types] IN ([Stock],[Sales],[4WeekSales],[ATP]) ) up
PIVOT (
MAX([Type])
FOR [Size] IN (' + #SizeColums + ')
) p
'
This actually uses UNPIVOT first to get the breakdown by size, then pivots based on size.
You'll get a result like this based on the sample data.
Whse Types 10 12 14
-------- -------------- ----------- ----------- -----------
London 4WeekSales 6 8
London ATP 7 10
London Sales 3 6
London Stock 2 4
New York 4WeekSales 7 9
New York ATP 5 12
New York Sales 5 8
New York Stock 7 6

Related

MS SQL Converting Rows to Column

I need to convert the table from (A) into (B).
I am able to get it work by joining the same table multiple times and use Max operator to assign the fields, but is there any better way to achieve this as Max operator could cause performance issue on huge table.
Can this be done by using pivot and will it cause any performance issue on huge table?
Btw, ID in below example is only 1 of the fields as example, there are other fields that need to achieve the same thing.
(A)
Class ID
1 11
1 12
1 13
2 11
2 12
2 13
(B)
Class ID2 ID3 ID4
1 11 12 13
2 11
12 13
You can you PIVOT:
SELECT *
FROM
(SELECT * FROM MY_TABLE
) pivot ( MAX(id) FOR id IN ([11],[12],[13]) );
Assuming you need to go DYNAMIC
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(concat('ID',1+Row_Number() over (Partition By Class Order By ID))) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Class],' + #SQL + '
From (
Select [Class]
,ID
,Col = concat(''ID'',1+Row_Number() over (Partition By [Class] Order By [ID]))
From YourTable
) A
Pivot (max(ID) For [Col] in (' + #SQL + ') ) P'
Exec(#SQL);
Returns
Class ID2 ID3 ID4
1 11 12 13
2 11 12 13

How can I unpivot and then pivot my table so the columns become rows and one column becomes a row?

How can I accomplish this with unpivot and pivot.
I've seen this question asked before and has a solution with case statement and union all
In SQL, how can I count the number of values in a column and then pivot it so the column becomes the row?
and here PIVOT/UNPIVOT multiple rows and columns but I have 20 rows and 24 columns and the query would become very long and I suspect inefficient. Does anyone know how I can do this with unpivot and pivot or is case and union all the only viable option?
Hour A B C D E ... Z
-----------------------------------------
0 4 2 3 0 6 2
1 3 5 7 1 8 7
2 2 6 1 1 4 3
3 2 2 0 3 0 2
4 3 9 6 2 2 8
...
23 6 5 2 3 8 6
Field 0 1 2 3 ...23
-------- -- -- -
A 2 0 2 2 4
B 7 2 8 1 6
....
Z 6 7 7 3 8
This is what I've tried in terms of pivot but I didn't get far:
select B,[0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23] from CTE2
pivot(
sum(A)
for hour in ([0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23])) as pvt;
Just to clarify, the numbers in the table are just random numbers I've put to simulate data, they aren't transposed as they should be.
Well, I know you say you've solved it so this probably isn't necessary and you can feel free to use whatever answer you currently have, but here's an example of how you could approach this problem in general.
IF OBJECT_ID('tmpTable', 'U') IS NOT NULL DROP TABLE tmpTable;
CREATE TABLE tmpTable (i INT, a INT, b INT, c INT, d INT);
INSERT tmpTable VALUES (1,69,69,10,1)
, (2,5,0,2,3)
, (3,5,5,5,5)
, (4,1,2,3,4);
DECLARE #applycols NVARCHAR(MAX);
SELECT #applycols = STUFF(
(SELECT ',(' + QUOTENAME(COLUMN_NAME) + ', ''' + COLUMN_NAME + ''')'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmpTable'
AND COLUMN_NAME <> 'i'
FOR XML PATH ('')),1,1,'');
DECLARE #aggcols NVARCHAR(MAX) = '';
SELECT #aggcols += ', MAX(CASE WHEN i = ' + CAST(i AS NVARCHAR(255)) + ' THEN piv.colval END) ' + QUOTENAME(CAST(i AS NVARCHAR(255)))
FROM tmpTable;
DECLARE #SQL NVARCHAR(MAX) = 'SELECT piv.col' + #aggcols + '
FROM tmpTable
CROSS APPLY (VALUES ' + #applycols + ') piv(colval, col)
GROUP BY piv.col;';
EXEC(#SQL);
DROP TABLE tmpTable;
Essentially, it's using dynamic SQL to determine all the columns/values and then using a simple CROSS APPLY / MAX(CASE... to get all the values.

How to keep column order same in dynamic pivot

I have below mentioned table :
drn RecNum Name Value
----------------------------------------------
1 1 ad1_pk 1
2 1 ad1_address1 P.O. Box 5036
3 1 ad1_address2 NULL
4 1 ad1_address3 NULL
5 1 ad1_ctyfk 56
6 1 ad1_postalcode 80155-5036
7 1 ad1_active Y
8 1 ad1_irstat A
9 1 ad1_irdata NULL
10 1 ad1_at1fk 1
1 2 ad1_pk 2
2 2 ad1_address1 1871 S. Broadway
3 2 ad1_address2 NULL
4 2 ad1_address3 NULL
5 2 ad1_ctyfk 1
6 2 ad1_postalcode 80210
7 2 ad1_active Y
8 2 ad1_irstat A
9 2 ad1_irdata NULL
10 2 ad1_at1fk 1
I am creating the pivot using the below mentioned query:
declare #var nvarchar(max)
declare #sql nvarchar(max)
set #var = stuff((select distinct ',' + name from temp
for xml path('')),1,1,'') -- **this is giving distinct column list but the order of columns get changed..**
set #sql = 'select * from temp
pivot(max(value) for name in (' + #var + ')) as pvt'
exec sp_executesql #sql
Is there a way to keep the order of the columns unchanged? I want the order of columns listed in #var to be same as in the table.
Add a GROUP BY and an ORDER BY clause (to replace the DISTINCT) where you build your column list as follows:
set #var = stuff((select ',' + min(name) from temp GROUP BY drn ORDER BY drn
for xml path('')),1,1,'')
And don't forget the the necessary aggregation (I've used MIN()). Thanks #Ionic.
This is because you're using a DISTINCT in your SELECT query. If you look at the execution plan, you can see DISTINCT SORT operation. This sorts your result based on the DISTINCT columns you specify, in this case it's Name:
To retain the order, you can try this:
set #var = stuff((
select ',' + name
from(
select
name,
drn,
rn = row_number() over(partition by name order by drn)
from temp
)t
where rn = 1
order by drn
for xml path('')),
1,1,'')

Multliple Pivot tables

We have a table like below
.
.
.
.
.
ID WOID InfoCode PersonID PersonSource FieldID FieldName FieldValue
--------------------------------------------------------------------------------------
1 2 AGMDDFR 7 CSS2C 1 Email sg
2 2 AGMDDFR 7 CSS2C 2 Phone 245345
3 2 AGMDDFR 7 CSS2C 3 FAX 345345
4 2 AGMDDFR 7 CSS2C 4 ArticleNumber 345
5 2 AGMDDFR 7 CSS2C 6 Description etete
6 2 ABC 7 CSS2C 12 Enter Email
7 2 AGMDDFR 8 CSS2C 1 Email tet#gmi.com
8 2 AGMDDFR 8 CSS2C 2 Phone 9988776655
9 2 AGMDDFR 8 CSS2C 3 FAX 898383838
10 2 AGMDDFR 8 CSS2C 4 Article Number 777777
11 2 AGMDDFR 8 CSS2C 6 Description asdff
we want output like below based on 'Info Code' we need to extract tables like below.
---1stTable(AGMDDFR)----
WOID PersonID PersonSource Email Phone Fax ArticleNumber Description
-----------------------------------------------------------------------------------------
2 7 CSS2C sg 245345 345345 345 etete
2 8 CSS2C tet#gmi.com 9988776655 898383838 777777 asdff
--2ndTable(ABC)
WOID PersonID PersonSource Enter Email
2 7 CSS2C ssss
Please help me how to solve this issue..
Since you need to return different columns for each InfoCode value, you'll need to look at using dynamic SQL to get the result. This will create a SQL string that contains the list of columns for each parameter. You'll then execute the SQL string to get the final result.
The basic syntax will be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#InfoCode varchar(10)
DECLARE #ParmDefinition NVARCHAR(500)
set #InfoCode = 'AGMDDFR'
SET #ParmDefinition = '#InfoCode varchar(10)'
select #cols = STUFF((SELECT ',' + QUOTENAME(FieldName)
from yourtable
where InfoCode = #InfoCode
group by FieldName, FieldID
order by FieldId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT WOID, PersonID, PersonSource, ' + #cols + '
from
(
select WOID, PersonID, PersonSource,
FieldName, FieldValue
from yourtable
where InfoCode = #InfoCode
) x
pivot
(
max(FieldValue)
for FieldName in (' + #cols + ')
) p '
EXEC sp_executesql #query, #ParmDefinition, #InfoCode = #InfoCode;
See SQL Fiddle with Demo

Transposing columns to rows using UNPIVOT

I have a table that for some reason has hardcoded values like so:
Row ID QtyC1 QtyC2 QtyC3 QtyC4 QtyN1 QtyN2 QtyN3 QtyN4
100 10 5 8 9 11 12 5 6
101 9 11 12 5 6 10 4 9
The table has 35 columns and around 12k records (meaning around 500k values) and is being added to and amended constantly.
I am trying to transpose this in a view into:
Row ID Year Period Val
100 C 1 10
100 C 2 5
100 C 3 8
100 C 4 9
100 N 1 11
100 N 2 12
100 N 3 5
100 N 4 6
So far I have managed to split it out into single values using this query:
SELECT Row ID, YP, Val
FROM (SELECT Row ID
, QtyC1 AS C1
, QtyC2 AS C2
, QtyC3 AS C3
, QtyC4 AS C4
, QtyN1 AS N1
, QtyN2 AS N2
, QtyN3 AS N3
, QtyN4 AS N4
FROM MyTable
) SUB
UNPIVOT (Val FOR YP IN (C1,C2,C3,C4,N1,N2,N3,N4)) AS PVT
This is getting me a single identifying value (eg C1) but how can I split it so I have a numeric period and a single character for the year (1 and C)?
I can see it might be possible just splitting up the string into two parts but I was hoping for a cleaner way if possible.
You can easily split the YP string using LEFT(), RIGHT(), SUBSTRING(), etc. My suggestion would be how you are handling your UNPIVOT. It looks like you have a lot of columns to UNPIVOT so my suggestion might be to implement dynamic SQL to perform this query. You would do it this way:
declare #colsUnpivot varchar(max),
#query AS NVARCHAR(MAX),
#cols varchar(max)
select #colsUnpivot = stuff((select ','
+quotename(replace(C.name, 'Qty', ''))
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Qty%'
for xml path('')), 1, 1, '')
select #cols = stuff((select ','
+quotename(C.name) + ' as ' + replace(C.name, 'Qty', '')
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Qty%'
for xml path('')), 1, 1, '')
set #query
= 'select rowid,
left(YP, 1) YP,
cast(right(YP, len(YP) - 1) as int) period,
Val
from
(
select rowid, ' + #cols + '
from yourtable
) x1
unpivot
(
val for YP IN (' + #colsUnpivot + ')
) u'
exec(#query)
see SQL Fiddle with Demo
Why would this seem unclean?
SELECT Row ID, left(YP, 1) as year, cast(right(yp, 1) as int) as period, Val
FROM (SELECT Row ID
, QtyC1 AS C1
, QtyC2 AS C2
, QtyC3 AS C3
, QtyC4 AS C4
, QtyN1 AS N1
, QtyN2 AS N2
, QtyN3 AS N3
, QtyN4 AS N4
FROM MyTable
) SUB
UNPIVOT (Val FOR YP IN (C1,C2,C3,C4,N1,N2,N3,N4)) AS PVT

Resources