SQL Server 2008 Vertical data to Horizontal - sql-server

I apologize for submitting another question on this topic, but I've read through many of the answers on this and I can't seem to get it to work for me.
I have three tables I need to join and pull info on. One of the tables is only 3 columns and stores the data vertically. I would like to transpose that data to a horizontal format.
The data will look like this if I just join and pull:
SELECT
a.app_id,
b.field_id,
c.field_name,
b.field_value
FROM table1 a
JOIN table2 b ON a.app_id = b.app_id
JOIN table3 c ON b.field_id = c.field_id --(table3 is a lookup table for field names)
Result:
app_id | field_id | field_name | field_value
-----------------------------------------------------
1234 | 101 | First Name | Joe
1234 | 102 | Last Name | Smith
1234 | 105 | DOB | 10/15/72
1234 | 107 | Mailing Addr | PO BOX 1234
1234 | 110 | Zip | 12345
1239 | 101 | First Name | Bob
1239 | 102 | Last Name | Johnson
1239 | 105 | DOB | 12/01/78
1239 | 107 | Mailing Addr | 1234 N Star Ave
1239 | 110 | Zip | 12456
Instead, I would like it to look like this:
app_id | First Name | Last Name | DOB | Mailing Addr | Zip
--------------------------------------------------------------------------
1234 | Joe | Smith | 10/15/72 | PO BOX 1234 | 12345
1239 | Bob | Johnson | 12/01/78 | 1234 N Star Ave | 12456
In the past, I just resorted to looking up all the field_id's I needed in my data and created CASE statements for each one. The app the users are using contains data for multiple products, and each product contains different fields. Considering the number of products supported and the number of fields for each product (many, many more than the basic example I showed, above) it takes a long time to look them up and write out huge chunks of CASE statements.
I was wondering if there's some cheat-code out there to achieve what I need without having to look up the field_ids and writing things out. I know the PIVOT function is likely what I'm looking for, however, I can't seem to get it to work correctly.
Think you guys could help out?

You can use the PIVOT function to convert your rows of data into columns.
Your original query can be used to retrieve all the data, the only change I would make to it would be to exclude the column b.field_id because this will alter the final display of the result.
If you have a known list of field_name values that you want to turn into columns, then you can hard-code your query:
select app_id,
[First Name], [Last Name], [DOB],
[Mailing Addr], [Zip]
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) d
pivot
(
max(field_value)
for field_name in ([First Name], [Last Name], [DOB],
[Mailing Addr], [Zip])
) piv;
See SQL Fiddle with Demo.
But if you are going to have an unknown number of values for field_name, then you will need to implement dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Field_name)
from Table3
group by field_name, Field_id
order by Field_id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT app_id,' + #cols + '
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) x
pivot
(
max(field_value)
for field_name in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both of these this will give a result:
| APP_ID | FIRST NAME | LAST NAME | DOB | MAILING ADDR | ZIP |
------------------------------------------------------------------------
| 1234 | Joe | Smith | 10/15/72 | PO Box 1234 | 12345 |
| 1239 | Bob | Johnson | 12/01/78 | 1234 N Star Ave | 12456 |

Try this
SELECT
[app_id]
,MAX([First Name]) AS [First Name]
,MAX([Last Name]) AS [Last Name]
,MAX([DOB]) AS [DOB]
,MAX([Mailing Addr]) AS [Mailing Addr]
,MAX([Zip]) AS [Zip]
FROM Table1
PIVOT
(
MAX([field_value]) FOR [field_name] IN ([First Name],[Last Name],[DOB],[Mailing Addr],[Zip])
) T
GROUP BY [app_id]
SQL FIDDLE DEMO

bluefeet's answer was the right one for me, but I needed distinct on the column list:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT Distinct ',' + QUOTENAME(Field_name)
from Table3
group by field_name, Field_id
order by ',' + QUOTENAME(Field_name)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT app_id,' + #cols + '
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) x
pivot
(
max(field_value)
for field_name in (' + #cols + ')
) p '
execute sp_executesql #query;

This would solve using group by and MAX function, instead of pivot:
SELECT PK_ID, MAX(PHONE) AS PHONE, MAX(MAIL) AS MAIL
FROM (
SELECT
PK_ID,
CASE
WHEN CONTACT_ALIAS.CONTACT_TYPE = 'COMPANY' THEN CONTACT_ALIAS.CONTACT_VALUE
END AS PHONE ,
CASE
WHEN CONTACT_ALIAS.CONTACT_TYPE = 'BUSINESS' THEN CONTACT_ALIAS.CONTACT_VALUE
END AS MAIL
FROM T_CONTACT_EMPLOYERS CONTACT_ALIAS
WHERE CONTACT_ALIAS.CONTACT_TYPE IN ('COMPANY' , 'BUSINESS')
) TEMP
GROUP BY PK_ID

