How do I create this pivot SQL query? - sql

I have the following tables in our SQL Server 2012 instance:
tblASSETS
------------------------------------
| ASSETID | ASSETTYPE | NAME |
|---------|-----------|------------|
| 1 | A | Printer A |
| 2 | A | Printer B |
| 3 | A | Printer C |
| 4 | B | Laptop A |
------------------------------------
tblASSETTYPES
--------------------------------------
| ASSETTYPE | TYPENAME | ICON |
|-----------|----------|-------------|
| A | Printer | Printer.png |
| B | Laptop | Laptop.png |
--------------------------------------
tblASSETCUSTOM
-------------------------------------------------------------
| CUSTOMID | ASSETID | MAKE | MODEL | PRINTEDPAGES |
|----------|---------|------|----------------|--------------|
| 1 | 1 | HP | Laserjet 4v | 530 |
| 2 | 2 | HP | Laserjet 4v | 10000 |
| 3 | 3 | HP | Officejet 1050 | NULL |
| 4 | 4 | HP | Probook 430 G3 | NULL |
-------------------------------------------------------------
tblOIDDATA
---------------------------------------------
| OIDDATAID | ASSETID | LABEL | DATA |
|-----------|---------|--------------|------|
| 1 | 1 | Black copies | 430 |
| 2 | 1 | Color copies | 110 |
| 3 | 2 | Black copies | 5300 |
| 4 | 2 | Scans | 450 |
---------------------------------------------
I want to build a query which returns all printers and all their details as columns. I already created this QUERY:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(label)
from tblOIDData
group by label
order by label
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'Select * from ( Select Top 1000000 tAT.icon As icon, tat.typename as [ASSET TYPE] ,tA.Name as [ASSET NAME], tac.Model as [DEVICE MODEL], snmp.label as label, TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) as PageCount, TA.Printepages as [PRINTED PAGES] from tblAssets as tA, tblAssetCustom as tAC, tsysAssetTypes as tAT, tblOIDData as SNMP where tA.AssetType = tAT.AssetType AND tAT.Typename = ''Printer'' AND tAC.AssetID = tA.AssetID AND snmp.AssetID = tA.AssetID ) as s PIVOT ( sum(PageCount) for [LABEL] IN (' + #cols + ') ) AS pvt'
execute(#query);
This almost give the desired result. The only thing I'm facing is that ASSETID 3 (Printer C) is not in the result. Probably because it is not in the tblOIDData table.
How can I include this Asset in my results also?

You could use a LEFT JOIN in your dynamic sql.
set #query = N'SELECT *
FROM
(
SELECT TOP 1000000
tAT.icon AS [icon],
tat.typename AS [ASSET TYPE],
tA.Name AS [ASSET NAME],
tac.Model AS [DEVICE MODEL],
snmp.label AS [label],
TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) AS [PageCount],
TA.Printepages AS [PRINTED PAGES]
FROM tblAssets AS tA
JOIN tblAssetCustom AS tAC ON tAC.AssetID = tA.AssetID
JOIN tsysAssetTypes AS tAT ON tAT.AssetType = tA.AssetType
LEFT JOIN tblOIDData AS SNMP ON snmp.AssetID = tA.AssetID
WHERE tAT.Typename = ''Printer''
) AS src
PIVOT
(
SUM([PageCount])
FOR [label] IN (' + #cols + N')
) AS pvt';

Related

PIVOT with groups and dynamic column names

