Column names of a CTE in SQL Server - sql

I know it is possible to SELECT, from sys.columns and from tempdb.sys.columns the names of the columns of a specific table.
Can the same be done from a CTE?
with SampleCTE as (
Select
'Tom' as Name
,'Bombadill' as Surname
,99999 as Age
,'Withywindle' as Address
)
is there any way to know that the columns of this CTE are Name,Surname,Age and Address, without resorting to dumping the CTE result to a temporary table and reading the columns from there?
Thanks!

Here is a "dynamic" approach without actually using Dynamic SQL.
Unpivot (dynamic or not) would be more performant
Example
with SampleCTE as (
Select
'Tom' as Name
,'Bombadill' as Surname
,99999 as Age
,'Withywindle' as Address
)
Select C.*
From SampleCTE A
Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)
Cross Apply (
Select Item = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From B.XMLData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('local-name(.)','varchar(100)') not in ('ID','ExcludeOtherCol')
) C
Returns
Item Value
Name Tom
Surname Bombadill
Age 99999
Address Withywindle

Yes, it is possible sys.dm_exec_describe_first_result_set :
This dynamic management function takes a Transact-SQL statement as a parameter and describes the metadata of the first result set for the statement.
SELECT name
FROM sys.dm_exec_describe_first_result_set(
N'
with SampleCTE as (
Select
''Tom'' as Name
,''Bombadill'' as Surname
,99999 as Age
,''Withywindle'' as Address
)
SELECT * FROM SampleCTE
', NULL, NULL);
db<>fiddle demo

Related

Remove Duplicate Texts in a Column

In my temp table I have a column for a list of email addresses which might be repeated. For example:
Row#1: test#gmail.com; test#gmail.com; test#yahoo.com; abc#gmail.com
Row#2: abc#yahoo.com; abcde#yahoo.com; abcde#yahoo.com
Desired Results:
Row#1: test#gmail.com; test#yahoo.com; abc#gmail.com
Row#2: abc#yahoo.com; abcde#yahoo.com
Is there a way to achieve this in SQL Server language?
Well, assuming SQL Server 2017, and that you have a key column (or combination of columns), you could use both STRING_SPLIT and STRING_AGG:
WITH CTE AS
(
SELECT DISTINCT
T.KeyColumn,
E.Value Email
FROM dbo.YourTable T
OUTER APPLY STRING_SPLIT(Email,';') E
)
SELECT KeyColumn,
STRING_AGG(Email,';') Email
FROM CTE
GROUP BY KeyColumn
;
UPDATE for SQL Server 2016:
With no STRING_AGG you'll have to use one the old ways; for instance:
WITH CTE AS
(
SELECT DISTINCT
T.KeyColumn,
E.Value Email
FROM dbo.YourTable T
OUTER APPLY STRING_SPLIT(Email,';') E
)
SELECT t.KeyColumn,
Email = STUFF(( SELECT ';' + CONVERT(varchar(255),Email)
FROM CTE
WHERE KeyColumn = t.KeyColumn
FOR XML PATH(''), TYPE).value('.[1]','nvarchar(max)'),1,1,'')
FROM CTE t
GROUP BY t.KeyColumn
;

SQL merging rows with dynamic column headings

I am trying to populate a Gridview to have checkboxes enabled per student, but depending to certain values from this query:
#SelectedDate is provided via a TextBox as a date only
SELECT v1.StudentID,
v1.StudentPreferredName + ' ' + v1.StudentFamilyName AS StudentName,
bcs.CheckStatusName,
rce.DateSubmitted,
rcp.RollCallPeriod
FROM tblBoardingRollCallEntries AS rce
INNER JOIN vwBoardingTenants AS v1
ON v1.StudentID = rce.StudentID
AND v1.[Year] = YEAR(#SelectedDate)
INNER JOIN tblBoardingCheckStatus AS bcs
ON bcs.CheckStatusID = rce.CheckStatusID
AND bcs.StatusActive = 1
INNER JOIN tblBoardingRollCallPeriods AS rcp
ON rcp.RollCallPeriodID = rce.RollCallPeriodID
AND rcp.PeriodYear = YEAR(#SelectedDate)
AND #SelectedDate BETWEEN rcp.PeriodStart AND rcp.PeriodEnd
AND rcp.RowStatus = 1
WHERE dbo.fnDateOnly(rce.DateSubmitted) = dbo.fnDateOnly(#SelectedDate)
My gridview:
Shows the following:
The data:
I want to be able to basically condense the rows in the GridView to be one student per row and the checkboxes ticked according to RollCallPeriod text.
I am playing with SQL pivots, to get the data to be as close as possible to what I am after so as to avoid code-behind, etc. However, I cannot get this to work.
select StudentID, [1],[10],[2],[3],[4],[5],[6],[7],[8],[9]
from
(
select StudentID, RollCallID, CheckStatusID
from tblBoardingRollCallEntries
unpivot
(
value for name in ([RollCallID],[StudentID],[CheckStatusID],[DateSubmitted],[StaffID])
) unpiv
) src
pivot
(
sum(RollCallPeriodID)
for RollCallPeriodID in ([1],[10],[2],[3],[4],[5],[6],[7],[8],[9])
) piv
I receive the following error:
Lookup Error - SQL Server Database Error: The type of column
"StudentID" conflicts with the type of other columns specified in the
UNPIVOT list.
Any other ideas?
Thanks
A couple of ways you can do this depending on your actual data.
This will give you the CheckStatusName as the value for the RollCallPeriod
SELECT *
FROM (
SELECT StudentName,
CheckStatusName,
RollCallPeriod
FROM [YourQueryGoesHere]
) t
PIVOT (
MAX(CheckStatusName)
FOR RollCallPeriod IN ([6:15 AM],[8:00 AM],[3:00 PM],[6:00 PM],[9:00 PM])
) p
Or you get the status and a COUNT() to show if that Student has a value for that CheckStatusName, RollCallPeriod
SELECT *
FROM (
SELECT StudentName,
CheckStatusName,
RollCallPeriod
FROM [YourQueryGoesHere]
) t
PIVOT (
COUNT(RollCallPeriod)
FOR RollCallPeriod IN ([6:15 AM],[8:00 AM],[3:00 PM],[6:00 PM],[9:00 PM])
) p
Two options:
Instead of unpivotting directly on tblBoardingRollCallEntries: first select columns cast to a VARCHAR(...) type in a derived table, then UNPIVOT the derived table. Shortened example:
select StudentID, RollCallID, CheckStatusID
from
(
SELECT ..., CAST(StudentId AS VARCHAR(128)) AS StudentId, ... FROM tblBoardingRollCallEntries)
) AS ups
unpivot
(
value for name in ([RollCallID],[StudentID],[CheckStatusID],[DateSubmitted],[StaffID])
) unpiv
Use CROSS APPLY (SELECT CAST(StudentId AS VARCHAR(128)) UNION ALL ... ) to unpivot, that way you can UNPIVOT casting the column directly to the appropriate type.

Comma separated column ids should show values or text(with function or query)

I have a table like this
Foreign table:
select * from table1
ID......NameIds
-------------------
1 ......1, 2 (its comma separated values)
Primary table(table2)
ID Name
-------------------
1 Cleo
2 Smith
I want to show table 1 as like (I require SQL function or query for it)
ID......NameIds
-------------------
1........Cleo, smith (show text/Name instead of values)
As per stated in comments - you should really rethink your table design, but it was interesting enough to try and write a query for that:
SELECT T1.ID, NameID, Name
INTO #Temporary
FROM #Table1 AS T1
CROSS APPLY (
SELECT CAST(('<X>' + REPLACE(T1.NameIDs, ',', '</X><X>') + '</X>') AS XML)
) AS X(XmlData)
CROSS APPLY (
SELECT NameID.value('.', 'INT')
FROM XmlData.nodes('X') AS T(NameID)
) AS T(NameID)
INNER JOIN #Table2 AS T2
ON T2.ID = T.NameID
SELECT ID, STUFF(T.Names, 1, 1, '') AS Names
FROM #Table1 AS T1
CROSS APPLY (
SELECT ',' + Name
FROM #Temporary AS T
WHERE T.ID = T1.ID
ORDER BY T.NameID
FOR XML PATH('')
) AS T(Names)
Result:
ID Names
--------------
1 Cleo,Smith
What it does, it splits your comma seperated list into rows, joins them on NameIDs and then concatenates them again. Guess how efficient is that?
It's probably not the most best way to do that, but it works.

Adding aliasing to field names in Pivot SQL query

I have a query below, and need to have field [Cmp-Goal-RF-148] (which is pivoted to be a column) - I need the column title to be something besides [Cmp-Goal-RF-148], so I suppose I need to alias it. Doing this throws up an error: ([Cmp-Goal-RF-148] AS 'Ghost'). What am I missing?
select *
from
(
select EmpRvwPdDtl.Emp, EmpRvwPdDtl.Rvwr,
EmpRvwPdDtl.RvwItm,
CAST(EmpRvwPdDtl.RvwItmCom as VARCHAR(MAX)) as comment
from EmpRvwPdDtl
inner join EmpRvwPd
on (EmpRvwPd.Emp=EmpRvwPdDtl.Emp)
where EmpRvwPdDtl.RvwItmCom is not null
AND EmpRvwPd.Sup='RM04'
) as s
PIVOT
(
MAX(comment) for RvwItm in ([Cmp-Goal-RF-148])
) as pvit
You will add the alias in the final SELECT list:
select Emp, Rvwr,
[Cmp-Goal-RF-148] as Ghost -- alias goes here
from
(
select EmpRvwPdDtl.Emp, EmpRvwPdDtl.Rvwr,
EmpRvwPdDtl.RvwItm,
CAST(EmpRvwPdDtl.RvwItmCom as VARCHAR(MAX)) as comment
from EmpRvwPdDtl
inner join EmpRvwPd
on (EmpRvwPd.Emp=EmpRvwPdDtl.Emp)
where EmpRvwPdDtl.RvwItmCom is not null
AND EmpRvwPd.Sup='RM04'
) as s
PIVOT
(
MAX(comment) for RvwItm in ([Cmp-Goal-RF-148])
) as pvit

SQL Server : Columns to Rows

Looking for elegant (or any) solution to convert columns to rows.
Here is an example: I have a table with the following schema:
[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]
Here is what I want to get as the result:
[ID] [EntityId] [IndicatorName] [IndicatorValue]
And the result values will be:
1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'
And so on..
Does this make sense? Do you have any suggestions on where to look and how to get it done in T-SQL?
You can use the UNPIVOT function to convert the columns into rows:
select id, entityId,
indicatorname,
indicatorvalue
from yourtable
unpivot
(
indicatorvalue
for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;
Note, the datatypes of the columns you are unpivoting must be the same so you might have to convert the datatypes prior to applying the unpivot.
You could also use CROSS APPLY with UNION ALL to convert the columns:
select id, entityid,
indicatorname,
indicatorvalue
from yourtable
cross apply
(
select 'Indicator1', Indicator1 union all
select 'Indicator2', Indicator2 union all
select 'Indicator3', Indicator3 union all
select 'Indicator4', Indicator4
) c (indicatorname, indicatorvalue);
Depending on your version of SQL Server you could even use CROSS APPLY with the VALUES clause:
select id, entityid,
indicatorname,
indicatorvalue
from yourtable
cross apply
(
values
('Indicator1', Indicator1),
('Indicator2', Indicator2),
('Indicator3', Indicator3),
('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);
Finally, if you have 150 columns to unpivot and you don't want to hard-code the entire query, then you could generate the sql statement using dynamic SQL:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsUnpivot
= stuff((select ','+quotename(C.column_name)
from information_schema.columns as C
where C.table_name = 'yourtable' and
C.column_name like 'Indicator%'
for xml path('')), 1, 1, '')
set #query
= 'select id, entityId,
indicatorname,
indicatorvalue
from yourtable
unpivot
(
indicatorvalue
for indicatorname in ('+ #colsunpivot +')
) u'
exec sp_executesql #query;
well If you have 150 columns then I think that UNPIVOT is not an option. So you could use xml trick
;with CTE1 as (
select ID, EntityID, (select t.* for xml raw('row'), type) as Data
from temp1 as t
), CTE2 as (
select
C.id, C.EntityID,
F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
F.C.value('.', 'nvarchar(max)') as IndicatorValue
from CTE1 as c
outer apply c.Data.nodes('row/#*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'
sql fiddle demo
You could also write dynamic SQL, but I like xml more - for dynamic SQL you have to have permissions to select data directly from table and that's not always an option.
UPDATEAs there a big flame in comments, I think I'll add some pros and cons of xml/dynamic SQL. I'll try to be as objective as I could and not mention elegantness and uglyness. If you got any other pros and cons, edit the answer or write in comments
cons
it's not as fast as dynamic SQL, rough tests gave me that xml is about 2.5 times slower that dynamic (it was one query on ~250000 rows table, so this estimate is no way exact). You could compare it yourself if you want, here's sqlfiddle example, on 100000 rows it was 29s (xml) vs 14s (dynamic);
may be it could be harder to understand for people not familiar with xpath;
pros
it's the same scope as your other queries, and that could be very handy. A few examples come to mind
you could query inserted and deleted tables inside your trigger (not possible with dynamic at all);
user don't have to have permissions on direct select from table. What I mean is if you have stored procedures layer and user have permissions to run sp, but don't have permissions to query tables directly, you still could use this query inside stored procedure;
you could query table variable you have populated in your scope (to pass it inside the dynamic SQL you have to either make it temporary table instead or create type and pass it as a parameter into dynamic SQL;
you can do this query inside the function (scalar or table-valued). It's not possible to use dynamic SQL inside the functions;
Just to help new readers, I've created an example to better understand #bluefeet's answer about UNPIVOT.
SELECT id
,entityId
,indicatorname
,indicatorvalue
FROM (VALUES
(1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
(2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
(3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
(4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
indicatorvalue
FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;
Just because I did not see it mentioned.
If 2016+, here is yet another option to dynamically unpivot data without actually using Dynamic SQL.
Example
Declare #YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into #YourTable Values
(1,'A','B')
,(2,'R','C')
,(3,'X','D')
Select A.[ID]
,Item = B.[Key]
,Value = B.[Value]
From #YourTable A
Cross Apply ( Select *
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper ))
Where [Key] not in ('ID','Other','Columns','ToExclude')
) B
Returns
ID Item Value
1 Col1 A
1 Col2 B
2 Col1 R
2 Col2 C
3 Col1 X
3 Col2 D
I needed a solution to convert columns to rows in Microsoft SQL Server, without knowing the colum names (used in trigger) and without dynamic sql (dynamic sql is too slow for use in a trigger).
I finally found this solution, which works fine:
SELECT
insRowTbl.PK,
insRowTbl.Username,
attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
attr.insRow.value('.', 'nvarchar(max)') as FieldValue
FROM ( Select
i.ID as PK,
i.LastModifiedBy as Username,
convert(xml, (select i.* for xml raw)) as insRowCol
FROM inserted as i
) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/#*') as attr(insRow)
As you can see, I convert the row into XML (Subquery select i,* for xml raw, this converts all columns into one xml column)
Then I CROSS APPLY a function to each XML attribute of this column, so that I get one row per attribute.
Overall, this converts columns into rows, without knowing the column names and without using dynamic sql. It is fast enough for my purpose.
(Edit: I just saw Roman Pekar answer above, who is doing the same.
I used the dynamic sql trigger with cursors first, which was 10 to 100 times slower than this solution, but maybe it was caused by the cursor, not by the dynamic sql. Anyway, this solution is very simple an universal, so its definitively an option).
I am leaving this comment at this place, because I want to reference this explanation in my post about the full audit trigger, that you can find here: https://stackoverflow.com/a/43800286/4160788
DECLARE #TableName varchar(max)=NULL
SELECT #TableName=COALESCE(#TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
FROM sysindexes AS i
INNER JOIN sysobjects AS o ON i.id = o.id
INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
WHERE i.indid < 2
AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
AND i.rowcnt >350
AND o.xtype !='TF'
ORDER BY o.name ASC
print #tablename
You can get list of tables which has rowcounts >350 . You can see at the solution list of table as row.
The opposite of this is to flatten a column into a csv eg
SELECT STRING_AGG ([value],',') FROM STRING_SPLIT('Akio,Hiraku,Kazuo', ',')