SQL pivot single to multiple column and multiple row - sql

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

Related

Pivot with limited number of columns

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.

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

convert Rows to Columns in SQL with same ID

Folks,
I have a table with data as below.
enter image description here
Please advise sql logic to be applied to achieve this output.
One method is to use conditional aggregation:
select travelid,
max(case when seqnum = 1 then earn end) as earn,
max(case when seqnum = 1 then burn end) as burn,
max(case when seqnum = 2 then earn end) as earnA,
max(case when seqnum = 2 then burn end) as burnB
from (select t.*, row_number() over (partition by travelid order by travelid) as seqnum
from t
) t
group by travelid
SQL Server Dynamic version :
--TEST DATA
CREATE TABLE #TestTable
([TravelID] int, [SitelD] int, [Flagi] int, [FIag2] int)
;
INSERT INTO #TestTable
([TravelID], [SitelD], [Flagi], [FIag2])
VALUES
(1001, 1, 1, 0),(1001, 1, 0, 1),(1001, 1, 3, 4),
(1002, 1, 1, 0),(1002, 2, 0, 1),(1002, 2, 3, 4)
;
--STEP 1 rank data
SELECT * into #rank_table from (
select *
,ROW_NUMBER() OVER (PARTITION BY [TravelID],[SitelD] order by [SitelD]) [rank]
from (
select * from #TestTable
) T100
)T;
--STEP 2 Group by row_count
SELECT * into #group_table from (
select [TravelID],[SitelD] ,count(1) [count]
from #TestTable T
group by [TravelID],[SitelD]
)T;
--Use Exec
DECLARE #select_sql AS NVARCHAR(MAX) = ' select T.[TravelID], T.[SitelD] ',
#join_sql AS NVARCHAR(MAX) = ' from #group_table T ',
#max_count INT = (SELECT max([count]) FROM #group_table),
#temp_string NVARCHAR(5),
#temp_string_addone NVARCHAR(5)
;
DECLARE #index int = 0 ;
WHILE #index < #max_count
BEGIN
sELECT #temp_string = Convert(nvarchar(10),#index);
sELECT #temp_string_addone = Convert(nvarchar(10),#index+1);
select #select_sql = #select_sql + ' , T'+#temp_string_addone+'.[Flagi] as Flag'+Convert(nvarchar(10),2*#index+1)+' '
+ ' , T'+#temp_string_addone+'.[FIag2] as Flag'+Convert(nvarchar(10),2*#index+2)+' ';
select #join_sql = #join_sql + 'left join #rank_table T'+#temp_string_addone+' on ' + ' T.[TravelID] = T'+#temp_string_addone+'.[TravelID] and '
+ ' T.[SitelD] = T'+#temp_string_addone+'.[SitelD] and '
+ 'T'+#temp_string_addone+'.[rank] = '+#temp_string_addone+' ';
SET #index = #index + 1;
END;
EXEC (#select_sql
+ #join_sql
+' order by [TravelID],[SitelD] ; ')
;
DEMO : convert Rows to Columns in SQL with same ID, Sql Server - rextester
with CTE as(
select
*
,ROW_NUMBER() OVER (PARTITION BY [TravelID],[SitelD] order by [SitelD]) [rk]
from [Table]
)
select distinct a.[TravelID], a.[SitelD], a.[Flagi] as Flag1, a.[FIag2] as Flag1, b.[Flagi] as Flag3, b.[FIag2] as Flag4
from (select * from CTE where [rk] = 1) a
left join (select * from CTE where [rk] = 2) b on a.[TravelID] = b.[TravelID] and a.[SitelD] = + b.[SitelD]

Two dimensional rank using T-SQL

