Dynamically append columns based on another table in SQL - sql

Suppose I want the following behavior in SQL
QTY Table
SERIAL_NO QTY CODE
1111111 1 AA
1111112 1 AA
1111111 2 BB
1111111 4 BB
1111113 7 CC
Code Table
CODE CODE_NAME
AA NameA
BB NameB
CC NameC
Query Result
SERIAL_NO NameA NameB NameC
1111111 1 6 0
1111112 1 0 0
1111113 0 0 7
NameA,B,C column in query result are basically the sums grouped by their respective code.
How do I achieve this behavior? The hardest part I'm trying to grasp is to dynamically add the columns to the query result based on the code table. For example, if the user adds another code called DD, the query result should automatically append NameD to the right side, and get the corresponding sum for that new code.

If you really need it in SQL you can leverage PIVOT and dynamic SQL in the following way
DECLARE #cols NVARCHAR(MAX), #colp NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF(
(
SELECT DISTINCT ',COALESCE(' + QUOTENAME(code_name) + ',0) AS ' + QUOTENAME(code_name)
FROM code
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #colp = STUFF(
(
SELECT DISTINCT ',' + QUOTENAME(code_name)
FROM code
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #sql = 'SELECT serial_no, ' + #cols +
' FROM
(
SELECT serial_no, code_name, qty
FROM qty q JOIN code c
ON q.code = c.code
) x
PIVOT
(
SUM(qty) FOR code_name IN (' + #colp + ')
) p
ORDER BY serial_no'
EXECUTE(#sql)
Output:
| SERIAL_NO | NAMEA | NAMEB | NAMEC |
|-----------|-------|-------|-------|
| 1111111 | 1 | 6 | 0 |
| 1111112 | 1 | 0 | 0 |
| 1111113 | 0 | 0 | 7 |
Here is SQLFiddle demo

Related

Dynamic sql pivot values coming in seperate columns

HI I am trying to write dynamic pivot as I have over 100 columns
ID Question Answer
1 Name peter
1 DOB 11/12/2003
1 address …..
1 Issue1 d
1 Issue2 a
2 Name sam
2 DOB 10/01/1998
2 address …..
2 Issue1 p
2 Issue2 f
I want the output like this:
ID Name DOB address Issue1 Issue2
1 peter 11/12/2003 …. d a
2 sam 10/01/1998 …. p f
Here is the code that I have used:-
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Question)
from #temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ID + #cols + N'
from #temp
pivot
(
max([answer])
for Question in (' + #cols + N')
) p '
exec sp_executesql #query;
But I am getting each answer in a separate row. I want all the answers of an ID to come in one row. Can somebody help me. Appreciate your time on this. Thank you.
I am getting the output like this, which is not I want. I want the output as above.
ID Name DOB address Issue1 Issue2
1 Peter
1 11/12/2003
1 …
1 d
1 a
It looks like you are looking for a way to dynamically create a pivot based on your table without aggregation. You can try the following:
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Question)
FROM #TEMP
GROUP BY QUESTION
ORDER BY QUESTION
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ID,' + #cols + ' FROM
(
SELECT ID, Question, Answer
FROM #TEMP
) x
pivot
(
MAX(Answer)
for Question in (' + #cols + ')
) p '
execute(#query);
Expected Output:
+----+---------+------------+--------+--------+-------+
| ID | address | DOB | Issue1 | Issue2 | Name |
+----+---------+------------+--------+--------+-------+
| 1 | ….. | 11/12/2003 | d | a | peter |
| 2 | ….. | 10/1/1998 | p | f | sam |
+----+---------+------------+--------+--------+-------+

Count the number of matches for a prefix in a SQL query

I have a table in an SQL server that looks like the one below and I want to count the number of unique occurrences where specific prefixes are used in the data column, like "21:00:00".
Dataset:
+-------------------------+
| data |
+-------------------------+
| 21:00:00:24:ff:5e:3a:bd |
| 50:01:43:80:18:6b:2a:4c |
| 21:00:00:1b:32:0f:a7:54 |
| 10:00:00:90:fa:a8:da:2a |
+-------------------------+
Desired query output:
+----------+----------+----------+
| 21:00:00 | 50:01:43 | 10:00:00 |
+----------+----------+----------+
| 2 | 1 | 1 |
+----------+----------+----------+
I have been able to get the query to count a single prefix at a time by using this:
SELECT COUNT(DISTINCT wwpn) AS "21:00:00" FROM table WHERE wwpn LIKE '21:00:00%'
However, I want to count multiple prefixes as shown in the desired query output.
I've been waiting for someone to do a dynamic pivot (like Matt said in the comments) but no one has done it yet : (...I tried it myself and this is what I managed...
Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + LEFT(QUOTENAME(data), 9) + ']'
FROM DataTable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select LEFT(data, 8) as data, COUNT(*) as count
from DataTable
GROUP BY LEFT(data, 8)
) x
pivot
(
max(count)
for data in (' + #cols + N')
) p '
exec sp_executesql #query;
Results:
10:00:00 | 21:00:00 | 50:01:43
---------|----------|---------
1 | 2 | 1
---------|----------|---------
Use this query:
SELECT LEFT([Data], 8) as prefix, count(*) as cnt
FROM tableName
GROUP BY LEFT([Data], 8);
If you know the prefixes in advance then you can do something simple like this;
Create test data;
CREATE TABLE #TestData (FieldName nvarchar(50))
INSERT INTO #TestData
VALUES
('21:00:00:24:ff:5e:3a:bd')
,('50:01:43:80:18:6b:2a:4c')
,('21:00:00:1b:32:0f:a7:54')
,('10:00:00:90:fa:a8:da:2a')
Query
SELECT
SUM(CASE WHEN FieldName LIKE '21:00:00%' THEN 1 ELSE 0 END) [21:00:00]
,SUM(CASE WHEN FieldName LIKE '50:01:43%' THEN 1 ELSE 0 END) [50:01:43]
,SUM(CASE WHEN FieldName LIKE '10:00:00%' THEN 1 ELSE 0 END) [10:00:00]
FROM #TestData
Result
21:00:00 50:01:43 10:00:00
2 1 1

Count number of values across multiple columns

I have a table with 11 columns. The first column includes the category names. The remaining 10 columns have values like white, green, big, damaged etc. and these values can change in time.
I need a SQL query to find how many are there in table (in 10 columns) each value.
Table 1:
+------------+------------+
| ID | decription |
+------------+------------+
| 1 | white |
| 2 | green |
| 3 | big |
| 4 | damaged |
+------------+------------+
Table 2:
+------------+-----------+-----------+-----------+
| CATEGORY | SECTION 1 | SECTION 2 | SECTION 3 |
+------------+-----------+-----------+-----------+
| Category 1 | white | green | big |
| Category 2 | big | damaged | white |
| Category 1 | white | green | big |
| Category 3 | big | damaged | white |
+------------+-----------+-----------+-----------+
Desired result:
+------------+-------+-------+-----+---------+
| CATEGORY | White | Green | Big | Damaged |
+------------+-------+-------+-----+---------+
| Category 1 | 20 | 10 | 9 | 50 |
| Category 2 | 25 | 21 | 15 | 5 |
+------------+-------+-------+-----+---------+
Is it possible doing like this dynamically just as query ?
its on MS sql in visual studio reporting
Thanks
You've got yourself a bit of a mess with the design and the desired result. The problem is that your table is denormalized and then the final result you want is also denormalized. You can get the final result by unpivoting your Section columns, then pivoting the values of those columns. You further add to the mess by needing to do this dynamically.
First, I'd advise you to rethink your table structure because this is far too messy to maintain.
In the meantime, before you even think about writing a dynamic version to get the result you have to get the logic correct via a static or hard-coded query. Now, you didn't state which version of SQL Server you are using but you first need to unpivot the Section columns. You can use either the UNPIVOT function or CROSS APPLY. Your query will start with something similar to the following:
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
See SQL Fiddle with Demo. This gets your data into the format:
| CATEGORY | VALUE |
|------------|---------|
| Category 1 | white |
| Category 1 | green |
| Category 1 | big |
| Category 2 | big |
| Category 2 | damaged |
| Category 2 | white |
Now you have multiple Category rows - one for each value that previously were in the Section columns. Since you want a total count of each word in the Category, you can now apply the pivot function:
select
category,
white, green, big, damaged
from
(
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
) un
pivot
(
count(value)
for value in (white, green, big, damaged)
) p;
See SQL Fiddle with Demo. This will give you the result that you want but now you need this to be done dynamically. You'll have to use dynamic SQL which will create a SQL string that will be executed giving you the final result.
If the number of columns to UNPIVOT is limited, then you will create a list of the new column values in a string and then execute it similar to:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from yourtable
cross apply
(
select Section1 union all
select Section2 union all
select Section3
) d (SectionValue)
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo
If you have an unknown number of columns to unpivot, then your process will be a bit more complicated. You'll need to generate a string with the columns to unpivot, you can use the sys.columns table to get this list:
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '')
Then you'll need to get a list of the new column values - but since these are dynamic we will need to generate this list with a bit of work. You'll need to unpivot the table to generate the list of values into a temporary table for use. Create a temp table to store the values:
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
Load the temp table with the data that you need to unpivot:
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u'
insert into #Category_Section exec(#unpivotquery);
See SQL Fiddle with Demo. You'll see that your data looks the same as the static version above. Now you need to create a string with the values from the temp table that will be used in the final query:
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Once you have all this you can put it together into a final query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#unpivotquery AS NVARCHAR(MAX);
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '');
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u';
insert into #Category_Section exec(#unpivotquery);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in ('+ #colsunpivot +')
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo. All versions will get you the end result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
If your values are stored in a separate table, then you would generate your list of values from that table:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(decription)
from descriptions
group by decription
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo and still get the same result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
select category,
SUM(CASE when section1='white' then 1 when section2='white' then 1 when section3='white' then 1 else 0 end) as white,
SUM(CASE when section1='green' then 1 when section2='green' then 1 when section3='green' then 1 else 0 end) as green,
SUM(CASE when section1='damaged' then 1 when section2='damaged' then 1 when section3='damaged' then 1 else 0 end) as damaged,
SUM(CASE when section1='big' then 1 when section2='big' then 1 when section3='big' then 1 else 0 end) as big
from test
group by category
SQLFiddle
You can extend more to n section values as shown above gor section1,section2,section3

