Dynamic SQL Pivot by Column SQL Server - sql-server

Supossing we have n columns with m rows
table 1:
someName1 someName2 someName3 ... someNameN
----------------------------------------------
12.5 12.34 56.6 ... 33.2
1.2323 12.5 57.2 ... 123.1
2.789 45.2 766.1 ... 56.2
45.23 34.3 7.4 ... 33.4
52.1 4.3 89.8 ... 67.3
How to use dynamic SQL to do in general
Output (A table with n rows, with,autoincrement ID, column name of Table1 and Sum of column like):
ID Column Result
--------------------------------
1 someName1 SUM(someName1)=12.5+1.2323+2.789+45.23+52.1
2 someName2 SUM(someName2)=12.34+12.5+45.2+34.3+4.3
3 someName3 SUM(someName3)=56.6+57.2+766.1+7.4+89.8
... ...
... ...
N someNameN SUM(someName3)=33.2+123.1+56.2+33.4+67.3
Where SUM(columnName) is the value of summing all values of Table 1, How to do this for any size of any table, where n could be 50, in other words a table with 50 columns??

It might look like this:
declare #tableName nvarchar(128) = N'table_with_50_columns'
declare #columnLikeFilter nvarchar(128) = N'someName%'
declare #columns nvarchar(2000) = '';
declare #sumColumns nvarchar(2000) = '';
select #columns = #columns + COLUMN_NAME + ',',
#sumColumns = #sumColumns + 'sum(' + COLUMN_NAME + ') as ' + COLUMN_NAME + ','
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #tableName and COLUMN_NAME like #columnLikeFilter
order by ORDINAL_POSITION ;
set #columns = left(#columns, len(#columns) - 1) ;
set #sumColumns = left(#sumColumns, len(#sumColumns) - 1) ;
declare #sql nvarchar(4000) =
N';with cteColumnts (ORDINAL_POSITION, COLUMN_NAME) as
(
select ORDINAL_POSITION, COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = N'''+ #tableName + ''' and COLUMN_NAME like ''' + #columnLikeFilter + '''
),
cteValues (ColumnName, SumValue) as
(
SELECT ColumnName, SumValue
FROM
(SELECT ' + #sumColumns + '
FROM dbo.' + #tableName + ') p
UNPIVOT
(SumValue FOR ColumnName IN
(' + #columns + ')
)AS unpvt
)
select row_number() over(order by ORDINAL_POSITION) as ID, ColumnName, SumValue
from cteColumnts c inner join cteValues v on COLUMN_NAME = ColumnName
order by ORDINAL_POSITION'
--print #sql
exec sp_executesql #sql

Related

How to combine rows dynamically

