Pivot table for string in SQL Server - sql-server

I have the following table with some details as shown below:
Example:
Creating table product:
create table product
(
slno int,
item nvarchar(20)
);
Inserting some records:
insert into product values(1,'HDD');
insert into product values(2,'PenDrive');
insert into product values(3,'RAM');
insert into product values(4,'DVD');
insert into product values(5,'RAM');
insert into product values(6,'HDD');
Table product contains:
select * from product;
slno item
----------
1 HDD
2 PenDrive
3 RAM
4 DVD
5 RAM
6 HDD
Now I want make a string of items for which i have written the following script:
select distinct
(
select distinct item+','
from product
FOR XML PATH('')
) temp
from product;
Result is:
temp
----------------------
DVD,HDD,PenDrive,RAM,
Note: Now I want to show the result in the following format: (In which I need to use the pivot table with the above query and need to display how many product have sold out).
DVD HDD PenDrive RAM
-----------------------
1 2 1 2
My Try:
select DVD,HDD,PenDrive,RAM
from
(
select distinct
(
select distinct item+','
from product
FOR XML PATH('')
) temp
from product
) as a
pivot
(
count(temp)
for temp in(DVD,HDD,PenDrive,RAM)
) pt
But getting result :
DVD HDD PenDrive RAM
------------------------
0 0 0 0