This is the data I'm dealing with:
I would like to find a way, in sql, of adding numbers to the yellow column which will rank the Names in such a way that I get the following.
note: This is the final pivoted result - in the sql table there is no need to pivot the data.
This ranking is decided via these rules:
The most recent week (ie Wk5 column) is the most important.
The next most recent week is next most important.
...so on to the left with the oldest week column "WK1" being the least important.
A data value that is small e.g. 1, is best. A data value that is high e.g. 7, is not good. A blank space is the worst and if at all possible should be located near the bottom of the page - but rules 1/2/3 always take precedence.
This is the data with a placeholder of 0 in the column Idx:
CREATE TABLE #values
(
Name varchar(5),
Idx int,
"Week" varchar(5),
Amount int
);
INSERT INTO #values
VALUES
('A',0,'WK1',3),
('T',0,'WK1',2),
('H',0,'WK1',1),
('P',0,'WK1',4),
('V',0,'WK1',6),
('N',0,'WK1',5),
('A',0,'WK2',2),
('F',0,'WK2',1),
('K',0,'WK2',3),
('P',0,'WK2',4),
('W',0,'WK2',7),
('V',0,'WK2',5),
('B',0,'WK2',6),
('A',0,'WK3',1),
('F',0,'WK3',2),
('T',0,'WK3',3),
('K',0,'WK3',4),
('W',0,'WK3',5),
('V',0,'WK3',6),
('N',0,'WK3',7),
('A',0,'WK4',2),
('F',0,'WK4',1),
('T',0,'WK4',5),
('K',0,'WK4',4),
('B',0,'WK4',6),
('A',0,'WK5',1),
('F',0,'WK5',2),
('T',0,'WK5',3),
('H',0,'WK5',4),
('K',0,'WK5',5);
This is my current attempt:
WITH
allData AS
(
SELECT Name,
"Week",
newRank = RANK() OVER (ORDER BY "Week" DESC,Amount)
FROM #values
)
,allData2 AS
(
SELECT *,
newRank2 = 1 / CONVERT(NUMERIC(18,10),newRank)
FROM allData
)
,allData3 AS
(
SELECT Name,
smRank = SUM(newRank2)
FROM allData2
GROUP BY Name
)
SELECT Name,
smRank,
rnk = RANK() OVER (ORDER BY smRank DESC)
INTO #RankA
FROM allData3;
UPDATE X
SET X.Idx = Y.rnk
FROM #values X
INNER JOIN #RankA Y ON
X.Name = Y.Name;
Unfortunately if I pivot the results, and then order by the Idx column it is not in the order I am aiming at.
This is based on two nested ROW_NUMBERs:
select *,
row_number()
over (order by "Week" desc, amount)
from
(
select *,
row_number()
over (partition by name
order by "Week" desc, amount) as rn
from #values
) as dt
where rn = 1 -- for each name find the latest week and it's lowest number
What if two names share the same week/amount? You might consider RANK or DENSE_RANK instead.
Using your #values table, here is how to pivot it (since the data you provided was not in the same table format) and then assign a value to the index based on your requirements.
select *
, ROW_NUMBER() OVER(ORDER BY CASE WHEN wk5 IS NULL THEN 1 ELSE 0 END, wk5, CASE WHEN wk4 IS NULL THEN 1 ELSE 0 END, wk4, CASE WHEN wk3 IS NULL THEN 1 ELSE 0 END,wk3, CASE WHEN wk2 IS NULL THEN 1 ELSE 0 END,wk2, CASE WHEN wk1 IS NULL THEN 1 ELSE 0 END, wk1) AS new_index
from (
select * from #values
) p
PIVOT (
MAX(Amount)
FOR [week] IN (wk1, wk2, wk3, wk4, wk5)) AS pvt
USING DYNAMIC FOR 52 WEEKS
DECLARE #COLS AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX)
SELECT #COLS = STUFF(( SELECT distinct ','+QUOTENAME(C.[week])
FROM #values AS C
FOR XML PATH('')), 1, 1, '')
SET #QUERY = '
select *
, ROW_NUMBER() OVER(ORDER BY CASE WHEN wk5 IS NULL THEN 1 ELSE 0 END, wk5, CASE WHEN wk4 IS NULL THEN 1 ELSE 0 END, wk4, CASE WHEN wk3 IS NULL THEN 1 ELSE 0 END,wk3, CASE WHEN wk2 IS NULL THEN 1 ELSE 0 END,wk2, CASE WHEN wk1 IS NULL THEN 1 ELSE 0 END, wk1) AS new_index
from (
select * from #values
) p
PIVOT (
MAX(Amount)
FOR [week] IN (' + #cols+ ')) AS pvt'
EXEC(#QUERY)

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;