SQL server pivot multiple columns to multiple rows and including nulls - sql-server

I have the following table:
+----------------------------------------------------------------------+
| Student Class1 Class1_score Class2 Class2_score Class3.... |
+----------------------------------------------------------------------+
| Alex English 70 Maths 100 NULL |
| John Science 50 NULL NULL NULL |
| Bruce Maths 50 Science 50 English |
+----------------------------------------------------------------------+
Which i want to pivot to something like the following:
+--------------------------+
| Student Class Score |
+--------------------------+
| Alex English 70 |
| Alex Maths 100 |
| Alex Science NULL |
| Alex History NULL |
| John English NULL |
+--------------------------+
Which includes NULL for classes that are not in the original table for that particular student (e.g. Science for Alex)
How can I achieve this in SQL?

Assuming that class type occurs only once in the classX columns:
select student,
'English' class,
coaslesce(case when class1 = 'english' then Class1_score end,
case when class2 = 'english' then Class2_score end,
case when class3 = 'english' then Class3_score end,
case when class4 = 'english' then Class4_score end) score
from your_table
union
select student,
'Maths' class,
coaslesce(case when class1 = 'Maths' then Class1_score end,
case when class2 = 'Maths' then Class2_score end,
case when class3 = 'Maths' then Class3_score end,
case when class4 = 'Maths' then Class4_score end) score
from your_table
union
select student,
'Science' class,
coaslesce(case when class1 = 'Science' then Class1_score end,
case when class2 = 'Science' then Class2_score end,
case when class3 = 'Science' then Class3_score end,
case when class4 = 'Science' then Class4_score end) score
from your_table
union
select student,
'History' class,
coaslesce(case when class1 = 'History' then Class1_score end,
case when class2 = 'History' then Class2_score end,
case when class3 = 'History' then Class3_score end,
case when class4 = 'History' then Class4_score end) score
from your_table

