Multi level cross tab dynamic query - sql

I am trying to find a query that can generate cross-tab output at multiple levels dynamically. I did find few solutions online that returns dynamic cross-tab results but it returns only at single level. Below is the SQL fiddle:
CREATE TABLE dbo.PopulationDetails
(
Country VARCHAR(50),
State VARCHAR(50),
Population BIGINT,
SeatsInHouse INT
)
INSERT INTO PopulationDetails
VALUES('United States','California', 38332521, 53),
('United States','Texas', 26448193, 36),
('United States','New York', 19651127, 27),
('United States','Florida', 19552860, 27),
('United States','Illinois', 12882135, 18)
I want my output should look like below. The number of states are not fixed and these may vary as per the requirement.
United States
California Texas New York Florida Illinois
Population 38332521 26448193 19651127 19552860 12882135
SeatsInHouse 53 36 27 27 18

As I said in my comment, multi-level column headers can't be done via SQL. You'd have to format the data in a presentation layer like your application or SSRS. If you want to get the country and state values "together", then you'd have to concatenate the names together and make that your new column names.
If you want to get the result in SQL, I'd start by concatenating the country and state, and unpivot the columns population and SeatsInHouse first. The basic syntax for this process would be:
select
country_state = replace(pd.Country +'_'+pd.State, ' ', ''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
('Population', pd.population),
('SeatsInHouse', pd.SeatsInHouse)
) c (col, value);
See SQL Fiddle with Demo. This gives a result:
| COUNTRY_STATE | COL | VALUE |
|-------------------------|--------------|----------|
| UnitedStates_California | Population | 38332521 |
| UnitedStates_California | SeatsInHouse | 53 |
| UnitedStates_Texas | Population | 26448193 |
| UnitedStates_Texas | SeatsInHouse | 36 |
| UnitedStates_NewYork | Population | 19651127 |
| UnitedStates_NewYork | SeatsInHouse | 27 |
You'll see that you now have two rows for each Country_State combination. You can now pivot those Country_State values into columns:
select col, UnitedStates_California, UnitedStates_Texas,
UnitedStates_NewYork, UnitedStates_Florida,
UnitedStates_Illinois
from
(
select
country_state = replace(pd.Country +'_'+pd.State, ' ', ''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
('Population', pd.population),
('SeatsInHouse', pd.SeatsInHouse)
) c (col, value)
) d
pivot
(
max(value)
for country_state in (UnitedStates_California, UnitedStates_Texas,
UnitedStates_NewYork, UnitedStates_Florida,
UnitedStates_Illinois)
) piv;
See SQL Fiddle with Demo.
Now, if you need this done dynamically then you'd have to use dynamic SQL which creates a string that is then executed.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(country_state)
from
(
select country_state = replace(Country +'_'+State, ' ', '')
from dbo.PopulationDetails
) d
group by country_state
order by country_state
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT col, ' + #cols + '
from
(
select
country_state = replace(pd.Country +''_''+pd.State, '' '', ''''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
(''Population'', pd.population),
(''SeatsInHouse'', pd.SeatsInHouse)
) c (col, value)
) x
pivot
(
max(value)
for country_state in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both gives a result:
| COL | UNITEDSTATES_CALIFORNIA | UNITEDSTATES_FLORIDA | UNITEDSTATES_ILLINOIS | UNITEDSTATES_NEWYORK | UNITEDSTATES_TEXAS |
|--------------|-------------------------|----------------------|-----------------------|----------------------|--------------------|
| Population | 38332521 | 19552860 | 12882135 | 19651127 | 26448193 |
| SeatsInHouse | 53 | 27 | 18 | 27 | 36 |

Related

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

How do I group pivot results in SQL?

I have been able to combine results using the pivot function. Been trying various examples on my solution to group these results together. Am I doing this wrong or is it a simple fix?
create table DBE_LOCATION
(
REF int,
STATUS varchar(1)
);
insert into DBE_LOCATION values
(1, 'A'),
(2, 'A');
create table SYS_SCREEN_FIELD
(
REF int,
FIELD_DISPLAY varchar(20),
ORDER_BY int
);
insert into SYS_SCREEN_FIELD values
(1, 'Location Name', 0),
(2, 'Address', 1),
(3, 'Suburb', 2),
(4, 'Postcode', 3),
(5, 'State', 4),
(6, 'Country', 5);
create table DBE_LOCATION_DATA
(
REF int,
FIELD_REF int,
LOCATION_REF int,
VALUE_TEXT_FIELD varchar(MAX)
);
insert into DBE_LOCATION_DATA values
(1, 1, 1, 'New York'),
(2, 1, 2, 'Japan'),
(3, 2, 1, '123 Address St'),
(4, 2, 2, '456 Address St');
Now the final thing would be to show a result set of each Location with the field display as the column name. Something like this if using the above example:
Ref Location Name Address Status
1 New York 123 Address St A
2 Japan 456 Address Ave A
Have got the following working in gathering the data and creating the dynamic columns:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.FIELD_REF, d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute(#query)
Results are not grouped by REF. How is this done?
SQL Fiddle Link
The problem is with the addition of the column FIELD_REF in your subquery. Even though you are not including this column in your final select list, since it is in your subquery the column is used during the grouping of the PIVOT.
You can see the issue if you include it in your final select, you get a result:
| REF | FIELD_REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|-----------|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | 1 | Adelaide | (null) | (null) | (null) | (null) | (null) | S |
| 1 | 2 | (null) | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
The FIELD_REF has a value of 1 and 2 for REF=1, when the aggregate and group by are applied, you will return multiple rows.
If you remove this column from your subquery you will get the result that you want:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute sp_executesql #query;
See SQL Fiddle with Demo. Now your final result is:
| REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | Adelaide | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
| 2 | Melbourne | 2 Melbourne St | (null) | (null) | (null) | (null) | S |

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;

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 |

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