Cast column name to specific character during Pivot table creation - SQL - sql

I have a table test which has a column Label. It has Data which is longers than 50 characters in length.
When I create a pivot table from the 'test' table , it uses those long charactered data as column name.
My requirement is to user cast function to limit the column names to say 26 characters.
I use the below script , but it doesnt work as desired.
create table V_Test as
select * from
(select * from Test) x
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p
Thanks in advance for the help

Since the label names are hard-coded anyway, then using aliases for them should be fine.
On MS SQL Server it would look something like this.
Just using a variable table and a temporary table for the purpose of demonstration.
DECLARE #Test TABLE (PlanID int, Label varchar(50), Average int);
insert into #Test values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
IF OBJECT_ID('tempdb..#tmpTest') IS NOT NULL DROP TABLE #tmpTest;
select
PlanID,
S03_CreatePlansdadsada as CreatePlans,
S03_CreatePlan_T01_NavigateTosdsadsaded as T01_NavigateTo,
S03_CreatePlan_T03_abcdefgmanagementsdasda as T03_managements,
S03_CreatePlan_T16_SetStatusToOngoingasdasda as T16_SetStatusToOng,
S03_CreatePlan_T17_Ldsdssdadsadas as T17_Lsd
into #tmpTest
from #Test
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p;
select * from #tmpTest;
This would return the following results:
PlanID CreatePlans T01_NavigateTo T03_managements T16_SetStatusToOng T17_Lsd
1 70 90 110 130 150
The dynamic way is a bit more complicated, since the SQL needs to be constructed so it can be executed. (beware of code injection)
IF OBJECT_ID('tempdb..#tmpTestData') IS NOT NULL DROP TABLE #tmpTestData;
CREATE TABLE #tmpTestData (PlanID int, Label varchar(50), Average int);
insert into #tmpTestData values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
declare #Columns nvarchar(max);
declare #AliasedColumns nvarchar(max);
set #Columns = STUFF((SELECT ', ' + QUOTENAME(Label) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
set #AliasedColumns = STUFF((SELECT ', ' + QUOTENAME(Label) +' as '+ QUOTENAME(substring(Label,5,14)) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
declare #SQL nvarchar(max);
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
set #SQL = 'select PlanID, '+ #AliasedColumns + '
into ##tmpPivotTest
from #tmpTestData pivot (sum(Average) for Label in ('+ #Columns +')) p';
--select #SQL;
exec (#SQL);
select * from ##tmpPivotTest;
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
For an Oracle database it would look something like this:
CREATE TABLE V_Test AS
SELECT * FROM (select Label, Average from Test)
PIVOT (
SUM(Average) AS sum_average FOR (Label) IN (
'S03_CreatePlansdadsada' as CreatePlans,
'S03_CreatePlan_T01_NavigateTosdsadsaded' as T01_NavigateTo,
'S03_CreatePlan_T03_abcdefgmanagementsdasda' as T03_Managements,
'S03_CreatePlan_T16_SetStatusToOngoingasdasda' as T16_SetStatusToOng,
'S03_CreatePlan_T17_Ldsdssdadsadas' as T17_Lsd
)
) p;

Related

Execute create table query from column in SQL server

I have two select statements which are to create table and insert values into table. How to execute all the queries inside the column at one go? Below is my code and the output:
select n.*
into #norm
from specNormalization n
select n.*
into #raw
from rawdata n
select distinct 'CREATE TABLE raw' + c.desttablename + ' (' + STUFF(
(select ','+QUOTENAME( c.[destfieldname] ) + c.datatype
from #norm c
group by c.destfieldname, c.datatype
for xml path ('')),1,1,'') as createTableSQL
select distinct 'INSERT INTO raw' + c.desttablename +
select d.contents
from #raw d join #norm c on d.tablename = c.desttablename
as insertTableSQL
rawdata table
desttablename
destfieldname
datatype
rawtable
SbjNum
int
rawtable
Surveyor
nvarchar(20)
rawtable
Location
nvarchar(20)
rawtable2
SbjNum
int
rawtable2
Name
nvarchar(20)
rawtable2
Address
nvarchar(20)
specnomalization table
tablename
destfieldname
contents
rawtable
SbjNum
1
rawtable
Surveyor
Alex
rawtable
Location
Georgia
rawtable2
SbjNum
1
rawtable2
Name
Sandra
rawtable2
Address
Portland
createTableSQL
CREATE TABLE rawtable ([Sbjnum])int, ([Surveyor])nvarchar(200), ([Location])nvarchar(200)
CREATE TABLE rawtable2 ([Sbjnum])int, ([Name])nvarchar(200), ([Address])nvarchar(200)
insertTableSQL
INSERT INTO rawtable SELECT [Sbjnum], [Surveyor], [Location] from #raw
INSERT INTO rawtable2 SELECT [Sbjnum], [Name], [Address] from #raw
You can create the create query by using stuff. Since you would have multiple tables so you have to group by the query to return multiple table names and it's columns separately. So the result query you can set into a variable and can execute by using
EXEC sp_executesql
So you can easily create as much as tables based on your table data.
The following query will help you to create multiple tables. This you can optimize if needed, but this will give you the insight to do the same for inserting values to the tables respectively.
Declare #sql nvarchar(MAX) = ( SELECT ' CREATE TABLE '+ desttablename +' (' + STUFF((SELECT ', ' + sn.[destfieldname] + ' ' +datatype
FROM dbo.specNormalization As sn
ORDER BY sn.destfieldname
FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, '') + ' )'
FROM dbo.specNormalization as P2
GROUP BY P2.desttablename FOR XML PATH('') )
-- Uncomment to see the created query
-- SELECT #sql
-- To execute the created query. This will create the tables with column and datatype
EXEC sp_executesql #sql
Hope this helps. Happy coding :)
Too long for a comment. Try to generate SELECT .. INTO .. FROM to create and populate a target table. Kind of
SELECT cast([Sbjnum] as int) [Sbjnum], cast([Surveyor] as nvarchar(200)) [Surveyor], cast([Location] as nvarchar(200)) [Location]
INTO rawtable
FROM #raw
If the source types are the same as target, skip cast functions. Can't tell what is the query to generate it as no sample data are provided.

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

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.

convert vertical sql result into horizontal output

I have a table, #t with 16 rows:
id int
description varchar(60)
balance decimal(6,2)
I need the description & balance data, and "select description, balance from #t order by id" will do the job. But ideally, I could do with showing the results horizontally rather than vertically.
Now I know I can build a new table with 16 columns and populate the balance for each such column using much dynamic sql, but, I'm also sure that this can be done a good deal more easily using pivot or something like that - though I dont really understand how.
Can someone please enlighted me?
Thanks
John
Assuming you are using SQL Server you can implement the PIVOT function to convert the rows of data into columns. The basic syntax will be:
select *
from
(
select description, balance
from yourtable
) d
pivot
(
sum(balance)
for description in ([desc1], desc2]) -- replace this with the names of your descriptions
) piv;
Of course if you have an unknown number of description values, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(description)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select description, balance
from yourtable
) x
pivot
(
sum(balance)
for description in (' + #cols + ')
) p '
execute sp_executesql #query

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