If you want all the Class column values should come for each Student column value, then declare 2 table variables, one for storing unique values of Student column values and other for storing unique values of Class column values. Then do a full outer join between these two tables and use this result set as a subset and join it with another table variable which contains separate rows for each student each class and score. And all these can be done by executing dynamic SQL query.
Query
-- table variabe to store unique class values
declare #sql as varchar(max) = 'declare #tbl_class as table([class] varchar(100));'
+ 'insert into #tbl_class([class]) ';
select #sql += stuff((
select ' union select [' + [column_name] + '] from [' + [table_name] + '] '
+ 'where [' + [column_name] + '] is not null'
from information_schema.columns
where [table_name] = 'your_table_name'
and [column_name] like 'class%[0-9]'
order by [ordinal_position]
for xml path('')
)
, 1, 7, ''
) + ';';
-- table variabe to store unique student values
select #sql += 'declare #tbl_student as table([student] varchar(100));'
+ 'insert into #tbl_student([student]) '
+ 'select distinct [Student] from [your_table_name];';
-- table variable to store student score and class values one by one
select #sql += 'declare #tbl_scores as table'
+ '([student] varchar(100), [class] varchar(100), [score] int);'
+ 'insert into #tbl_scores([student], [class], [score]) ';
select #sql += stuff((
select ' union all select [Student], '
+ '[' + t.[col_1] + '] as [class],'
+ '[' + t.[col_2] + '] as [score] '
+ 'from [' + t.[table_name] + '] '
from (
select [column_name] as [col_1], [column_name] + '_score' as [col_2],
[ordinal_position], [table_name]
from information_schema.columns
where [table_name] = 'your_table_name'
and [column_name] like 'class%[0-9]'
) t
order by t.[ordinal_position]
for xml path('')
)
, 1, 10, ''
) + ';';
-- final result
select #sql += 'select t.[student], t.[Class], tsc.[Score] from('
+ 'select ts.[student], tc.[Class]'
+ ' from #tbl_student as ts '
+ ' full outer join #tbl_class as tc '
+ 'on 1 = 1 ) t '
+ 'left join #tbl_scores as tsc '
+ 'on t.[student] = tsc.[student] '
+ 'and t.[Class] = tsc.[Class];';
exec(#sql);
Find a demo here

You are actually looking for unpivot (wide to tall), rather than pivot. One approach which should work across almost any database uses a series of unions:
SELECT Student, Class, Score
FROM
(
SELECT Student, Class1 AS Class, Class1_score AS Score, 1 AS position
FROM yourTable
UNION ALL
SELECT Student, Class2, Class2_score, 2
FROM yourTable
UNION ALL
SELECT Student, Class3, Class3_score, 3
FROM yourTable
) t
ORDER BY
Student, position;

Sample data
IF OBJECT_ID('dbo.tempdata') IS NOT NULL
DROP TABLE tempdata
;With cte(Student , Class1 , Class1_score, Class2, Class2_score, Class3,Class3_score)
AS
(
SELECT 'Alex' ,'English', 70,'Maths' , 100 , NULL ,NULL UNION ALL
SELECT 'John' ,'Science', 50,NULL ,NULL , NULL ,NULL UNION ALL
SELECT 'Bruce','Maths' , 50,'Science' , 50 , 'English',NULL
)
SELECT * INTO tempdata FROM cte
SELECT * FROM tempdata
By Using Cross apply and dynamic SQL
DECLARE #DynamicColumns NVARCHAR(max)
,#Sql NVARCHAR(max)
;WITH cte
AS
(
SELECT COLUMN_NAME,
DENSE_RANK()OVER(ORDER BY SUBSTRING(COLUMN_NAME,0,7)) As BatchNo
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tempdata' AND COLUMN_NAME <>'Student'
)
SELECT #DynamicColumns=STUFF((SELECT ', '+ReqCol FROM
(
SELECT
DISTINCT '('+STUFF((SELECT ', '+COLUMN_NAME
FROM CTE i WHERE i.BatchNo=o.BatchNo
FOR XML PATH ('')),1,1,'') + ')' AS ReqCol
FROM cte o
)dt
FOR XML PATH ('')),1,1,'')
SET #Sql='SELECT Student
,Class
,Score
FROM tempdata
CROSS APPLY (VALUES '+#DynamicColumns+'
) As Dt (Class, Score)
WHERE Class IS NOT NULL
'
PRINT #Sql
EXEC (#Sql)
Result
Student Class Score
---------------------
Alex English 70
Alex Maths 100
John Science 50
Bruce Maths 50
Bruce Science 50
Bruce English NULL

Related

Validate decimal and integer values by looping through list of dynamic columns

I was tasked to validate the decimal and integer values of the columns from a list of tables. I have around 10-12 tables having different column names.
I created a lookup table which has the table name and the column names of decimal and integer as shown below. for example 'Pricedetails' and 'Itemdetails' tables have many columns of which only the ones mentioned in the Lookup table are required.
lkpTable
TableName
requiredcolumns
Pricedetails
sellingPrice,RetailPrice,Wholesaleprice
Itemdetails
ItemID,Itemprice
Pricedetails
Priceid
Mafdate
MafName
sellingPrice
RetailPrice
Wholesaleprice
01
2020-01-01
Americas
25.00
43.33
33.66
02
2020-01-01
Americas
43.45
22.55
11.11
03
2021-01-01
Asia
-23.00
-34.00
23.00
Itemdetails
ItemID
ItemPrice
Itemlocation
ItemManuf
01
45.11
Americas
SA
02
25.00
Americas
SA
03
35.67
Americas
SA
I have created a stored procedure with table name as input parameter, and able to pull the required column names of the tables (input parameter) from the lookup table and store that resultset into a table variable, below is the code.
declare #resultset Table
(
id INT identity(1,1),
tablename varchar(200) ,
ColumnNames varchar(max)
)
declare #tblname varchar(200),#sql varchar(max),#cols varchar(max),
INSERT INTO #resultset
select tablename,ColumnNames
from lkptable where tablename ='itemdetails'
select #cols = ColumnNames from #resultset;
select #tblname = TableName from #resultset;
----- Split the comma separated columnnames
Create table ##splitcols
(
ID int identity(1,1),
Name varchar(50)
)
Insert into ##splitcols
select value from string_split(#cols,',')
set #sql = 'select ' +#cols + ' from ' +#tblname
--print (#cols)
exec (#sql)
select * from ##splitcols
On executing the above code i get the below result sets, similarly what ever table name i provide i can get the required columns and its relevant data, now i am stuck at this point on how to validate whether the columns are decimal or int. I tried using while loop and cursor to pass the Name value from Resultset2, to the new dynamic query, somehow i don't find any way on how to validate.
ItemID
ItemPrice
01
45.11
02
25.00
03
35.67
Resultset2
ID
Name
01
ItemID
02
ItemPrice
you can validate in this way
insert into #splitcols values (1.1),(1.11)
select case when (t * 100)%10 = 0 then 1 else 0 end as valid from #splitcols
Similarly for number/integer
You can generate one giant UNION ALL query using dynamic SQL, then run it.
Each query would be of the form:
SELECT
TableName = 'SomeTable',
ColumnName,
IsInt
FROM (
SELECT
[Column1] = CASE WHEN COUNT(CASE WHEN ROUND([Column1], 0) <> [Column1] THEN 1 END) = 0 THEN 'All ints' ELSE 'Not All ints' END,
[Column2] = CASE WHEN COUNT(CASE WHEN ROUND([Column2], 0) <> [Column2] THEN 1 END) = 0 THEN 'All ints' ELSE 'Not All ints' END
FROM SomeTable
) t
UNPIVOT (
ColumnName FOR IsInt IN (
[Column1], [Column2]
)
) u
The script is as follows
DECLARE #sql nvarchar(max);
SELECT #sql = STRING_AGG('
SELECT
TableName = ' + QUOTENAME(t.name, '''') + ',
ColumnName,
IsInt
FROM (
SELECT ' + c.BeforePivotCoumns + '
FROM ' + QUOTENAME(t.name) + '
) t
UNPIVOT (
ColumnName FOR IsInt IN (
' + c.UnpivotColumns + '
)
) u
', '
UNION ALL '
)
FROM sys.tables t
JOIN lkpTable lkp ON lkp.TableName = t.name
CROSS APPLY (
SELECT
BeforePivotCoumns = STRING_AGG(CAST('
' + QUOTENAME(c.name) + ' = CASE WHEN COUNT(CASE WHEN ROUND(' + QUOTENAME(c.name) + ', 0) <> ' + QUOTENAME(c.name) + ' THEN 1 END) = 0 THEN ''All ints'' ELSE ''Not All ints'' END'
AS nvarchar(max)), ','),
UnpivotColumns = STRING_AGG(QUOTENAME(c.name), ', ')
FROM sys.columns c
JOIN STRING_SPLIT(lkp.requiredcolumns, ',') req ON req.value = c.name
WHERE c.object_id = t.object_id
) c;
PRINT #sql;
EXEC sp_executesql #sql;
db<>fiddle
If you are on an older version of SQL Server then you can't use STRING_AGG and instead you need to hack it with FOR XML and STUFF.
DECLARE #unionall nvarchar(100) = '
UNION ALL ';
DECLARE #sql nvarchar(max);
SET #sql = STUFF(
(SELECT #unionall + '
SELECT
TableName = ' + QUOTENAME(t.name, '''') + ',
ColumnName,
IsInt
FROM (
SELECT ' + STUFF(c1.BeforePivotCoumns.value('text()[1]','nvarchar(max)'), 1, 1, '') + '
FROM ' + QUOTENAME(t.name) + '
) t
UNPIVOT (
ColumnName FOR IsInt IN (
' + STUFF(c2.UnpivotColumns.value('text()[1]','nvarchar(max)'), 1, 1, '') + '
)
) u'
FROM sys.tables t
JOIN (
SELECT DISTINCT
lkp.TableName
FROM lkpTable lkp
) lkp ON lkp.TableName = t.name
CROSS APPLY (
SELECT
',
' + QUOTENAME(c.name) + ' = CASE WHEN COUNT(CASE WHEN ROUND(' + QUOTENAME(c.name) + ', 0) <> ' + QUOTENAME(c.name) + ' THEN 1 END) = 0 THEN ''All ints'' ELSE ''Not All ints'' END'
FROM lkpTable lkp2
CROSS APPLY STRING_SPLIT(lkp2.requiredcolumns, ',') req
JOIN sys.columns c ON req.value = c.name
WHERE c.object_id = t.object_id
AND lkp2.TableName = lkp.TableName
FOR XML PATH(''), TYPE
) c1(BeforePivotCoumns)
CROSS APPLY (
SELECT
', ' + QUOTENAME(c.name)
FROM lkpTable lkp2
CROSS APPLY STRING_SPLIT(lkp2.requiredcolumns, ',') req
JOIN sys.columns c ON req.value = c.name
WHERE c.object_id = t.object_id
AND lkp2.TableName = lkp.TableName
FOR XML PATH(''), TYPE
) c2(UnpivotColumns)
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)'), 1, LEN(#unionall), '');
PRINT #sql;
EXEC sp_executesql #sql;
db<>fiddle

Formatting SQL Server Query Output

I like to get the SQL Server query output in the following way. Currently my output is:
Class | Student ID | Student Name
------+------------+---------------
121 S1 Test 1
121 S2 Test 2
500 S22 Test a
500 S23 Test b
But I want the data output in the following way -
Class: 121
--------------------------------
Student ID | Student Name
-----------+---------------
S1 Test 1
S2 Test 2
Class: 500
--------------------------------
Student ID | Student Name
-----------+---------------
S22 Test a
S23 Test b
Could someone help me how can I achieve these?
Thanks
While I would agree SQL isn't the place to do your formatting.... sometimes you get stuck....
This Code
select '121' as Class, 'S1 ' as [Student ID], 'Test 1' as [Student Name] into #test
union select '121', 'S2 ', 'Test 2'
union select '500', 'S22 ', 'Test a'
union select '500', 'S23 ', 'Test b'
;
WITH class
AS (
SELECT DISTINCT class
FROM #test
)
,student
AS (
SELECT class
,(
SELECT STUFF((
SELECT CHAR(13) + t.[Student ID] + space(len('Student ID | ') - len(t.[Student ID])) + [Student Name]
FROM #Test t
WHERE t.[class] = class.[class]
FOR XML PATH('')
,type
).value('.[1]', 'nvarchar(max)'), 1, 1, '')
) AS info
FROM class
)
,theoutput
AS (
SELECT 'Class: ' + class + CHAR(13) + '------------------------' + CHAR(13) + 'Student ID | Student Name' + CHAR(13) + info + CHAR(13) + CHAR(13) AS txt
FROM student c
)
SELECT STUFF((
SELECT CHAR(13) + txt
FROM theoutput
FOR XML PATH('')
,type
).value('.[1]', 'nvarchar(max)'), 1, 1, '')
Will Produce
Class: 121
------------------------
Student ID | Student Name
S1 Test 1
S2 Test 2
Class: 500
------------------------
Student ID | Student Name
S22 Test a
S23 Test b
By using While loop we can get the Desired result Created sample data
IF OBJECT_ID('Tempdb..#TEMP') IS NOT NULL
Drop table #TEMP
;With cte(Class, StudentID, StudentName)
AS
(
SELECT 121,'S1' ,'Test 1' UNION ALL
SELECT 121,'S2' ,'Test 2' UNION ALL
SELECT 500,'S22','Test a' UNION ALL
SELECT 500,'S23','Test b'
)
SELECT * INTO #TEMP FROM CTe
SET NOCOUNT ON
DECLARE #CLassCOUnt INT
,#minID INT
,#maxid INT
,#Sql NVARCHAR(max)
,#SelectQuery INT
DECLARE #Table TABLE (
ID INT IDENTITY
,CLass INT
)
INSERT INTO #Table (CLass)
SELECT DISTINCT Class
FROM #TEMP
SELECT #minID = min(Id)
FROM #Table
SELECT #maxid = MAX(Id)
FROM #Table
WHILE (#minID <= #maxid)
BEGIN
SELECT #SelectQuery = Class
FROM #Table
WHERE ID = #minID
SELECT #Sql = 'SELECT Class, StudentID, StudentName FROM #TEMP WHERE Class=' + CAST(#SelectQuery AS VARCHAR)
PRINT 'Result Of Class:'+ CAST(#SelectQuery AS VARCHAR)+CHAR(13)+CHAR(10)+'------------------------'
PRINT #Sql
EXEC (#Sql)
SET #minID = #minID + 1
END
SET NOCOUNT OFF

Count non-empty rows with unknown column names

Given a table that has unknown column names, how can I list all column names and the amount of rows that have a non-empty (NULL or empty string) value in that column?
Small example:
Col1 | Col2 | Col3 | dkasldk | dD? d3# !(
1 | | 2 | |
| 2 | d | ddd939 |
f | f | 84 | |
Should yield:
Column Name | Values
Col1 | 2
Col2 | 2
Col3 | 3
dkasldk | 1
dD? d3# !( | 0
I already tried around with using INFORMATION_SCHEMA.COLUMNS and creating dynamic SQL queries, but to no avail:
select N'select c.COLUMN_NAME,
(select sum(case when p.' + QUOTENAME(c.COLUMN_NAME) + ' != '''' then 1 else 0 end) from [Produkte] p) [Produkte]
from INFORMATION_SCHEMA.COLUMNS c
where c.TABLE_NAME = ''Produkte'' and c.COLUMN_NAME = ''' + c.COLUMN_NAME + '''' select_statement
from INFORMATION_SCHEMA.COLUMNS c
where c.TABLE_NAME = 'Produkte'
I cannot quite wrap my head around how to combine the two problems (for which I did find solution in their own, but not the combination) of Dynamic Column Names and Count empty values...
You can do something like this using UNPIVOT..
DECLARE #TableName NVARCHAR(MAX) = N'Produkte',
#CountColumns NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX)
SELECT #CountColumns = COALESCE(#CountColumns + ',', '') + 'COUNT(NULLIF(' + QUOTENAME(c.COLUMN_NAME) + ','''')) AS ' + QUOTENAME(c.COLUMN_NAME),
#Columns = COALESCE(#Columns + ',', '') + QUOTENAME(c.COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME = #TableName
SET #SQL = 'SELECT [Column Name], [Values]
FROM (
SELECT '
+ #CountColumns + '
FROM ' + #TableName + '
) t
UNPIVOT (
[Values]
FOR [Column Name] IN (' + #Columns + ')
) up
'
EXEC(#SQL)
SQL Fiddle Demo
You can use a dynamic UNPIVOT.
Using these variables:
DECLARE #qry NVARCHAR(MAX)
DECLARE #cols NVARCHAR(MAX) = ''
you can select columns names in a format suitable for the UNPIVOT operation with the following dynamic sql:
SELECT #cols = STUFF((SELECT ',[' + c.COLUMN_NAME + ']'
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME = 'Produkte'
FOR XML PATH('')), 1, 1, '')
Finally use #cols to build the query:
SET #qry = 'SELECT t.Col, COUNT(*)
FROM (SELECT Val, Col
FROM Produkte
UNPIVOT (
Val FOR Col IN (' + #cols + ')) unpvt
) AS t
WHERE t.Val <> '''' OR t.Val IS NOT NULL
GROUP BY t.Col'
... and execute it to get desired result:
EXEC(#qry)
Ultimately the SQL you are after is something like this:
SELECT ColumnName, NonEmpty
FROM ( SELECT A = 1,
[Col1] = COUNT(CASE WHEN [Col1] <> '' THEN 1 END),
[Col2] = COUNT(CASE WHEN [Col2] <> '' THEN 1 END),
[Col3] = COUNT(CASE WHEN [Col3] <> '' THEN 1 END),
[dkasldk] = COUNT(CASE WHEN [dkasldk] <> '' THEN 1 END),
[dD? d3# !(] = COUNT(CASE WHEN [dD? d3# !(] <> '' THEN 1 END)
FROM #T
) AS t
UNPIVOT
( NonEmpty
FOR ColumnName IN ([Col1],[Col2],[Col3],[dkasldk],[dD? d3# !(])
) AS upvt;
UNPIVOT Will convert your data from
Col1 Col2 Col3
-------------------------
1 3 2
To the format you require:
ColumnName NonEmpty
-------------------------
Col1 1
Col2 3
Col3 2
You can build up dynamically using something like this:
-- SAMPLE DATA
USE TempDB;
CREATE TABLE #T
(
[Col1] VARCHAR(1),
[Col2] VARCHAR(1),
[Col3] VARCHAR(2),
[dkasldk] VARCHAR(6),
[dD? d3# !(] VARCHAR(1)
);
INSERT #T ([Col1], [Col2], [Col3], [dkasldk], [dD? d3# !(])
VALUES
('1', NULL, '2', NULL, ''),
('', '2', 'd', 'ddd939', NULL),
('f', 'f', '84', NULL, '');
DECLARE #TableName SYSNAME = '#T';
-- VALID INPUT TABLE
IF OBJECT_ID(#TableName, 'U') IS NULL
AND OBJECT_ID(#TableName, 'V') IS NULL
BEGIN
PRINT 'Invalid table or View';
RETURN
END
-- BUILD DYNAMIC SQL
DECLARE #SQL NVARCHAR(MAX) =
CONCAT('SELECT ColumnName, NonEmpty FROM (SELECT A = 1, ' ,
STUFF(( SELECT CONCAT(',',
QUOTENAME(name),
' = COUNT(CASE WHEN ',
QUOTENAME(Name),
' <> '''' THEN 1 END)')
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, ''),
' FROM ',
#TableName,
') AS t UNPIVOT (NonEmpty FOR ColumnName IN (',
STUFF(( SELECT CONCAT(',', QUOTENAME(name))
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, ''),
')) AS upvt');
-- EXECUTE DYNAMIC SQL
EXECUTE sp_executesql #SQL;

Get a list of all columns that do not have only NULL values in SQL Server

I NEVER do complicated stuff in SQL - until now...
I have a database with over 2000 tables, each table has about 200 columns.
I need to get a list of all the columns in one of those tables that are populated at least 1 time.
I can get a list of all the columns like this:
SELECT [name] AS [Column name]
FROM syscolumns with (nolock)
WHERE id = (SELECT id FROM sysobjects where name like 'DOCSDB_TDCCINS')
But I need only the columns that are populated 1 or more times.
Any help would be appreciated.
Here is how I would do it, first run this:
SELECT 'SELECT '''+syscolumns.name+''' FROM '+sysobjects.name+' HAVING COUNT('+syscolumns.name+') > 0'
FROM syscolumns with (nolock)
JOIN sysobjects with (nolock) ON syscolumns.id = sysobjects.id
WHERE syscolumns.id = (SELECT id FROM sysobjects where name like 'Email')
Copy all the select statements and run them.
This will give you a list of the column names without nulls.
(nb I did not test because I don't have an SQL server available right now, so I could have a typo)
It may be also be useful to count the non-null instances, obviously 0 or not 0 was your initial question, and counting the instances versus exists not/exists will be slower.
select 'union select ''' + Column_Name + ''',count(*)'
+ ' from ' + table_name
+ ' where ' + column_name + ' is not null'
from
(
select * from information_schema.columns with (nolock)
where Is_Nullable = 'YES'
AND Table_Name like 'DOCSDB_TDCCINS'
) DD
Then remove the superfluous leading 'union' and run the query
A different idea is to create a dynamic unpivot for every table.
Declare #q NVarchar(MAX) = NULL
;With D AS (
SELECT TABLE_SCHEMA
, TABLE_NAME
, STUFF((SELECT ', ' + QUOTENAME(ci.COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS ci
WHERE (ci.TABLE_NAME = c.TABLE_NAME)
AND (ci.TABLE_SCHEMA = c.TABLE_SCHEMA)
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')
,1,2,'') AS _Cols
, STUFF((SELECT ', Count(' + QUOTENAME(ci.COLUMN_NAME) + ') '
+ QUOTENAME(ci.COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS ci
WHERE (ci.TABLE_NAME = c.TABLE_NAME)
AND (ci.TABLE_SCHEMA = c.TABLE_SCHEMA)
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')
,1,2,'') AS _ColsCount
FROM INFORMATION_SCHEMA.COLUMNS c
GROUP BY TABLE_SCHEMA, TABLE_NAME
)
SELECT #q = COALESCE(#q + ' UNION ALL ', '') + '
SELECT ''' + TABLE_SCHEMA + ''' _Schema, ''' + TABLE_NAME + ''' _Table, _Column
FROM (SELECT ' + _ColsCount + ' from ' + TABLE_SCHEMA + '.' + TABLE_NAME + ') x
UNPIVOT
(_Count FOR _Column IN (' + _Cols + ')) u
WHERE _Count > 0'
FROM D
exec sp_executesql #q
In the CTE _Cols returns the comma separated quoted name of the columns of the table, while _ColsCount returns the same list with the COUNT function, for example for a table of mine a row of D is
TABLE_SCHEMA | TABLE_NAME | _Cols | _ColsCount
------------- ----------------- ------------------------------ -----------------------------------------------------------------------------
dbo | AnnualInterests | [Product_ID], [Rate], [Term] | Count([Product_ID]) [Product_ID], Count([Rate]) [Rate], Count([Term]) [Term]
while the main query trasform this line in the UNPIVOT to return the columns in rows
SELECT 'dbo' _Schema, 'AnnualInterests' _Table, _Column
FROM (SELECT Count([Product_ID]) [Product_ID], Count([Term]) [Term]
, Count([Rate]) [Rate] from dbo.AnnualInterests) x
UNPIVOT
(_Count FOR _Column IN ([Product_ID], [Term], [Rate])
WHERE _Count > 0
using the string variable concatenation and sp_executesql to run the string complete the script.
Hope you can achieve this by a simple alteration on your code like
SELECT [name] AS [Column name]
FROM syscolumns with (nolock)
WHERE id = (SELECT id FROM sysobjects where name like 'DOCSDB_TDCCINS')
and (select count(*) from DOCSDB_TDCCINS)>0

joining two datatable which have dynamic column using stored procedure in dynamic sql

How can I join (inner) the two datatable dt1 and dt2 that have dynamic coloumn. using stored procedure in dynamic sql.
dt1 contains
emp id empname SickLeave Casualleave
1 h 1
dt2 contains
empid empname SickLeave Casualleave
1 h 5 5
I have to show output like
empid empname SickLeave Casualleave
1 h 1/5 0/5
Please guide me
Thanks
You can do this using STUFF() like this:
WITH CTE(empid, empname, SickLeave, Casualleave)
AS (SELECT * FROM dt1
UNION ALL
SELECT * FROM dt2
)
SELECT distinct empid, empname
, SickLeave =
STUFF((SELECT ' / ' + CONVERT(VARCHAR(20),[SickLeave])
FROM CTE b
WHERE b.empid = a.empid
FOR XML PATH('')), 1, 2, '')
, Casualleave =
STUFF((SELECT ' / ' + CONVERT(VARCHAR(20),[Casualleave])
FROM CTE C
WHERE C.empid = a.empid
FOR XML PATH('')), 1, 2, '')
FROM CTE a
GROUP BY Empid, empname;
Output:
| EMPID | EMPNAME | SICKLEAVE | CASUALLEAVE |
---------------------------------------------
| 1 | h | 1 / 5 | 0 / 5 |
See this SQLFiddle
You don't need dynamic SQL...
SELECT emp_id
, empname
, dt1_sickleave + '/' + dt2_sickleave As sickleave
, dt1_casualleave + '/' + dt2_casualleave As casualleave
FROM (
SELECT Coalesce(dt1.emp_id, dt2.emp_id) As emp_id
, Coalesce(dt1.empname, dt2.empname) As empname
, Cast(Coalesce(dt1.sickleave , 0) As varchar(10)) As dt1_sickleave
, Cast(Coalesce(dt1.casualleave, 0) As varchar(10)) As dt1_casualleave
, Cast(Coalesce(dt2.sickleave , 0) As varchar(10)) As dt2_sickleave
, Cast(Coalesce(dt2.casualleave, 0) As varchar(10)) As dt2_casualleave
FROM dt1
FULL
JOIN dt2
ON dt2.emp_id = dt1.emp_id
) As x
select t1.[empid],t1.[empname],
Cast(COALESCE(t1.[SickLeave],0) as char)
+'/'
+cast(COALESCE(t2.[SickLeave],0) as char) as SickLeave,
Cast(COALESCE(t1.[Casualleave],0) as char)
+'/'
+cast(COALESCE(t2.[Casualleave],0) as char) as Casualleave
from Table1 t1 inner join Table2 t2 on
t1.[empid]=t2.[empid];
fiddle
So maybe this will do what you want. This query gets all the column names from sys.columns named like '%leave%' where the table is either dt1or dt2 and builds a dynamic query that it then executes. It relies on the sys.columns and sys.tables (which might be bad) and has the source table names dt1and dt2hard coded.
In my opinion this is not a good solution, and the correct solution to the problem might be to alter the data model so that leavebecomes an entity of it's own.
I've commented out the EXECUTEat the end and left the PRINTin so you can see what the query will do before executing it.
DECLARE #Columns VARCHAR(MAX)
SELECT #Columns = COALESCE(#Columns + ',' + name + '', '' + name + '') FROM (
SELECT DISTINCT
'CAST(ISNULL(dt1.' + name + ',0) AS VARCHAR) + ''/'' + CAST(dt2.' + name + ' AS VARCHAR) AS ' + name + ' ' AS name
FROM sys.columns
WHERE NAME LIKE '%leave%'
AND object_id IN (SELECT object_id FROM sys.tables WHERE name IN ('dt1', 'dt2'))) LeaveColumns
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'
SELECT
dt1.empid,
dt1.empname, ' + #Columns + '
FROM dt1
INNER JOIN dt2 ON dt1.empid=dt2.empid'
PRINT #SQL -- Uncomment to see the query which will be run
--EXECUTE(#SQL)
For me this gives the following output (with a slightly altered table structure where I included more leavecolumns):
SELECT
dt1.empid,
dt1.empname,
CAST(ISNULL(dt1.casualleave,0) AS VARCHAR) + '/' + CAST(dt2.casualleave AS VARCHAR) AS casualleave ,
CAST(ISNULL(dt1.sickleave,0) AS VARCHAR) + '/' + CAST(dt2.sickleave AS VARCHAR) AS sickleave ,
CAST(ISNULL(dt1.someotherleave,0) AS VARCHAR) + '/' + CAST(dt2.someotherleave AS VARCHAR) AS someotherleave ,
CAST(ISNULL(dt1.yetanotherleave,0) AS VARCHAR) + '/' + CAST(dt2.yetanotherleave AS VARCHAR) AS yetanotherleave
FROM dt1
INNER JOIN dt2 ON dt1.empid=dt2.empid

Resources