Dynamic Pivot Columns in SQL Server - sql

I have a table named Property with following columns in SQL Server:
Id Name
There are some property in this table that certain object in other table should give value to it.
Id Object_Id Property_Id Value
I want to make a pivot table like below that has one column for each property I've declared in 1'st table:
Object_Id Property1 Property2 Property3 ...
I want to know how can I get columns of pivot dynamically from table. Because the rows in 1'st table will change.

Something like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM property
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query =
'SELECT *
FROM
(
SELECT
o.object_id,
p.Name,
o.value
FROM propertyObjects AS o
INNER JOIN property AS p ON o.Property_Id = p.Id
) AS t
PIVOT
(
MAX(value)
FOR Name IN( ' + #cols + ' )' +
' ) AS p ; ';
execute(#query);
SQL Fiddle Demo.
This will give you something like this:
| OBJECT_ID | PROPERTY1 | PROPERTY2 | PROPERTY3 | PROPERTY4 |
-------------------------------------------------------------
| 1 | ee | fd | fdf | ewre |
| 2 | dsd | sss | dfew | dff |

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 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

SQL Double Dynamic Pivot

I am working on a double dynamic pivot based on 2 columns (HardwarePhase & HardwarePhase_Result).
Using the first result set in the image below, is the raw data that I have. Each set of 5 items (highlighted in images) are grouped based on HardwareTestCaseID.
The second result set in the image, is the current results that I'm getting from how I've constructed this query. Ideally, the result of the second column would be the same results, but instead it would be grouping the results.
The grouping is supposed to be based on the HardwareTestCaseID, but however, this is not happening.
The results I actually want are shown here. (There should be multiple rows, but this is just how it should be grouped per 5 entries).
This is the query I am currently using:
NOTE: The #col variables are built up based on the list of HardwarePhases (P0, M1, M2, M3).
select #query = 'SELECT ' + #colsNames + ',' + #colsResultNames + ', HardwareTestCaseID FROM
(
SELECT HardwarePhase_Result, HardwarePhase, ResultValue, HardwareTestCaseID, HardwareStatus
FROM #temp4
) as x
pivot
(
MAX(ResultValue)
FOR HardwarePhase_Result IN (' + #colsResult + ')
) as p
pivot
(
MAX(HardwareStatus)
FOR HardwarePhase IN (' + #cols + ')
) as p2 ';
using this table:
create table #temp4
(
HardwarePhase nvarchar(max),
HardwarePhase_Result nvarchar(max),
ResultValue bigint,
HardwareTestCaseID bigint,
HardwareStatus nvarchar(max),
Block nvarchar(max)
);
I personally would do it slightly different since you want to PIVOT on two columns. I would look at unpivoting the data in the multiple columns first, then apply the PIVOT function. I also would suggest that you start with writing a hard-coded version of the query first then convert it to dynamic SQL - this allows you to get the correct logic.
To unpivot the data, I would use CROSS APPLY so you can convert the pairs of columns into rows at the same time, the syntax would be similar to the following:
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
See SQL Fiddle with Demo. Your data is then in the format:
| COL | VALUE | HARDWARETESTCASEID |
|-----------|-------------|--------------------|
| P0 | Not Started | 365 |
| P0_Result | 1 | 365 |
| M1 | Pass | 365 |
| M1_Result | 1 | 365 |
| M4 | Pass | 365 |
| M4_Result | 1 | 365 |
| M2 | Blocked | 365 |
| M2_Result | 1 | 365 |
Then you just apply the pivot function to the data:
select M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result,
HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) d
pivot
(
max(value)
for col IN (M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result)
) piv;
See SQL Fiddle with Demo.
Once you have the logic down, then convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from temp4
cross apply
(
select HardwarePhase, 1 union all
select HardwarePhase_Result, 2
) c (col, so)
group by col, so
order by so, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' , HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This process gets a result:
| M1 | M2 | M3 | M4 | P0 | M1_RESULT | M2_RESULT | M3_RESULT | M4_RESULT | P0_RESULT | HARDWARETESTCASEID |
|---------|---------|---------|---------|-------------|-----------|-----------|-----------|-----------|-----------|--------------------|
| Pass | Blocked | Pass | Pass | Not Started | 1 | 1 | 1 | 1 | 1 | 365 |
| Blocked | Blocked | Blocked | Blocked | Pass | 1 | (null) | 1 | 1 | 1 | 366 |
--This is Just AWESOME. Simplified it for just one table as it's a much more common case (and could not find anything even close to this elegant after trying for hours)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(your_key_column)
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
group by your_key_column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT your_row_heading_columns,' + #cols + '
INTO YOUR_NEW_PIVOTED_TABLE
from
(
select your_row_heading_columns,your_key_column,your_value_column
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
) x
pivot
(
max(your_value_column)
for your_key_column in (' + #cols + ')
) p '
execute sp_executesql #query;

Display a different column value in a PIVOT table

I have the following code -
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM JobPhases
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT *
FROM
(
SELECT c.Registration, p.Name, [x] = 1
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
) JobDetails
PIVOT
( SUM(x)
FOR Name IN (' + #cols + ')
) pvt'
Which generates this -
JobID | Repair & Reshape | Refit Stripped Parts | Polishing
1000 | id | id | id
1001 | id | id | id
1002 | id | id | id
1003 | id | id | id
1004 | id | id | id
But, I would like it to show j.EstimatedTime instead of j.ID.
The table structure -
JobDetails
ID - PK Auto increment
JobID - Int (Joined to Jobs table)
PhaseID - String (joined to JobPhases table)
EstimatedTime - decimal
JobPhases
ID - PK String
Name - VarChar(150)
Any ideas how I can achieve this?
Thanks
Try changing [x] = 1 to [x] = j.EstimatedTime .

Convert rows into columns by grouping one row value

My table is like
CompanyName | DirectorName
ADS | Rai
ADS | Rao
ADS | Raj
ADS | Rio
SAS | Josh
SAS | John
But I want result like:
CompanyName | Director1 | Director2 | Director3 | Director4
ADS |Rai |Rao |Raj |Rio
SAS |Josh |John | |
For each company director name count varies. So kindly suggest how to form the data as per requirement dynamically.
Try this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME('Director' + CAST(ROW_NUMBER()
OVER(PARTITION BY c.companyname
ORDER BY c.companyname)
AS VARCHAR(10)))
FROM CompaniesDirectors c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'');
SET #query = 'SELECT companyname, ' + #cols + ' from
(
SELECT
c.CompanyName,
c.DirectorName,
''Director'' + CAST(ROW_NUMBER()
OVER(PARTITION BY c.companyname
ORDER BY c.companyname)
AS VARCHAR(10)) director_num
FROM CompaniesDirectors c
) x
PIVOT
(
MAX(directorname)
FOR director_num IN (' + #cols + ')
) p ';
EXECUTE(#query);
This should give you something like:
companyname Director1 Director2 Director3 Director4
AB Foo Bar rr tt
ADS Rai Rao Raj Rio
SQL Fiddle Demo1
1: Thanks to #bluefeet