USe of SQL Pivot
SELECT [Id], [FirstName], [LastName], [Email]
FROM
(
SELECT Id, Att_Id, Att_Value FROM VerticalTable
) as source
PIVOT
(
MAX(Att_Value) FOR Att_Id IN ([FirstName], [LastName], [Email])
) as target

Related

Need to Dynamically Pivot Table Making Date Column New Table Headers

I've seen just about every kind of pivot example on the Internet, both text based tutorial and video. Not one is solving the issue at hand.
What I want is to pivot the following table:
Sales Table
SalesId | SalesDate | SalesLocation | SalesAmount
-----------------------------------------------------
1 | 2012-03 | New York, NY | 3,000
2 | 2012-04 | Miami, FL | 2,500
3 | 2012-05 | Carmel, CA | 2,850
4 | 2012-06 | Berkeley, CA | 1,900
5 | 2012-07 | Akron, OH | 4,200
6 | 2012-08 | Portland, OR | 2,200
I would like to PIVOT this table to show each row as a column, not distinct SUM of columns. Each row is specifically it's own column, with the date as the header, as if the table has been turned 90 degrees clockwise like an Excel spreadsheet, for date forecasting:
Account Info | 2012-03 | 2012-04 | 2012-05 | 2012-06
---------------------------------------------------------------------
SalesId | 1 | 2 | 3 | 4
SalesLocation | New York, NY | Miami, FL | Carmel, CA | Berkeley, CA
SalesAmount | 3,000 | 2,500 | 2,850 | 1,900
Most of the tutorials on this subject involve creating sums of several rows that have either the same city, the same status or the same type.
For this situation, each row already has a distinct unique date that will never be duplicated, so all records are unique and therefore do not require a function like SUM.
So far, after viewing an incredibly clear solution by JoyceW on the PIVOT Using Date Column thread at the ASP.net forums, I have gotten this far:
declare #cols varchar(max)
select #cols = (select distinct SalesDate from SalesTable for xml path(''))
select #cols = replace(#cols, '', '[')
select #cols = replace(#cols, '', '],')
select #cols = left(#cols, len(#cols) - 1)
declare #query varchar(max)
select #query = 'select * from (select SalesId, SalesLocation, SalesAmount
from SalesTable) src pivot (max(SalesAmount) for SalesDate in ('
+ #cols + ')) piv;'
execute(#query)
While this is not what I was looking for, it's the only tutorial that has allowed me to actually return a result set where the top columns are actually the dates (yay), as seen below:
SalesId | SalesLocation | SalesAmount | 2012-03 | 2012-04 | 2012-05
1 | New York, NY | 3,000 | {null} | {null} | 4
2 | Miami, FL | 2,500 | {null} | 3 | {null}
3 | Carmel, CA | 2,850 | {null} | {null} | {null}
4 | Berkeley, CA | 1,900 | 1 | {null} | {null}
5 | Akron, OH | 4,200 | {null} | {null} | {null}
6 | Portland, OR | 2,200 | {null} | 2 | {null}
Not really sure what's going on here, but it's a lot closer than I was before, I'm actually receiving a result set. Any ideas about how to fine tune this where the rows showing up need to be rotated or pivoted as well?
It seems that I'm only pivoting the headers and not the entire table as I would like. Any suggestions would be greatly appreciated.
As you want multiple aggregation columns in your pivot you have to do the iterations for each aggregation value. I am using Sales as the table name. Please change the table name if it is different for you. Below query should give you the result you want.
DECLARE #cols AS NVARCHAR(MAX),
#Convertcols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX),
#PivotColumn Varchar(100),
#i Int = 1,
#AggColumn Varchar(100)
DECLARE #Columns TABLE (ID int IDENTITY(1,1), AccountInfo Varchar(max))
SELECT #cols = STUFF((SELECT ', '+ QUOTENAME(SalesDate)
from Sales
group by SalesDate
order by SalesDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #Convertcols = STUFF((SELECT ', CAST( '+ QUOTENAME(SalesDate) + ' AS VARCHAR(max)) AS ' + QUOTENAME(SalesDate)
from Sales
group by SalesDate
order by SalesDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
INSERT INTO #Columns
SELECT C.name
FROM SYS.COLUMNS C
INNER JOIN SYS.TABLES T ON C.OBJECT_ID = T.OBJECT_ID
WHERE T.NAME = 'Sales'
AND C.name <> 'SalesDate'
While #i <= (SELECT MAX(ID) FROM #Columns)
BEGIN
SELECT #AggColumn = AccountInfo FROM #Columns Where ID = #i
set #query = 'SELECT AccountInfo,' + #Convertcols +
' FROM
(
SELECT ''' + #AggColumn + ''' AS AccountInfo, SalesDate, '+ #AggColumn +'
FROM Sales
) X
PIVOT
(
Max('+ #AggColumn +')
FOR SalesDate in (' + #cols + ')
) P '
SET #SQL = CONCAT(#SQL, CHAR(10), CASE WHEN #i = 1 THEN '' ELSE 'UNION ' END, CHAR(10) , #query)
SET #i = #i+1
END
EXECUTE (#SQL)
Result:
In order for you to be able to PIVOT, you'll need to have at least one non-pivoted column - something which your model is lacking.
Documentation: https://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396

SQL Server Transpose Rows into Columns using IDs as Column Names

I have a large file that has the following fields:
Table 1:
+---------+--------+-----------+
| User_Id | Key_Id | Value |
+---------+--------+-----------+
| 100 | 74 | 37 |
| 100 | 65 | Male |
| 100 | 279 | G235467 |
+---------+--------+-----------+
and I have another file that tells what each 'Key_Id' is called (they are column names) e.g.
Table 2:
+--------+------------------+
| Key_Id | Key |
+--------+------------------+
| 65 | Gender |
| 66 | Height |
| 74 | Age |
| 279 | ReferenceNo |
I want to create a table using the Key_Id names found in the Key column of table 2, transpose all of the values from table 1 into table 2, but also include the User_Id from table 1 as this relates to an individual.
PS. Table 2 has nearly 300 keys that would need turning into individual fields
So ultimately I would like a table that looks like this:
+---------+---------+--------+-------+--------------+--------+
| User_Id | Gender | Height | Age | ReferenceNo | etc |
+---------+---------+--------+-------+--------------+--------+
| 100 | Male | | 37 | G235467 | |
So that each User_Id is a row and that all the Keys are columns with their respective values
You can use a dynamic sql query as below.
Query
declare #sql as varchar(max);
select #sql = 'select t1.[User_Id], ' + stuff((select +
', max(case t2.[Key_Id] when ' + cast([Key_Id] as varchar(100)) +
' then t1.[Value] end) as [' + [Key] + '] '
from Table2
for xml path('')
), 1, 2, '') +
'from Table1 t1 left join Table2 t2 on t1.[Key_Id] = t2.[Key_Id] group by t1.[User_Id];'
exec(#sql);
Find a demo here
You need to get a coma-separated list of those 300 key names to be used in PIVOT/UNPIVOT operators in T-SQL like described here
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot
you can use pivot as below:
Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in ([Gender], [Height], [Age], [ReferenceNo]) ) p
For dynamic list of keys you can use dynamic sql as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select ','+QuoteName([Key]) from table2 group by [Key] for xml path('')),1,1,'')
Set #Query = 'Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in (' + #cols1 + ') ) p '
Select #Query --Check the generated query and execute by uncommenting below query
--exec sp_executesql #Query

