I\'ve got the following tables:
EntryTag
---------
EntryID
TagID
Example putput (EntryID, TagID):
1 2
1 4
1 5
2 3
2 4
2 5
e
select ET1.EntryID,
(
select ', '+T.Name
from Tags as T
inner join EntryTag as ET2
on T.TagID = ET2.TagID
where ET1.EntryID = ET2.EntryID
for xml path(''), type
).value('substring(text()[1], 3)', 'varchar(max)') as TagsCommaDelimited
from EntryTag as ET1
group by ET1.EntryID
Dissecting the query
The main query does a group by
so you only get one row for each EntryID
.
The column TagsCommaDelimited
is created with a correlated subquery.
In SQL Server for xml path is used to create a XML representation of a query result. You have good control over how the XML is created by using column aliases and the parameters to path
and root
.
The concatenated value ', '+T.Name
in the corelated subquery will not have a column name and the empty parameter to for xml path('')
creates the xml without any tags at all. There will be only one text value returned.
When you add type to a for xml
query the data type will be XML
.
To get a value out of a XML you should use the value() method. You could cast to a string but if you did that you would for instance get &
in the string wherever you have used &
.
The first parameter in the value()
function is the xQuery expression used to get the value you want. Use text()
to specify that you only want the value for the current element. [1]
is telling SQL Server that you want the first text node found (you only have one here) but it is still necessary.
The string created by the for xml
query has an extra comma and a space at the beginning of the string and that needs to be removed. Here I use the XQuery function substring to get everything but the first two characters.
The second parameter to value()
specifies the datatype that should be returned.
DECLARE @TableOne TABLE
(
EntryID INT,
TagID INT
)
DECLARE @TableTwo TABLE
(
TagID INT,
Name NVARCHAR(100)
)
INSERT INTO @TableOne (EntryID,TagID)
VALUES (1,2)
,(1,4)
,(1,5)
,(2,3)
,(2,4)
,(2,1)
INSERT INTO @TableTwo (TagID,Name)
VALUES (1,'Daniel')
,(2,'Samuel')
,(3,'Petkov')
,(4,'Ivan')
,(5,'Jack')
/*
In this CTE we are going to format the values int the folowing way:
1 2,4,5
2 1,3,4
Or for eaech EntryIDs, we will have all its TagIDs
*/
;WITH CTE AS
(
SELECT DISTINCT EntryID
,(SELECT SUBSTRING((SELECT ',' + CAST(TagID AS NVARCHAR(10)) FROM @TableOne AS T1 WHERE T1.EntryID=T2.EntryID ORDER BY TagID FOR XML PATH('')),2,200)) AS CSVTags
FROM @TableOne T2
)
/*
Here we are replacing the EntryIDs with their names from the @TableTwo:
*/
SELECT EntryID
,(SELECT SUBSTRING((SELECT ',' + Name FROM @TableTwo WHERE CSVTags LIKE '%'+CAST(TagID AS NVARCHAR(5))+'%' ORDER BY TagID FOR XML PATH('')),2,200) AS CSV)
FROM CTE