I have 3 tables that hold items properties, because each group of items can have different number of properties I hold them as key-value.
Table Package
| ID | Name |
| 1 | TVs |
| 2 | Laptops |
Table PackageItem
| ID | PackageId | Description |
| 1 | 1 | Samsung TV |
| 2 | 1 | Sony TV |
| 3 | 2 | Apple laptop |
| 4 | 2 | Lenovo |
Table PackageItemDetail
| ID | PackageItemId | Key | Value | PropertyOrder |
| 1 | 1 | Brand | Samsung | 1 |
| 2 | 1 | Size | 42 inch | 2 |
| 3 | 1 | Power consumption | A+ | 3 |
| 4 | 1 | Remote | Smart | 5 |
| 5 | 1 | Weight | 15kg | 4 |
| 6 | 2 | Brand | Sony | 1 |
| 7 | 2 | Size | 50 inch | 2 |
| 8 | 2 | Power consumption | A+++ | 3 |
| 9 | 2 | Remote | Standard | 5 |
| 10 | 2 | Weight | 20kg | 4 |
| 11 | 3 | Brand | Apple | 1 |
| 12 | 3 | Procesor | Intel | 2 |
| 13 | 4 | Brand | Lenovo | 1 |
| 14 | 4 | Procesor | Intel I7 | 2 |
If I select items and their properties for specific package using those queries:
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID (NOLOCK) JOIN PackageItem PI (NOLOCK) ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ORDER BY PID.PackageItemId, PID.PropertyOrder;
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID (NOLOCK) JOIN PackageItem PI (NOLOCK) ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2 ORDER BY PID.PackageItemId, PID.PropertyOrder;
I get those results:
Each Package always have same number or properties, so TVs have 5 properties and Laptops have 2 properties.
I'd like to transform those tables into those:
| Brand | Size | Power consumption | Weight | Remote |
| Samsung | 42 inch | A+ | 15kg | Smart |
| Sony | 50 inch | A+++ | 20kg | Standard |
| Brand | Procesor |
| Apple | Intel |
| Lenovo | Intel I7 |
I was able to create simple Pivot using:
SELECT
[Brand]
,[Procesor]
FROM
( SELECT
PI.[Id]
,PID.[Key]
,PID.[Value]
FROM
PackageItemDetail PID ( NOLOCK )
JOIN PackageItem PI ( NOLOCK ) ON PID.PackageItemId = PI.Id
WHERE
PI.PackageId = 2
) AS SourceTable PIVOT
( MAX(Value) FOR [Key] IN ( [Brand], [Procesor] ) ) AS PivotTable;
but this way I must specify properties by hand, but I'd like to have them dynamic (so same query will work for different packages)
I've created SQL Fiddle with sample data: http://sqlfiddle.com/#!18/e8c51/1
I tried to apply dynamic SQL in for your problem.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME([Key])
FROM (SELECT DISTINCT [Key] FROM PackageItemDetail PID
JOIN PackageItem PI ON PID.PackageItemId = PI.Id
WHERE PI.PackageId = 2) AS Courses
SET #DynamicPivotQuery =
N'SELECT
'+ #ColumnName +'
FROM
( SELECT
PI.[Id]
,PID.[Key]
,PID.[Value]
FROM
PackageItemDetail PID ( NOLOCK )
JOIN PackageItem PI ( NOLOCK ) ON PID.PackageItemId = PI.Id
WHERE
PI.PackageId = 2
) AS SourceTable PIVOT
( MAX(Value) FOR [Key] IN (' + #ColumnName + ')) AS PivotTable'
EXEC sp_executesql #DynamicPivotQuery
Below is the link to the demo of the query:
http://sqlfiddle.com/#!18/e8c51/44
For explanation, you can go to this link:
http://sqlhints.com/tag/dynamic-pivot-column-names/
DECLARE #COLUMN1 VARCHAR(250),#STATEMENT1 NVARCHAR(MAX),#COLUMN2 VARCHAR(250),#STATEMENT2 NVARCHAR(MAX);
SELECT #COLUMN1 = COALESCE(#COLUMN1+',','')+QUOTENAME([Key]) FROM(
SELECT DISTINCT PID.[Key] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1) A
SET #STATEMENT1 = N'
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ) as a
PIVOT (MIN([Value]) FOR [Key] IN ('+#COLUMN1+'))AS P1
UNION ALL
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ) as a
PIVOT (MAX([Value]) FOR [Key] IN ('+#COLUMN1+'))AS P2'
--PRINT #STATEMENT1
EXEC (#STATEMENT1)
SELECT #COLUMN2 = COALESCE(#COLUMN2+',','')+QUOTENAME([Key]) FROM(
SELECT DISTINCT PID.[Key] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2) A1
SET #STATEMENT2 = N'
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2) as a
PIVOT (MIN([Value]) FOR [Key] IN ('+#COLUMN2+'))AS P1
UNION ALL
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2 ) as a
PIVOT (MAX([Value]) FOR [Key] IN ('+#COLUMN2+'))AS P2'
--PRINT #STATEMENT2
EXEC (#STATEMENT2)
OUTPUT FOR #STATEMENT1
Brand Power consumption Remote Size Weight
Samsung A+ Smart 42 inch 15kg
Sony A+++ Standard 50 inch 20kg
OUTPUT FOR #STATEMENT2
Brand Procesor
Apple Intel
Lenovo Intel I7

how to convert multiple rows to single row?

How to get sql with Query
I have three tables in the form below
Table A
ID | Title |Count
--- |-------- |-----
1 |Mouse | 50
2 |pen | 60
Table B
ID | CompName|
--- |---------|
1 |Comp1 |
2 | Comp2 |
3 |Comp3 |
Table T
|---------------------|
|IDA | IDB | CountT|
|-------|-----|-------|
|1 | 1 | 5 |
|2 | 1 | 6 |
|1 | 2 | 7 |
+---------------------+
I want to make such a report
| object | Copm1 | Comp2 | Comp3 |Sum|remaining |
|--------|-------|-------|-------|---|--------- |
| Mouse | 5 | 7 | 0 | 12| 38 |
| pen | 6 | 0 | 0 | 6 | 54 |
My answer to my question
I was able to get the final answer using the PIVOT function
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') +
QUOTENAME(CompName) from B
set #SQLQuery=N'select pvt.title as object, ' + #PivotColumns + '
FROM
(select title, CountT,CompName
from T
inner join A on T.IDA = A.ID
inner join B on B.ID = T.IDA) AS src PIVOT
(
max(CountT)
FOR CompName IN (' + #PivotColumns + ')
) AS pvt;'
EXEC sp_executesql #SQLQuery
select a.title as object,
sum(case when b.id=1 then T.countT else 0 end) as Comp1,
sum(case when b.id=2 then T.countT else 0 end) as Comp2,
sum(case when b.id=3 then T.countT else 0 end) as Comp3,
sum(t.countt) as 'Sum',
max(a.count)-sum(t.countt) as Remaining
from TableT t
inner join tableA A on a.id=t.IDA
inner join TableB b on b.id=t.IDB
group by a.Title
http://rextester.com/l/sql_server_online_compiler

String concatenation in Dynamic SQL using PIVOT table

I have to work on a mapping from an ERP system to a MySQL database. The structure that is present in the ERP system is:
_____________________________________
| Article | Feature | Criterion |
|---------|---------------|-----------|
| Art1 | size | 4*10 |
| Art1 | color | red |
| Art1 | functionality | doesA |
| Art1 | ... | ... |
| Art2 | size | 2*5 |
| Art2 | color | green |
| Art2 | functionality | doesB |
| Art2 | ... | ... |
-------------------------------------
What i need to do is map it like this:
________________________________________________
| Article | size | color | functionality | ... |
|---------|------|-------|---------------|-------|
| Art1 | 4*10 | red | doesA | ... |
| Art2 | 2*5 | green | doesB | ... |
------------------------------------------------
I can access the ERP system via T-SQL and can perform a working dynamic query, that provides me a table and looks like:
DECLARE #cols AS nvarchar(MAX),
#query AS nvarchar(MAX)
SELECT #cols = stuff((SELECT DISTINCT ', ' + quotename(f.Feature) + ''
FROM CRITERION c, FEATURE f
WHERE --necessary joins
FOR xml PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = N'SELECT Article, ' + #cols + N'
FROM (
SELECT Article, Feature, Criterion
FROM --necessary tables
WHERE --necessary joins
) x
pivot
(
max(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
EXEC sp_executesql #query;
The problem that is coming up now is, that the system features multiple selection for some of the features:
_____________________________________
| Article | Feature | Criterion |
|---------|---------------|-----------|
| Art3 | color | red |
| Art3 | color | green |
-------------------------------------
and the query just gives me the first result in the table.
________________________________________
| Article | size | color | functionality |
|---------|------|-------|---------------|
| Art3 | ... | red | ... |
----------------------------------------
So my question is, if there is any way to add a string concatenation either in the subquery 'x' or in the pivot table 'p', so the result becomes following:
_____________________________________________
| Article | size | color | functionality |
|---------|------|------------|---------------|
| Art3 | ... | red, green | ... |
---------------------------------------------
#Serg has the right idea but the fields seem to be off. This should be closer.
SET #query = N'
SELECT Article, ' + #cols + N'
FROM (
SELECT Article,
Feature,
Criterion = STUFF(
(SELECT '', '' + t2.Criterion
FROM t1 as t2
WHERE t2.Article = t1.Article
AND t2.[Feature] = t1.[Feature]
FOR XML PATH('''')), 1, 2,'''')
FROM (SELECT Article, Feature, Criterion
FROM --necessary tables
WHERE --necessary joins) t1
) x
pivot
(
MAX(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
GROUP BY features first using the same FOR XML trick. Kind of
SET #query = N'SELECT Article, ' + #cols + N'
FROM (
SELECT Article, Criterion,
Feature = stuff(
(SELECT '',''+ t2.Feature
FROM ttt as t2
WHERE t2.Article = t1.Article AND
t2.Criterion = t1.Criterion
FOR XML PATH(''))
,1,1,'''')
FROM ttt t1
GROUP BY Article, Criterion
) x
pivot
(
max(Criterion)
FOR Feature IN (' + #cols + N')
) p
'
Replace ttt with real data sources.

SQL server: Transpose Rows to Columns (n:m relationship)

After trying it myself for some hours now I need to ask for help. I only did some basic SQL until now.
I want to solve the following:
(I have translated a couple of things for you to understand the context)
I have three tables:
Workers (Mitarbeiter in German - mitID)
| mitID | Name | FamName | DOB | abtIDref |
|-------|--------|---------|------------|----------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 |
| 2 | Robert | Downey | 4.4.1965 | 2 |
INFO: abtIDref is an 1:n relation for the Workplace, but not involved here
Skills (Faehigkeiten in German - faeID)
| faeID | Descr | time | cost |
|-------|-------|------|------|
| 1 | HV | 2 | 0 |
| 2 | PEV | 1 | 0 |
| 3 | Drive | 8 | 250 |
| 4 | Nex | 20 | 1200 |
Link-List
| linkID | mitIDref | feaIDref | when |
|--------|----------|----------|------------|
| 1 | 2 | 1 | 27.07.2014 |
| 2 | 2 | 2 | 01.01.2016 |
| 3 | 2 | 3 | 20.01.2016 |
| 4 | 1 | 3 | 05.06.2015 |
| 5 | 1 | 4 | 02.11.2015 |
The desired result is:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|-----------|------------|------------|------------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | 05.06.2015 | 02.11.2015 |
| 2 | Robert | Downey | 4.4.1965 | 2 | 27.7.2014 | 01.01.2016 | 20.01.2015 | |
Alternative it could be:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|----|-----|-------|-----|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | x | x |
| 2 | Robert | Downey | 4.4.1965 | 2 | x | x | x | |
The goal is that users/admins can add up new skills and someone can see on this resultlist, if a person has this skill.
What did i try:
I've come across multiple examples of dynamic SQL and the pivot function, but I don't know how to use it in my case, because I don't run a function like AVG() or MIN().
I tried it like this:
DECLARE #columns AS VARCHAR(MAX);
DECLARE #sql AS VARCHAR(MAX);
select #columns = substring((Select DISTINCT ',' + QUOTENAME(faeID) FROM mdb_Fähigkeiten FOR XML PATH ('')),2, 1000);
SELECT #sql = 'SELECT * FROM mdb_Mitarbeiter
PIVOT
(
MAX(Value)
FOR mitID IN( ' + #columns + ' )
);';
execute(#sql);
And a second approach was:
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(Question)
FROM #t1 -- your table here
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
select #collist
declare #q nvarchar(max)
set #q = '
select *
from (
select
Vorname, Bezeichnung, faeIDref
from (
select #t1.*, #t2.Answer, #t2.parent
from #t1
inner join #t2 on #t1.QID = #t2.QID
) as x
) as source
pivot (
max(Answer)
for Question in (' + #collist + ')
) as pvt
'
exec (#q)
But TBH I don't get the functions found.
I hope you can provide me with some guidance what I have to change (or even if I can) achieve this.
I believe the query below is what you are looking for. Adjust the column and table names as needed to fit your database.
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols= ISNULL(#cols + ',','') + QUOTENAME(Descr)
FROM Faehigkeiten ORDER BY faeID
SET #sql = N'
SELECT mitID, Name, FamName, DOB, abtIDref, ' + #cols + '
FROM (
SELECT mitID, Name, FamName, DOB, abtIDref, [when], descr
FROM Mitarbeiter m
JOIN [Link-List] l ON m.mitID = l.mitIDref
JOIN Faehigkeiten f ON f.faeID = l.feaIDref
) a
PIVOT(MAX([when]) FOR descr IN (' + #cols + ')) p'
EXEC sp_executesql #sql

Dynamic field content as Row Sql

I have the following dataset on a sql database
----------------------------------
| ID | NAME | AGE | STATUS |
-----------------------------------
| 1ASDF | Brenda | 21 | Single |
-----------------------------------
| 2FDSH | Ging | 24 | Married|
-----------------------------------
| 3SDFD | Judie | 18 | Widow |
-----------------------------------
| 4GWWX | Sophie | 21 | Married|
-----------------------------------
| 5JDSI | Mylene | 24 | Singe |
-----------------------------------
I want to query that dataset so that i can have this structure in my result
--------------------------------------
| AGE | SINGLE | MARRIED | WIDOW |
--------------------------------------
| 21 | 1 | 1 | 0 |
--------------------------------------
| 24 | 1 | 1 | 0 |
--------------------------------------
| 18 | 0 | 0 | 1 |
--------------------------------------
And the status column can be dynamic so there will be more columns to come.
Is this possible?
Since you are using SQL Server, you can use the PIVOT table operator like this:
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN(Single, Married, Widow)
) AS p;
SQL Fiddle Demo
To do it dynamically you have to use dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(status)
FROM tablename
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = '
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN( ' +#cols + ')
) AS p;';
execute(#query);
Updated SQL Fiddle Demo