SQL Server - Rows to Columns without Aggregation - sql

I have data that looks like this:
address | id
12AnyStreet | 1234
12AnyStreet | 1235
12AnyStreet | 1236
12AnyStreet | 1237
My goal is to make it look like this:
Address id1 id2 id3 id4
123Any 1234 1235 1246 1237
Based on some Googling and what not, I was able to generate the following CTE:
with cust_cte (Address, id, RID) as (
SELECT Address, id,
ROW_NUMBER() OVER (PARTITION BY (Address) ORDER BY Address) AS RID
FROM tab)
The next step would be to pivot so that for each RID, I place the associated id in the column. However, I can't seem to get the example I found to work. Rather than post the rest of the example which may not even really apply, I'll leave it up to the audience. Other novel approaches not necessarily utilizing the CTE are also appreciated. This will be chugging through a lot of data, so efficiency is important.

You can transform this data using the PIVOT function in SQL Server. In order to PIVOT the data, you will want to create your new column using the row_number().
If you have a known number of values, then you can hard-code the query:
select *
from
(
select address, id,
'id_'+cast(row_number() over(partition by address
order by id) as varchar(20)) rn
from yourtable
) src
pivot
(
max(id)
for rn in ([id_1], [id_2], [id_3], [id_4])
) piv
See SQL Fiddle with Demo
But if the values are unknown then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ QUOTENAME(rn)
from
(
select 'id_'+cast(row_number() over(partition by address
order by id) as varchar(20)) rn
from yourtable
) src
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT address,' + #cols + ' from
(
select address, id,
''id_''+cast(row_number() over(partition by address
order by id) as varchar(20)) rn
from yourtable
) x
pivot
(
max(id)
for rn in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result of both queries is:
| ADDRESS | ID_1 | ID_2 | ID_3 | ID_4 |
-------------------------------------------
| 12AnyStreet | 1234 | 1235 | 1236 | 1237 |

Related

SQL 2008 - Combine multiple row values into one row with 'n' columns [duplicate]

I have the following data:
DECLARE #DataSource TABLE
(
[ColumnA] INT
,[ColumnB] INT
,[ColumnC] INT
)
INSERT INTO #DataSource ([ColumnA], [ColumnB], [ColumnC])
VALUES (5060,1006,100118)
,(5060,1006,100119)
,(5060,1006,100120)
,(5060,1007,100121)
,(5060,1007,100122)
,(5060,1012,100123)
SELECT [ColumnA]
,[ColumnB]
,[ColumnC]
FROM #DataSource
and I need to converted like this:
The difficult part is that the data is dynamic (I do not know how many columns I will have) and I am not able to use a standard pivot here because the values in ColumnC are different and as a result I am going to have as many columns as values appears in ColumnC.
Is there any technique to achieve this?
Any kind of help (answers, articles, suggestions) will be appreciated.
My suggestion whenever you are working with PIVOT is to alway write the query first with the values hard-coded, then you can easily convert the query to a dynamic solution.
Since you are going to have multiple values of columnC that will be converted to columns, then you need to look at using the row_number() windowing function to generate a unique sequence for each columnc based on the values of columnA and columnB.
The starting point for your query will be:
select [ColumnA],
[ColumnB],
[ColumnC],
'SampleTitle'+
cast(row_number() over(partition by columna, columnb
order by columnc) as varchar(10)) seq
from DataSource;
See Demo. This query will generate the list of new columns names SampleTitle1, etc:
| COLUMNA | COLUMNB | COLUMNC | SEQ |
|---------|---------|---------|--------------|
| 5060 | 1006 | 100118 | SampleTitle1 |
| 5060 | 1006 | 100119 | SampleTitle2 |
| 5060 | 1006 | 100120 | SampleTitle3 |
You can then apply the pivot on columnC with the new column names listed in seq:
select columnA, columnB,
SampleTitle1, SampleTitle2, SampleTitle3
from
(
select [ColumnA],
[ColumnB],
[ColumnC],
'SampleTitle'+
cast(row_number() over(partition by columna, columnb
order by columnc) as varchar(10)) seq
from DataSource
) d
pivot
(
max(columnc)
for seq in (SampleTitle1, SampleTitle2, SampleTitle3)
) piv;
See SQL Fiddle with Demo.
Once you have the correct logic, you can convert the data to dynamic SQL. The key here is generating the list of new column names. I typically use FOR XML PATH for this similar to:
select STUFF((SELECT distinct ',' + QUOTENAME(seq)
from
(
select 'SampleTitle'+
cast(row_number() over(partition by columna, columnb
order by columnc) as varchar(10)) seq
from DataSource
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
See Demo. Once you have the list of column names, then you will generate your sql string to execute, the full code will be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(seq)
from
(
select 'SampleTitle'+
cast(row_number() over(partition by columna, columnb
order by columnc) as varchar(10)) seq
from DataSource
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT columnA, ColumnB,' + #cols + '
from
(
select [ColumnA],
[ColumnB],
[ColumnC],
''SampleTitle''+
cast(row_number() over(partition by columna, columnb
order by columnc) as varchar(10)) seq
from DataSource
) x
pivot
(
max(columnc)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. These give a result:
| COLUMNA | COLUMNB | SAMPLETITLE1 | SAMPLETITLE2 | SAMPLETITLE3 |
|---------|---------|--------------|--------------|--------------|
| 5060 | 1006 | 100118 | 100119 | 100120 |
| 5060 | 1007 | 100121 | 100122 | (null) |
| 5060 | 1012 | 100123 | (null) | (null) |

Pivot Table issue: no identifier in original table or enumeration

I'm having an issue with using pivot or dynamic tables.
I found this example to be very helpful in understanding how I can accomplish this task; however, I am missing the inside identifier column.
Dynamic Pivot Columns in SQL Server
There is an SQL fiddle provided here in the post above: http://www.sqlfiddle.com/#!3/7fad2/6
You can see that in the second table propertyObjects, there is a count 1, 2, 3, 4 for each objectID. I do not have that propertyID count. This is all I have
case category
1 xx
1 xyx
1 abc
2 ghj
2 asdf
3 dfgh
As you can see I have a number of different categories for each case, but no category identifier field.
This is what I need:
case cat1 cat2 cat3
1 xx xyx abc
2 ghj asdf
3 dfgh
So I am thinking I might need to add a column to the source table and somehow enumerate the categories per case. This would make it possible for me to use the pivot in the provided example. Thoughts?
I tried to use row_number to accomplish this, but it does not stop at each case number, it just continues on counting the entire table.
Since you have multiple values for each case, then you will need to use row_number() to get the separate columns for each category.
Before you write the dynamic SQL version I would first write a hard-coded version. The code will be similar to:
SELECT [case], cat1, cat2, cat3
FROM
(
SELECT [case], category,
'cat'+
cast(row_number() over(partition by [case]
order by category) as varchar(10)) seq
FROM yourTable
) x
PIVOT
(
max(category)
for seq in (cat1, cat2, cat3)
)p;
See SQL Fiddle with Demo.
Now you have the logic down, then you can convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('cat'+cast(seq as varchar(10)))
from
(
select row_number() over(partition by [case]
order by category) seq
from yourtable
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [case],' + #cols + '
from
(
SELECT [case], category,
''cat''+
cast(row_number() over(partition by [case]
order by category) as varchar(10)) seq
FROM yourTable
) x
pivot
(
max(category)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This will give you the result:
| CASE | CAT1 | CAT2 | CAT3 |
|------|------|--------|--------|
| 1 | abc | xx | xyx |
| 2 | asdf | ghj | (null) |
| 3 | dfgh | (null) | (null) |

How do I Pivot Vertical Data to Horizontal Data SQL with Variable Row Lengths?

Okay I have the following table.
Name ID Website
Aaron | 2305 | CoolSave1
Aaron | 8464 | DiscoWorld1
Adriana | 2956 | NewCin1
Adriana | 5991 | NewCin2
Adriana | 4563 NewCin3
I would like to transform it into the following way.
Adriana | 2956 | NewCin1 | 5991 | NewCin2 | 4563 | NewCin3
Aaron | 2305 | CoolSave1 | 8464 | DiscoWorld | NULL | NULL
As you can see i am trying to take the first name from the first table and make a single row with all the IDs / Websites associated with that name. The problem is, there is a variable amount of websites that may be associated with each name. To handle this i'd like to just make a table with with the number of fields sequal to the max line item, and then for the subsequent lineitems, plug in a NULL where there are not enough data.
In order to get the result, you will need to apply both the UNPIVOT and the PIVOT functions to the data. The UNPIVOT will take the columns (ID, website) and convert them to rows, once this is done, then you can PIVOT the data back into columns.
The UNPIVOT code will be similar to the following:
select name,
col+'_'+cast(col_num as varchar(10)) col,
value
from
(
select name,
cast(id as varchar(11)) id,
website,
row_number() over(partition by name order by id) col_num
from yt
) src
unpivot
(
value
for col in (id, website)
) unpiv;
See SQL Fiddle with Demo. This gives a result:
| NAME | COL | VALUE |
-------------------------------------
| Aaron | id_1 | 2305 |
| Aaron | website_1 | CoolSave1 |
| Aaron | id_2 | 8464 |
| Aaron | website_2 | DiscoWorld1 |
As you can see I applied a row_number() to the data prior to the unpivot, the row number is used to generate the new column names. The columns in the UNPIVOT must also be of the same datatype, I applied a cast to the id column in the subquery to convert the data to a varchar prior to the pivot.
The col values are then used in the PIVOT. Once the data has been unpivoted, you apply the PIVOT function:
select *
from
(
select name,
col+'_'+cast(col_num as varchar(10)) col,
value
from
(
select name,
cast(id as varchar(11)) id,
website,
row_number() over(partition by name order by id) col_num
from yt
) src
unpivot
(
value
for col in (id, website)
) unpiv
) d
pivot
(
max(value)
for col in (id_1, website_1, id_2, website_2, id_3, website_3)
) piv;
See SQL Fiddle with Demo.
The above version works great if you have a limited or known number of values. But if the number of rows is unknown, then you will need to use dynamic SQL to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME( col+'_'+cast(col_num as varchar(10)))
from
(
select row_number() over(partition by name order by id) col_num
from yt
) t
cross apply
(
select 'id' col union all
select 'website'
) c
group by col, col_num
order by col_num, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + '
from
(
select name,
col+''_''+cast(col_num as varchar(10)) col,
value
from
(
select name,
cast(id as varchar(11)) id,
website,
row_number() over(partition by name order by id) col_num
from yt
) src
unpivot
(
value
for col in (id, website)
) unpiv
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both versions give the result:
| NAME | ID_1 | WEBSITE_1 | ID_2 | WEBSITE_2 | ID_3 | WEBSITE_3 |
------------------------------------------------------------------------
| Aaron | 2305 | CoolSave1 | 8464 | DiscoWorld1 | (null) | (null) |
| Adriana | 2956 | NewCin1 | 4563 | NewCin3 | 5991 | NewCin2 |

Pivot table data in tabular format, first column should be column name and rest should be value

I just want to compare products that's why I need to show products with there attributes in column format. The First column will show attributes and rest of column shows attributes value for each column.
Simple select statement is as follows
SELECT product_Id,product_Name,product_Price ,product_Description,product_weight
from tblProduct
WHERE product_Id in (1139,1140,1144)
Now I want to show my output in following way
I know that I need to use pivot to get that desired output. and i have tried in following way
SELECT MAX(ISNULL([1139],'')),MAX(ISNULL([1140],'')),MAX(ISNULL([1144],''))
FROM
(
SELECT product_Id,product_Name,product_Price,product_Description,product_weight
FROM tblProduct where product_Id in (1139,1140,1144)
)s
PIVOT
(
MAX(product_Name)
FOR product_Id in ([1139],[1140],[1144])
) AS PVT
but this only done for a single column but i needed for all attributes.
Since you are attempting to aggregate for attributes that exist in multiple columns, that is an indicator that you will need to apply both the UNPIVOT and the PIVOT functions.
The UNPIVOT will take your column values and convert them to rows. The code to unpivot will be similar to this:
select product_id, header, value
from
(
select product_id,
product_name,
cast(product_price as varchar(10)) product_price,
product_weight
from tblProduct
) p
unpivot
(
value
for header in (product_name, product_price, product_weight)
) unp
See SQL Fiddle with Demo. You will notice that there is a subquery in this that converts the product_price column to a varchar. This is because the datatypes of the columns you want in rows must be the same. So you might have to perform data conversions to get this to work properly.
The unpivot generates a result that looks like this:
| PRODUCT_ID | HEADER | VALUE |
---------------------------------------
| 141 | product_name | A141 |
| 141 | product_price | 200 |
| 141 | product_weight | 200gm |
Once the data is in the rows, you can apply the PIVOT function to the product_id column values.
select header, [141], [142], [143], [144]
from
(
select product_id, header, value
from
(
select product_id,
product_name,
cast(product_price as varchar(10)) product_price,
product_weight
from tblProduct
) p
unpivot
(
value
for header in (product_name, product_price, product_weight)
) unp
) d
pivot
(
max(value)
for product_id in ([141], [142], [143], [144])
) piv
See SQL Fiddle with Demo. This gives a result:
| HEADER | 141 | 142 | 143 | 144 |
--------------------------------------------------
| product_name | A141 | A142 | A143 | A144 |
| product_price | 200 | 300 | 4000 | 5000 |
| product_weight | 200gm | 300gm | 400gm | 100gm |
The above version will work great if you have a known number of product_id values that you want as columns. But if you have an unknown number, then you will want to implement dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(product_id)
from tblProduct
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT header, ' + #cols + '
from
(
select product_id, header, value
from
(
select product_id,
product_name,
cast(product_price as varchar(10)) product_price,
product_weight
from tblProduct
) p
unpivot
(
value
for header in (product_name, product_price, product_weight)
) unp
) x
pivot
(
max(value)
for product_id in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. This will generate the same result as the static/hard-coded version of the query.

Flatten variable length DB rows into a single column

I've searched and can't find a solution to this that exactly fits my needs, nor can I find one that I can modify. I have a database table, for simplicity we'll say it has three columns (packageID, carrier, and sequence). For any package there can be one or more carriers that have handled the package. I can do a query like
SELECT packageID, carrier
FROM packageFlow
ORDER BY sequence
to get a list of all the people that have handled packages that looks like:
packageID, carrier
1, Bob
1, Jim
1, Sally
1, Ron
2, Reggie
2, Mary
2, Bruce
What I need though is to get the results into rows that look like:
packageID|carrier1|carrier2|carrier3|carrier4
1 |Bob |Jim |Sally |Ron
2 |Reggie |Mary |Bruce
Pivot doesn't seem to do what I need since I'm not aggregating anything and I can't get a CTE to work correctly either. I'd appreciate any nudges in the right direction.
This data transformation is a PIVOT. Starting in SQL Server 2005, there is a function that will convert the rows into columns.
If you have a known number of values, then you can hard-code your query:
select *
from
(
select packageid, carrier,
'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) src
pivot
(
max(carrier)
for col in (Carrier_1, Carrier_2, Carrier_3, Carrier_4)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of Carrier values that you want to turn into columns, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.col)
from
(
select 'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageFlow
) t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT packageid,' + #cols + ' from
(
select packageid, carrier,
''Carrier_''+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) x
pivot
(
max(carrier)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Note: you will replace the order by packageid with order by sequence
The result of both queries is:
| PACKAGEID | CARRIER_1 | CARRIER_2 | CARRIER_3 | CARRIER_4 |
-------------------------------------------------------------
| 1 | Bob | Jim | Sally | Ron |
| 2 | Reggie | Mary | Bruce | (null) |