You don't need to use the FOR XML PATH to create a string of the columns unless you need a dynamic SQL version to get your final result.
Using PIVOT you can easily hard-code your values for your query:
select DVD, HDD, PenDrive, RAM
from
(
select item
from product
) d
pivot
(
count(item)
for item in (DVD, HDD, PenDrive, RAM)
) piv;
See SQL Fiddle with Demo.
Now if you had an unknown values that you needed to be the final columns, then you'd create a list of the items and execute a SQL string via dynamic SQL similar to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(item)
from product
group by item
order by item
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select item
from product
) x
pivot
(
count(item)
for item in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo

Related

Exporting dynamic column pivot result (Dynamic SQL resultset with varying number of columns) into excel file using SSIS

I have a dynamic pivot sql script (the pivoted columns are dynamic). I wanted to export the result set into excel file and email it with send mail task in ssis. Does anyone know how to do that? Below is my Dynamic column pivot sql script
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName([Response Code]) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = 'Select [Employee],' + #SQL + '
From (
Select [Employee],[Response Code],Cnt=1,Lvl=0 from YourTable
Union All
Select [Employee],[Response Code],Cnt=0,Lvl=0 from (Select Distinct [Employee] from YourTable) A Join (Select Distinct [Response Code] from YourTable) B on 1=1
Union All
Select ''Total'',[Response Code],count(*),1 From YourTable Group By [Response Code]
) A
Pivot (sum(Cnt) For [Response Code] in (' + #SQL + ') ) p'
Exec(#SQL);
The above script will return the table like this
Employee ptb ulm vml wrn
Emp A 0 0 2 1
Emp B 0 2 0 1
Emp C 1 0 1 0
Total 1 2 3 2
I need to export the above result table in to excel file. I know how to do if the column is static using SSIS ;but I am struggling with the dynamic column pivot. Could anyone please help me. Thank you very much for your time and help

Can I create dynamic pivot query from key value table with different data types?

I have a key-value pairs table. I have created a script using dynamic sql that pivots the table correctly. However, the keys have different data types. Is there a way to cast the keys during or after the pivot dynamically? I can have the data types in the same key-value pairs table for each key or in a separate table linkable by the key.
Dynamic sql is used because I wont always know the columns. There will always be a data type.
An example of the starting table:
sampleid key value datatype
------------------------------------
1001 Name Andrew varchar(50)
1001 Date 20150129 datetime
1002 Name Anna varchar(50)
1002 Date 20150129 datetime
Final result would be this with name as nvarchar and date as datetime:
The script is a stored procedure that creates this into a view. The view is being accessed via external applications like SAS and Olive which pick up the datatype from the view. Of course this isn't ideal but it is what I have been tasked with attempting!
sampleid name date
-----------------------------
1001 Andrew 20150129
1002 Anna 20150129
You can do this using dynamic SQL, you'll just have to crate two separate lists of "new columns" as a string. One list will include the column names being converted to your datatype, the second will be used for the PIVOT function.
The code would be:
DECLARE
#cols AS NVARCHAR(MAX),
#colsConversion AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
-- get the list of [key] items for the columns used in the PIVOT
select #cols
= STUFF((SELECT ', ' + QUOTENAME([key])
from yourtable
group by [key], datatype
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- get the list of columns for the final select
-- include a conversion of the columns into the correct datatype
select #colsConversion
= STUFF((SELECT ', cast(' + QUOTENAME([key]) +' as '+ datatype+') as ' + QUOTENAME([key])
from yourtable
group by [key], datatype
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- the converted columns go in the final select list
-- while the other #cols are used inside the PIVOT
set #query = 'SELECT sampleid, ' + #colsConversion + '
from
(
select sampleid, [key], value
from yourtable
) x
pivot
(
max(value)
for [key] in (' + #cols + ')
) p; '
exec sp_executesql #query;
See SQL Fiddle with Demo

Compare two rows and identify columns whose values are different

The Situation
We have an application where we store machine settings in a SQL table. When the user changes a parameter of the machine, we create a "revision", that means we insert a row into a table. This table has about 200 columns.
In our application, the user can take a look on each revision.
The Problem
We want to highlight the parameters that have changed since the last revision.
The Question
Is there an SQL-only way to get the column names of the differences between two rows?
An Example
ID | p_x | p_y | p_z
--------------------
11 | xxx | yyy | zzz
12 | xxy | yyy | zzy
The query should return p_x and p_z.
EDIT
The table has 200 columns, not rows...
MY WAY OUT
My intention was to find a "one-line-SQL-statement" for this problem.
I see in the answers below, it's kind a bigger thing in SQL.
As there is no short, SQL-included solution for this problem, solving it in the backend of our software (c#) is of course much easier!
But as this is not a real "answer" to my question, I don't mark it as answered.
Thanks for the help.
You say:
We want to highlight the parameters that have changed since the last revision.
This implies that you want the display (or report) to make the parameters that changed stand out.
If you're going to show all the parameters anyway, it would be a lot easier to do this programmatically in the front end. It would be a much simpler problem in a programming language. Unfortunately, not knowing what your front end is, I can't give you particular recommendations.
If you really can't do it in the front end but have to receive this information in a query from the database (you did say "SQL-only"), you need to specify the format you'd like the data in. A single-column list of the columns that changed between the two records? A list of columns with a flag indicating which columns did or didn't change?
But here's one way that would work, though in the process it converts all your fields to nvarchars before it does its comparison:
Use the technique described here (disclaimer: that's my blog) to transform your records into ID-name-value pairs.
Join the resulting data set to itself on ID, so that you can compare the values and print those that have changed:
with A as (
-- We're going to return the product ID, plus an XML version of the
-- entire record.
select ID
, (
Select *
from myTable
where ID = pp.ID
for xml auto, type) as X
from myTable pp )
, B as (
-- We're going to run an Xml query against the XML field, and transform it
-- into a series of name-value pairs. But X2 will still be a single XML
-- field, associated with this ID.
select Id
, X.query(
'for $f in myTable/#*
return
<data name="{ local-name($f) }" value="{ data($f) }" />
')
as X2 from A
)
, C as (
-- We're going to run the Nodes function against the X2 field, splitting
-- our list of "data" elements into individual nodes. We will then use
-- the Value function to extract the name and value.
select B.ID as ID
, norm.data.value('#name', 'nvarchar(max)') as Name
, norm.data.value('#value', 'nvarchar(max)') as Value
from B cross apply B.X2.nodes('/myTable') as norm(data))
-- Select our results.
select *
from ( select * from C where ID = 123) C1
full outer join ( select * from C where ID = 345) C2
on C1.Name = c2.Name
where c1.Value <> c2.Value
or not (c1.Value is null and c2.Value is null)
You can use unpivot and pivot. The key is to transpose data so that you can use where [11] != [12].
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tblName
UNPIVOT
(
val
FOR colName IN ([p_x],[p_y],[p_z])
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
SELECT colName
--SELECT *
FROM CTE WHERE [11] != [12]
If there are only a few columns in the table, it's easy to simply put [p_x],[p_y],[p_z], but obviously it's not convenient to type 50 or more columns. Even though you may use this trick to drag and drop, or copy/paste, the column names from the table, it's still bulky. And for that, you may use the SELECT * EXCEPT strategy with dynamic sql.
DECLARE #TSQL NVARCHAR(MAX), #colNames NVARCHAR(MAX)
SELECT #colNames = COALESCE(#colNames + ',' ,'') + [name]
FROM syscolumns WHERE name <> 'ID' and id = (SELECT id FROM sysobjects WHERE name = 'tablelName')
SET #TSQL = '
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tablelName
UNPIVOT
(
val
FOR colName IN (' + #colNames + ')
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
--SELECT colName
SELECT *
FROM CTE WHERE [11] != [12]
'
EXEC sp_executesql #TSQL
Here's one way using UNPIVOT:
;WITH
cte AS
(
SELECT CASE WHEN t1.p_x <> t2.p_x THEN 1 ELSE 0 END As p_x,
CASE WHEN t1.p_y <> t2.p_y THEN 1 ELSE 0 END As p_y,
CASE WHEN t1.p_z <> t2.p_z THEN 1 ELSE 0 END As p_z
FROM MyTable t1, MyTable t2
WHERE t1.ID = 11 AND t2.ID = 12 -- enter the two revisions to compare here
)
SELECT *
FROM cte
UNPIVOT (
Changed FOR ColumnName IN (p_x, p_y, p_z)
) upvt
WHERE upvt.Changed = 1
You have to add code to handle NULLs during the comparisons. You can also build the query dynamically if there are lots of columns in your table.
for sql server 2012 you can do something like that (duplicate it for
each column):
SELECT iif((p_x != lead(p_x) over(ORDER BY p_x)),
(SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl'
AND
TABLE_SCHEMA='schema'
AND
ORDINAL_POSITION='1')
,NULL)
FROM tbl
for sql server 2008 try
DECLARE #x int =11 -- first id
WHILE #x!=(SELECT count(1) FROM tbl)
BEGIN --comparison of two adjacent rows
if (SELECT p_x FROM tbl WHERE id=#x)!=(SELECT p_x FROM tbl WHERE id=#x+1)
BEGIN
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl' --insert your table
AND
TABLE_SCHEMA='schema' --insert your schema
AND
ORDINAL_POSITION='1' --first column 'p_x'
END
set #x=#x+1
END

Extracting data column-wise from table in SQL Server

I currently have a piece of code that pivots a table in which the row data is inserted dynamically. The code is shown below:
CREATE TABLE Table1
([empname] varchar(6), [empqual] varchar(10), [emprank] int, [empexp] int)
INSERT INTO Table1
([empname], [empqual], [emprank], [empexp])
VALUES
('Joyce', 'UNIVERSITY', 1, 11),
('Angela', 'MASTERS', 2, 10),
('Lily', 'MASTERS', 3, 9),
('Sasha', 'UNIVERSITY', 3, 9),
('Harry', 'UNIVERSITY', 3, 9)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + 'Column' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank))
FROM Table1 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = '
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empname) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empqual) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,emprank) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empexp) as e,
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
'
EXECUTE (#query)
The result of the above code is as shown below:
Column1 Column2 Column3 Column4 Column5
1 2 3 3 3
11 10 9 9 9
Joyce Angela Lily Sasha Harry
UNIVERSITY MASTERS MASTERS UNIVERSITY UNIVERSITY
Now, my application requires that I display each of the columns in this table separately, i.e. each of the columns, and not rows, of this table needs to be exported from this table and transferred, possibly into a temporary table, from which it can be displayed easily.
I am well aware of the fact that relational DBs are designed in such a way so as to consider rows, not columns, as individual entities. However, I am constrained by the application on which I am working, which requires code that extracts the data in this table column-wise so that they can be displayed separately.
How would I go about doing this?
This is a terminology overlap.
In SQL, "row" means (roughly) a single entity, and "column" means a property on the entities.
In UI, "row" means data arranged horizontally, and "column" means data arranged vertically.
Your requirement is that you should display your entities vertically. So, retrieve your entities (SQL rows) and then add code in your application to display this data in UI columns. It's unfortunate and perhaps confusing that the terms are the same here, but remember, your database structure (and choice of terminology) is completely irrelevant to your UI layout.
As to what code in your application is required to display the data... well, you haven't even told us what language it's in, so I can't help there.

Pivot table with one row and four columns

-- Pivot table with one row and four columns
SELECT 'Values' tValues,
ID,Name,ValueID,Value FROM (
Select ID,Name,ValueID,Value FROM Table WHERE OptionID = 1000000
) AS SourceTable
PIVOT (
COUNT(tValues)
FOR tValues IN ( ID,Attribute,ValueID,Value )
) AS PivotTable;
I'm going off the example at Microsoft.com: http://msdn.microsoft.com/en-us/library/ms177410.aspx
But there are a few things about Pivot i don't really understand, so don't be surprised when you see it in the code above, such as COUNT(tValues), I have no idea what this is for, by judging from the example on microsoft, it seems to be always some sort of numeric value, so i figured i'd try it to see if it would return something, but all it returns is an error. Anyhow, if someone out there can share why this query doesn't work, and possibly explain what the numeric value above the FOR is used for?
The Table containts an x amount of rows, with four columns, so it looks like this:
ID | Name | ValueID | Value
100 | Color | 10000 | Black
101 | Size | 10005 | Large
The output should be like this:
Name_100 | Color | Name_101 | Size |
10000 | Black | 10005 | Large |
Something like this maybe.
This will only work if the name column is unique. If not then you might want to append an id on it.
So first some test data:
CREATE TABLE tblValues
(
ID INT,
Name VARCHAR(100),
ValueID INT,
Value VARCHAR(100)
)
INSERT INTO tblValues
VALUES
(100,'Color',10000,'Black'),
(101,'Size',10005,'Large')
Then you need to get the columns to pivot on:
DECLARE #cols VARCHAR(MAX)
;WITH CTE AS
(
SELECT
'Name_'+CAST(tbl.ID AS VARCHAR(100)) AS Name,
'Name_'+CAST(tbl.ID AS VARCHAR(100)) AS Sort,
tbl.ID
FROM
tblValues AS tbl
UNION ALL
SELECT
tbl.Name,
'Value_'+CAST(tbl.ID AS VARCHAR(100)) AS Sort,
tbl.ID
FROM
tblValues AS tbl
)
SELECT
#cols = COALESCE(#cols + ','+QUOTENAME(Name),
QUOTENAME(Name))
FROM
CTE
ORDER BY
CTE.ID,
CTE.Sort
Then declaring and executing the dynamic sql like this:
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
''Name_''+CAST(tbl.ID AS VARCHAR(100)) AS pivotName,
CAST(tbl.ValueID AS VARCHAR(100)) AS name
FROM
tblValues AS tbl
UNION ALL
SELECT
tbl.Name AS pivotName,
tbl.Value AS name
FROM
tblValues AS tbl
) AS p
PIVOT
(
MAX(name)
FOR pivotName IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
Then in my case I will drop the table I have created
DROP TABLE tblValues
Edit
Or in you case it should be something like this:
First the columns:
DECLARE #cols VARCHAR(MAX)
;WITH CTE AS
(
SELECT
'Name_'+CAST(tbl.ID AS VARCHAR(100)) AS Name,
'Name_'+CAST(tbl.ID AS VARCHAR(100)) AS Sort,
tbl.ID
FROM
[Table] AS tbl
WHERE
tbl.OptionID = 1000000
UNION ALL
SELECT
tbl.Name,
'Value_'+CAST(tbl.ID AS VARCHAR(100)) AS Sort,
tbl.ID
FROM
[Table] AS tbl
WHERE
tbl.OptionID = 1000000
)
SELECT
#cols = COALESCE(#cols + ','+QUOTENAME(Name),
QUOTENAME(Name))
FROM
CTE
ORDER BY
CTE.ID,
CTE.Sort
Then the dynamic sql.
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
''Name_''+CAST(tbl.ID AS VARCHAR(100)) AS pivotName,
CAST(tbl.ValueID AS VARCHAR(100)) AS name
FROM
[Table] AS tbl
WHERE
tbl.OptionID = 1000000
UNION ALL
SELECT
tbl.Name AS pivotName,
tbl.Value AS name
FROM
[Table] AS tbl
WHERE
tbl.OptionID = 1000000
) AS p
PIVOT
(
MAX(name)
FOR pivotName IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
You do not need to create the table or drop the table. That was just because I did not have your table in my database and that if someone else want's to run the example.
If you want to use Pivot tables with a variable number of columns, then I'd suggest using something along the lines of;
DECLARE #cols VARCHAR(4000)
DECLARE #query VARCHAR(8000)
SELECT #cols = STUFF(( SELECT DISTINCT
'],[' + Name
FROM Table
ORDER BY '],[' + Name
FOR XML PATH('')
), 1, 2, '') + ']'
SET #query =
'SELECT * FROM
(
SELECT col1, col2, col3, whateverColYourInterestedIn, Name, Value
FROM Table
)t
PIVOT (MAX(Value) FOR Name
IN ('+#cols+')) AS pvt'
EXECUTE (#query)
That is probably not quite right, but it should hopefully be a starting point for you.
For more info, check out links such as this or this.

Resources