SQL Server Pivot on this data

I'm having a rough go trying to pivot my data :( It is dynamic data and structured like this:
| Date | Source | Amount |
--------------------------------------------
| 12/1/2016 | Source1 | $0 |
| 12/1/2016 | Source2 | $2 |
| 12/1/2016 | Source3 | $5 |
| 12/1/2016 | Source4 | $4 |
There can be unlimited sources and I want to pivot it by source/date:
| Date | Source1 | Source 2 | Source 3 | Source 4 |
--------------------------------------------------------------------------------------
| 12/1/2016 | $0 | $2 | $5 | $4 |
Something like that, anyway.
I have tried coding a lot of ways, so I'll just put in what I thought it could be:
SELECT myDate , Source, Amount
FROM mydb
PIVOT
(max(source) FOR source IN (select distinct source from mydb) as myPivotTable
WHERE (myDate > #StartDate)
of course, that doesn't work. This was going to be part of a stored procedure, just not quite there. Was hoping to pivot on that data so I can do some line trends in SSRS.
I also followed another example and tried this:
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(source)
from mydb where myDate > #StartDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
And then tried to use that in the place of the "select distinct". However, the sources are text and that didn't seem to do much for me.
Here is a simple Dynamic Pivot on Source
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName([Source]) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Date],' + #SQL + '
From YourTable
Pivot (sum(Amount) For [Source] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Date Source1 Source2 Source3 Source4
2016-12-01 0 2 5 4
Now, If you want Source to be sequenced Like Source 1, Source 2 it would require just a minor tweak
FYI the generated SQL would look like this
Select [Date],[Source1],[Source2],[Source3],[Source4]
From YourTable
Pivot (sum(Amount) For [Source] in ([Source1],[Source2],[Source3],[Source4]) ) p

SQL Server making rows into columns

I'm trying to take three tables that I have and show the data in a way the user asked me to do it. The tables look like this. (I should add that I am using MS SQL Server)
First Table: The ID is varchar, since it's an ID they use to identify assets and they use numbers as well as letters.
aID| status | group |
-----------------------
1 | acti | group1 |
2 | inac | group2 |
A3 | acti | group1 |
Second Table: This table is fixed. It has around 20 values and the IDs are all numbers
atID| traitname |
------------------
1 | trait1 |
2 | trait2 |
3 | trait3 |
Third Table: This table is used to identify the traits the assets in the first table have. The fields that have the same name as fields in the above tables are obviously linked.
tID| aID | atID | trait |
----------------------------------
1 | 1 | 1 | NAME |
2 | 1 | 2 | INFO |
3 | 2 | 3 | GOES |
4 | 2 | 1 | HERE |
5 | A3 | 2 | HAHA |
Now, the user wants the program to output the data in the following format:
aID| status | group | trait1 | trait2 | trait 3
-------------------------------------------------
1 | acti | group1 | NAME | INFO | NULL
2 | inac | group2 | HERE | NULL | GOES
A3 | acti | group1 | NULL | HAHA | NULL
I understand that to achieve this, I have to use the Pivot command in SQL. However, I've read and tried to understand it but I just can't seem to get it. Especially the part where it asks for a MAX value. I don't get why I need that MAX.
Also, the examples I've seen are for one table. I'm not sure if I can do it with three tables. I do have a query that joins all three of them with the information I need. However, I don't know how to proceed from there. Please, any help with this will be appreciated. Thank you.
There are several ways that you can get the result, including using the PIVOT function.
You can use an aggregate function with a CASE expression:
select t1.aid, t1.status, t1.[group],
max(case when t2.traitname = 'trait1' then t3.trait end) trait1,
max(case when t2.traitname = 'trait2' then t3.trait end) trait2,
max(case when t2.traitname = 'trait3' then t3.trait end) trait3
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
group by t1.aid, t1.status, t1.[group];
See SQL Fiddle with Demo
The PIVOT function requires an aggregate function this is why you would need to use either the MIN or MAX function (since you have a string value).
If you have a limited number of traitnames then you could hard-code the query:
select aid, status, [group],
trait1, trait2, trait3
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) d
pivot
(
max(trait)
for traitname in (trait1, trait2, trait3)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you will want to look at using dynamic SQL to get the final result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(traitname)
from Table2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT aid, status, [group],' + #cols + '
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) x
pivot
(
max(trait)
for traitname in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo

SQL combine column names and data types with column values

I am using Sql Server 2012 for my DBMS.
In my database I have a Product table that is related to a series of catalog tables. These catalog tables represent the various Product Categories. The idea being that while all products have certain attributes in common (our internal identifier for it, a supplier, a cost, etc) the specifics of the products will vary (furniture is not described the same way as a digital product would be described).
Additionally, each product has a parent entity. A parent may have multiple children (or products in this case).
My task is to select the products and related information for a given list of parent Id's and populate that information into an XML document.
The tables I'm currently working with are:
PRODUCT
PRODUCT_DIGITAL
PRODUCT_FURNITURE
Product has a PK/FK relationship with PRODUCT_DIGITAL on Product_Id. Same type of relationship exist for PRODUCT to PRODUCT_FURNITURE.
If product looks like this:
PRODUCT_ID -- PRODUCT_CATEGORY -- PARENT_ID -- PARENT_TYPE -- DELIVERY_IN_DAYS
100 DIG 1 1 7
101 DIG 1 1 8
102 DIG 1 1 1
103 DIG 2 1 2
104 DIG 2 1 1
And PRODUCT_DIGITAL looks like this:
PRODUCT_ID -- PRODUCT_TYPE -- PRODUCT_NAME -- PRODUCT_MNEMONIC
100 A IMG1 IMAWTRFL
101 B SND1 SNDENGRV
102 B SND2 SNDHRSLF
103 A IMG2 IMGNBRTO
104 B SND3 SNDGTWNE
In the end I want result set that looks like this:
PRODUCT_CATEGORY -- PRODUCT_ID -- PRODUCT_TYPE -- PARENT_ID -- DELIVERY_IN_DAYS -- PROD_EXTENSION_NAME -- PROD_EXTENSION_TYPE -- PROD_EXTENSION_VALUE
DIG 100 A 1 7 PRODUCT_NAME STRING IMG1
DIG 100 A 1 7 PRODUCT_MNEMONIC STRING IMAWTRFL
DIG 101 B 1 8 PRODUCT_NAME STRING SND1
DIG 101 B 1 8 PRODUCT_MNEMONIC STRING SNDENGRV
DIG 102 B 1 1 PRODUCT_NAME STRING SND2
DIG 102 B 1 1 PRODUCT_MNEMONIC STRING SNDHRSLF
DIG 103 A 2 2 PRODUCT_NAME STRING IMG2
DIG 103 A 2 2 PRODUCT_MNEMONIC STRING IMGNBRTO
DIG 104 B 2 1 PRODUCT_NAME STRING SND3
DIG 104 B 2 1 PRODUCT_MNEMONIC STRING SNDGTWNE
My original searching brought me to UNPIVOT - but so far I have not been able to get my head around it. What I ended up doing was creating a temp table and updating it in passes, then joining back to the products table for the final select:
create table #tbl(product_id int, prod_extension_name varchar(100), prod_extension_type varchar(100), prod_extension_value varchar(1000))
insert into #tbl
select p.product_id, c.column_name,
case c.data_type
when 'varchar' then 'string'
else data_type end as data_type
, null
from dbo.product p, information_schema.columns c
where c.table_name = 'PRODUCT_DIGITAL'
and c.column_name in ('PRODUCT_NAME','PRODUCT_MNEMONIC')
update #tbl
set prod_extension_value = p.product_name
from dbo.product p
where #tbl.product_id = p.product_id
and #tbl.colname = 'PRODUCT_NAME'
update #tbl
set prod_extension_value = p.product_mnemonic
from dbo.product p
where #tbl.product_id = p.product_id
and #tbl.colname = 'PRODUCT_MNEMONIC'
select p.product_category, p.product_id, pd.product_category, #tbl.prod_extension_name, #tbl.prod_extension_type, #tbl.prod_extension_value
from dbo.product p inner join dbo.product_digital pd on p.product_id = pd.product_id
inner join #tbl on p.product_id = #tbl.product_id
order by product_id
Can someone point me to a better way to do this? It seems like I should be able to do this faster, without having to make multiple updates, etc.
I think this will do what you want. This implements the UNPIVOT function and then get the column information from the information_schema.columns view to get the result:
select product_id,
product_category,
parent_id,
delivery_in_days,
PROD_EXTENSION_NAME,
case when c.data_type = 'varchar'
then 'STRING' else null end as PROD_EXTENSION_TYPE,
PROD_EXTENSION_VALUE
from
(
select
p.product_id,
p.product_category,
p.parent_id,
p.delivery_in_days,
PRODUCT_NAME,
PRODUCT_MNEMONIC
from product p
left join product_digital pd
on p.product_id = pd.product_id
) src
unpivot
(
PROD_EXTENSION_VALUE
for PROD_EXTENSION_NAME in (PRODUCT_NAME, PRODUCT_MNEMONIC)
) up
inner join
(
select c.column_name, c.data_type
from information_schema.columns c
where c.table_name = 'PRODUCT_DIGITAL'
and c.column_name in ('PRODUCT_NAME','PRODUCT_MNEMONIC')
) c
on up.PROD_EXTENSION_NAME = c.column_name
See SQL Fiddle with Demo
The result is:
| PRODUCT_ID | PRODUCT_CATEGORY | PARENT_ID | DELIVERY_IN_DAYS | PROD_EXTENSION_NAME | PROD_EXTENSION_TYPE | PROD_EXTENSION_VALUE |
-----------------------------------------------------------------------------------------------------------------------------------
| 100 | DIG | 1 | 7 | PRODUCT_NAME | STRING | IMG1 |
| 100 | DIG | 1 | 7 | PRODUCT_MNEMONIC | STRING | IMAWTRFL |
| 101 | DIG | 1 | 8 | PRODUCT_NAME | STRING | SND1 |
| 101 | DIG | 1 | 8 | PRODUCT_MNEMONIC | STRING | SNDENGRV |
| 102 | DIG | 1 | 1 | PRODUCT_NAME | STRING | SND2 |
| 102 | DIG | 1 | 1 | PRODUCT_MNEMONIC | STRING | SNDHRSLF |
| 103 | DIG | 2 | 2 | PRODUCT_NAME | STRING | IMG2 |
| 103 | DIG | 2 | 2 | PRODUCT_MNEMONIC | STRING | IMGNBRTO |
| 104 | DIG | 2 | 1 | PRODUCT_NAME | STRING | SND3 |
| 104 | DIG | 2 | 1 | PRODUCT_MNEMONIC | STRING | SNDGTWNE |
Edit #1: If you want to perform this type of transformation dynamically then you will need to use dynamic SQL. to do this you will use the following.
First, you will need to get the list of columns to UNPIVOT:
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('PRODUCT_DIGITAL') and
C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE') -- include the items you DO NOT want to unpivot
for xml path('')), 1, 1, '')
The final script will be:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('PRODUCT_DIGITAL') and
C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE')
for xml path('')), 1, 1, '')
select #cols = stuff((select ', '''+C.name + ''''
from sys.columns as C
where C.object_id = object_id('PRODUCT_DIGITAL') and
C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE')
for xml path('')), 1, 1, '')
set #query
= ' select
product_id,
product_category,
parent_id,
delivery_in_days,
PROD_EXTENSION_NAME,
case when c.data_type = ''varchar''
then ''STRING'' else null end as PROD_EXTENSION_TYPE,
PROD_EXTENSION_VALUE
from
(
select
p.product_id,
p.product_category,
p.parent_id,
p.delivery_in_days,
PRODUCT_NAME,
PRODUCT_MNEMONIC
from product p
left join product_digital pd
on p.product_id = pd.product_id
) src
unpivot
(
PROD_EXTENSION_VALUE
for PROD_EXTENSION_NAME in ('+ #colsunpivot +')
) up
inner join
(
select c.column_name, c.data_type
from information_schema.columns c
where c.table_name = ''PRODUCT_DIGITAL''
and c.column_name in ('+#cols+')
) c
on up.PROD_EXTENSION_NAME = c.column_name'
exec(#query)
See SQL Fiddle with Demo. This will produce the same result as the original version except it is dynamic.
I might be jumping the gun, but you say that your final output should be XML.
While I have not included all your requirements, would something like this suit your requirements? (I think I may have over simplified your requirements)
;WITH PRODUCT (PRODUCT_ID, PRODUCT_CATEGORY, PARENT_ID, PARENT_TYPE, DELIVERY_IN_DAYS) AS
(
SELECT 100, 'DIG', 1, 1, 7 UNION ALL
SELECT 101, 'DIG', 1, 1, 8 UNION ALL
SELECT 102, 'DIG', 1, 1, 1 UNION ALL
SELECT 103, 'DIG', 2, 1, 2 UNION ALL
SELECT 104, 'DIG', 2, 1, 1
)
,PRODUCT_DIGITAL (PRODUCT_ID, PRODUCT_TYPE, PRODUCT_NAME, PRODUCT_MNEMONIC) AS
(
SELECT 100, 'A', 'IMG1', 'IMAWTRFL' UNION ALL
SELECT 101, 'B', 'SND1', 'SNDENGRV' UNION ALL
SELECT 102, 'B', 'SND2', 'SNDHRSLF' UNION ALL
SELECT 103, 'A', 'IMG2', 'IMGNBRTO' UNION ALL
SELECT 104, 'B', 'SND3', 'SNDGTWNE'
)
SELECT P.PRODUCT_CATEGORY
,P.PRODUCT_ID
,P.PARENT_TYPE
,P.PARENT_ID
,P.DELIVERY_IN_DAYS
,PD.PRODUCT_NAME
,PD.PRODUCT_MNEMONIC
FROM PRODUCT P
JOIN PRODUCT_DIGITAL PD ON P.PRODUCT_ID = PD.PRODUCT_ID
FOR XML PATH

Resources