Pivot with limited number of columns - sql

I'm having a problem with query in Microsoft SQL environment. I'm getting serial numbers which I want to transform from rows to columns due to customer's template. Limitation for me is customer's template - max number of columns could be 10. I don't know how to reach my vision where each 11-20, 21-30, etc. columns will be in new row. See example with 13 rows from database:
What I'm able to do:
Product01
Product02
Product03
Product04
Product05
Product06
Product07
Product08
Product09
Product10
Product11
Product12
Product13
BK018001B6
BK018001B7
BK018001B8
BK018001B9
BK018001BB
BK018001BC
BK018001BD
BK018001BF
BK018001BG
BK018001BH
BK018001BJ
BK018001BK
BK018001BL
What I want to do:
Product01
Product02
Product03
Product04
Product05
Product06
Product07
Product08
Product09
Product10
BK018001B6
BK018001B7
BK018001B8
BK018001B9
BK018001BB
BK018001BC
BK018001BD
BK018001BF
BK018001BG
BK018001BH
BK018001BJ
BK018001BK
BK018001BL
I have found following working playground for Microsoft SQL: https://sqlzoo.net/. Unfortunately there is no way to provide you working playground as a link. You need to copy my SQL code to reproduce that:
CREATE TABLE #sourceTable(SerialNo VARCHAR(20), ProductRowNumber VARCHAR(10))
INSERT INTO #sourceTable
(
SerialNo,
ProductRowNumber
)
VALUES
('BK018001B6', 'Product01'),
('BK018001B7', 'Product02'),
('BK018001B8', 'Product03'),
('BK018001B9', 'Product04'),
('BK018001BB', 'Product05'),
('BK018001BC', 'Product06'),
('BK018001BD', 'Product07'),
('BK018001BF', 'Product08'),
('BK018001BG', 'Product09'),
('BK018001BH', 'Product10'),
('BK018001BJ', 'Product11'),
('BK018001BK', 'Product12'),
('BK018001BL', 'Product13')
CREATE TABLE #productsTempTable (ProductSerialNo VARCHAR(20), ProductRowNumber VARCHAR(10))
INSERT INTO #productsTempTable
SELECT SerialNo, ProductRowNumber FROM #sourceTable
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF(
(SELECT ',' + QUOTENAME(ProductRowNumber)
FROM #productsTempTable
GROUP BY ProductRowNumber
ORDER BY ProductRowNumber
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
SET #query = N'SELECT ' + #cols + N' FROM
(
SELECT ProductSerialNo, ProductRowNumber
FROM #productsTempTable
) x
PIVOT
(
MAX(ProductSerialNo)
FOR ProductRowNumber IN (' + #cols + N')
) p'
EXEC sp_executesql #query;
DROP table #productsTempTable, #sourceTable
In real the column "ProductRowNumber" I'm composing this way for completeness:
'Product' + RIGHT(100 + CONVERT(VARCHAR(10), ROW_NUMBER() OVER(ORDER BY p.id)), 2) AS 'ProductRowNumber'
Could you help me with this issue please?
Thank you.

This pivot does not need to be dynamic. We just need to do a bit of arithmetic with row number:
SELECT
Product01, Product02, Product03, Product04, Product05, Product06, Product07, Product08, Product09, Product10
FROM (
SELECT
SerialNo,
rn10 = (ROW_NUMBER() OVER (ORDER BY ProductRowNumber) - 1) / 10,
ProductRN = CONCAT(
'Product',
FORMAT((ROW_NUMBER() OVER (ORDER BY ProductRowNumber) - 1) % 10 + 1, '00'))
FROM #sourceTable st
) st
PIVOT (
MAX(SerialNo) FOR ProductRN IN
(Product01, Product02, Product03, Product04, Product05, Product06, Product07, Product08, Product09, Product10)
) pvt;
db<>fiddle
In your real query, you can replace the ROW_NUMBER with your existing row-numbering.

I think the conditional aggregate approach is pretty simple:
select max(case when seqnum % 10 = 1 then serialno end) as prod_1,
max(case when seqnum % 10 = 2 then serialno end) as prod_2,
max(case when seqnum % 10 = 3 then serialno end) as prod_3,
max(case when seqnum % 10 = 4 then serialno end) as prod_4,
max(case when seqnum % 10 = 5 then serialno end) as prod_5,
max(case when seqnum % 10 = 6 then serialno end) as prod_6,
max(case when seqnum % 10 = 7 then serialno end) as prod_7,
max(case when seqnum % 10 = 8 then serialno end) as prod_8,
max(case when seqnum % 10 = 9 then serialno end) as prod_9,
max(case when seqnum % 10 = 10 then serialno end) as prod_10
from (select t.*, row_number() over (order by ProductRowNumber) as seqnum
from sourceTable t
) t
group by ceiling(seqnum / 10.0)
order by min(seqnum);
Here is a db<>fiddle.

Related

SQL pivot single to multiple column and multiple row

Can somebody help me with SQL using pivot?
I asked before this using numbers but not now im using it in a string format
For example I have a list of Names shown below with 1 column:
and I want the result to become this. the will limit 10 column only
use row_number() to generate a running sequence, and then, use modulus % to get the column and divide by 10 to get the row. Use PIVOT() to get the required result
select *
from
(
select [Names],
[row] = row_number() over (order by [Names]) / 10 + 1,
[col] = (row_number() over (order by [Names]) - 1) % 10 + 1
from yourtable
) d
pivot
(
max([Names])
for col in ([1], [2],[3],[4],[5],[6],[7],[8],[9],[10])
) p
I would just use conditional aggregation:
select max(case when seqnum % 10 = 0 then name end) as name_0,
max(case when seqnum % 10 = 1 then name end) as name_1,
max(case when seqnum % 10 = 2 then name end) as name_2,
max(case when seqnum % 10 = 3 then name end) as name_3,
max(case when seqnum % 10 = 4 then name end) as name_4,
max(case when seqnum % 10 = 5 then name end) as name_5,
max(case when seqnum % 10 = 6 then name end) as name_6,
max(case when seqnum % 10 = 7 then name end) as name_7,
max(case when seqnum % 10 = 8 then name end) as name_8,
max(case when seqnum % 10 = 9 then name end) as name_9
from (select t.*,
row_number() over (order by (select null)) - 1 as seqnum
from t
) t
group by floor(seqnum / 10);
Note: The ordering of the names in the result set is not guaranteed. Each name will appear in one position. If the ordering matters, then you need an additional column that specifies the ordering (and you can use that in the group by.
This code solves your question using dynamic pivot and without sorting the data with select null
declare #groups varchar(max) set #groups = (select count(0)/2 from Mytable )
declare #columns varchar(max) set #columns = ''
select #columns = coalesce(#columns + '[' + cast(col as varchar(MAX)) + '],', '')
FROM (
select col from (
select (row_number() over (order by (SELECT NULL)) - 1) % (#groups) + 1 as col
from Mytable group by Names
) t
group by col
) m
set #columns = left(#columns,LEN(#columns)-1)
DECLARE #SQLString nvarchar(max);
set #SQLString = '
select * from
(
select Names, row/'+#groups+' +1 as row , col from (
select [Names],
[row] = row_number() over (order by (SELECT NULL))+1 / '+#groups+' -1,
[col] = (row_number() over (order by (SELECT NULL)) - 1) % '+#groups+' + 1
from Mytable
) x
) m
PIVOT
( MAX(Names)
FOR col in (' + #columns + ')
) AS PVT'
EXECUTE sp_executesql #SQLString

Convert three rows values into columns, NOT as comma separated value

I have table structure like
select catalog_item_id,metal_type,metal_color
from catalog_item_castings
where catalog_Item_Id =465173
It returns output as:
And I want output as:
And I want to insert this data into new temp table in SQL Server.
Thanks in advance.
Conditional aggregation is an option:
SELECT
catalog_item_id,
MAX(CASE WHEN rn % 3 = 1 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_1,
MAX(CASE WHEN rn % 3 = 2 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_2,
MAX(CASE WHEN rn % 3 = 0 THEN CONCAT(metal_type, '/', metal_color) END) AS Casting_3
FROM (
SELECT
catalog_item_id, metal_type, metal_color, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn
FROM (VALUES
(465173, 'na', 'METALCOLOR'),
(465173, 'na', 'METAL-001'),
(465173, 'na', 'na')
) catalog_item_castings (catalog_item_id, metal_type, metal_color)
WHERE catalog_Item_Id = 465173
) t
GROUP BY catalog_item_id
-- or if you have more than three rows per [catalog_item_id]
-- GROUP BY catalog_item_id, (rn - 1) / 3
Result:
catalog_item_id Casting_1 Casting_2 Casting_3
-------------------------------------------------
465173 na/METALCOLOR na/METAL-001 na/na
You can use Conditional Aggregation within a Dynamic Pivot Statement in order to include all distinct combinations of the columns [metal_type] and [metal_color], even different values for combinations are inserted in the future :
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = (SELECT STRING_AGG(CONCAT('MAX(CASE WHEN [dr]=',dr,
' THEN CONCAT([metal_type],''/'',[metal_color]) END) AS [Casting_',dr,']'),',')
WITHIN GROUP ( ORDER BY dr )
FROM
(
SELECT DISTINCT
DENSE_RANK() OVER
(PARTITION BY [catalog_item_id]
ORDER BY CONCAT([metal_type],[metal_color])) AS dr
FROM [catalog_item_castings] ) c);
SET #query =
'SELECT [catalog_item_id],'+ #cols +
' FROM
(
SELECT *, DENSE_RANK() OVER
( PARTITION BY [catalog_item_id]
ORDER BY CONCAT([metal_type], [metal_color]) ) AS dr
FROM [catalog_item_castings]
) c
GROUP BY [catalog_item_id]';
EXEC sp_executesql #query;
Demo

how to do pivot on multiple columns [duplicate]

This question already has answers here:
SQL Server Pivot Table with multiple column aggregates
(3 answers)
Closed 3 years ago.
Hi I have sample data
Declare #table table
(Name Varchar(10),
Cnt INT,
Vol INT,
Descc VARCHAR(10))
INSERT INTO #table(Name,cnt,vol,Descc)values ('Mohan',21,8,'Fed')
INSERT INTO #table(Name,cnt,vol,Descc)values ('Mohan',1,391,'Fed:::')
Data :
Name Cnt Vol Descc
Mohan 21 8 Fed
Mohan 1 391 Fed:::
How can I get output like this
Name Cnt1 Vol1 Descc1 cnt2 vol2 Descc2
Mohan 21 8 Fed 1 391 Fed::
script I have followed :
Select [1],[2] from (
select NAme,Cnt,vol,DESCc,ROW_NUMBER()OVER(PARTITION BY ID ORDER BY (SELECT NULL))P,'P'+CAST(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY (SELECT NULL))AS VARCHAR)PP from #table )T
PIVOT (MAX(ID) FOR P IN ([1],[2])) AS P
PIVOT (MAX(ID) FOR PP IN ([P1],[P2])) AS P
You can do conditional aggregation :
select Name, max(case when seq = 1 then Cnt end) as cnt1,
max(case when seq = 1 then Vol end) as Vol1,
max(case when seq = 1 then Descc end) as Descc1,
max(case when seq = 2 then Cnt end) as cnt2,
max(case when seq = 2 then Vol end) as Vol2,
max(case when seq = 2 then Descc end) as Descc2
from (select t.*, row_number() over (partition by name order by (select 1 )) as seq
from #table t
) t
group by Name;
Here is a db<>fiddle.
By Dynamic sql
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
DROP TABLE #TEMP
DECLARE #table table
(
Name Varchar(10),
Cnt INT,
Vol INT,
Descc VARCHAR(10)
)
INSERT INTO #table(Name,cnt,vol,Descc)values ('Mohan',21,8,'Fed')
INSERT INTO #table(Name,cnt,vol,Descc)values ('Mohan',1,391,'Fed')
;WITH CTE
AS
(
SELECT
ROW_NUMBER()OVER(ORDER BY Name) AS Id,*
FROM #table i
)
SELECT ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) AS Seq,
id,
Name,
Data1,
Data2
+CAST(id AS VARCHAR(10)) AS ReqCol
INTO #TEMP
FROM CTE
CROSS APPLY (VALUES ( CAST(CNT AS varchar(10)),'CNT'),
(CAST(Vol AS varchar(10)),'vol'),
(Descc,'Descc')
)AS Dt (Data1,Data2)
SET NOCOUNT ON
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(CAST(ReqCol AS VARCHAR(10)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+QUOTENAME(CAST(ReqCol AS VARCHAR(10)))+') AS '+QUOTENAME(CAST(ReqCol AS VARCHAR(10)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SET #Sql='SELECT Name,'+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #TEMP
)AS src
PIVOT
(
MAX(Data1) FOR [ReqCol] IN ('+#DynamicColumn+')
) AS Pvt
GROUP BY Name '
EXEC (#Sql)
PRINT #Sql
SET NOCOUNT OFF
Result
Name CNT1 vol1 Descc1 CNT2 vol2 Descc2
---------------------------------------------------------
Mohan 21 8 Fed 1 391 Fed

Trying to Sum up Cross-Tab Data in SQL

I have a table where every ID has one or more places, and each place comes with a count. Places can be repeated within IDs. It is stored in rows like so:
ID ColumnName DataValue
1 place1 ABC
1 count1 5
2 place1 BEC
2 count1 12
2 place2 CDE
2 count2 6
2 place3 BEC
2 count3 9
3 place1 BBC
3 count1 5
3 place2 BBC
3 count2 4
Ultimately, I want a table where every possible place name is its own column, and the count per place per ID is summed up, like so:
ID ABC BEC CDE BBC
1 5 0 0 0
2 0 21 6 0
3 0 0 0 9
I don't know the best way to go about this. There are around 50 different possible place names, so specifically listing them out in a query isn't ideal. I know I ultimately have to pivot the data, but I don't know if I should do it before or after I sum up the counts. And whether it's before or after, I haven't been able to figure out how to go about summing it up.
Any ideas/help would be greatly appreciated. At this point, I'm having a hard time finding where to even start. I've seen a few posts with similar problems, but nothing quite as convoluted as this.
EDIT:
Right now I'm working with this to pivot the table, but this leaves me with columns named place1, place2, .... count1, count2,...
and I don't know how to appropriately sum up the counts and make new columns with the place names.
DECLARE #cols NVARCHAR(MAX), #query NVARCHAR(MAX);
SET #cols = STUFF(
(
SELECT DISTINCT
','+QUOTENAME(c.[ColumnName])
FROM #temp c FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #query = 'SELECT [ID], '+#cols+'from (SELECT [ID],
[DataValue] AS [amount],
[ColumnName] AS [category]
FROM #temp
)x pivot (max(amount) for category in ('+#cols+')) p';
EXECUTE (#query);
Your table structure is pretty bad. You'll need to normalize your data before you can attempt to pivot it. Try this:
;WITH IDs AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Place = datavalue
FROM #temp
WHERE ISNUMERIC(datavalue) = 0
)
,Counts AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Cnt = CAST(datavalue AS INT)
FROM #temp
WHERE ISNUMERIC(datavalue) = 1
)
SELECT
piv.id
,ABC = ISNULL(piv.ABC, 0)
,BEC = ISNULL(piv.BEC, 0)
,CDE = ISNULL(piv.CDE, 0)
,BBC = ISNULL(piv.BBC, 0)
FROM (SELECT i.id, i.Place, c.Cnt FROM IDs i JOIN Counts c ON c.id = i.id AND c.ColId = i.ColId) src
PIVOT ( SUM(Cnt)
FOR Place IN ([ABC], [BEC], [CDE], [BBC])
) piv;
Doing it with dynamic SQL would yield the following:
SET #query =
';WITH IDs AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Place = datavalue
FROM #temp
WHERE ISNUMERIC(datavalue) = 0
)
,Counts AS
(
SELECT DISTINCT
id
,ColId = RIGHT(ColumnName, LEN(ColumnName) - 5)
,Cnt = CAST(datavalue AS INT)
FROM #temp
WHERE ISNUMERIC(datavalue) = 1
)
SELECT [ID], '+#cols+'
FROM
(
SELECT i.id, i.Place, c.Cnt
FROM IDs i
JOIN Counts c ON c.id = i.id AND c.ColId = i.ColId
) src
PIVOT
(SUM(Cnt) FOR Place IN ('+#cols+')) piv;';
EXECUTE (#query);
Try this out:
SELECT id,
COALESCE(ABC, 0) AS ABC,
COALESCE(BBC, 0) AS BBC,
COALESCE(BEC, 0) AS BEC,
COALESCE(CDE, 0) AS CDE
FROM
(SELECT id,
MIN(CASE WHEN columnname LIKE 'place%' THEN datavalue END) AS col,
CAST(MIN(CASE WHEN columnname LIKE 'count%' THEN datavalue END) AS INT) AS val
FROM t
GROUP BY id, RIGHT(columnname, 1)
) src
PIVOT
(SUM(val)
FOR col in ([ABC], [BBC], [BEC], [CDE])) pvt
Tested here: http://rextester.com/XUTJ68690
In the src query, you need to re-format your data, so that you have a unique id and place in each row. From there a pivot will work.
If the count is always immediately after the place, the following query will generate a data set for pivoting.
The result data set before pivoting has the following columns:
id, placename, count
select placeTable.id, placeTable.datavalue, countTable.datavalue
from
(select *, row_number() over (order by id, %%physloc%%) as rownum
from test
where isnumeric(datavalue) = 1
) as countTable
join
(select *, row_number() over (order by id, %%physloc%%) as rownum
from test
where isnumeric(datavalue) <> 1
) as placeTable
on countTable.id = placeTable.id and
countTable.rownum = placeTable.rownum
Tested on sqlfidde mssqlserver: http://sqlfiddle.com/#!6/701c91/18
Here is one other approach using PIVOT operator with dynamic style
declare #Col varchar(2000) = '',
#Query varchar(2000) = ''
set #Col = stuff(
(select ','+QUOTENAME(DataValue)
from table where isnumeric(DataValue) = 0
group by DataValue for xml path('')),1,1,'')
set #Query = 'select id, '+#Col+' from
(
select id, DataValue,
cast((case when isnumeric(DataValue) = 1 then DataValue else lead(DataValue) over (order by id) end) as int) Value
from table
) as a
PIVOT
(
sum(Value) for DataValue in ('+#Col+')
)pvt'
EXECUTE (#Query)
Note : I have used lead() function to access next rows data if i found character string values and replace with numeric data values
Result :
id ABC BBC BEC CDE
1 5 NULL NULL NULL
2 NULL NULL 21 6
3 NULL 9 NULL NULL

Display data from same column in multiple columns without duplicating SQL Server

I have some data in SQL Server like this -
Num Alphabet
1 A
1 B
2 C
2 D
2 E
3 F
Can you help me make an SQL query that will display the data like this -
Alpha1 Alpha2 Alpha3
A C F
B D
E
You need to enumerate the values before you pivot them. Here is one method for getting the results you want:
select max(case when num = 1 then alphabet end) as alpha1,
max(case when num = 2 then alphabet end) as alpha2,
max(case when num = 3 then alphabet end) as alpha3
from (select t.*, row_number() over (partition by num order by alphabet) as seqnum
from table t
) t
group by seqnum;
Try this , this will take care of any number of alphabets in your column
A dynamic Pivot Query
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.[num])
FROM [YourTable] c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT num, ' + #cols + ' from
(
select num, Alphabet, ROW_NUMBER() over (PARTITION BY num order by Alphabet asc) as uid
from [YourTable]
) x
pivot
(
MIN(Alphabet)
for [uid] in (' + #cols + ')
) p '
execute(#query)
print #query
You can achieve this by using a subquery and group by clause:
select max(case when num = 1 then Alphabet end) as alpha1,
max(case when num = 2 then Alphabet end) as alph2,
max(case when num = 3 then Alphabet end) as alph3
from (select *, row_number()
over (partition by num order by alphabet) as output
from tblTemp) temp
group by output;