Transforming shape of a SQL Table using Pivot - sql

I have some data stored in a table in the following format
BatchID EntityChanged ChangeValue
EERS ABC DEF
EERS ABCD XYZ
EERS Something SomeValue
New Batch SomethingMore GHI
The list of values that can appear in the "EntityChanged" columns is finite, known beforehand and has no spaces etc in it. For the sake of argument let us say that this list is - ABC, ABCD, Something, SomethingMore
Then for the above dataset I would like an output of
BatchID ABC ABCD Something SomethingMore
EERS DEF XYZ SomeValue NULL
New Batch NULL NULL NULL GHI
Using Pivot I could only go so far.
Can someone please help me slice this data in the desired way?

You can use a PIVOT for this, either via Static PIVOT or a Dynamic PIVOT which might be good if you have a unknown number of columns.
Static (See SQL Fiddle with Demo):
create table t2
(
batchid varchar(10),
entitychanged varchar(20),
changevalue varchar(10)
)
insert into t2 values ('EERS', 'ABC', 'DEF')
insert into t2 values ('EERS', 'ABCD', 'XYZ')
insert into t2 values ('EERS', 'Something', 'SomeValue')
insert into t2 values ('New Batch', 'SomethingMore', 'GHI')
select *
from
(
select batchid, entitychanged, changevalue
from t2
) x
pivot
(
min(changevalue)
for entitychanged in ([ABC], [ABCD], [Something], [SomethingMore])
) p
Dynamic (see SQL Fiddle with Demo):
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(entitychanged)
from t2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT batchid, ' + #cols + ' from
(
select batchid, entitychanged, changevalue
FROM t2
) x
pivot
(
min(changevalue)
for entitychanged in (' + #cols + ')
) p '
execute(#query)
Both will give you the same result.

Try this:
SELECT BatchID,
MAX(CASE WHEN EntityChanged = 'ABC' THEN ChangeValue END) 'ABC',
MAX(CASE WHEN EntityChanged = 'ABCD' THEN ChangeValue END) 'ABCD',
MAX(CASE WHEN EntityChanged = 'Something' THEN ChangeValue END) 'Something',
MAX(CASE WHEN EntityChanged = 'SomethingMore' THEN ChangeValue END) 'SomethingMore'
FROM YourTable t
GROUP BY BatchID
DEMO

Related

How to pivot data in SQL with multiple conditions

I am working on a data where it looks like this
I want to pivot it in this way :
I have written in this way :
select *
from (select * FROM records) as test
PIVOT (
max(value) for
[Question_ID] in ((SELECT distinct [Question_ID] from records order by 1))) AS PivotTable
Can you please help me?
If you know the number of columns in PIVOT, you can directly hardcode like below.
SELECT * FROM
(
SELECT * FROM test
) t
PIVOT (
MAX(value) for
[Question_ID] in ([120],[122],[149],[150],[151],[189],[253],[554],[774] )) AS Pivot_Table
If not,you have to use Dynamic Pivot table for unknown number of column values.
DECLARE
#columns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the QuestionIDs
SELECT
#columns+=QUOTENAME(Question_ID) + ','
FROM
test
GROUP BY Question_ID
ORDER BY Question_ID;
-- remove the last comma
SET #columns = LEFT(#columns, LEN(#columns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM
(
SELECT * FROM TEST
) t
PIVOT(
MAX(value) for
[Question_ID] IN ('+ #columns +')
) AS pivot_table;';
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
CHECK DEMO HERE
you got your wish result
select *
from (select * FROM #temp) as test--your table name replace with #temp
PIVOT (
max(value) for
[Question_ID] in ([120],[122],[149],[150],[151],[189],[253],[554],[774] )) AS PivotTable
or if you want to dynamic your query
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(Question_ID)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
--print #cols
set #query = 'SELECT ID, ' + #cols + ' from
(
select ID
, Question_ID
, [Value]
from #temp
) x
pivot
(
max([Value])
for [Question_ID] in (' + #cols + ')
) p '
execute(#query)
drop table #temp
----------
If you are sure that the number of columns will not change, then you can use case statement
CREATE TABLE #TBL (ID INT, Question_ID INT, [Value] VARCHAR(20))
INSERT INTO #TBL VALUES
(1,149,'abc'),(1,150,'def'),(1,151,'aff'),(1,122,'www'),
(2,120,'add'),(2,150,'sub'),(2,189,'asf'),(3,253,'asd'),
(3,150,'gfre'),(3,554,'cvew'),(3,744,'qwert')
-- aNSWER
SELECT
ID,
MAX(CASE WHEN Question_ID = 149 THEN [Value] END) AS '149',
MAX(CASE WHEN Question_ID = 150 THEN [Value] END) AS '150',
MAX(CASE WHEN Question_ID = 151 THEN [Value] END) AS '151',
MAX(CASE WHEN Question_ID = 122 THEN [Value] END) AS '122',
MAX(CASE WHEN Question_ID = 120 THEN [Value] END) AS '120',
MAX(CASE WHEN Question_ID = 150 THEN [Value] END) AS '150',
MAX(CASE WHEN Question_ID = 189 THEN [Value] END) AS '189',
MAX(CASE WHEN Question_ID = 253 THEN [Value] END) AS '253',
MAX(CASE WHEN Question_ID = 150 THEN [Value] END) AS '150',
MAX(CASE WHEN Question_ID = 554 THEN [Value] END) AS '554',
MAX(CASE WHEN Question_ID = 744 THEN [Value] END) AS '744'
FROM #TBL
GROUP BY ID
DROP TABLE #TBL
Output
ID 149 150 151 122 120 150 189 253 150 554 744
1 abc def aff www NULL def NULL NULL def NULL NULL
2 NULL sub NULL NULL add sub asf NULL sub NULL NULL
3 NULL gfre NULL NULL NULL gfre NULL asd gfre cvew qwert

pivot data from one table

input table
country tag short
UK F1 Units
UK F2 Volume
UK F3 Value
FR T3 Units
FR T2 Volume
FR T1 Value
result output i want :
country Units Volume Value
uk f1 f2 f3
fr t1 t2 t3
If there are a fixed number of different short values, simply use case expressions to do conditional aggregation:
select country,
max(case when short = 'Units' then tag end) as Units,
max(case when short = 'Volume' then tag end) as Volume,
max(case when short = 'Value' then tag end) as val
from tablename
group by country
For solution you have to use dynamic pivoting.
create table #temp
(
country varchar(30),tag varchar(20),short varchar(300)
)
insert into #temp values ('UK', 'F1', 'Units')
insert into #temp values ('UK', 'F2' , 'Volume')
insert into #temp values ('UK' ,'F3', 'Value')
insert into #temp values ('FR', 'T3' , 'Units')
insert into #temp values ('FR' , 'T2', 'Volume')
insert into #temp values ('FR', 'T1' , 'Value')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.short)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT country, ' + #cols + ' from
(
select country
, tag
, short
from #temp
) x
pivot
(
max(tag)
for short in (' + #cols + ')
) p '
execute(#query)
drop table #temp
Table Structure
CREATE TABLE tablename
(
[country] [NVARCHAR](10) NULL,
[tag] [NVARCHAR](10) NULL,
[short] [NVARCHAR](10) NULL
)
INSERT INTO tablename
VALUES
('UK','F1','Units'),
('UK','F2','Volume'),
('UK','F3','Value'),
('FR','T3','Units'),
('FR','T2','Volume'),
('FR','T1','Value');
Using Pivot function
SELECT *
FROM tablename
PIVOT ( Max(tag)
FOR short IN ([Units], [volume], [Value]) ) piv;
Online Demo: Link
Using Dynamic SQL PIVOT
DECLARE #cols AS NVARCHAR(max),
#query AS NVARCHAR(max)
SELECT #cols = Stuff((SELECT distinct ',' + Quotename(short)
FROM tablename
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1,'');
SET #query = 'SELECT *
FROM tablename
PIVOT ( Max(tag)
FOR short IN (' + #cols + ') ) piv;';
EXECUTE(#query);
Online Demo: Link
Result
+---------+-------+--------+-------+
| country | Units | volume | Value |
+---------+-------+--------+-------+
| FR | T3 | T2 | T1 |
| UK | F1 | F2 | F3 |
+---------+-------+--------+-------+

How to format select query output

I have table like this below,
Type Model Year
121232323 Test1 2000
121232323 Test2 2001
I want output like below, how to write query for that.?
121232323 Test1 Test2
2000 2001
Using Simple Pivot we can achieve
DECLARE #Table1 TABLE
( Type int, Model varchar(5), Year int)
;
INSERT INTO #Table1
( Type , Model , Year )
VALUES
(121232323, 'Test1', 2000),
(121232323, 'Test2', 2001)
;
select Type,[Test1],[Test2] from #Table1
PIVOT (MAX(YEAR) FOR MODEL IN ([Test1],[Test2]))PVT
I don't know about Originalnr , but you can do this with conditional aggregation if you have a limited amount of tests :
SELECT t.Type,
MAX(CASE WHEN t.Model = 'Test1' THEN t.Year END) as Test1,
MAX(CASE WHEN t.Model = 'Test2' THEN t.Year END) as Test2
FROM YourTable t
GROUP BY t.Type
Another method other than PIVOT is to use conditional aggregation:
SELECT
Type,
Test1 = MAX(CASE WHEN Model = 'Test1' THEN [year] END),
Test2 = MAX(CASE WHEN Model = 'Test2' THEN [year] END)
FROM tbl
GROUP BY Type
If you have unlimited number of Models you can do it using dynamic crosstab:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
Type' + CHAR(10) +
(SELECT DISTINCT
' , MAX(CASE WHEN Model =''' + Model + ''' THEN [year] END) AS ' + QUOTENAME(Model) + CHAR(10)
FROM tbl
FOR XML PATH('')
) +
'FROM tbl
GROUP BY Type;';
PRINT(#sql);
EXEC(#sql);
ONLINE DEMO

Dynamic SELECT statement, generate columns based on present and future values

Currently building a SELECT statement in SQL Server 2008 but would like to make this SELECT statement dynamic, so the columns can be defined based on values in a table. I heard about pivot table and cursors, but seems kind of hard to understand at my current level, here is the code;
DECLARE #date DATE = null
IF #date is null
set # date = GETDATE() as DATE
SELECT
Name,
value1,
value2,
value3,
value4
FROM ref_Table a
FULL OUTER JOIN (
SELECT
PK_ID ID,
sum(case when FK_ContainerType_ID = 1 then 1 else null) Box,
sum(case when FK_ContainerType_ID = 2 then 1 else null) Pallet,
sum(case when FK_ContainerType_ID = 3 then 1 else null) Bag,
sum(case when FK_ContainerType_ID = 4 then 1 else null) Drum
from
Packages
WHERE
#date between PackageStart AND PackageEnd
group by PK_ID ) b on a.Name = b.ID
where
Group = 0
The following works great for me , but PK_Type_ID and the name of the column(PackageNameX,..) are hard coded, I need to be dynamic and it can build itself based on present or futures values in the Package table.
Any help or guidance on the right direction would be greatly appreciated...,
As requested
ref_Table (PK_ID, Name)
1, John
2, Mary
3, Albert
4, Jane
Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
1 , 1, 4, 1JAN2014, 30JAN2014
2 , 2, 3, 1JAN2014, 30JAN2014
3 , 3, 2, 1JAN2014, 30JAN2014
4 , 4, 1, 1JAN2014, 30JAN2014
ContainerType (PK_ID, Type)
1, Box
2, Pallet
3, Bag
4, Drum
and the result should look like this;
Name Box Pallet Bag Drum
---------------------------------------
John 1
Mary 1
Albert 1
Jane 1
The following code like I said works great, the issue is the Container table is going to grow and I need to replicated the same report without hard coding the columns.
What you need to build is called a dynamic pivot. There are plenty of good references on Stack if you search out that term.
Here is a solution to your scenario:
IF OBJECT_ID('tempdb..##ref_Table') IS NOT NULL
DROP TABLE ##ref_Table
IF OBJECT_ID('tempdb..##Packages') IS NOT NULL
DROP TABLE ##Packages
IF OBJECT_ID('tempdb..##ContainerType') IS NOT NULL
DROP TABLE ##ContainerType
SET NOCOUNT ON
CREATE TABLE ##ref_Table (PK_ID INT, NAME NVARCHAR(50))
CREATE TABLE ##Packages (PK_ID INT, FK_ref_Table_ID INT, FK_ContainerType_ID INT, PackageStartDate DATE, PackageEndDate DATE)
CREATE TABLE ##ContainerType (PK_ID INT, [Type] NVARCHAR(50))
INSERT INTO ##ref_Table (PK_ID,NAME)
SELECT 1,'John' UNION
SELECT 2,'Mary' UNION
SELECT 3,'Albert' UNION
SELECT 4,'Jane'
INSERT INTO ##Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
SELECT 1,1,4,'2014-01-01','2014-01-30' UNION
SELECT 2,2,3,'2014-01-01','2014-01-30' UNION
SELECT 3,3,2,'2014-01-01','2014-01-30' UNION
SELECT 4,4,1,'2014-01-01','2014-01-30'
INSERT INTO ##ContainerType (PK_ID, [Type])
SELECT 1,'Box' UNION
SELECT 2,'Pallet' UNION
SELECT 3,'Bag' UNION
SELECT 4,'Drum'
DECLARE #DATE DATE, #PARAMDEF NVARCHAR(MAX), #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
SET #DATE = '2014-01-15'
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(T.[Type])
FROM ##ContainerType T
FOR XML PATH, TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #SQL = 'SELECT [Name], ' + #COLS + '
FROM (SELECT [Name], [Type], 1 AS Value
FROM ##ref_Table R
JOIN ##Packages P ON R.PK_ID = P.FK_ref_Table_ID
JOIN ##ContainerType T ON P.FK_ContainerType_ID = T.PK_ID
WHERE #DATE BETWEEN P.PackageStartDate AND P.PackageEndDate) X
PIVOT (COUNT(Value) FOR [Type] IN (' + #COLS + ')) P
'
PRINT #COLS
PRINT #SQL
SET #PARAMDEF = '#DATE DATE'
EXEC SP_EXECUTESQL #SQL, #PARAMDEF, #DATE=#DATE
Output:
Name Bag Box Drum Pallet
Albert 0 0 0 1
Jane 0 1 0 0
John 0 0 1 0
Mary 1 0 0 0
Static Query:
SELECT [Name],[Box],[Pallet],[Bag],[Drum] FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( [Box],[Pallet],[Bag],[Drum])
) AS PivotTable
) AS Main
ORDER BY RFID
Dynamic Query:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + [Type] + ']'
FROM ContanerType
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT [Name],' + #columnList + ' FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( ' + #columnList + ')
) AS PivotTable
) AS Main
ORDER BY RFID;'
EXEC sp_executesql #pivotsql
Following my tutorial below will help you to understand the PIVOT functionality:
We write sql queries in order to get different result sets like full, partial, calculated, grouped, sorted etc from the database tables. However sometimes we have requirements that we have to rotate our tables. Sounds confusing?
Let's keep it simple and consider the following two screen grabs.
SQL Table:
Expected Results:
Wow, that's look like a lot of work! That is a combination of tricky sql, temporary tables, loops, aggregation......, blah blah blah
Don't worry let's keep it simple, stupid(KISS).
MS SQL Server 2005 and above has a function called PIVOT. It s very simple to use and powerful. With the help of this function we will be able to rotate sql tables and result sets.
Simple steps to make it happen:
Identify all the columns those will be part of the desired result set.
Find the column on which we will apply aggregation(sum,ave,max,min etc)
Identify the column which values will be the column header.
Specify the column values mentioned in step3 with comma separated and surrounded by square brackets.
So, if we now follow above four steps and extract information from the above sales table, it will be as below:
Year, Month, SalesAmount
SalesAmount
Month
[Jan],[Feb] ,[Mar] .... etc
We are nearly there if all the above steps made sense to you so far.
Now we have all the information we need. All we have to do now is to fill the below template with required information.
Template:
Our SQL query should look like below:
SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( [Jan],[Feb] ,[Mar],
[Apr],[May],[Jun] ,[Jul],
[Aug],[Sep] ,[Oct],[Nov] ,[Dec])
) AS PivotTable;
In the above query we have hard coded the column names. Well it's not fun when you have to specify a number of columns.
However, there is a work arround as follows:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + SalesMonth + ']'
FROM Sales
GROUP BY SalesMonth
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( ' + #columnList +' )
) AS PivotTable;'
EXEC sp_executesql #pivotsql
Hopefully this tutorial will be a help to someone somewhere.
Enjoy coding.

Pivot Structure in SQL

I need a sql query to populate data in a pivotal structure, lets say I have the below table as example, I need a query in SQL server 2008
ID|EMAIL|Name|Region|Catergory
1|a#a.com|A|AMERICA|PP
2|b#b.com|B|EMEA|CC
3|c#c.com|C|APAC|PP
4|d#d.com|D|APAC|DD
5|E#c.com|E|EMEA|CC
6|E#d.com|ED|UNKNOW|CC
Is there any way to get the below pivot structure table,
Below is the result i would want from the above table in counts,
Category|AMERICAS|EMEA|APAC|UNKNOW
PP|1|0|0|0
CC|0|2|0
DD|0|0|1|0
SELECT *
FROM
(
SELECT id, Catergory, region
FROM table1
) AS t
PIVOT
(
COUNT(ID)
FOR Region IN([AMERICAS],
[EMEA],
[APAC],
[UNKNOW])
) AS p;
SQL Fiddle Demo
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Region)
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT *
FROM
(
SELECT id, Catergory, region
FROM table1
) AS t
PIVOT
(
COUNT(ID)
FOR Region IN(' + #cols + ')) AS p;';
execute(#query);
Updated SQL Fiddle Demo
Try this one -
DECLARE #temp TABLE
(
ID INT,
Name CHAR(2),
Region VARCHAR(10),
Catergory CHAR(2)
)
INSERT INTO #temp (ID, Name, Region, Catergory)
VALUES
(1, 'A', 'AMERICA', 'PP'),
(2, 'B', 'EMEA', 'CC'),
(3, 'C', 'APAC', 'PP'),
(4, 'D', 'APAC', 'DD'),
(5, 'E', 'EMEA', 'CC'),
(6, 'ED', 'UNKNOW', 'CC')
SELECT *
FROM (
SELECT Name, Region, Catergory
FROM #temp
) src
PIVOT (
COUNT(Name)
FOR Region IN (AMERICAS, EMEA, APAC, UNKNOW)
) pvt
Output -
Catergory AMERICAS EMEA APAC UNKNOW
--------- ----------- ----------- ----------- -----------
CC 0 2 0 1
DD 0 0 1 0
PP 0 0 1 0