How to convert data in one row into one column in SQL Server [duplicate] - sql

How do I simply switch columns with rows in SQL?
Is there any simple command to transpose?
ie turn this result:
Paul | John | Tim | Eric
Red 1 5 1 3
Green 8 4 3 5
Blue 2 2 9 1
into this:
Red | Green | Blue
Paul 1 8 2
John 5 4 2
Tim 1 3 9
Eric 3 5 1
PIVOT seems too complex for this scenario.

There are several ways that you can transform this data. In your original post, you stated that PIVOT seems too complex for this scenario, but it can be applied very easily using both the UNPIVOT and PIVOT functions in SQL Server.
However, if you do not have access to those functions this can be replicated using UNION ALL to UNPIVOT and then an aggregate function with a CASE statement to PIVOT:
Create Table:
CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);
INSERT INTO yourTable
([color], [Paul], [John], [Tim], [Eric])
VALUES
('Red', 1, 5, 1, 3),
('Green', 8, 4, 3, 5),
('Blue', 2, 2, 9, 1);
Union All, Aggregate and CASE Version:
select name,
sum(case when color = 'Red' then value else 0 end) Red,
sum(case when color = 'Green' then value else 0 end) Green,
sum(case when color = 'Blue' then value else 0 end) Blue
from
(
select color, Paul value, 'Paul' name
from yourTable
union all
select color, John value, 'John' name
from yourTable
union all
select color, Tim value, 'Tim' name
from yourTable
union all
select color, Eric value, 'Eric' name
from yourTable
) src
group by name
See SQL Fiddle with Demo
The UNION ALL performs the UNPIVOT of the data by transforming the columns Paul, John, Tim, Eric into separate rows. Then you apply the aggregate function sum() with the case statement to get the new columns for each color.
Unpivot and Pivot Static Version:
Both the UNPIVOT and PIVOT functions in SQL server make this transformation much easier. If you know all of the values that you want to transform, you can hard-code them into a static version to get the result:
select name, [Red], [Green], [Blue]
from
(
select color, name, value
from yourtable
unpivot
(
value for name in (Paul, John, Tim, Eric)
) unpiv
) src
pivot
(
sum(value)
for color in ([Red], [Green], [Blue])
) piv
See SQL Fiddle with Demo
The inner query with the UNPIVOT performs the same function as the UNION ALL. It takes the list of columns and turns it into rows, the PIVOT then performs the final transformation into columns.
Dynamic Pivot Version:
If you have an unknown number of columns (Paul, John, Tim, Eric in your example) and then an unknown number of colors to transform you can use dynamic sql to generate the list to UNPIVOT and then PIVOT:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot 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 <> 'color'
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(color)
from yourtable t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select name, '+#colsPivot+'
from
(
select color, name, value
from yourtable
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for color in ('+#colsPivot+')
) piv'
exec(#query)
See SQL Fiddle with Demo
The dynamic version queries both yourtable and then the sys.columns table to generate the list of items to UNPIVOT and PIVOT. This is then added to a query string to be executed. The plus of the dynamic version is if you have a changing list of colors and/or names this will generate the list at run-time.
All three queries will produce the same result:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |

This normally requires you to know ALL the column AND row labels beforehand. As you can see in the query below, the labels are all listed in their entirely in both the UNPIVOT and the (re)PIVOT operations.
MS SQL Server 2012 Schema Setup:
create table tbl (
color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select
'Red' ,1 ,5 ,1 ,3 union all select
'Green' ,8 ,4 ,3 ,5 union all select
'Blue' ,2 ,2 ,9 ,1;
Query 1:
select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p
Results:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
Additional Notes:
Given a table name, you can determine all the column names from sys.columns or FOR XML trickery using local-name().
You can also build up the list of distinct colors (or values for one column) using FOR XML.
The above can be combined into a dynamic sql batch to handle any table.

I'd like to point out few more solutions to transposing columns and rows in SQL.
The first one is - using CURSOR. Although the general consensus in the professional community is to stay away from SQL Server Cursors, there are still instances whereby the use of cursors is recommended. Anyway, Cursors present us with another option to transpose rows into columns.
Vertical expansion
Similar to the PIVOT, the cursor has the dynamic capability to append more rows as your dataset expands to include more policy numbers.
Horizontal expansion
Unlike the PIVOT, the cursor excels in this area as it is able to expand to include newly added document, without altering the script.
Performance breakdown
The major limitation of transposing rows into columns using CURSOR is a disadvantage that is linked to using cursors in general – they come at significant performance cost. This is because the Cursor generates a separate query for each FETCH NEXT operation.
Another solution of transposing rows into columns is by using XML.
The XML solution to transposing rows into columns is basically an optimal version of the PIVOT in that it addresses the dynamic column limitation.
The XML version of the script addresses this limitation by using a combination of XML Path, dynamic T-SQL and some built-in functions (i.e. STUFF, QUOTENAME).
Vertical expansion
Similar to the PIVOT and the Cursor, newly added policies are able to be retrieved in the XML version of the script without altering the original script.
Horizontal expansion
Unlike the PIVOT, newly added documents can be displayed without altering the script.
Performance breakdown
In terms of IO, the statistics of the XML version of the script is almost similar to the PIVOT – the only difference is that the XML has a second scan of dtTranspose table but this time from a logical read – data cache.
You can find some more about these solutions (including some actual T-SQL exmaples) in this article:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

Based on this solution from bluefeet here is a stored procedure that uses dynamic sql to generate the transposed table. It requires that all the fields are numeric except for the transposed column (the column that will be the header in the resulting table):
/****** Object: StoredProcedure [dbo].[SQLTranspose] Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for transposing.
-- Parameters: #TableName - Table to transpose
-- #FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose]
-- Add the parameters for the stored procedure here
#TableName NVarchar(MAX) = '',
#FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#queryPivot AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#columnToPivot as NVARCHAR(MAX),
#tableToPivot as NVARCHAR(MAX),
#colsResult as xml
select #tableToPivot = #TableName;
select #columnToPivot = #FieldNameTranspose
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot
for xml path('')), 1, 1, '')
set #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename('+#columnToPivot+')
from '+#tableToPivot+' t
where '+#columnToPivot+' <> ''''
FOR XML PATH(''''), TYPE)'
exec sp_executesql #queryPivot, N'#colsResult xml out', #colsResult out
select #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query
= 'select name, rowid, '+#colsPivot+'
from
(
select '+#columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+#columnToPivot+' order by '+#columnToPivot+') as rowid
from '+#tableToPivot+'
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for '+#columnToPivot+' in ('+#colsPivot+')
) piv
order by rowid'
exec(#query)
END
You can test it with the table provided with this command:
exec SQLTranspose 'yourTable', 'color'

I'm doing UnPivot first and storing the results in CTE and using the CTE in Pivot operation.
Demo
with cte as
(
select 'Paul' as Name, color, Paul as Value
from yourTable
union all
select 'John' as Name, color, John as Value
from yourTable
union all
select 'Tim' as Name, color, Tim as Value
from yourTable
union all
select 'Eric' as Name, color, Eric as Value
from yourTable
)
select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot
(
max(Value)
for color IN ([Red], [Green], [Blue])
) as Dtpivot;

Adding to #Paco Zarate's terrific answer above, if you want to transpose a table which has multiple types of columns, then add this to the end of line 39, so it only transposes int columns:
and C.system_type_id = 56 --56 = type int
Here is the full query that is being changed:
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot and C.system_type_id = 56 --56 = type int
for xml path('')), 1, 1, '')
To find other system_type_id's, run this:
select name, system_type_id from sys.types order by name

This way Convert all Data From Filelds(Columns) In Table To Record (Row).
Declare #TableName [nvarchar](128)
Declare #ExecStr nvarchar(max)
Declare #Where nvarchar(max)
Set #TableName = 'myTableName'
--Enter Filtering If Exists
Set #Where = ''
--Set #ExecStr = N'Select * From '+quotename(#TableName)+#Where
--Exec(#ExecStr)
Drop Table If Exists #tmp_Col2Row
Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)
Set #ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select #ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(#TableName)+#Where+Char(10)+' Union All '
from sys.columns as C
where (C.object_id = object_id(#TableName))
for xml path(''))
Select #ExecStr = Left(#ExecStr,Len(#ExecStr)-Len(' Union All '))
--Print #ExecStr
Exec (#ExecStr)
Select * From #tmp_Col2Row
Go

I like to share the code i'm using to transpose a splited text based on +bluefeet answer. In this aproach i'm implemented as a procedure in MS SQL 2005
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit #InputToSplit VARCHAR(8000)
,#Delimeter VARCHAR(8000) = ','
AS
BEGIN
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX)
,#query AS NVARCHAR(MAX)
,#queryPivot AS NVARCHAR(MAX)
,#colsPivot AS NVARCHAR(MAX)
,#columnToPivot AS NVARCHAR(MAX)
,#tableToPivot AS NVARCHAR(MAX)
,#colsResult AS XML
SELECT #tableToPivot = '#tempSplitedTable'
SELECT #columnToPivot = 'col_number'
CREATE TABLE #tempSplitedTable (
col_number INT
,col_value VARCHAR(8000)
)
INSERT INTO #tempSplitedTable (
col_number
,col_value
)
SELECT ROW_NUMBER() OVER (
ORDER BY (
SELECT 100
)
) AS RowNumber
,item
FROM [DB].[ESCHEME].[fnSplit](#InputToSplit, #Delimeter)
SELECT #colsUnpivot = STUFF((
SELECT ',' + quotename(C.NAME)
FROM [tempdb].sys.columns AS C
WHERE C.object_id = object_id('tempdb..' + #tableToPivot)
AND C.NAME <> #columnToPivot
FOR XML path('')
), 1, 1, '')
SET #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename(' + #columnToPivot + ')
from ' + #tableToPivot + ' t
where ' + #columnToPivot + ' <> ''''
FOR XML PATH(''''), TYPE)'
EXEC sp_executesql #queryPivot
,N'#colsResult xml out'
,#colsResult OUT
SELECT #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'select name, rowid, ' + #colsPivot + '
from
(
select ' + #columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + #columnToPivot + ' order by ' + #columnToPivot + ') as rowid
from ' + #tableToPivot + '
unpivot
(
value for name in (' + #colsUnpivot + ')
) unpiv
) src
pivot
(
MAX(value)
for ' + #columnToPivot + ' in (' + #colsPivot + ')
) piv
order by rowid'
EXEC (#query)
DROP TABLE #tempSplitedTable
END
GO
I'm mixing this solution with the information about howto order rows without order by (SQLAuthority.com) and the split function on MSDN (social.msdn.microsoft.com)
When you execute the prodecure
DECLARE #RC int
DECLARE #InputToSplit varchar(MAX)
DECLARE #Delimeter varchar(1)
set #InputToSplit = 'hello|beautiful|world'
set #Delimeter = '|'
EXECUTE #RC = [TransposeSplit]
#InputToSplit
,#Delimeter
GO
you obtaint the next result
name rowid 1 2 3
col_value 1 hello beautiful world

I was able to use Paco Zarate's solution and it works beautifully. I did have to add one line ("SET ANSI_WARNINGS ON"), but that may be something unique to the way I used it or called it. There is a problem with my usage and I hope someone can help me with it:
The solution works only with an actual SQL table. I tried it with a temporary table and also an in-memory (declared) table but it doesn't work with those. So in my calling code I create a table on my SQL database and then call SQLTranspose. Again, it works great. It's just what I want. Here's my problem:
In order for the overall solution to be truly dynamic I need to create that table where I temporarily store the prepared information that I'm sending to SQLTranspose "on the fly", and then delete that table once SQLTranspose is called. The table deletion is presenting a problem with my ultimate implementation plan. The code needs to run from an end-user application (a button on a Microsoft Access form/menu). When I use this SQL process (create a SQL table, call SQLTranspose, delete SQL table) the end user application hits an error because the SQL account used does not have the rights to drop a table.
So I figure there are a few possible solutions:
Find a way to make SQLTranspose work with a temporary table or a declared table variable.
Figure out another method for the transposition of rows and columns that doesn't require an actual SQL table.
Figure out an appropriate method of allowing the SQL account used by my end users to drop a table. It's a single shared SQL account coded into my Access application. It appears that permission is a dbo-type privilege that cannot be granted.
I recognize that some of this may warrant another, separate thread and question. However, since there is a possibility that one solution may be simply a different way to do the transposing of rows and columns I'll make my first post here in this thread.
EDIT: I also did replace sum(value) with max(value) in the 6th line from the end, as Paco suggested.
EDIT:
I figured out something that works for me. I don't know if it's the best answer or not.
I have a read-only user account that is used to execute strored procedures and therefore generate reporting output from a database. Since the SQLTranspose function I created will only work with a "legitimate" table (not a declared table and not a temporary table) I had to figure out a way for a read-only user account to create (and then later delete) a table.
I reasoned that for my purposes it's okay for the user account to be allowed to create a table. The user still could not delete the table though. My solution was to create a schema where the user account is authorized. Then whenever I create, use, or delete that table refer it with the schema specified.
I first issued this command from a 'sa' or 'sysadmin' account:
CREATE SCHEMA ro AUTHORIZATION
When any time I refer to my "tmpoutput" table I specify it like this example:
drop table ro.tmpoutput

Related

Converting 200+ rows to column in SQL Server using PIVOT

I have done some research and so far, this one makes sense to me:
Convert row value in to column in SQL server (PIVOT)
However, I am wondering if there is any way to do this without having to declare the needed columns one by one, as I have more than 200 columns to retrieve.
Here's a sneak peek of what I have as a table:
ID | Col_Name | Col_Value
------------------------------
1 | Item_Num | 12345
2 | Item_Date | 34567
3 | Item_Name | 97454
4 | Item_App1 | 234567
5 | Item_App2 | 345678
Take note that I have 200+ distinct values for Col_Name and I want it to be Column fields.
I am using this one as of now but only for 5 columns:
SELECT * FROM
(
SELECT [ID], [Col_Name], [Col_Value]
FROM myTable
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in ([Item_Num], [Item_Date], [Item_Name], [Item_App1], [Item_App2])
) AS p
ORDER BY [ID]
Is there any way this could be done considering the performance? Thanks in advance.
You can use dynamic sql to create the list of col_name:
declare #pivot_col varchar(max);
declare #sql varchar(max);
select #pivot_col = string_agg( cast(col_name as varchar(max)), ', ') within group ( order by col_name ) from ( select distinct col_name from tmp_table ) A;
set #sql = 'SELECT *
FROM (
SELECT [ID], [Col_Name], [Col_Value]
FROM tmp_table
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in (' + #pivot_col + ' )
) AS p
ORDER BY [ID]';
exec ( #sql );
The PIVOT / UNPIVOT operators are built on the principles of an Entity Attribute Value model (EAV). The idea behind an EAV model is that you can extend database entities without performing database schema changes. For that reason an EAV model stores all attributes of an entity in one table as key/value pairs.
If that is the idea behind your design then use the dynamic sql query i posted above, otherwise use a regular record with 200+ columns for each id, as suggested by Gordon.
You can read about the performance of PIVOT / UNPIVOT operators here.
EDIT:
For sql server 2016 version:
declare #pivot_col varchar(max);
declare #sql varchar(max);
select #pivot_col = STUFF( (SELECT ',' + CAST(col_name AS VARCHAR(max)) AS [text()] FROM ( select distinct col_name from tmp_table ) A ORDER BY col_name FOR XML PATH('')), 1, 1, NULL);
set #sql = 'SELECT *
FROM ( SELECT [ID], [Col_Name], [Col_Value]
FROM tmp_table
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in (' + #pivot_col + ' )
) AS p
ORDER BY [ID]';
exec ( #sql );

SQL dynamic unpivot table as function

I currently have a query which I am using to unpivot an existing table.
Some background information on the table - each year a new column is added to the table to indicate the $ values for a project ID for that year. With every column added one will be dropped. All these columns are prefixed with 'YR_' followed by the new year. There are constantly 20 'YR_' columns.
I am required to unpivot the 'YR_' columns so that they appear as per below, allowing me to utilize the information easier for several reports -
Before unpivot -
ProjectID YR_16 YR_17 YR_18 YR_19 YR_20
10 0 100 20 25 100
After unpivot -
ProjectID YR Value
10 YR_16 0
10 YR_17 100
10 YR_18 20
10 YR_19 25
10 YR_20 100
Below is the query I am using to create the unpivot table, which will dynamically pick up columns as they are added/dropped
declare #query as NVARCHAR(max);
Declare #cols as NVARCHAR(250) = STUFF((
Select distinct ',' + QUOTENAME(Column_Name)
From INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'F1BWK_PLM_CAPEX'
And COLUMN_NAME like 'YR_%'
For XML Path(''), type)
.value('.','NVARCHAR(MAX)')
,1,1,'');
Select #query = 'With Unpivoted as
(
Select * from F1BWK_PLM_CAPEX U
Unpivot (
Val
For Yr in (' + #cols + ')
)
As UnpivotTable
)
Select U.*
From Unpivoted U
inner join [dbo].[F1_SYPAR_CTL] CTL
on CTL.value = U.WS_VERS
and CTL.PARAM_NAME like ''MBRC_CURR_PLM_BUDVER''
inner join [dbo].[F1_SYPAR_CTL] CTL2
on CTL2.value = U.WS_NAME
and CTL2.PARAM_NAME like ''MBRC_CURR_PLM_BUDWSH''';
Exec(#query);
I am having issues in turning this query into a function so that I can call upon it and save it in SQL Server so that other members of my team can use it when they require it.
This is my first time using unpivot tables and creating functions. Open to suggestions on changing my dynamic unpivot query to best suits my needs.
Thanks in advance
In SQL Server you are not allowed to have dynamic SQL in functions.
You can create an SP that will return a record set generated using your code above.
UPDATE:
For the sake of completeness:
You can also call the above SP using (OPENQUERY) which would allow you to join to other tables without having to save to a temp table first but IMO it is an ugly way do this.
END UPDATE:
Another way is to create a VIEW that would un-pivot this table and create an SP that would regenerate this view after the process that adds a new column is completed e.g.
CREATE TABLE F1BWK_PLM_CAPEX( Val INT, Yr_17 INT, Yr_18 INT )
INSERT INTO F1BWK_PLM_CAPEX
SELECT 1, 10, 12
CREATE VIEW F1BWK_PLM_CAPEX_UNPIVOTED
AS
SELECT *
FROM F1BWK_PLM_CAPEX AS U
UNPIVOT (
Val2 FOR Yr IN (Yr_17, Yr_18)
)
AS UnpivotTable;
CREATE PROCEDURE dbo.Create_F1BWK_PLM_CAPEX_UNPIVOTED
AS
SET NOCOUNT ON
DECLARE #query as NVARCHAR(max);
DECLARE #cols as NVARCHAR(250) =
STUFF((
SELECT distinct ',' + QUOTENAME( Column_Name )
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'F1BWK_PLM_CAPEX'
AND COLUMN_NAME like 'YR_%'
For XML Path(''), type)
.value('.','NVARCHAR(MAX)' )
,1,1,'');
SELECT #query = '
ALTER VIEW F1BWK_PLM_CAPEX_UNPIVOTED
AS
SELECT *
FROM F1BWK_PLM_CAPEX AS U
UNPIVOT (
Val2 FOR Yr IN ( ' + #cols + ' )
)
AS UnpivotTable
';
EXEC(#query);
RETURN;
Now your query would become this:
SELECT *
FROM F1BWK_PLM_CAPEX_UNPIVOTED AS U
INNER JOIN [dbo].[F1_SYPAR_CTL] AS CTL
ON CTL.value = U.WS_VERS and CTL.PARAM_NAME = 'MBRC_CURR_PLM_BUDVER'
INNER JOIN [dbo].[F1_SYPAR_CTL] AS CTL2
ON CTL2.value = U.WS_NAME AND CTL2.PARAM_NAME = 'MBRC_CURR_PLM_BUDWSH'
At the end of the year a process runs that adds a new column:
ALTER TABLE F1BWK_PLM_CAPEX
ADD Yr_19 INT NOT NULL DEFAULT( 10 )
This process needs to run this SP to regenerate the view:
EXEC Create_F1BWK_PLM_CAPEX_UNPIVOTED
Notes:
I have replaced LIKE with = as your query matches on a full value. Only use LIKE when you need to specify wild cards.
I have also fixed a syntax error by changing Val to Val2 in the UNPIVOT query

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.

Splitting a string of unlimited length SQL

I have a data column with values like this:
Table1
ID|GROUPNAME |MEMBER
1|GRP1_ML_Unit1_Role1|GRP=User1,DC=com;GRP=User2,DC=com
2|GRP2_ML_Unit2_Role2|GRP=User3,DC=com;GRP=User4,DC=com;GRP=User5,DC=com
3|GRP3_ML_Unit3_Role3|GRP=User6,DC=com;GRP=User7,DC=com;GRP=User8,DC=com;GRP=User8,DC=com
Expected output
ID|GRP1 |GRP2|GRP3 |GRP4 |MEM1 |MEM2 |MEM3 |MEM4|MEM5|
1 |GRP1 |ML |Unit1|Role1|GRP=User1,DC=com|GRP=User2,DC=com| | |
2 |GRP2 |ML |Unit2|Role2|GRP=User3,DC=com|GRP=User4,DC=com|GRP=User5,DC=com| |
3 |GRP3 |ML |Unit3|Role3|GRP=User6,DC=com|GRP=User7,DC=com|GRP=User8,DC=com|GRP=User8,DC=com |
Thanks,
Ryl
The completed solution is below with the sample data you gave me.
First, create a temp table and fill it with data.
-- Drop the table
drop table #member;
go
-- Sample table
create table #member
(
member_id int not null,
group_name varchar(256),
member_data varchar(8000)
);
go
-- Sample data
insert into #member values
(1, 'GRP1_ML_Unit1_Role1', 'GRP=User1,DC=com;GRP=User2,DC=com'),
(2, 'GRP2_ML_Unit2_Role2', 'GRP=User3,DC=com;GRP=User4,DC=com;GRP=User5,DC=com'),
(3, 'GRP3_ML_Unit3_Role3', 'GRP=User6,DC=com;GRP=User7,DC=com;GRP=User8,DC=com;GRP=User8,DC=com');
go
-- Show the data
select * from #member;
go
Second, copy down one of the many string splitters out there. I ended up installing Jeff Moden's string spliter for 8K max strings.
The query is almost there. However, each column we want is a row. We need to dynamically pivot the table.
--
-- Almost there!
--
-- Data in columns, instead of rows
select m.member_id, m.group_name, s.Item as cols_data, 'MEM' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data,';') s
go
Last but not least, figure out the number of columns. Write dynamic TSQL to pivot our dat and get our result.
--
-- Write dynamic sql to solve
--
DECLARE
#cols AS nvarchar(MAX),
#query AS nvarchar(MAX);
-- Get a dynamic number of columns
SET #cols = STUFF(
(
SELECT distinct ',' + QUOTENAME(c.cols_name)
FROM
(
select m.member_id, m.group_name, s.Item as cols_data, 'MEM' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data,';') s
) as c
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'');
print #cols;
-- Make dynamic pivot query
set #query = 'SELECT member_id as ID1, group_name as GROUP1, ' + #cols + ' from
(
select m.member_id, m.group_name, s.Item as cols_data, ''MEM'' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data, '';'') s
) x
pivot
(
max(cols_data)
for cols_name in (' + #cols + ')
) p ';
execute(#query)
A screen shot of the results in the desired format.

Simple way to transpose columns and rows in SQL?

How do I simply switch columns with rows in SQL?
Is there any simple command to transpose?
ie turn this result:
Paul | John | Tim | Eric
Red 1 5 1 3
Green 8 4 3 5
Blue 2 2 9 1
into this:
Red | Green | Blue
Paul 1 8 2
John 5 4 2
Tim 1 3 9
Eric 3 5 1
PIVOT seems too complex for this scenario.
There are several ways that you can transform this data. In your original post, you stated that PIVOT seems too complex for this scenario, but it can be applied very easily using both the UNPIVOT and PIVOT functions in SQL Server.
However, if you do not have access to those functions this can be replicated using UNION ALL to UNPIVOT and then an aggregate function with a CASE statement to PIVOT:
Create Table:
CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);
INSERT INTO yourTable
([color], [Paul], [John], [Tim], [Eric])
VALUES
('Red', 1, 5, 1, 3),
('Green', 8, 4, 3, 5),
('Blue', 2, 2, 9, 1);
Union All, Aggregate and CASE Version:
select name,
sum(case when color = 'Red' then value else 0 end) Red,
sum(case when color = 'Green' then value else 0 end) Green,
sum(case when color = 'Blue' then value else 0 end) Blue
from
(
select color, Paul value, 'Paul' name
from yourTable
union all
select color, John value, 'John' name
from yourTable
union all
select color, Tim value, 'Tim' name
from yourTable
union all
select color, Eric value, 'Eric' name
from yourTable
) src
group by name
See SQL Fiddle with Demo
The UNION ALL performs the UNPIVOT of the data by transforming the columns Paul, John, Tim, Eric into separate rows. Then you apply the aggregate function sum() with the case statement to get the new columns for each color.
Unpivot and Pivot Static Version:
Both the UNPIVOT and PIVOT functions in SQL server make this transformation much easier. If you know all of the values that you want to transform, you can hard-code them into a static version to get the result:
select name, [Red], [Green], [Blue]
from
(
select color, name, value
from yourtable
unpivot
(
value for name in (Paul, John, Tim, Eric)
) unpiv
) src
pivot
(
sum(value)
for color in ([Red], [Green], [Blue])
) piv
See SQL Fiddle with Demo
The inner query with the UNPIVOT performs the same function as the UNION ALL. It takes the list of columns and turns it into rows, the PIVOT then performs the final transformation into columns.
Dynamic Pivot Version:
If you have an unknown number of columns (Paul, John, Tim, Eric in your example) and then an unknown number of colors to transform you can use dynamic sql to generate the list to UNPIVOT and then PIVOT:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot 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 <> 'color'
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(color)
from yourtable t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select name, '+#colsPivot+'
from
(
select color, name, value
from yourtable
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for color in ('+#colsPivot+')
) piv'
exec(#query)
See SQL Fiddle with Demo
The dynamic version queries both yourtable and then the sys.columns table to generate the list of items to UNPIVOT and PIVOT. This is then added to a query string to be executed. The plus of the dynamic version is if you have a changing list of colors and/or names this will generate the list at run-time.
All three queries will produce the same result:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
This normally requires you to know ALL the column AND row labels beforehand. As you can see in the query below, the labels are all listed in their entirely in both the UNPIVOT and the (re)PIVOT operations.
MS SQL Server 2012 Schema Setup:
create table tbl (
color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select
'Red' ,1 ,5 ,1 ,3 union all select
'Green' ,8 ,4 ,3 ,5 union all select
'Blue' ,2 ,2 ,9 ,1;
Query 1:
select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p
Results:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
Additional Notes:
Given a table name, you can determine all the column names from sys.columns or FOR XML trickery using local-name().
You can also build up the list of distinct colors (or values for one column) using FOR XML.
The above can be combined into a dynamic sql batch to handle any table.
I'd like to point out few more solutions to transposing columns and rows in SQL.
The first one is - using CURSOR. Although the general consensus in the professional community is to stay away from SQL Server Cursors, there are still instances whereby the use of cursors is recommended. Anyway, Cursors present us with another option to transpose rows into columns.
Vertical expansion
Similar to the PIVOT, the cursor has the dynamic capability to append more rows as your dataset expands to include more policy numbers.
Horizontal expansion
Unlike the PIVOT, the cursor excels in this area as it is able to expand to include newly added document, without altering the script.
Performance breakdown
The major limitation of transposing rows into columns using CURSOR is a disadvantage that is linked to using cursors in general – they come at significant performance cost. This is because the Cursor generates a separate query for each FETCH NEXT operation.
Another solution of transposing rows into columns is by using XML.
The XML solution to transposing rows into columns is basically an optimal version of the PIVOT in that it addresses the dynamic column limitation.
The XML version of the script addresses this limitation by using a combination of XML Path, dynamic T-SQL and some built-in functions (i.e. STUFF, QUOTENAME).
Vertical expansion
Similar to the PIVOT and the Cursor, newly added policies are able to be retrieved in the XML version of the script without altering the original script.
Horizontal expansion
Unlike the PIVOT, newly added documents can be displayed without altering the script.
Performance breakdown
In terms of IO, the statistics of the XML version of the script is almost similar to the PIVOT – the only difference is that the XML has a second scan of dtTranspose table but this time from a logical read – data cache.
You can find some more about these solutions (including some actual T-SQL exmaples) in this article:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
Based on this solution from bluefeet here is a stored procedure that uses dynamic sql to generate the transposed table. It requires that all the fields are numeric except for the transposed column (the column that will be the header in the resulting table):
/****** Object: StoredProcedure [dbo].[SQLTranspose] Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for transposing.
-- Parameters: #TableName - Table to transpose
-- #FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose]
-- Add the parameters for the stored procedure here
#TableName NVarchar(MAX) = '',
#FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#queryPivot AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#columnToPivot as NVARCHAR(MAX),
#tableToPivot as NVARCHAR(MAX),
#colsResult as xml
select #tableToPivot = #TableName;
select #columnToPivot = #FieldNameTranspose
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot
for xml path('')), 1, 1, '')
set #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename('+#columnToPivot+')
from '+#tableToPivot+' t
where '+#columnToPivot+' <> ''''
FOR XML PATH(''''), TYPE)'
exec sp_executesql #queryPivot, N'#colsResult xml out', #colsResult out
select #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query
= 'select name, rowid, '+#colsPivot+'
from
(
select '+#columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+#columnToPivot+' order by '+#columnToPivot+') as rowid
from '+#tableToPivot+'
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for '+#columnToPivot+' in ('+#colsPivot+')
) piv
order by rowid'
exec(#query)
END
You can test it with the table provided with this command:
exec SQLTranspose 'yourTable', 'color'
I'm doing UnPivot first and storing the results in CTE and using the CTE in Pivot operation.
Demo
with cte as
(
select 'Paul' as Name, color, Paul as Value
from yourTable
union all
select 'John' as Name, color, John as Value
from yourTable
union all
select 'Tim' as Name, color, Tim as Value
from yourTable
union all
select 'Eric' as Name, color, Eric as Value
from yourTable
)
select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot
(
max(Value)
for color IN ([Red], [Green], [Blue])
) as Dtpivot;
Adding to #Paco Zarate's terrific answer above, if you want to transpose a table which has multiple types of columns, then add this to the end of line 39, so it only transposes int columns:
and C.system_type_id = 56 --56 = type int
Here is the full query that is being changed:
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot and C.system_type_id = 56 --56 = type int
for xml path('')), 1, 1, '')
To find other system_type_id's, run this:
select name, system_type_id from sys.types order by name
This way Convert all Data From Filelds(Columns) In Table To Record (Row).
Declare #TableName [nvarchar](128)
Declare #ExecStr nvarchar(max)
Declare #Where nvarchar(max)
Set #TableName = 'myTableName'
--Enter Filtering If Exists
Set #Where = ''
--Set #ExecStr = N'Select * From '+quotename(#TableName)+#Where
--Exec(#ExecStr)
Drop Table If Exists #tmp_Col2Row
Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)
Set #ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select #ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(#TableName)+#Where+Char(10)+' Union All '
from sys.columns as C
where (C.object_id = object_id(#TableName))
for xml path(''))
Select #ExecStr = Left(#ExecStr,Len(#ExecStr)-Len(' Union All '))
--Print #ExecStr
Exec (#ExecStr)
Select * From #tmp_Col2Row
Go
I like to share the code i'm using to transpose a splited text based on +bluefeet answer. In this aproach i'm implemented as a procedure in MS SQL 2005
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit #InputToSplit VARCHAR(8000)
,#Delimeter VARCHAR(8000) = ','
AS
BEGIN
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX)
,#query AS NVARCHAR(MAX)
,#queryPivot AS NVARCHAR(MAX)
,#colsPivot AS NVARCHAR(MAX)
,#columnToPivot AS NVARCHAR(MAX)
,#tableToPivot AS NVARCHAR(MAX)
,#colsResult AS XML
SELECT #tableToPivot = '#tempSplitedTable'
SELECT #columnToPivot = 'col_number'
CREATE TABLE #tempSplitedTable (
col_number INT
,col_value VARCHAR(8000)
)
INSERT INTO #tempSplitedTable (
col_number
,col_value
)
SELECT ROW_NUMBER() OVER (
ORDER BY (
SELECT 100
)
) AS RowNumber
,item
FROM [DB].[ESCHEME].[fnSplit](#InputToSplit, #Delimeter)
SELECT #colsUnpivot = STUFF((
SELECT ',' + quotename(C.NAME)
FROM [tempdb].sys.columns AS C
WHERE C.object_id = object_id('tempdb..' + #tableToPivot)
AND C.NAME <> #columnToPivot
FOR XML path('')
), 1, 1, '')
SET #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename(' + #columnToPivot + ')
from ' + #tableToPivot + ' t
where ' + #columnToPivot + ' <> ''''
FOR XML PATH(''''), TYPE)'
EXEC sp_executesql #queryPivot
,N'#colsResult xml out'
,#colsResult OUT
SELECT #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'select name, rowid, ' + #colsPivot + '
from
(
select ' + #columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + #columnToPivot + ' order by ' + #columnToPivot + ') as rowid
from ' + #tableToPivot + '
unpivot
(
value for name in (' + #colsUnpivot + ')
) unpiv
) src
pivot
(
MAX(value)
for ' + #columnToPivot + ' in (' + #colsPivot + ')
) piv
order by rowid'
EXEC (#query)
DROP TABLE #tempSplitedTable
END
GO
I'm mixing this solution with the information about howto order rows without order by (SQLAuthority.com) and the split function on MSDN (social.msdn.microsoft.com)
When you execute the prodecure
DECLARE #RC int
DECLARE #InputToSplit varchar(MAX)
DECLARE #Delimeter varchar(1)
set #InputToSplit = 'hello|beautiful|world'
set #Delimeter = '|'
EXECUTE #RC = [TransposeSplit]
#InputToSplit
,#Delimeter
GO
you obtaint the next result
name rowid 1 2 3
col_value 1 hello beautiful world
I was able to use Paco Zarate's solution and it works beautifully. I did have to add one line ("SET ANSI_WARNINGS ON"), but that may be something unique to the way I used it or called it. There is a problem with my usage and I hope someone can help me with it:
The solution works only with an actual SQL table. I tried it with a temporary table and also an in-memory (declared) table but it doesn't work with those. So in my calling code I create a table on my SQL database and then call SQLTranspose. Again, it works great. It's just what I want. Here's my problem:
In order for the overall solution to be truly dynamic I need to create that table where I temporarily store the prepared information that I'm sending to SQLTranspose "on the fly", and then delete that table once SQLTranspose is called. The table deletion is presenting a problem with my ultimate implementation plan. The code needs to run from an end-user application (a button on a Microsoft Access form/menu). When I use this SQL process (create a SQL table, call SQLTranspose, delete SQL table) the end user application hits an error because the SQL account used does not have the rights to drop a table.
So I figure there are a few possible solutions:
Find a way to make SQLTranspose work with a temporary table or a declared table variable.
Figure out another method for the transposition of rows and columns that doesn't require an actual SQL table.
Figure out an appropriate method of allowing the SQL account used by my end users to drop a table. It's a single shared SQL account coded into my Access application. It appears that permission is a dbo-type privilege that cannot be granted.
I recognize that some of this may warrant another, separate thread and question. However, since there is a possibility that one solution may be simply a different way to do the transposing of rows and columns I'll make my first post here in this thread.
EDIT: I also did replace sum(value) with max(value) in the 6th line from the end, as Paco suggested.
EDIT:
I figured out something that works for me. I don't know if it's the best answer or not.
I have a read-only user account that is used to execute strored procedures and therefore generate reporting output from a database. Since the SQLTranspose function I created will only work with a "legitimate" table (not a declared table and not a temporary table) I had to figure out a way for a read-only user account to create (and then later delete) a table.
I reasoned that for my purposes it's okay for the user account to be allowed to create a table. The user still could not delete the table though. My solution was to create a schema where the user account is authorized. Then whenever I create, use, or delete that table refer it with the schema specified.
I first issued this command from a 'sa' or 'sysadmin' account:
CREATE SCHEMA ro AUTHORIZATION
When any time I refer to my "tmpoutput" table I specify it like this example:
drop table ro.tmpoutput