Select rows to columns from table - sql

How to simply SELECT rows from VALUE column to get a two-dimensional table with 6 columns and 2 rows.
Below is the source table:

Here's a solution using SQL Pivot.
How to create the example dataset:
IF OBJECT_ID('SourceTable') Is Not Null Drop Table SourceTable
Create Table SourceTable (
COL_IDX int,
ROW_IDX int,
VALUE nvarchar(100)
)
Insert into SourceTable (COL_IDX, ROW_IDX, VALUE)
Values
(1,1,'after 45 min')
,(2,1,'98')
,(3,1,'95')
,(4,1,'99')
,(5,1,'1.1')
,(6,1,'12')
,(1,2,'after 60 min')
,(2,2,'98')
,(3,2,'96')
,(4,2,'101')
,(5,2,'1.4')
,(6,2,'12')
How to pivot the data:
SELECT ROW_IDX AS MyIndex, [1],[2],[3],[4],[5],[6]
FROM
(SELECT * FROM SourceTable) AS q
PIVOT (
MAX(Value)
FOR COL_IDX IN ([1],[2],[3],[4],[5],[6])
) AS PivotedTable
Result:
MyIndex 1 2 3 4 5 6
1 after 45 min 98 95 99 1.1 12
2 after 60 min 98 96 101 1.4 12
A pivot in SQL uses an aggregate function; here I used "max" but it doesn't really matter because there aren't multiple values for a ROW_IDX/COL_IDX combo.
It sounds like you might need to dynamically generate your columns, in which case you can use dynamic SQL to get the same result:
DROP TABLE IF EXISTS #headings
SELECT DISTINCT COL_IDX='[' + Cast(COL_IDX AS NVARCHAR(6)) + ']'
INTO #headings
FROM SourceTable
DECLARE #Columns nvarchar(1000) = (SELECT string_agg(COL_IDX, ', ') FROM #headings)
DECLARE #MyCommand nvarchar(max) =
'SELECT *
FROM SourceTable
PIVOT (
MAX(VALUE)
FOR COL_IDX IN (~Columns~)
) PivotTable'
SET #MyCommand = Replace(#MyCommand, '~Columns~', #Columns)
EXEC (#MyCommand)
DROP TABLE IF EXISTS #headings

A typical solution to pivot a table over a fixed set of columns is to use conditional aggregation:
select
row_idx,
max(case when col_idx = 1 then value end) col1,
max(case when col_idx = 2 then value end) col2,
max(case when col_idx = 3 then value end) col3,
max(case when col_idx = 4 then value end) col4,
max(case when col_idx = 5 then value end) col5,
max(case when col_idx = 6 then value end) col6
from mytable
group by row_idx
order by row_idx

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.

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

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

Uneven pivot without aggregation (filling in the blanks)

I have the following table
Header: user, status, value
row1: u1,A,3
row2: u1,B,5
row3: u1,B,2
row4, u2,A,4
row5: u2,C,8
and I want the output to be a crosstab with NULL if there are not sufficient values from one user to another. In the example the output would be:
Header: status, u1, u2
row1: A,3,4
row2: B, 5, NULL
row3: B, 2, NULL
row4: C, NULL, 8
(I am using SQL Server 2016.)
Not clear if you needed Dynamic (i.e. user columns). Small matter if needed.
Example
Select [Status]
,u1 = max(case when [user]='u1' then Value end)
,u2 = max(case when [user]='u2' then Value end)
From (
Select *
,Grp = Value - Row_Number() over (Partition By [Status] Order by Value)
From YourTable
) A
Group By [Status],[Grp]
Order By 1,2,3
Returns
Status u1 u2
A 3 4
B 2 NULL
B 5 NULL
C NULL 8
EDIT - Dynamic Approach
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName([User]) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Status],' + #SQL + '
From (
Select *
,Grp = Value - Row_Number() over (Partition By [Status] Order by Value)
From YourTable
) A
Pivot (max([Value]) For [User] in (' + #SQL + ') ) p
Order By 1,2,3
'
Exec(#SQL);

Dynamic Row Data into Column

I have a column which has 100 rows of data. I need to get the top 4 but in instead of rows I need to convert it into columns. Like Col1, Col2, Col3 and Col4.
I have tried
SELECT
MAX (CASE
WHEN rss_name = 'BBC-Sports'
THEN rss_name
END) AS col1,
MAX (CASE
WHEN rss_name = 'Talk Sports'
THEN rss_name
END) AS col2,
MAX (CASE
WHEN rss_name = 'Sky Sports'
THEN rss_name
END) AS col3,
MAX (CASE
WHEN rss_name = 'Crick Info'
THEN rss_name
END) AS col4
FROM
RSS
but it only works with static values:
I need
Col1, Col2, Col3, Col4
Sports,Talk Sports,Sky Sports,Crick Info
but since this is not constant data it will change and the values in Col keep changing.
You could use a derived table to set your column order then use your conditional aggregation on that.
SELECT
MAX(CASE WHEN Col_Rn = 1 THEN Rss_Name END) AS Col1,
MAX(CASE WHEN Col_Rn = 2 THEN Rss_Name END) AS Col2,
MAX(CASE WHEN Col_Rn = 3 THEN Rss_Name END) AS Col3,
MAX(CASE WHEN Col_Rn = 4 THEN Rss_Name END) AS Col4
FROM (
SELECT Rss_Name,
Row_Number() OVER (ORDER BY Rss_Name) AS Col_Rn -- set your order here
FROM RSS
) t
You need to use Dynamic Pivot. But in your case besides you need an extra column for Column names in Pivot like COL_1, COL_2....
Schema: (From your Image. Its better if you provide this sample data in Text).
CREATE TABLE #TAB (Rss_Name VARCHAR(50))
INSERT INTO #TAB
SELECT 'Sports'
UNION ALL
SELECT 'Talk Sports'
UNION ALL
SELECT 'Sky Sports'
UNION ALL
SELECT 'Crick Info'
Now Prepare your dynamic query as below
DECLARE #SQL VARCHAR(MAX)='',#PVT_COL VARCHAR(MAX)='';
--Preparing Dynamic Column List
SELECT #PVT_COL =#PVT_COL
+ '[COL_'+CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS VARCHAR(4))+'],'
FROM #TAB
SELECT #PVT_COL = LEFT(#PVT_COL,LEN(#PVT_COL)-1)
SELECT #SQL =
'SELECT * FROM (
SELECT Rss_Name
,''COL_''+CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS VARCHAR(4)) AS COL_NME
FROM #TAB
)AS A
PIVOT
(
MAX(Rss_Name) FOR COL_NME IN ('+#PVT_COL+')
)PVT'
EXEC (#SQL)
Result:
+--------+-------------+------------+------------+
| COL_1 | COL_2 | COL_3 | COL_4 |
+--------+-------------+------------+------------+
| Sports | Talk Sports | Sky Sports | Crick Info |
+--------+-------------+------------+------------+