How to combine rows dynamically - sql-server

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.

Related

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'''.

Finding MIN and MAX Values for All Table Columns

This query works as intended, but it's just really slow. Does anyone here have recommendations to improve performance?
I essentially just creating a temp table to store all the table and column names, and cycling through them via a WHILE statement, to create dynamic inserts into another table with the details I want.
My latest run took about 21 minutes, which isn't entirely terrible (considering the task), but I'd love to get some input on how/where it can be fine tuned.
USE <DATABASE>;
IF NOT EXISTS(SELECT *
FROM sys.schemas WHERE name='temp')
BEGIN
EXEC ('CREATE SCHEMA temp');
END;
IF OBJECT_ID('temp.columns') IS NOT NULL
BEGIN
DROP TABLE temp.columns
END;
SELECT [table_name]
, [column_name]
, [data_type]
, [is_nullable]
, [numeric_scale]
, [ordinal_position]
INTO [temp].[columns]
FROM information_schema.columns c
WHERE table_schema = 'dbo'
-- AND table_name = 'CONTACTS'
;
IF OBJECT_ID('_TableColumnsUsed') IS NOT NULL
BEGIN
DROP TABLE _TableColumnsUsed
END;
CREATE TABLE _TableColumnsUsed (Table_Name VARCHAR(255) NULL, Column_Position INT, Column_Name VARCHAR(255) NULL, Min_Value VARCHAR(MAX) NULL, Max_Value VARCHAR(MAX) NULL);
DECLARE
#CurrentTable VARCHAR(255)
, #CurrentColumn VARCHAR(255)
, #CurrentIsNullable VARCHAR(3)
, #CurrentNumeric BIT
, #CurrentPosition INT
, #SQL VARCHAR(MAX);
WHILE
(
SELECT COUNT(1)
FROM temp.columns
) > 0
BEGIN
SELECT TOP 1 #CurrentTable = [Table_Name]
, #CurrentColumn = [Column_Name]
, #CurrentIsNullable = [is_nullable]
, #CurrentNumeric = IIF([numeric_scale] IS NULL, 0, 1)
, #CurrentPosition = [ordinal_position]
FROM temp.columns c
WHERE [table_name] NOT IN ('_TableColumnsUsed')
ORDER BY [table_name]
, [ordinal_position];
SET #SQL = 'INSERT INTO _TableColumnsUsed (Table_Name, Column_Position, Column_Name, Min_Value, Max_Value)
SELECT Table_Name = '''+#CurrentTable+'''
, Column_Position = '+CAST(#CurrentPosition AS VARCHAR(3))+'
, Column_Name = '''+#CurrentColumn+'''
, Min_Value = MIN(CAST('+#CurrentColumn+' AS VARCHAR(MAX)))
, Max_Value = MAX(CAST('+#CurrentColumn+' AS VARCHAR(MAX)))
FROM '+#CurrentTable+'
WHERE '+IIF(#CurrentIsNullable = 'NO', '1=1',
CASE
WHEN #CurrentNumeric = 0
THEN 'ISNULL(CAST('+#CurrentColumn+' AS VARCHAR(MAX)),'''') <> '''''
WHEN #CurrentNumeric = 1
THEN 'ISNULL('+#CurrentColumn+',0.00) <> 0.00'
ELSE '1=1'
END);
EXEC (#SQL);
DELETE c
FROM [temp].[columns] [c]
WHERE [c].[table_name] = #CurrentTable
AND [c].[column_name] = #CurrentColumn;
END;
/* -- Dynamic SQL Output Example
SELECT Table_Name = 'CONTACTS'
, Column_Position = 17
, Column_Name = 'ZIP'
, Min_Value = MIN(CAST(ZIP AS VARCHAR(MAX)))
, Max_Value = MAX(CAST(ZIP AS VARCHAR(MAX)))
FROM CONTACTS
WHERE 1=1
*/
SELECT Table_Name, Column_Position, Column_Name, Min_Value, Max_Value
FROM _TableColumnsUsed;
Try this, it should work in seconds:
DECLARE #cmd NVARCHAR(MAX)=
(
SELECT STUFF(
(
SELECT ' UNION ALL SELECT ''' + c.TABLE_SCHEMA + ''' AS TableSchema '
+ ',''' + c.TABLE_NAME + ''' AS TableName '
+ ',''' + c.COLUMN_NAME + ''' AS ColumnName '
+ ',''' + c.DATA_TYPE + ''' AS ColumnType '
+ ',CAST(MIN(' + QUOTENAME(c.COLUMN_NAME)+') AS NVARCHAR(MAX)) AS MinValue '
+ ',CAST(MAX(' + QUOTENAME(c.COLUMN_NAME)+') AS NVARCHAR(MAX)) AS MaxValue '
+ ' FROM ' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)
+ ' WHERE ' + QUOTENAME(c.COLUMN_NAME) + ' IS NOT NULL'
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.DATA_TYPE IN('bigint','float','int','datetime') --add all types you want to check, be aware of implicit conversions!
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'),1,10,'')
);
--PRINT #cmd
EXEC(#cmd);
The statement creates an all-in-one UNION ALL query which is executed via EXEC
You can uncomment the PRINT to see the executed statement.
Can't say that it will run any faster, depends on the size of your database but here's a modded version of a profiling script I created that should meet your needs.
DECLARE #sqlStatement NVARCHAR(MAX) = '';
WITH CTE AS
(
SELECT
SCH.[name] AS [Schema]
,TAB.[name] AS [Table]
,COL.[name] AS [Column]
FROM sys.columns AS COL
JOIN sys.tables AS TAB
ON COL.[object_id] = TAB.[object_id]
JOIN sys.schemas AS SCH
ON TAB.[schema_id] = SCH.[schema_id]
WHERE COL.system_type_id NOT IN (104,240)
)
SELECT #sqlStatement +=
'UNION ALL
SELECT
'''+[Schema]+''' AS [Schema]
,'''+[Table]+''' AS [Table]
,'''+[Column]+''' AS [Column]
,CONVERT(NVARCHAR(MAX),MAX(['+[Column]+'])) AS ColumnMax
,CONVERT(NVARCHAR(MAX),MIN(['+[Column]+'])) AS ColumnMin
FROM ['+[Schema]+'].['+[Table]+']
'
FROM CTE
;
SET #sqlStatement = STUFF(#sqlStatement,1,10,'');
EXEC sp_executesql #sqlStatement;

Select any column with respect to cell containing

I have a table named a. Some cells containing a string 'Empty' in many columns. I want to find this columns. Can you help me?.
Try this dynamic query, it will check all the columns with character data and list the columns which has the word 'Empty'.
DECLARE #SearchText VARCHAR(50) = 'Empty'
DECLARE #sql NVARCHAR(MAX) = 'SELECT '
SELECT #sql = #sql + 'MAX(CASE WHEN ' + c.COLUMN_NAME + ' LIKE ''%'+ #SearchText +'%'' THEN ''' + c.COLUMN_NAME +''' ELSE '''' END) + '','' + '
FROM INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_SCHEMA = 'dbo' and c.TABLE_NAME = 'a'
AND c.DATA_TYPE IN ('varchar','char','nvarchar','nchar','sysname')
SET #sql = #sql + ''''' FROM dbo.a'
EXEC sys.sp_executesql #sql
Hope this helps
Use the LIKE operator:
SELECT a.*
FROM a
WHERE a.col1 LIKE '%Empty%' OR a.col2 LIKE '%Empty%' OR ...
In sql server you can get object id of table then using that object id you can fetch columns. In that case it will be as below:
Step 1: First get Object Id of table
select * from sys.tables order by name
Step 2: Now get columns of your table and search in it:
select * from a where 'Empty' in (select name from sys.columns where object_id =1977058079)
Note: object_id is what you get fetch in first step for you relevant table
You can do it using unpivot with an help of dynamic query , here i have done below an working sample for you , there might be some modification you might have to do to put the below psedo code with your working .
Sample table structure been used :
create table ColTest
(
name1 varchar(10),
name2 varchar(10),
name3 varchar(10),
name4 varchar(10)
)
insert into ColTest values ('sdas','asdasda','ewrewr','erefds')
insert into ColTest values ('sdas','asdasda','EMPTY','erefds')
insert into ColTest values ('EMPTY','asdasda','ewrewr','erefds')
DECLARE #table_name SYSNAME
SELECT #table_name = 'ColTest'
DECLARE #tmpTable SYSNAME
SELECT #tmpTable = 'ColTest2'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
SELECT * into
' + #tmpTable + '
FROM ' + #table_name + '
UNPIVOT (
cell_value FOR column_name IN (
' + STUFF((
SELECT ', [' + c.name + ']'
FROM sys.columns c WITH(NOLOCK)
LEFT JOIN (
SELECT i.[object_id], i.column_id
FROM sys.index_columns i WITH(NOLOCK)
WHERE i.index_id = 1
) i ON c.[object_id] = i.[object_id] AND c.column_id = i.column_id
WHERE c.[object_id] = OBJECT_ID(#table_name)
AND i.[object_id] IS NULL
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + '
)
) unpiv'
PRINT #SQL
EXEC sys.sp_executesql #SQL
select * from ColTest2 where cell_value = 'EMPTY'
I'd suggest dynamic SQL
--First you set the variable #TableName to your actual table's name.
DECLARE #TableName VARCHAR(100)='a';
--The following statement will create a list of all columns with a data type containing the word "char" (others should not hold the value Empty)
DECLARE #ColList VARCHAR(MAX)=
STUFF(
(
SELECT ' OR ' + COLUMN_NAME + ' LIKE ''%empty%'''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#TableName AND DATA_TYPE LIKE '%char%'
FOR XML PATH('')
),1,4,'');
--This statement builds a command
DECLARE #cmd VARCHAR(MAX)=
(
SELECT 'SELECT * FROM [' + #TableName + '] WHERE ' + #ColList
);
--Here you can see the command
PRINT #cmd;
--And here it is executed
EXEC(#cmd);

Concatenate an arbitrary length of columns per row

How can I concatenate an arbitrary length of columns per row? I tried the following, but the function as is requires one to specify the column names:
SELECT CONCAT([C1], [C2], ...) FROM [dbo.table];
How can I achieve the same result without specifying each column name explicitly?
You'd need to use dynamic SQL. You can query the system catalg view sys.columns to get the column names, and then use SQL Server's XML Extension to concatenate the rows to a single string giving your final SQL to execute:
DECLARE #TableName SYSNAME = 'dbo.YourTable';
DECLARE #SQL NVARCHAR(MAX) = 'SELECT CONCAT(' +
STUFF(( SELECT ',' + QUOTENAME(c.Name)
FROM sys.columns c
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '') + ')
FROM ' + #TableName + ';';
EXECUTE sp_executesql #SQL;
ADDENDUM
If you want to delimit your columns, you can add a further concatenation while you are creating your column list:
DECLARE #TableName SYSNAME = 'dbo.YourTable',
#Delimiter VARCHAR(10) = ', ';
DECLARE #SQL NVARCHAR(MAX) = 'SELECT CONCAT(' +
STUFF(( SELECT ',''' + #Delimiter + ''',' + QUOTENAME(c.Name)
FROM sys.columns c
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, LEN(#Delimiter) + 5, '') + ')
FROM ' + #TableName + ';';
EXECUTE sp_executesql #SQL;
ADDENDUM 2
To avoid the delimiter being added when the value is null, e.g instead of ending up with:
1,,,2,3
You simply get
1,2,3
You need to slightly amend the logic, before it was generating a query that was like:
CONCAT([C1], ',', [C2], ',', [C3])
Instead you want:
CONCAT([C1], ',' + [C2], ',' + [C3])
Because you are now using ',' + [C2] if [C2] is null, the result will be null, so the delimiter will be removed:
DECLARE #TableName SYSNAME = 'dbo.YourTable',
#Delimiter VARCHAR(10) = ', ';
DECLARE #SQL NVARCHAR(MAX) = 'SELECT CONCAT(' +
STUFF(( SELECT ',''' + #Delimiter + ''' + ' + QUOTENAME(c.Name)
FROM sys.columns c
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, LEN(#Delimiter) + 7, '') + ')
FROM ' + #TableName + ';';
EXECUTE sp_executesql #SQL;
ADDENDUM 3
To remove the first column you can use ROW_NUMBER() on the sys.columns system catalog view, then exclude the first column:
DECLARE #TableName SYSNAME = 'dbo.YourTable',
#Delimiter VARCHAR(10) = ', ';
DECLARE #SQL NVARCHAR(MAX) = 'SELECT CONCAT(' +
STUFF(( SELECT ',''' + #Delimiter + ''' + ' + QUOTENAME(c.Name)
FROM ( SELECT name,
RowNumber = ROW_NUMBER() OVER(ORDER BY column_id)
FROM sys.columns c
WHERE [object_id] = OBJECT_ID(#TableName)
) AS c
WHERE c.RowNumber != 1 -- not first column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, LEN(#Delimiter) + 7, '') + ')
FROM ' + #TableName + ';';
EXECUTE sp_executesql #SQL;
You need to use Dynamic-SQL for this:
Warning:
I've used tempdb (tempdb.sys.columns) because I cannot create normal tables in demo. In your case use your normal database. And change condition to: WHERE object_id = OBJECT_ID('table_name').
LiveDemo
CREATE TABLE #tab(ID INT, C1 INT, C2 INT, C3 INT);
INSERT INTO #tab VALUES (1, 1,2,3), (2, 2,3,4);
DECLARE #cols NVARCHAR(MAX);
SET #cols = STUFF(
(SELECT ',' + QUOTENAME(name)
FROM tempdb.sys.columns
WHERE
object_id = (SELECT object_id
FROM tempdb.sys.objects
WHERE NAME like '#tab%' AND Type = 'U')
AND name LIKE 'C%'
ORDER BY column_id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
DECLARE #query NVARCHAR(MAX)=
N'SELECT ID, CONCAT(<placeholder>) AS concated_columns FROM #tab';
SET #query = REPLACE(#query, '<placeholder>', #cols);
EXEC [dbo].[sp_executesql]
#query;
EDIT:
If you need specific character between concatenated values use:
(SELECT ',' + CONCAT(QUOTENAME(name) , ','' ''' )
LiveDemo2
To concat all columns in arbitrary table:
DECLARE #Columns nvarchar(MAX)
SELECT #Columns = ISNULL(#Columns + ',','') + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableName' AND TABLE_SCHEMA='dbo'
DECLARE #sql nvarchar(MAX)
SET #sql = N'SELECT CONCAT('+#Columns+')
FROM dbo.TableName'
EXEC sp_executesql #sql
Use dynamic SQL and remember to cater for nulls!
declare #sql nvarchar(max), #t sysname, #c sysname
select #sql = 'select ', #t = '[dbo].[CONTACTS]' /* <---- YOUR TABLE NAME HERE */
declare cols cursor for
select name from sys.columns where object_id = object_id(#t) order by column_id
open cols
fetch next from cols INTO #c
while ##FETCH_STATUS = 0
begin
select #sql = #sql + 'convert(nvarchar(max), isnull(' + #c + ', '''')) + '
fetch next from cols INTO #c
end
close cols
deallocate cols
select #sql = left(#sql, len(#sql)-2) + ' from ' + #t
exec sp_executesql #sql
If your table has a primary key column, you could use a correlated subquery like the example below. SqlFiddle here.
SELECT (
( SELECT *
FROM dbo.table1 AS t2
WHERE t2.C1 = t1.C1 --specify primary key column(s) here
FOR
XML PATH('test')
, TYPE
)).value('/test[1]', 'nvarchar(MAX)') AS ConcatenatedValue
FROM dbo.table1 AS t1;

Dynamic SQL Pivot by Column 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

Resources