pivot in sql server 2012 with uniqeness - sql

if i have a table like given below.
declare #mytble table
(
orders int,
product varchar (50),
quantity int
)
INSERT #mytble
SELECT 100,'CUP','1' UNION ALL
SELECT 100, 'PLATE',2 UNION ALL
SELECT 101,'CUP','1' UNION ALL
SELECT 102,'CUP','2' UNION ALL
SELECT 103, 'CUP',1 UNION ALL
SELECT 103,'PLATE','3' UNION ALL
SELECT 103,'GLASS','1'
SELECT * FROM #mytble
will it be possible to get output like this.
any suggestion please.

With some dynamic SQL and Dense_Rank() function
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(concat('Product',Dense_Rank() over (Order By Product)))
+ ',' + QuoteName(concat('Quantity',Dense_Rank() over (Order By Product)))
From myTable For XML Path('')),1,1,'')
Select #SQL = 'Select Orders,' + #SQL + '
From (
Select Orders,Item=concat(''Product'',Dense_Rank() over (Order By Product)),Val=cast(Product as varchar(max)) From myTable
Union All
Select Orders,Item=concat(''Quantity'',Dense_Rank() over (Order By Product)),Val=cast(Quantity as varchar(max)) From myTable
) A
Pivot (max(Val) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Orders Product1 Quantity1 Product2 Quantity2 Product3 Quantity3
100 CUP 1 NULL NULL PLATE 2
101 CUP 1 NULL NULL NULL NULL
102 CUP 2 NULL NULL NULL NULL
103 CUP 1 GLASS 1 PLATE 3

Related

Select every 10 row as a new column in sql server

I have a SQL Table with data as shown,
Item Qty
------------
A1 59
A2 76
A3 86
A1 12
A2 17
A3 15
A1 23
A2 39
A3 07
Here we can see Item is repeated.So, i would like to group the records by Item.
I want to get the output table look like below
Item Qty1 Qty2 qty3
----------------------------------
A1 59 12 23
A2 76 17 39
A3 86 15 07
Use pivot with ranking function ROW_NUMBER:
WITH CTE
AS
(
SELECT
Item, QTy, ROW_NUMBER() OVER(PARTITION BY ITem ORDER BY Item) AS RN
FROM tablename
)
SELECT Item, [1] AS Qty1, [2] AS Qty2, [3] AS Qty3
FROM CTE
PIVOT
(
SUM(Qty)
FOR rn IN([1], [2], [3])
) AS p;
demo
Results:
| Item | Qty1 | Qty2 | Qty3 |
|------|------|------|------|
| A1 | 59 | 12 | 23 |
| A2 | 39 | 17 | 76 |
| A3 | 86 | 15 | 7 |
If these quantities are not fixed and they are not 3 always, you need to do it dynamically like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
DECLARE #colNames AS NVARCHAR(MaX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(CAST(RN AS NVARCHAR(10)))
FROM
(
SELECT
Item, QTy, ROW_NUMBER() OVER(PARTITION BY ITem ORDER BY Item) AS RN
FROM tablename ) as t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
select #colNames = STUFF((SELECT distinct ',' +
'[' + CAST(RN AS NVARCHAR(10)) + '] AS Qty' +
CAST(RN AS NVARCHAR(10))
FROM
(
SELECT
Item, QTy, ROW_NUMBER() OVER(PARTITION BY ITem ORDER BY Item) AS RN
FROM tablename ) as t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'WITH CTE
AS
(
SELECT
Item, QTy, ROW_NUMBER() OVER(PARTITION BY ITem ORDER BY Item) AS RN
FROM tablename
)
SELECT Item,' + #colNames + '
FROM CTE
PIVOT
(
SUM(Qty)
FOR rn IN(' + #cols + ')
) AS p;';
execute(#query);
demo
You can use pivot as below:
Select * from (
Select *, Qtys = Concat('Qty', Row_Number() over(partition by Item order by Item))
from #itemdata
) a
pivot (max(qty) for qtys in ([Qty1],[Qty2],[Qty3])) p
For dynamic list you can query as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select top (select max(cnt) from (select count(*) cnt from #itemdata group by item ) a) ','+
QuoteName(Concat('Qty', Row_Number() over(order by (select Null)))) from
master..spt_values c1, master..spt_values c2 for xml path('')),1,1,'')
Select #query = ' Select * from (
Select *, Qtys = Concat(''Qty'', Row_Number() over(partition by Item order by Item))
from #itemdata
) a
pivot (max(qty) for qtys in (' + #cols1 + ')) p '
Exec sp_executesql #query

SQL PIVOT result set [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 8 years ago.
Looking for help with pivoting a result set as I'm very new to it.
Here I have test data inserted into a table.
CREATE TABLE #temp (procCode int, member_id varchar(10))
INSERT INTO #temp(procCode,member_id)
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90658,'jjjkk'
UNION all
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90658,'jjjkk'
UNION all
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90649,'jjjj'
UNION all
SELECT 90649,'k'
UNION all
SELECT 906,'jjjj'
UNION all
SELECT 906,'jjjj'
select
member_id,procCode, COUNT(*) as countProcCode
FROM #temp
GROUP BY member_id,procCode
This right now outputs data like this:
member_id procCode CountProcCode
jjjj 906 2
jjjj 90649 1
jjjkk 90658 2
k 90649 1
jjjj 90658 3
k 90658 3
How I need it to display is like this:
member_id Count906 count90649 count90658
jjjj 2 1 3
k 0 1 3
jjjkk 0 0 2
Any help is greatly appreciated.
There are more than just these procCodes and member_id so I couldn't really say where member_id in (506,50658,50649) as there are additional ones that could appear.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.procCode)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT member_id, ' + #cols + ' from
(
select member_id, member_id as b
, procCode
from #temp
) x
pivot
(
count(b)
for procCode in (' + #cols + ')
) p '
execute(#query)
drop table #temp
Results
member_id 906 90649 90658
jjjj 2 1 3
jjjkk 0 0 2
k 0 1 3
Recomended reading

Dynamic columns not getting created in T-SQL

I have the following tables:
tblengineeringlookupcolumnmaster
elccolumnid | elclookupcode | elccolumnname | elcisrequired
1 | 64 | FirstName | 1
2 | 64 | LastName | 1
3 | 65 | abc | 1
4 | 65 | xyz | 1
tblengineeringlookupdetail
eldrecordId | eldlookupcode |eldlookupsequence |eldlookupvalue | eldlookupvaluedescription
245 | 64 | 0 | Red | Aravinth,Arumugam
246 | 64 | 1 | Blue | Santhosh,Chandran
247 | 64 | 2 | Green | Karthik,Balasubramanian
When I pass '64' as the parameter to the procedure.
I got the output as:
FirstName | LastName | eldRecordId
-------------------------------------
Aravinth | Arumugam | 245
Santhosh | Chandran | 246
Karthik | Balasubramanian| 247
The Store procedure used is
//SP
-- Select the columns to be used
DECLARE #tcol TABLE
(
ID INT IDENTITY(1,1)
, elclookupcode INT
, elccolumnname VARCHAR(100)
)
-- Insert the records into the table
INSERT INTO #tcol (elclookupcode, elccolumnname)
SELECT elclookupcode,elccolumnname FROM tblEngineeringLookupColumnMaster WHERE elclookupcode=#LookupCode
-- Select the columns which should be as output as a table
DECLARE #temp TABLE
(
elcLookupCode INT
, RecordId INT
, txt VARCHAR(8000)
)
-- Select the records from the table and insert
INSERT INTO #temp (elcLookupCode, RecordId, txt)
SELECT eldLookupCode,eldRecordId, eldLookupValueDescription FROM tblEngineeringLookupDetail WHERE eldLookupCode=#LookupCode
DECLARE #SQL NVARCHAR(MAX)
-- Have a table for the selected values
;WITH cte AS
(
SELECT
token = ', [' + d2.elccolumnname + '] = ''' + d.token + ''''
, d.RecordId
FROM (
SELECT
token = t.c.value('.', 'VARCHAR(50)')
, a.RecordId
, a.elcLookupCode
, rn = ROW_NUMBER() OVER (PARTITION BY a.RecordId ORDER BY a.RecordId)
FROM (
SELECT
RecordId
, elcLookupCode
, txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') t(c)
) d
-- Select the columns to be mapped
JOIN (
SELECT
elclookupcode
, elccolumnname
, rn = ROW_NUMBER() OVER (PARTITION BY elclookupcode ORDER BY elclookupcode)
FROM #tcol
) d2 ON d.elcLookupCode = d2.elclookupcode AND d2.rn = d.rn
)
-- Join all the records taken
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL SELECT '+ STUFF((
SELECT t2.token
FROM cte t2
WHERE t2.RecordId = t.RecordId
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, ''), ',[RecordId] = ' + CAST(RecordId AS VARCHAR(10))
FROM #temp t
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
EXEC sp_executesql #SQL
END
The question now is:
If I pass '65' as parameter to the procedure,Where there are values in the first table(tblengineeringlookupcolumnmaster) and no corresponding values in the second table(tblengineeringlookupdetail) I'm getting no results( A message showing 3rows affected).
But I have to get the columns headers alone.
'Sample:
xyz | abc | eldrecordId
NULL |NULL | NULL
Why i'm not getting this? Where i'm wrong?
Try this one -
DECLARE #tcol TABLE
(
ID INT IDENTITY(1,1)
, elclookupcode INT
, elccolumnname VARCHAR(20)
)
INSERT INTO #tcol (elclookupcode, elccolumnname)
VALUES
(65, 'FirstName'),
(65, 'LastName')
DECLARE #temp TABLE
(
elcLookupCode INT
, eldRecordId INT
, txt VARCHAR(100)
)
INSERT INTO #temp (elcLookupCode, eldRecordId, txt)
VALUES
(64, 245, 'Aravinth,Arumugam'),
(64, 246, 'Santhosh,Chandran'),
(64, 247, 'Karthik,Balasubramanian')
DECLARE #SQL NVARCHAR(MAX)
;WITH cte AS
(
SELECT
token = ', [' + d2.elccolumnname + '] = ''' + d.token + ''''
, d.eldRecordId
FROM (
SELECT
token = t.c.value('.', 'VARCHAR(50)')
, a.eldRecordId
, a.elcLookupCode
, rn = ROW_NUMBER() OVER (PARTITION BY a.eldRecordId ORDER BY a.eldRecordId)
FROM (
SELECT
eldRecordId
, elcLookupCode
, txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') t(c)
) d
LEFT JOIN (
SELECT
elclookupcode
, elccolumnname
, rn = ROW_NUMBER() OVER (PARTITION BY elclookupcode ORDER BY elclookupcode)
FROM #tcol
) d2 ON d.elcLookupCode = d2.elclookupcode AND d2.rn = d.rn
)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL SELECT [eldRecordId] = ' + CAST(eldRecordId AS VARCHAR(10)) + ', ' +
'[elcLookupCode] = ' + CAST(elcLookupCode AS VARCHAR(10)) + ISNULL(STUFF((
SELECT t2.token
FROM cte t2
WHERE t2.eldRecordId = t.eldRecordId
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, ', '), '')
FROM #temp t
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
EXEC sys.sp_executesql #SQL

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost:

Displaying Columns as Rows in SQL Server 2005

I have read dozens of solutions to similar transposition problems as the one I am about to propose but oddly none that exactly mirrors my issue. I am simply trying to flip my rows to columns in a simple dashboard type data set.
The data when pulled from various transaction tables looks like this:
DatePeriod PeriodNumberOverall Transactions Customers Visits
'Jan 2012' 1 100 50 150
'Feb 2012' 2 200 100 300
'Mar 2012' 3 300 200 600
and I want to be able to generate the following:
Jan 2012 Feb 2012 Mar 2012
Transactions 100 200 300
Customers 50 100 200
Visits 150 300 600
The metrics will be static (Transactions, Customers and Visits), but the date periods will be dynamic (IE - more added as months go by).
Again, I have ready many examples leveraging pivot, unpivot, store procedures, UNION ALLs, etc, but nothing where I am not doing any aggregating, just literally transposing the whole output. I have also found an easy way to do this in Visual Studio 2005 using a matrix with an embedded list, but I can't export the final output to excel which is a requirement. Any help would be greatly appreciated.
In order to get the result that you want you need to first UNPIVOT the data and then PIVOT theDatePeriod` Values.
The UNPIVOT will transform the multiple columns of Transactions, Customers and Visits into multiple rows. The other answers are using a UNION ALL to unpivot but SQL Server 2005 was the first year the UNPIVOT function was supported.
The query to unpivot the data is:
select dateperiod,
col, value
from transactions
unpivot
(
value for col in (Transactions, Customers, Visits)
) u
See Demo. This transforms your current columns into multiple rows, so the data looks like the following:
| DATEPERIOD | COL | VALUE |
-------------------------------------
| Jan 2012 | Transactions | 100 |
| Jan 2012 | Customers | 50 |
| Jan 2012 | Visits | 150 |
| Feb 2012 | Transactions | 200 |
Now, since the data is in rows, you can apply the PIVOT function to the DatePeriod column:
select col, [Jan 2012], [Feb 2012], [Mar 2012]
from
(
select dateperiod,
t.col, value, c.SortOrder
from
(
select dateperiod,
col, value
from transactions
unpivot
(
value for col in (Transactions, Customers, Visits)
) u
) t
inner join
(
select 'Transactions' col, 1 SortOrder
union all
select 'Customers' col, 2 SortOrder
union all
select 'Visits' col, 3 SortOrder
) c
on t.col = c.col
) d
pivot
(
sum(value)
for dateperiod in ([Jan 2012], [Feb 2012], [Mar 2012])
) piv
order by SortOrder;
See SQL Fiddle with Demo.
If you have an unknown number of date period's then you will use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(dateperiod)
from transactions
group by dateperiod, PeriodNumberOverall
order by PeriodNumberOverall
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT col, ' + #cols + '
from
(
select dateperiod,
t.col, value, c.SortOrder
from
(
select dateperiod,
col, value
from transactions
unpivot
(
value for col in (Transactions, Customers, Visits)
) u
) t
inner join
(
select ''Transactions'' col, 1 SortOrder
union all
select ''Customers'' col, 2 SortOrder
union all
select ''Visits'' col, 3 SortOrder
) c
on t.col = c.col
) x
pivot
(
sum(value)
for dateperiod in (' + #cols + ')
) p
order by SortOrder'
execute(#query)
See SQL Fiddle with Demo. Both will give the result:
| COL | JAN 2012 | FEB 2012 | MAR 2012 |
-------------------------------------------------
| Transactions | 100 | 200 | 300 |
| Customers | 50 | 100 | 200 |
| Visits | 150 | 300 | 600 |
You need to dynamically create a SQL statement with PIVOT and APPLY operators on the fly and then run that command. If your metrics static(Transactions, Customers and Visits), hence we can use CROSS APPLY operator with VALUES As a Table Source.
For SQL Server2008+
DECLARE #cols nvarchar( max),
#query nvarchar(max)
SELECT #cols =
STUFF((SELECT ',' + QUOTENAME(t.DatePeriod) AS ColName
FROM dbo.test62 t
FOR XML PATH(''), TYPE).value ('.', 'nvarchar(max)'), 1, 1, '')
SET #query =
'SELECT *
FROM (
SELECT t.DatePeriod, COALESCE(o.Transactions, o.Customers, o.Visits) AS PvtVals, o.PvtColumns, o.OrderColumns
FROM dbo.test62 t CROSS APPLY (
VALUES(t.Transactions, NULL, NULL, ''Transaction'', 1),
(NULL, t.Customers, NULL, ''Customers'', 2),
(NULL, NULL, t.Visits, ''Visits'', 3)
) o (Transactions, Customers, Visits, PvtColumns, OrderColumns)
) p
PIVOT
(
MAX(PvtVals) FOR DatePeriod IN (' + #cols + ')
) AS pvt
ORDER BY pvt.OrderColumns '
EXEC(#query)
Result:
PvtColumns Jan 2012 Fed 2012 Mar 2012
Transaction 100 200 300
Customers 50 100 200
Visits 150 300 600
Demo on SQLFiddle
For SQL Server 2005
DECLARE #cols nvarchar( max),
#query nvarchar(max)
SELECT #cols =
STUFF((SELECT ',' + QUOTENAME(t.DatePeriod) AS ColName
FROM dbo.test62 t
FOR XML PATH(''), TYPE).value ('.', 'nvarchar(max)'), 1, 1, '')
SET #query =
'SELECT *
FROM (
SELECT t.DatePeriod, COALESCE(o.Transactions, o.Customers, o.Visits) AS PvtVals, o.PvtColumns, o.OrderColumns
FROM dbo.test62 t CROSS APPLY (
SELECT t.Transactions, NULL, NULL, ''Transaction'', 1
UNION ALL
SELECT NULL, t.Customers, NULL, ''Customers'', 2
UNION ALL
SELECT NULL, NULL, t.Visits, ''Visits'', 3
) o (Transactions, Customers, Visits, PvtColumns, OrderColumns)
) p
PIVOT
(
MAX(PvtVals) FOR DatePeriod IN (' + #cols + ')
) AS pvt
ORDER BY pvt.OrderColumns'
EXEC(#query)
If you can know how many different date period in advance, then you can use fixed query like following:
;with CTE_UNIONTable
as
(
select [DatePeriod],[PeriodNumberOverall],[Transactions] as [value], 'Transactions' as subType from table1
UNION ALL
select [DatePeriod],[PeriodNumberOverall],[Customers] as [value], 'Customers' as subType from table1
UNION ALL
select [DatePeriod],[PeriodNumberOverall],[Visits] as [value], 'Visits' as subType from table1
), CTE_MiddleResult
as
(
select * from CTE_UNIONTable
pivot
(
max(value)
for DatePeriod in ([Jan 2012],[Feb 2012],[Mar 2012])
) as P
)
select SubType, max([Jan 2012]) as [Jan 2012] ,max([Feb 2012]) as [Feb 2012], max([Mar 2012]) as [Feb 2012]
from CTE_MiddleResult
group by SubType
SQL FIDDLE DEMO
If how many date period is unpredictable, then #Alexander already gave the solution, the following code is just a second opinion, instead of using APPLY, using UNION ALL
DECLARE #cols nvarchar( max),
#query nvarchar (max),
#selective nvarchar(max)
SELECT #cols =
STUFF((SELECT ',' + QUOTENAME(t.DatePeriod) AS ColName
FROM table1 t
FOR XML PATH( ''), TYPE).value ('.', 'nvarchar(max)'),1,1,'')
SELECT #selective =
STUFF((SELECT ',MAX(' + QUOTENAME(t.DatePeriod) +') as ' + QUOTENAME(t.DatePeriod) AS ColName
FROM table1 t
FOR XML PATH( ''), TYPE).value ('.', 'nvarchar(max)'),1,1,'')
set #query = '
;with CTE_UNIONTable
as
(
select [DatePeriod],[PeriodNumberOverall],[Transactions] as [value], ''Transactions'' as subType from table1
UNION ALL
select [DatePeriod],[PeriodNumberOverall],[Customers] as [value], ''Customers'' as subType from table1
UNION ALL
select [DatePeriod],[PeriodNumberOverall],[Visits] as [value], ''Visits'' as subType from table1
), CTE_MiddleResult
as
(
select * from CTE_UNIONTable
pivot
(
max(value)
for DatePeriod in ('+#cols+')
) as P
)
select SubType,' + #selective + '
from CTE_MiddleResult
group by SubType'
exec(#query)
SQL FIDDLE DEMO