Dynamic columns not getting created in T-SQL - sql

I have the following tables:
tblengineeringlookupcolumnmaster
elccolumnid | elclookupcode | elccolumnname | elcisrequired
1 | 64 | FirstName | 1
2 | 64 | LastName | 1
3 | 65 | abc | 1
4 | 65 | xyz | 1
tblengineeringlookupdetail
eldrecordId | eldlookupcode |eldlookupsequence |eldlookupvalue | eldlookupvaluedescription
245 | 64 | 0 | Red | Aravinth,Arumugam
246 | 64 | 1 | Blue | Santhosh,Chandran
247 | 64 | 2 | Green | Karthik,Balasubramanian
When I pass '64' as the parameter to the procedure.
I got the output as:
FirstName | LastName | eldRecordId
-------------------------------------
Aravinth | Arumugam | 245
Santhosh | Chandran | 246
Karthik | Balasubramanian| 247
The Store procedure used is
//SP
-- Select the columns to be used
DECLARE #tcol TABLE
(
ID INT IDENTITY(1,1)
, elclookupcode INT
, elccolumnname VARCHAR(100)
)
-- Insert the records into the table
INSERT INTO #tcol (elclookupcode, elccolumnname)
SELECT elclookupcode,elccolumnname FROM tblEngineeringLookupColumnMaster WHERE elclookupcode=#LookupCode
-- Select the columns which should be as output as a table
DECLARE #temp TABLE
(
elcLookupCode INT
, RecordId INT
, txt VARCHAR(8000)
)
-- Select the records from the table and insert
INSERT INTO #temp (elcLookupCode, RecordId, txt)
SELECT eldLookupCode,eldRecordId, eldLookupValueDescription FROM tblEngineeringLookupDetail WHERE eldLookupCode=#LookupCode
DECLARE #SQL NVARCHAR(MAX)
-- Have a table for the selected values
;WITH cte AS
(
SELECT
token = ', [' + d2.elccolumnname + '] = ''' + d.token + ''''
, d.RecordId
FROM (
SELECT
token = t.c.value('.', 'VARCHAR(50)')
, a.RecordId
, a.elcLookupCode
, rn = ROW_NUMBER() OVER (PARTITION BY a.RecordId ORDER BY a.RecordId)
FROM (
SELECT
RecordId
, elcLookupCode
, txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') t(c)
) d
-- Select the columns to be mapped
JOIN (
SELECT
elclookupcode
, elccolumnname
, rn = ROW_NUMBER() OVER (PARTITION BY elclookupcode ORDER BY elclookupcode)
FROM #tcol
) d2 ON d.elcLookupCode = d2.elclookupcode AND d2.rn = d.rn
)
-- Join all the records taken
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL SELECT '+ STUFF((
SELECT t2.token
FROM cte t2
WHERE t2.RecordId = t.RecordId
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, ''), ',[RecordId] = ' + CAST(RecordId AS VARCHAR(10))
FROM #temp t
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
EXEC sp_executesql #SQL
END
The question now is:
If I pass '65' as parameter to the procedure,Where there are values in the first table(tblengineeringlookupcolumnmaster) and no corresponding values in the second table(tblengineeringlookupdetail) I'm getting no results( A message showing 3rows affected).
But I have to get the columns headers alone.
'Sample:
xyz | abc | eldrecordId
NULL |NULL | NULL
Why i'm not getting this? Where i'm wrong?

Try this one -
DECLARE #tcol TABLE
(
ID INT IDENTITY(1,1)
, elclookupcode INT
, elccolumnname VARCHAR(20)
)
INSERT INTO #tcol (elclookupcode, elccolumnname)
VALUES
(65, 'FirstName'),
(65, 'LastName')
DECLARE #temp TABLE
(
elcLookupCode INT
, eldRecordId INT
, txt VARCHAR(100)
)
INSERT INTO #temp (elcLookupCode, eldRecordId, txt)
VALUES
(64, 245, 'Aravinth,Arumugam'),
(64, 246, 'Santhosh,Chandran'),
(64, 247, 'Karthik,Balasubramanian')
DECLARE #SQL NVARCHAR(MAX)
;WITH cte AS
(
SELECT
token = ', [' + d2.elccolumnname + '] = ''' + d.token + ''''
, d.eldRecordId
FROM (
SELECT
token = t.c.value('.', 'VARCHAR(50)')
, a.eldRecordId
, a.elcLookupCode
, rn = ROW_NUMBER() OVER (PARTITION BY a.eldRecordId ORDER BY a.eldRecordId)
FROM (
SELECT
eldRecordId
, elcLookupCode
, txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') t(c)
) d
LEFT JOIN (
SELECT
elclookupcode
, elccolumnname
, rn = ROW_NUMBER() OVER (PARTITION BY elclookupcode ORDER BY elclookupcode)
FROM #tcol
) d2 ON d.elcLookupCode = d2.elclookupcode AND d2.rn = d.rn
)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL SELECT [eldRecordId] = ' + CAST(eldRecordId AS VARCHAR(10)) + ', ' +
'[elcLookupCode] = ' + CAST(elcLookupCode AS VARCHAR(10)) + ISNULL(STUFF((
SELECT t2.token
FROM cte t2
WHERE t2.eldRecordId = t.eldRecordId
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, ', '), '')
FROM #temp t
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
EXEC sys.sp_executesql #SQL

Related

SQL query in out time attendance each days

I'm a newbie in SQL Server; I have trouble to write this query.
This is my sample data:
| Id | date | time |
+----+-----------+------------+
| 1 | 2020-11-1 | 07:30:00 |
| 1 | 2020-11-1 | 15:50:00 |
| 2 | 2020-11-1 | 07:30:00 |
| 2 | 2020-11-1 | 15:54:00 |
| 1 | 2020-11-2 | 07:31:00 |
| 1 | 2020-11-2 | 15:46:00 |
This is the result I need:
| id |in_2020_11_01 | out_2020_11_01 | in_2020_11_02 | out_2020_11_02 |
+----+--------------+-----------------+----------------+----------------+
| 1 | 07:30:00 | 15:50:00 | 07:31:00 | 15:46:00 |
| 2 | 07:30:00 | 15:54:00 | 00:00:00 | 00:00:00 |
How can I build the result? Is it possible to get a result like that?
Thanks in advance
You need to use dynamic T-SQL and build a PIVOT statement. Here is full working example:
DROP TABLE IF EXISTS #DataSource;
CREATE TABLE #DataSource
(
[Id] INT
,[date] DATE
,[time] TIME
);
INSERT INTO #DataSource ([Id], [date], [time])
VALUES (1, '2020-11-1', '07:30:00')
,(1, '2020-11-1', '15:50:00')
,(2, '2020-11-1', '07:30:00')
,(2, '2020-11-1', '15:54:00')
,(1, '2020-11-2', '07:31:00')
,(1, '2020-11-2', '15:46:00');
DECLARE #ColumnsPIVOT VARCHAR(MAX)
,#ColumnsSELECT VARCHAR(MAX)
,#DyanmicTSQLSTatement NVARCHAR(MAX);
WITH DataSource ([date], [prefix], [column_name]) AS
(
SELECT DISTINCT [date]
,[prefix]
,[prefix] + '_' + REPLACE([date], '-', '_')
FROM #DataSource
CROSS APPLY
(
VALUES ('in')
,('out')
) DS ([prefix])
)
SELECT #ColumnsPIVOT = STUFF
(
(
SELECT ',' + QUOTENAME([column_name])
FROM DataSource
ORDER BY [date]
,[prefix]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
)
,#ColumnsSELECT = STUFF
(
(
SELECT ', ISNULL(' + QUOTENAME([column_name]) + ', ''00:00:00'') AS ' + QUOTENAME([column_name])
FROM DataSource
ORDER BY [date]
,[prefix]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
);
-- SQL Server 2017+
/*
WITH DataSource ([date], [prefix], [column_name]) AS
(
SELECT DISTINCT [date]
,[prefix]
,[prefix] + '_' + REPLACE([date], '-', '_')
FROM #DataSource
CROSS APPLY
(
VALUES ('in')
,('out')
) DS ([prefix])
)
SELECT #ColumnsPIVOT = STRING_AGG(QUOTENAME([column_name]), ',') WITHIN GROUP(ORDER BY [date], [prefix])
,#ColumnsSELECT = STRING_AGG('ISNULL(' + QUOTENAME([column_name]) + ', ''00:00:00'') AS ' + QUOTENAME([column_name]), ',') WITHIN GROUP(ORDER BY [date], [prefix])
FROM DataSource;
*/
SET #DyanmicTSQLSTatement= 'SELECT [ID], ' + #ColumnsSELECT + '
FROM
(
SELECT [id]
,CASE ROW_NUMBER() OVER (PARTITION BY [ID], [Date] ORDER BY [Time])
WHEN 1 THEN ''in''
WHEN 2 THEN ''out''
END + ''_'' + REPLACE([date], ''-'', ''_'')
,[time]
FROM #DataSource
) DS ([id], [date], [time])
PIVOT
(
MAX([time]) FOR [date] IN (' + #ColumnsPIVOT +')
) PVT';
EXEC sp_executesql #DyanmicTSQLSTatement;

Cache SQL query to create 1 row from multiple records

I have the below record and would like to create 1 row record.
I tried STUFF, FOR XML PATH and did not work
+-----------+-------+---------+
| CLIENT_ID | Event | DX_Code |
+-----------+-------+---------+
| 54 | 5 | F45.72 |
| 54 | 5 | X45.34 |
| 54 | 5 | M98.32 |
+-----------+-------+---------+
Output = 54, 5, F45.72 X45.34 M98.32
You can do that by using STUFF() with FOR XML PATH('') as
CREATE TABLE T
([CLIENT_ID] int, [Event] int, [DX_Code] varchar(6))
;
INSERT INTO T
([CLIENT_ID], [Event], [DX_Code])
VALUES
(54, 5, 'F45.72'),
(54, 5, 'X45.34'),
(54, 5, 'M98.32')
;
SELECT DISTINCT T1.[CLIENT_ID],
T1.[Event],
STUFF(
(
SELECT ',' + T2.[DX_Code]
FROM T T2
WHERE T2.[CLIENT_ID] = T1.[CLIENT_ID]
AND T2.[Event] = T1.[Event]
FOR XML PATH ('')
) , 1, 1, ''
) Result
FROM T T1;
This should give you the expected result
SELECT CAST(t1.CLIENT_ID AS VARCHAR) + ','+ CAST(t1.Event AS VARCHAR)+ ','+
STUFF(( SELECT ' ' + t2.DX_Code AS [text()]
FROM #temp t2
WHERE
t2.CLIENT_ID = t1.CLIENT_ID
and t2.Event = t1.Event
FOR XML PATH('')
), 1, 1, '' )
AS OutputText
FROM #temp t1
GROUP BY t1.CLIENT_ID,t1.Event
Output:
54,5,F45.72 X45.34 M98.32

pivot in sql server 2012 with uniqeness

if i have a table like given below.
declare #mytble table
(
orders int,
product varchar (50),
quantity int
)
INSERT #mytble
SELECT 100,'CUP','1' UNION ALL
SELECT 100, 'PLATE',2 UNION ALL
SELECT 101,'CUP','1' UNION ALL
SELECT 102,'CUP','2' UNION ALL
SELECT 103, 'CUP',1 UNION ALL
SELECT 103,'PLATE','3' UNION ALL
SELECT 103,'GLASS','1'
SELECT * FROM #mytble
will it be possible to get output like this.
any suggestion please.
With some dynamic SQL and Dense_Rank() function
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(concat('Product',Dense_Rank() over (Order By Product)))
+ ',' + QuoteName(concat('Quantity',Dense_Rank() over (Order By Product)))
From myTable For XML Path('')),1,1,'')
Select #SQL = 'Select Orders,' + #SQL + '
From (
Select Orders,Item=concat(''Product'',Dense_Rank() over (Order By Product)),Val=cast(Product as varchar(max)) From myTable
Union All
Select Orders,Item=concat(''Quantity'',Dense_Rank() over (Order By Product)),Val=cast(Quantity as varchar(max)) From myTable
) A
Pivot (max(Val) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Orders Product1 Quantity1 Product2 Quantity2 Product3 Quantity3
100 CUP 1 NULL NULL PLATE 2
101 CUP 1 NULL NULL NULL NULL
102 CUP 2 NULL NULL NULL NULL
103 CUP 1 GLASS 1 PLATE 3

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost:

Change Columns Name dynamically in sql

I have a query result like this :
Date User1 User2 User3 ....
----------------------------------
1/1/2000 55 78 98 ...
1/1/2001 26 33 56 ...
1/1/2002 88 67 12 ...
The number of columns is not known because it is the result of a pivot query.
I would like to change the name the columns to something that looks like this :
Date User1 (blue) User2 (green) User3(brown)
The color is an information I retrieve from another table.
How can I achieve this ?
Thanks
Edit : Here is the query.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(C.Name)
from [History]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Date],' + #cols +'
from
(
select [Date], Name, Value
from [History]
) x
pivot
(
max(value)
for Name in (' + #cols + ')
) p '
execute(#query)
SQL Fiddle
Schema Setup:
create table history (date datetime, name varchar(10), value int);
insert history values
('20130101', 'user1', 123),
('20130101', 'user2', 124),
('20130101', 'user3', 125),
('20130102', 'user1', 223),
('20130102', 'user3', 223),
('20130103', 'user2', 323);
create table colours (name varchar(10), colour_name varchar(10));
insert colours values
('user1', 'blue'),
('user2', 'green'),
('user3', 'brown');
Query:
DECLARE #scols nvarchar(max),
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((
SELECT ',' + QUOTENAME(C.Name)
from (select distinct name from [History]) C
ORDER BY C.Name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'');
select #scols = STUFF((
SELECT ',' + QUOTENAME(Name) + ' AS ' + QUOTENAME(colour_Name)
from (select distinct c.name, x.colour_name
from [History] C
JOIN colours x on x.name = c.name) y
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'');
set #query = '
SELECT [Date],' + #scols +'
from (
select [Date], Name, Value
from [History]
) x
pivot
(
max(value)
for Name in (' + #cols + ')
) p ';
-- print #query --<< uncomment this line to see the query that gets generated
exec (#query);
Results:
| DATE | BLUE | GREEN | BROWN |
-------------------------------------------------------------
| January, 01 2013 00:00:00+0000 | 123 | 124 | 125 |
| January, 02 2013 00:00:00+0000 | 223 | (null) | 223 |
| January, 03 2013 00:00:00+0000 | (null) | 323 | (null) |
To get the mapping You can use a lookup table of old column name to new column name for example
CREATE TABLE colname(
oldname varchar(20),
newname varchar(20)
)
insert into colname values ( 'user1','user1 (blue)');
insert into colname values ( 'user2','user2 (green)');
then you can build an sql statement that uses this mapping
declare #sq varchar(2000)
set #sq ='select date'
select #sq = #sq + ',' + oldname + ' as [' + newname +']' from colname
set #sq = #sq + 'from ( existing query goes here ) '
select #sq
when the sql in #sq looks good you can replace the last select with
exec ( #sq )
to run the query
select Date, User1 as blue,User2 as green,User3 as brown from tableName
Use query like this.
Make use of 'as' keyword for changing column name.