Subtotals and grand totals SQL Pivot

Currently have a script that creates a pivot table with current year values subtraction prior year values.
use devmreports
-- Creates dynamic values for pivot table
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- Pivot table for YOY change in booked passengers
set #query
=
'SELECT Region,
CityPair,
Year,
' + #cols + '
FROM
(
SELECT ABR.Region,
ABR.CityPair,
ABR.Year,
ABR.Month,
ABR.Adv_B - ABRP.Adv_B as Total
FROM ABR LEFT OUTER JOIN
ABRP ON
ABR.Month = ABRP.Month AND
ABR.CityPair = ABRP.CityPair) P
PIVOT
(
SUM(Total)
FOR MONTH IN
(
'+#cols+'))as pvt'
execute (#Query)
Current Pivot looks like this:
+------------+----------+----+-----+-----+----+
| Region | CityPair | 8 | 9 | 10 | 11 |
+------------+----------+----+-----+-----+----+
| A | 1 | 16 | 17 | 18 | 7 |
| A | 2 | 17 | -20 | -10 | 1 |
| B | 3 | 5 | 8 | 4 | -3 |
| B | 4 | 21 | 10 | 3 | 2 |
| C | 5 | 15 | -14 | -12 | 1 |
+------------+----------+----+-----+-----+----+
What I would like to have is this:
+-----------------+----------+----+-----+-----+----+
| Region | CityPair | 8 | 9 | 10 | 11 |
+-----------------+----------+----+-----+-----+----+
| A | 1 | 16 | 17 | 18 | 7 |
| A | 2 | 17 | -20 | -10 | 1 |
| A Total | | 33 | -3 | 8 | 8 |
| B | 3 | 5 | 8 | 4 | -3 |
| B | 4 | 21 | 10 | 3 | 2 |
| B Total | | 26 | 18 | 7 | -1 |
| C | 5 | 15 | -14 | -12 | 1 |
| C Total | | 15 | -14 | -12 | 1 |
| Grand Total | | 74 | 1 | 3 | 8 |
+-----------------+----------+----+-----+-----+----+
Any assistance would be greatly appreciated.
My suggestion would be to look at using GROUP BY ROLLUP to get the total rows.
The basic syntax if you were hard-coding the query would be:
select
case
when region is null then 'Grand Total'
when citypair is null then region +' Total'
else region end region,
coalesce(cast(citypair as varchar(10)), '') citypair,
sum([8]) [8],
sum([9]) [9]
from
(
select region, citypair, month, total
from yourtable
) d
pivot
(
sum(total)
for month in ([8], [9])
) piv
GROUP BY rollup(region, citypair);
See SQL Fiddle with Demo. Then to use your dynamic SQL version you could alter the code to use:
-- Creates dynamic values for pivot table
DECLARE #cols AS NVARCHAR(MAX),
#colsRollup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsRollup = STUFF((SELECT ', sum(' + QUOTENAME(month)+ ') as '+ QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- Pivot table for YOY change in booked passengers
set #query
=
'SELECT
case
when region is null then ''Grand Total''
when citypair is null then region +'' Total' '
else region end region,
coalesce(cast(citypair as varchar(10)), '''') citypair,
' + #colsRollup + '
FROM
(
SELECT ABR.Region,
ABR.CityPair,
ABR.Year,
ABR.Month,
ABR.Adv_B - ABRP.Adv_B as Total
FROM ABR LEFT OUTER JOIN
ABRP ON
ABR.Month = ABRP.Month AND
ABR.CityPair = ABRP.CityPair
) P
PIVOT
(
SUM(Total)
FOR MONTH IN ('+#cols+')
)as pvt
GROUP BY rollup(region, citypair);'
execute sp_executesql #Query
That's why I prefer to pivot by hand with aggregates and not by pivot. Here's a query which will show you all sums with totals and grand totals:
select
case
when grouping(Region) = 1 then 'Grand Total'
when grouping(CityPair) = 1 then Region + ' Total'
else Region
end as Region,
isnull(cast(CityPair as nvarchar(max)), '') as CityPair,
sum(case when Month = 8 then Value end) as [8],
sum(case when Month = 9 then Value end) as [9],
sum(case when Month = 10 then Value end) as [10],
sum(case when Month = 11 then Value end) as [11]
from test
group by rollup(Region, CityPair)
sql fiddle demo
Here's dynamic one:
declare #stmt nvarchar(max)
select
#stmt = isnull(#stmt + ',', '') +
'sum(case when Month = ' + cast(Month as nvarchar(max)) +
' then Value end) as [' + cast(Month as nvarchar(max)) + ']'
from (select distinct Month from test) as a
select #stmt = '
select
case
when grouping(Region) = 1 then ''Grand Total''
when grouping(CityPair) = 1 then Region + '' Total''
else Region
end as Region,
isnull(cast(CityPair as nvarchar(max)), ''''), ' + #stmt + '
from test
group by rollup(Region, CityPair)'
exec sp_executesql #stmt = #stmt
sql fiddle demo
As always, tried to make it as readable as possible.
I'm suggesting to use group by rollup instead of with rollup, because last one will be deprecated:
WITH ROLLUP This feature will be removed in a future version of
Microsoft SQL Server. Avoid using this feature in new development
work, and plan to modify applications that currently use this feature.

how to use pivot when column headers are coming from database

I am getting data in the following format from a sql table:
DisplayName PropertySystemName PropertyDefaultName PropertyValue
S1 P1 Property 1 Value 1
S1 P2 Property 2 Value 2
S1 P3 Property 3 Value 3
S1 P4 Property 4 Value 4
S1 P5 Property 5 Value 5
S1 P6 Property 6 Value 6
S1 P7 Property 7 Value 7
S1 P8 Property 8 Value 8
S1 P9 Property 9 Value 9
S1 P10 Property 10 Value 10
This is the desired output:
DisplayName Property 1 Property 2 Property 3
S1 Value 1 Value 2 Value 3
This is the query i have but it does not produce the desired output.
Select me.DisplayName,
Min(Case PropertySystemName When 'P1' Then PropertyValue End) PropertyDefaultName,
Min(Case PropertySystemName When 'P2' Then PropertyValue End) PropertyDefaultName,
Min(Case PropertySystemName When 'P3' Then PropertyValue End) PropertyDefaultName
FROM vManagedEntity me
This is the output of above query:
DisplayName PropertyDefaultName PropertyDefaultName PropertyDefaultName
S1 Value 1 Value 2 Value 3
How do i modify the query in order to produce the desired output without hard coding the Column Header as it is already present in the table.
You cannot use the value from the PropertyDefaultName column as a column header unless you use dynamic sql. Using Dynamic SQL will allow you to pull the column values and the header names directly from the tables:
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(PropertySystemName)
from vManagedEntity
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT ',' + QUOTENAME(PropertySystemName)
+ ' as '+ replace(PropertyDefaultName, ' ', '')
from vManagedEntity
group by PropertySystemName, PropertyDefaultName
order by cast(replace(PropertySystemName, 'P', '') as int)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT DisplayName, ' + #colNames + ' from
(
select DisplayName, PropertySystemName,
PropertyValue
from vManagedEntity
) x
pivot
(
min(PropertyValue)
for PropertySystemName in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with demo
The result is:
| DISPLAYNAME | PROPERTY1 | PROPERTY2 | PROPERTY3 | PROPERTY4 | PROPERTY5 | PROPERTY6 | PROPERTY7 | PROPERTY8 | PROPERTY9 | PROPERTY10 |
----------------------------------------------------------------------------------------------------------------------------------------
| S1 | Value 1 | Value 2 | Value 3 | Value 4 | Value 5 | Value 6 | Value 7 | Value 8 | Value 9 | Value 10 |