I have a table with many columns (even number of columns). Now I need to combine the second column and third column, the forth and fifth column, sixth and seventh column....etc. How to achieve this?
I tried static one, but what about dynamic one. Assume that there are 100 or more columns.
create table tb11 ( [id] int,[A] varchar(20),[B] varchar(20),
[C] varchar(20),[D] varchar(20))
insert into tb11 values
(1,'a','b','c','d'),
(2,'e','f','g','h'),
(3,'i','j','k','l')
select * from tb11
/*
id A B C D
---- --- ---- --- ----
1 a b c d
2 e f g h
3 i j k l
*/
select id,
[A] + [B] as '1' ,
[C] + [D] as '2'
from tb11
/*output with 3 columns
id 1 2
---- ----- ------
1 ab cd
2 ef gh
3 ij kl
*/
Try this
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = (
SELECT '
' +
STUFF((
SELECT ', ' + c.name + ' + ' + c2.name + ' AS [' + c.name + c2.name +']'
FROM sys.columns c
INNER JOIN sys.columns c2 ON c2.object_id = c.object_id
AND c2.column_id = c.column_id + 1
WHERE c.[object_id] = o.[object_id]
AND c.column_id > 1
AND c.column_id % 2 = 0
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, 'SELECT id, ') + '
FROM [' + SCHEMA_NAME(o.[schema_id]) + '].[' + o.name + ']' -- select *
FROM sys.objects o
WHERE o.[type] = 'U'
AND o.is_ms_shipped = 0
AND [name] = 'tb11'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
PRINT #SQL
EXEC sys.sp_executesql #SQL
You may use schema of tables to get the list of columns and create dynamic columns name from them.
Similarly by using that schema only create you table script for new structure.
Try this:
GO
;with cte as (
select ORDINAL_POSITION as slno, COLUMN_NAME from information_schema.columns where table_name = 'tb11'
)
select * into #tab_col from cte
declare #max int
set #max = (select Count(*) -1 from #tab_col)
declare #loop int
set #loop = 0
create table newtab (
id int )
declare #columns nvarchar(max) = ''
declare #values nvarchar(max) = ''
while(#Loop <= (#Max/2))
Begin
declare #Col1 varchar(100)
declare #Col2 varchar(100)
set #Col1 = (select Column_name from #tab_col where slno = #Loop+2)
set #Col2 = (select Column_name from #tab_col where slno = #Loop+3)
declare #alter nvarchar(max)
set #alter = ' Alter table newtab add [' + cast(((#Loop/2)+1) as varchar(100)) + '] nvarchar(max) '
set #columns = #columns + ',[' + (select cast(((#Loop/2)+1) as varchar(100))) + ']'
set #values = #values + ',''' + #Col1 + #Col2 + ''''
exec sp_executesql #alter
set #loop = #loop + 2
End
set #values =( select substring( #values, 2, len(#values)))
select #values
set #columns =( select substring( #columns, 2, len(#columns)))
select #columns
declare #altertab nvarchar(max)
set #altertab = ' insert into newtab ( id, ' + #columns + ' ) values ( 1, ' + #values + ' )'
exec sp_executesql #altertab
drop table newtab
Drop table #tab_col
GO
This will not exactly give you your answer but you'll get some idea.

How to get distinct count of values of all the columns of a table based on where condition in sql server?

I have a table with records which has 100 columns, I need to get the count of distinct values of all the columns from this table based on some condition (where clause).
Below query is working fine, but I'm not able to use the where clause. So it's giving the result for all the records of the table. But I want it to be based on some condition lets say column file_id = 1;. My question is how to use where clause with the below query. Or if there is any other alternative way to solve this problem.
declare #SQL nvarchar(max)
set #SQL = ''
;with cols as (
select Table_Schema, Table_Name, Column_Name, Row_Number() over(partition by Table_Schema, Table_Name
order by ORDINAL_POSITION) as RowNum
from INFORMATION_SCHEMA.COLUMNS
)
select #SQL = #SQL + case when RowNum = 1 then '' else ' union all ' end
+ ' select ''' + Column_Name + ''' as Column_Name, count(distinct ' + quotename (Column_Name) + ' ) As DistinctCountValue,
count( '+ quotename (Column_Name) + ') as CountValue FROM ' + quotename (Table_Schema) + '.' + quotename (Table_Name)
from cols
where Table_Name = 'table_name' --print #SQL
execute (#SQL)
I am using the dynamic query because I need to reuse this query for other tables also.
First get the columns and use stuff to generate the select in this way:
SELECT COUNT(ColumnA) AS ColumnA, COUNT(ColumnB AS ColumnB), COUNT(ColumnC) AS ColumnC....
That way you only select on your table once to get all counts, After that, use CROSS APPLY to "unpivot" those columns and return the output on one row per column
CROSS APPLY(
VALUES(1, 'ColumnA', ColumnA), (2, 'ColumnB', ColumnB), (3, 'ColumnC', ColumnC)
)(ID, ColumnName, DistinctCountValue)
For the filter, use sp_executesql and send the file_id as parameter
exec SP_executesql #SQL, N'#FID INT', #FID = #FileID
Since you are using all columns of the table Row_Number() over(partition by Table_Schema, Table_Name order by ORDINAL_POSITION) as RowNum becomes redundant, ORDINAL_POSITION already has the value that you are looking for
declare #tablename nvarchar(50) = 'MyTestTable'
declare #fileID int = 1
declare #SQL nvarchar(max)
set #SQL = ''
;with cols as (
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName
)
select #SQL = ';WITH CTE AS (SELECT
' +
STUFF((
SELECT ', COUNT(DISTINCT ' + QUOTENAME(COLUMN_NAME) + ') AS ' + QUOTENAME(COLUMN_NAME)
FROM cols
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '')
+ '
FROM ' + #TableName + '
WHERE File_ID = #FID
)
SELECT B.*
FROM CTE
CROSS APPLY (
VALUES ' +STUFF((
SELECT ',( ' + CAST(ORDINAL_POSITION AS VARCHAR) + ',' + QUOTENAME(COLUMN_NAME,'''') + ',' + QUOTENAME(COLUMN_NAME) + ')'
FROM cols
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') + '
)B (ID,ColumnName,DistinctCountValue)
'
from cols
exec SP_executesql #SQL, N'#FID INT', #FID = #FileID
The query below creates a table of all the column names and uses a while loop to select the count for whatever WHERE clause you want to use. This should be pretty flexible for any table; just update the top variables. Note that this will not count a column where its value is null. You can add a case to the #Query parameter if that's what you want. Since it processes each row individually, I added in a temp table so you only hit the db once.
IF OBJECT_ID('tempdb..##SourceValues') IS NOT NULL
DROP TABLE ##SourceValues
DECLARE #Schema VARCHAR(50) = 'SomeSchema'
DECLARE #Table VARCHAR(50) = 'SomeTable'
DECLARE #WhereClause VARCHAR(MAX) = ' Some WHERE clause'
DECLARE #ColumnName VARCHAR(50)
DECLARE #ProcessedRows TABLE(ColumnName VARCHAR(50), DistinctCount INT)
DECLARE #Columns TABLE(RowNumber INT, ColumnName VARCHAR(100))
INSERT INTO #Columns SELECT ROW_NUMBER() OVER(ORDER BY COLUMN_NAME DESC), COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #Table
DECLARE #Count INT = (SELECT MAX(RowNumber) FROM #Columns)
DECLARE #Counter INT = 0
DECLARE #DistinctCount INT
DECLARE #Query NVARCHAR(MAX)
EXEC('SELECT * INTO ##SourceValues FROM ' + #Table +' (NOLOCK)')
WHILE #Counter < #Count
BEGIN
SET #Counter += 1
SET #ColumnName = (SELECT ColumnName FROM #Columns WHERE RowNumber = #Counter)
SET #Query = 'SELECT #OutPut = COUNT(' + #ColumnName + ') FROM ' + #Schema + '.' + ' ##SourceValues ' + #WhereClause
EXECUTE sp_executesql #Query, N'#Output INT OUT', #DistinctCount OUT
INSERT INTO #ProcessedRows(ColumnName, DistinctCount) VALUES (#ColumnName, #DistinctCount)
END
SELECT * FROM #ProcessedRows
Let's try some different approach.
Get all values unpivoted as Param/Value:
1) Collect list of tables and columns to be used in dynamic SQL:
DROP TABLE IF EXISTS #Base;
;WITH SchemaData AS (
SELECT t.name AS [TableName],c.name AS [ColumnName],c.column_id AS [ColumnOrderID]
FROM sys.tables t
INNER JOIN sys.columns c ON c.object_id = t.object_id
)
SELECT t.TableName
,STUFF((SELECT ',CONVERT(NVARCHAR(MAX),' + QUOTENAME([ColumnName]) + ') AS ' + QUOTENAME([ColumnName])
FROM SchemaData a WHERE (a.TableName = t.TableName) FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS [SelectClause]
,STUFF((SELECT ',' + QUOTENAME([ColumnName]) FROM SchemaData a WHERE (a.TableName = t.TableName) FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS [UnpivotClause]
INTO #Base
FROM SchemaData t
GROUP BY t.TableName
;
2) Get all data inside a temp table
DROP TABLE IF EXISTS #Result;
CREATE TABLE #Result(TableName NVARCHAR(255),ColumnName NVARCHAR(255),[Value] NVARCHAR(MAX));
DECLARE #TableName NVARCHAR(255),#SelectClause NVARCHAR(MAX),#UnpivotClause NVARCHAR(MAX);
DECLARE crPopulateResult CURSOR LOCAL FAST_FORWARD READ_ONLY FOR SELECT b.TableName,b.SelectClause,b.UnpivotClause FROM #Base b;
OPEN crPopulateResult;
FETCH NEXT FROM crPopulateResult INTO #TableName,#SelectClause,#UnpivotClause;
DECLARE #dSql NVARCHAR(MAX);
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #dSql = N' INSERT INTO #Result(TableName,[ColumnName],[Value])
SELECT up.TableName,up.Param AS [ColumnName],up.[Value]
FROM (
SELECT ''' + #TableName + N''' AS [TableName]
,' + #SelectClause + N'
FROM ' + QUOTENAME(#TableName) + N'
) a
UNPIVOT(Value FOR Param IN (' + #UnpivotClause + N')) up
';
EXEC sp_executesql #stmt = #dSql;
FETCH NEXT FROM crPopulateResult INTO #TableName,#SelectClause,#UnpivotClause;
END
CLOSE crPopulateResult;
DEALLOCATE crPopulateResult;
3) Any filters can be applied with #Results, including Table names, column names, data filters, etc:
SELECT r.TableName,r.ColumnName,COUNT(*) AS [CountValue],COUNT(DISTINCT r.[Value]) AS [DistinctCountValue]
FROM #Result r
--
--WHERE r.ColumnName = 'file_id' AND r.[Value] = '1'
--
GROUP BY r.TableName,r.ColumnName
ORDER BY r.TableName,r.ColumnName
;
To use this with a where clause with this query you just have to put the where clause in the construction after the table name so if you wanted to filter on file_id='1' then you would have:
FROM ' + quotename (Table_Schema) + '.' + quotename (Table_Name) +'where file_id =''1'' '
You can add a #where variable and concatenate that with your big union construction (as part of your select ... from cols). For example:
declare #SQL nvarchar(max)
declare #where nvarchar(max) = ' where file_id = 1'
set #SQL = ''
;with cols as (
select Table_Schema, Table_Name, Column_Name, Row_Number() over(partition by Table_Schema, Table_Name
order by ORDINAL_POSITION) as RowNum
from INFORMATION_SCHEMA.COLUMNS
)
select #SQL = #SQL + case when RowNum = 1 then '' else ' union all ' end
+ ' select ''' + Column_Name + ''' as Column_Name, count(distinct ' + quotename (Column_Name) + ' ) As DistinctCountValue,
count( '+ quotename (Column_Name) + ') as CountValue FROM ' + quotename (Table_Schema) + '.' + quotename (Table_Name)
+ #where
from cols
where Table_Name = 'table_name' --print #SQL
execute (#SQL)
Note that you'll need to escape single quotes in #where if you're searching for a string. For example, declare #where nvarchar(max) = ' where state = ''CT'''.

Can we select one less than all columns from table

Please help me:
I have 100 columns in a table & i want to select 99 columns but i don't want to write 99 columns. Is there any way.
Thanks in advance,
Nishant Khanna
In MSSQL:
declare #schema varchar(128)
declare #table varchar(128)
declare #exceptedColumn varchar(128)
declare #sql nvarchar(max)
set #schema = 'dbo'
set #table = 'MyTable'
set #exceptedColumn = 'DontWantThat'
set #sql = 'SELECT ' + (
select STUFF((select ',' + '[' + columns.name + ']' from sys.columns
join sys.tables on tables.object_id = columns.object_id
join sys.schemas on schemas.schema_id = tables.schema_id
where schemas.name = #schema and tables.name = #table and columns.name <> #exceptedColumn
for xml path('')), 1, 1, '')
) + ' FROM [' + #schema + '].[' + #table + ']'
exec sp_executesql #sql
Can you try this?
select * into [#my_table] from [my_table];
alter table [#my_table] drop column [my_column];
select * from [#my_table];
Replace the **TableName** with yours and Column name that you don't want in your query.
This will surely work
SELECT left(replace((
SELECT COLUMN_NAME + ','
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '**TableName**'
FOR XML path('')
), '**Column_Name_That_You_Don't want in your query**' + ',', ''), (
len(replace((
SELECT COLUMN_NAME + ','
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '**TableName**'
FOR XML path('')
), '**Column_Name_That_You_Don't want in your query**' + ',', ''))
) - 1)
Create a view with the 99 columns and use that in your code instead of the actual table.

Can you return the results a query from table and Fields returned in a query

The following is the results of a query used to Search Columns
DECLARE #Temp TABLE (
[CoreTable] VARCHAR(250),
[CoreTableDecription] VARCHAR(250),
[FieldName] VARCHAR(250),
[cnt] VARCHAR(250)
)
declare #Keyword1 VARCHAR(100) = '%Prob%'
declare #Keyword2 VARCHAR(100) = '%Prob%'
--------------------------------------------------------------
INSERT INTO #Temp (CoreTable, CoreTableDecription, FieldName, cnt)
VALUES
('PRO','PROTOS_PersonalInformation','AnyCommunicationProblem','1'),
('PRO','PROTOS_PersonalInformation','SightProblems','1'),
('PRO','PROTOS_PersonalInformation','SpeechProblems','1'),
('PRO','PROTOSMODEL_Antenatal_Status','Other_Antenatal_Problems','1'),
('PRO','PROTOSMODEL_Antenatal_Status','Other_Antenatal_Problems_Text','1'),
('PRO','PROTOSMODEL_Baby_Details','Neonatal_medical_problems','1'),
('PRO','PROTOSMODEL_Baby_Details','Neonatal_medical_problems_Text','1'),
('PRO','PROTOSMODEL_Baby_Postnatal','Any_skin_problems_detected','1'),
('PRO','PROTOSMODEL_Baby_Postnatal','Any_skin_problems_detected_Bruising','1'),
('PRO','PROTOSMODEL_Baby_Postnatal','Any_skin_problems_detected_Naevus','1'),
('PRO','PROTOSMODEL_Baby_Postnatal','Any_skin_problems_detected_Rash','1'),
('PRO','PROTOSMODEL_Baby_Postnatal','Any_skin_problems_detected_Text','1')
Select * from #Temp
This is great for finding/search and the query used is below
declare #Keyword1 VARCHAR(100) = '%Prob%'
declare #Keyword2 VARCHAR(100) = '%Prob%'
select
LEFT(o.name,3) CoreTable,
o.name CoreTableDesc,
--o.name AS TableName,
c.name AS FieldName
,COUNT(c.name) cnt
from sys.columns c
inner join sys.objects o on c.object_id=o.object_id
where c.name LIKE #Keyword1
and LEFT(o.name,3) in ('PRO')
and c.name LIKE #Keyword2
GROUP BY c.name, LEFT(o.name,3), o.name
-- order by LEFT(o.name,3), c.name
However what I then need to do is run a manual query to determine the values in the columns/tables (see below):
select AnyCommunicationProblem, count(*) cnt
from PROTOS_PersonalInformation
GROUP BY AnyCommunicationProblem
This returns simplythe rows NULL - 2682, No - 87687, Yes - 135478
I'm wondering if there is a way to do this automatically from the results of the first query which display the Table name and Column names
Thanks all the answer was below but I was unsure how to get the count if the data items in
DECLARE
#TABLENAME VARCHAR(255),
#FIELDNAME VARCHAR(255),
#SQL VARCHAR(MAX),
#FieldNamePart1 as varchar(50),
#FieldNamePart2 as varchar(50)
SET #SQL = ''
SET #FieldNamePart1 = 'Type'
SET #FieldNamePart2 = 'Method'
IF #FieldNamepart2 = ''
DECLARE CRS CURSOR FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CHARINDEX('PROTOSMODEL',TABLE_NAME) <> 0
AND CHARINDEX(#FieldNamePart1, COLUMN_NAME) <> 0
-- AND CHARINDEX('DATE', COLUMN_NAME) = 0
-- AND CHARINDEX('TIME', COLUMN_NAME) = 0
ELSE
DECLARE CRS CURSOR FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CHARINDEX('PROTOSMODEL',TABLE_NAME) <> 0
AND CHARINDEX(#FieldNamePart1, COLUMN_NAME) <> 0
AND CHARINDEX(#FieldNamePart2, COLUMN_NAME) <> 0
-- AND CHARINDEX('DATE', COLUMN_NAME) = 0
-- AND CHARINDEX('TIME', COLUMN_NAME) = 0
OPEN CRS
FETCH NEXT FROM CRS INTO #TABLENAME, #FIELDNAME
WHILE ##FETCH_STATUS = 0
BEGIN
-- SET #SQL = #SQL + CAST('SELECT DISTINCT ''' AS VARCHAR(MAX)) + CAST(#TABLENAME AS VARCHAR(MAX)) + CAST(''' AS PROTOSMODEL_TABLE, ''' AS VARCHAR(MAX)) + CAST(#FIELDNAME AS VARCHAR(MAX)) + CAST(''' AS PROTOSMODEL_FIELD, ' AS VARCHAR(MAX)) + CAST(#FIELDNAME AS VARCHAR(MAX)) + CAST(' AS FIELD_VALUE FROM ' AS VARCHAR(MAX)) + CAST(#TABLENAME AS VARCHAR(MAX)) + CAST(' UNION ALL ' AS VARCHAR(MAX))
SET #SQL = #SQL + 'SELECT DISTINCT ''' + #TABLENAME + ''' AS PROTOSMODEL_TABLE, ''' + #FIELDNAME + ''' AS PROTOSMODEL_FIELD, [' + #FIELDNAME + '] AS FIELD_VALUE FROM ' + #TABLENAME + ' UNION ALL '
FETCH NEXT FROM CRS INTO #TABLENAME, #FIELDNAME
END
CLOSE CRS
DEALLOCATE CRS
SET #SQL = SUBSTRING(#SQL, 1, LEN(#SQL) - 10)
EXEC (#SQL)
GO

Pivot in sql server returns error "incorrect syntax near the keyword 'for': The DUPLICATE has not the answer [duplicate]

I'm looking for an efficient way to convert rows to columns in SQL server, I heard that PIVOT is not very fast, and I need to deal with lot of records.
This is my example:
-------------------------------
| Id | Value | ColumnName |
-------------------------------
| 1 | John | FirstName |
| 2 | 2.4 | Amount |
| 3 | ZH1E4A | PostalCode |
| 4 | Fork | LastName |
| 5 | 857685 | AccountNumber |
-------------------------------
This is my result:
---------------------------------------------------------------------
| FirstName |Amount| PostalCode | LastName | AccountNumber |
---------------------------------------------------------------------
| John | 2.4 | ZH1E4A | Fork | 857685 |
---------------------------------------------------------------------
How can I build the result?
There are several ways that you can transform data from multiple rows into columns.
Using PIVOT
In SQL Server you can use the PIVOT function to transform the data from rows to columns:
select Firstname, Amount, PostalCode, LastName, AccountNumber
from
(
select value, columnname
from yourtable
) d
pivot
(
max(value)
for columnname in (Firstname, Amount, PostalCode, LastName, AccountNumber)
) piv;
See Demo.
Pivot with unknown number of columnnames
If you have an unknown number of columnnames that you want to transpose, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(ColumnName)
from yourtable
group by ColumnName, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select value, ColumnName
from yourtable
) x
pivot
(
max(value)
for ColumnName in (' + #cols + N')
) p '
exec sp_executesql #query;
See Demo.
Using an aggregate function
If you do not want to use the PIVOT function, then you can use an aggregate function with a CASE expression:
select
max(case when columnname = 'FirstName' then value end) Firstname,
max(case when columnname = 'Amount' then value end) Amount,
max(case when columnname = 'PostalCode' then value end) PostalCode,
max(case when columnname = 'LastName' then value end) LastName,
max(case when columnname = 'AccountNumber' then value end) AccountNumber
from yourtable
See Demo.
Using multiple joins
This could also be completed using multiple joins, but you will need some column to associate each of the rows which you do not have in your sample data. But the basic syntax would be:
select fn.value as FirstName,
a.value as Amount,
pc.value as PostalCode,
ln.value as LastName,
an.value as AccountNumber
from yourtable fn
left join yourtable a
on fn.somecol = a.somecol
and a.columnname = 'Amount'
left join yourtable pc
on fn.somecol = pc.somecol
and pc.columnname = 'PostalCode'
left join yourtable ln
on fn.somecol = ln.somecol
and ln.columnname = 'LastName'
left join yourtable an
on fn.somecol = an.somecol
and an.columnname = 'AccountNumber'
where fn.columnname = 'Firstname'
This is rather a method than just a single script but gives you much more flexibility.
First of all There are 3 objects:
User defined TABLE type [ColumnActionList] -> holds data as
parameter
SP [proc_PivotPrepare] -> prepares our data
SP [proc_PivotExecute] -> execute the script
CREATE TYPE [dbo].[ColumnActionList] AS TABLE
(
[ID] [smallint] NOT NULL,
[ColumnName] nvarchar NOT NULL,
[Action] nchar NOT NULL
);
GO
CREATE PROCEDURE [dbo].[proc_PivotPrepare]
(
#DB_Name nvarchar(128),
#TableName nvarchar(128)
)
AS
SELECT #DB_Name = ISNULL(#DB_Name,db_name())
DECLARE #SQL_Code nvarchar(max)
DECLARE #MyTab TABLE (ID smallint identity(1,1), [Column_Name] nvarchar(128), [Type] nchar(1), [Set Action SQL] nvarchar(max));
SELECT #SQL_Code = 'SELECT [<| SQL_Code |>] = '' '' '
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''-----| Declare user defined type [ID] / [ColumnName] / [PivotAction] '' '
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''DECLARE #ColumnListWithActions ColumnActionList;'''
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''-----| Set [PivotAction] (''''S'''' as default) to select dimentions and values '' '
+ 'UNION ALL '
+ 'SELECT ''-----|'''
+ 'UNION ALL '
+ 'SELECT ''-----| ''''S'''' = Stable column || ''''D'''' = Dimention column || ''''V'''' = Value column '' '
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''INSERT INTO #ColumnListWithActions VALUES ('' + CAST( ROW_NUMBER() OVER (ORDER BY [NAME]) as nvarchar(10)) + '', '' + '''''''' + [NAME] + ''''''''+ '', ''''S'''');'''
+ 'FROM [' + #DB_Name + '].sys.columns '
+ 'WHERE object_id = object_id(''[' + #DB_Name + ']..[' + #TableName + ']'') '
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''-----| Execute sp_PivotExecute with parameters: columns and dimentions and main table name'' '
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
+ 'UNION ALL '
+ 'SELECT ''EXEC [dbo].[sp_PivotExecute] #ColumnListWithActions, ' + '''''' + #TableName + '''''' + ';'''
+ 'UNION ALL '
+ 'SELECT ''----------------------------------------------------------------------------------------------------'' '
EXECUTE SP_EXECUTESQL #SQL_Code;
GO
CREATE PROCEDURE [dbo].[sp_PivotExecute]
(
#ColumnListWithActions ColumnActionList ReadOnly
,#TableName nvarchar(128)
)
AS
--#######################################################################################################################
--###| Step 1 - Select our user-defined-table-variable into temp table
--#######################################################################################################################
IF OBJECT_ID('tempdb.dbo.#ColumnListWithActions', 'U') IS NOT NULL DROP TABLE #ColumnListWithActions;
SELECT * INTO #ColumnListWithActions FROM #ColumnListWithActions;
--#######################################################################################################################
--###| Step 2 - Preparing lists of column groups as strings:
--#######################################################################################################################
DECLARE #ColumnName nvarchar(128)
DECLARE #Destiny nchar(1)
DECLARE #ListOfColumns_Stable nvarchar(max)
DECLARE #ListOfColumns_Dimension nvarchar(max)
DECLARE #ListOfColumns_Variable nvarchar(max)
--############################
--###| Cursor for List of Stable Columns
--############################
DECLARE ColumnListStringCreator_S CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'S'
OPEN ColumnListStringCreator_S;
FETCH NEXT FROM ColumnListStringCreator_S
INTO #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #ListOfColumns_Stable = ISNULL(#ListOfColumns_Stable, '') + ' [' + #ColumnName + '] ,';
FETCH NEXT FROM ColumnListStringCreator_S INTO #ColumnName
END
CLOSE ColumnListStringCreator_S;
DEALLOCATE ColumnListStringCreator_S;
--############################
--###| Cursor for List of Dimension Columns
--############################
DECLARE ColumnListStringCreator_D CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'D'
OPEN ColumnListStringCreator_D;
FETCH NEXT FROM ColumnListStringCreator_D
INTO #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #ListOfColumns_Dimension = ISNULL(#ListOfColumns_Dimension, '') + ' [' + #ColumnName + '] ,';
FETCH NEXT FROM ColumnListStringCreator_D INTO #ColumnName
END
CLOSE ColumnListStringCreator_D;
DEALLOCATE ColumnListStringCreator_D;
--############################
--###| Cursor for List of Variable Columns
--############################
DECLARE ColumnListStringCreator_V CURSOR FOR
SELECT [ColumnName]
FROM #ColumnListWithActions
WHERE [Action] = 'V'
OPEN ColumnListStringCreator_V;
FETCH NEXT FROM ColumnListStringCreator_V
INTO #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #ListOfColumns_Variable = ISNULL(#ListOfColumns_Variable, '') + ' [' + #ColumnName + '] ,';
FETCH NEXT FROM ColumnListStringCreator_V INTO #ColumnName
END
CLOSE ColumnListStringCreator_V;
DEALLOCATE ColumnListStringCreator_V;
SELECT #ListOfColumns_Variable = LEFT(#ListOfColumns_Variable, LEN(#ListOfColumns_Variable) - 1);
SELECT #ListOfColumns_Dimension = LEFT(#ListOfColumns_Dimension, LEN(#ListOfColumns_Dimension) - 1);
SELECT #ListOfColumns_Stable = LEFT(#ListOfColumns_Stable, LEN(#ListOfColumns_Stable) - 1);
--#######################################################################################################################
--###| Step 3 - Preparing table with all possible connections between Dimension columns excluding NULLs
--#######################################################################################################################
DECLARE #DIM_TAB TABLE ([DIM_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO #DIM_TAB
SELECT [DIM_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName] FROM #ColumnListWithActions WHERE [Action] = 'D';
DECLARE #DIM_ID smallint;
SELECT #DIM_ID = 1;
DECLARE #SQL_Dimentions nvarchar(max);
IF OBJECT_ID('tempdb.dbo.##ALL_Dimentions', 'U') IS NOT NULL DROP TABLE ##ALL_Dimentions;
SELECT #SQL_Dimentions = 'SELECT [xxx_ID_xxx] = ROW_NUMBER() OVER (ORDER BY ' + #ListOfColumns_Dimension + '), ' + #ListOfColumns_Dimension
+ ' INTO ##ALL_Dimentions '
+ ' FROM (SELECT DISTINCT' + #ListOfColumns_Dimension + ' FROM ' + #TableName
+ ' WHERE ' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = #DIM_ID) + ' IS NOT NULL ';
SELECT #DIM_ID = #DIM_ID + 1;
WHILE #DIM_ID <= (SELECT MAX([DIM_ID]) FROM #DIM_TAB)
BEGIN
SELECT #SQL_Dimentions = #SQL_Dimentions + 'AND ' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = #DIM_ID) + ' IS NOT NULL ';
SELECT #DIM_ID = #DIM_ID + 1;
END
SELECT #SQL_Dimentions = #SQL_Dimentions + ' )x';
EXECUTE SP_EXECUTESQL #SQL_Dimentions;
--#######################################################################################################################
--###| Step 4 - Preparing table with all possible connections between Stable columns excluding NULLs
--#######################################################################################################################
DECLARE #StabPos_TAB TABLE ([StabPos_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO #StabPos_TAB
SELECT [StabPos_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName] FROM #ColumnListWithActions WHERE [Action] = 'S';
DECLARE #StabPos_ID smallint;
SELECT #StabPos_ID = 1;
DECLARE #SQL_MainStableColumnTable nvarchar(max);
IF OBJECT_ID('tempdb.dbo.##ALL_StableColumns', 'U') IS NOT NULL DROP TABLE ##ALL_StableColumns;
SELECT #SQL_MainStableColumnTable = 'SELECT xxx_ID_xxx = ROW_NUMBER() OVER (ORDER BY ' + #ListOfColumns_Stable + '), ' + #ListOfColumns_Stable
+ ' INTO ##ALL_StableColumns '
+ ' FROM (SELECT DISTINCT' + #ListOfColumns_Stable + ' FROM ' + #TableName
+ ' WHERE ' + (SELECT [ColumnName] FROM #StabPos_TAB WHERE [StabPos_ID] = #StabPos_ID) + ' IS NOT NULL ';
SELECT #StabPos_ID = #StabPos_ID + 1;
WHILE #StabPos_ID <= (SELECT MAX([StabPos_ID]) FROM #StabPos_TAB)
BEGIN
SELECT #SQL_MainStableColumnTable = #SQL_MainStableColumnTable + 'AND ' + (SELECT [ColumnName] FROM #StabPos_TAB WHERE [StabPos_ID] = #StabPos_ID) + ' IS NOT NULL ';
SELECT #StabPos_ID = #StabPos_ID + 1;
END
SELECT #SQL_MainStableColumnTable = #SQL_MainStableColumnTable + ' )x';
EXECUTE SP_EXECUTESQL #SQL_MainStableColumnTable;
--#######################################################################################################################
--###| Step 5 - Preparing table with all options ID
--#######################################################################################################################
DECLARE #FULL_SQL_1 NVARCHAR(MAX)
SELECT #FULL_SQL_1 = ''
DECLARE #i smallint
IF OBJECT_ID('tempdb.dbo.##FinalTab', 'U') IS NOT NULL DROP TABLE ##FinalTab;
SELECT #FULL_SQL_1 = 'SELECT t.*, dim.[xxx_ID_xxx] '
+ ' INTO ##FinalTab '
+ 'FROM ' + #TableName + ' t '
+ 'JOIN ##ALL_Dimentions dim '
+ 'ON t.' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = 1) + ' = dim.' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = 1);
SELECT #i = 2
WHILE #i <= (SELECT MAX([DIM_ID]) FROM #DIM_TAB)
BEGIN
SELECT #FULL_SQL_1 = #FULL_SQL_1 + ' AND t.' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = #i) + ' = dim.' + (SELECT [ColumnName] FROM #DIM_TAB WHERE [DIM_ID] = #i)
SELECT #i = #i +1
END
EXECUTE SP_EXECUTESQL #FULL_SQL_1
--#######################################################################################################################
--###| Step 6 - Selecting final data
--#######################################################################################################################
DECLARE #STAB_TAB TABLE ([STAB_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO #STAB_TAB
SELECT [STAB_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName]
FROM #ColumnListWithActions WHERE [Action] = 'S';
DECLARE #VAR_TAB TABLE ([VAR_ID] smallint, [ColumnName] nvarchar(128))
INSERT INTO #VAR_TAB
SELECT [VAR_ID] = ROW_NUMBER() OVER(ORDER BY [ColumnName]), [ColumnName]
FROM #ColumnListWithActions WHERE [Action] = 'V';
DECLARE #y smallint;
DECLARE #x smallint;
DECLARE #z smallint;
DECLARE #FinalCode nvarchar(max)
SELECT #FinalCode = ' SELECT ID1.*'
SELECT #y = 1
WHILE #y <= (SELECT MAX([xxx_ID_xxx]) FROM ##FinalTab)
BEGIN
SELECT #z = 1
WHILE #z <= (SELECT MAX([VAR_ID]) FROM #VAR_TAB)
BEGIN
SELECT #FinalCode = #FinalCode + ', [ID' + CAST((#y) as varchar(10)) + '.' + (SELECT [ColumnName] FROM #VAR_TAB WHERE [VAR_ID] = #z) + '] = ID' + CAST((#y + 1) as varchar(10)) + '.' + (SELECT [ColumnName] FROM #VAR_TAB WHERE [VAR_ID] = #z)
SELECT #z = #z + 1
END
SELECT #y = #y + 1
END
SELECT #FinalCode = #FinalCode +
' FROM ( SELECT * FROM ##ALL_StableColumns)ID1';
SELECT #y = 1
WHILE #y <= (SELECT MAX([xxx_ID_xxx]) FROM ##FinalTab)
BEGIN
SELECT #x = 1
SELECT #FinalCode = #FinalCode
+ ' LEFT JOIN (SELECT ' + #ListOfColumns_Stable + ' , ' + #ListOfColumns_Variable
+ ' FROM ##FinalTab WHERE [xxx_ID_xxx] = '
+ CAST(#y as varchar(10)) + ' )ID' + CAST((#y + 1) as varchar(10))
+ ' ON 1 = 1'
WHILE #x <= (SELECT MAX([STAB_ID]) FROM #STAB_TAB)
BEGIN
SELECT #FinalCode = #FinalCode + ' AND ID1.' + (SELECT [ColumnName] FROM #STAB_TAB WHERE [STAB_ID] = #x) + ' = ID' + CAST((#y+1) as varchar(10)) + '.' + (SELECT [ColumnName] FROM #STAB_TAB WHERE [STAB_ID] = #x)
SELECT #x = #x +1
END
SELECT #y = #y + 1
END
SELECT * FROM ##ALL_Dimentions;
EXECUTE SP_EXECUTESQL #FinalCode;
From executing the first query (by passing source DB and table name) you will get a pre-created execution query for the second SP, all you have to do is define is the column from your source:
+ Stable
+ Value (will be used to concentrate values based on that)
+ Dim (column you want to use to pivot by)
Names and datatypes will be defined automatically!
I cant recommend it for any production environments but does the job for adhoc BI requests.
I modified Taryn's answer ("Pivot with unknown number of columnnames" version) to show more than 1 row in the result. This requires to have an additional "Group" column
DROP TABLE #yourtable
CREATE table #yourtable
([Id] int,[Group] int, [Value] varchar(6), [ColumnName] varchar(13))
;
INSERT INTO #yourtable
([Id],[Group], [Value], [ColumnName])
VALUES
(1,1, 'John', 'FirstName'),
(2,1, '2.4', 'Amount'),
(3,1, 'ZH1E4A', 'PostalCode'),
(4,1, 'Fork', 'LastName'),
(5,1, '857685', 'AccountNumber'),
(6,2, 'Pedro', 'FirstName'),
(7,2, '5.1', 'Amount'),
(8,2, '123456', 'PostalCode'),
(9,2, 'Torres', 'LastName'),
(10,2, '857686', 'AccountNumber')
;
;
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(ColumnName)
from #yourtable
group by [Group], ColumnName, id
having [group] = (SELECT TOP 1 MIN([Group])FROM #yourtable)
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select value, ColumnName,[Group]
from #yourtable
GROUP BY [Group],ColumnName,Value
) x
pivot
(
max(value)
for ColumnName in (' + #cols + N')
) p '
exec sp_executesql #query;
Please try
CREATE TABLE pvt (Present int, [Absent] int);
GO
INSERT INTO pvt VALUES (10,40);
GO
--Unpivot the table.
SELECT Code, Value
FROM
(SELECT Present, Absent
FROM pvt) p
UNPIVOT
(Value FOR Code IN
(Present, [Absent])
)AS unpvt;
GO
DROP TABLE pvt
One more option which could be very useful is using CROSS APPLY
-- Original data
SELECT * FROM (VALUES ('1', 1, 2, 3),('2', 11, 22, 33)) AS Stage(id,col1,col2,col3)
-- row to columns using CROSS APPLY
SELECT Stage.id,v.idd, v.colc
FROM (VALUES ('1', 1, 2, 3),('2', 11, 22, 33)) AS Stage(id,col1,col2,col3)
CROSS APPLY (VALUES ('col1', col1),('col2', col2),('col3', col3)) AS v(idd,colc)
GO

Resources