SQL Server to produce XML data rows from JOINed select statement - sql

I have a three tables in SQL Server 2008 which are setup as follows:
EMPLOYEE TABLE
empid(PK)
1
2
joined to EMPLOYEEATTRIBUTES
dataId(PK) | empId(FK) | attributeid | attributeVal
10 | 1 | A1 | somevalue1
20 | 1 | A2 | somevalue2
30 | 2 | A1 | somevalue3
40 | 2 | A3 | somevalue4
joined to ATTRIBUTES
attributeid | attributeName
A1 | attribute1
A2 | attribute2
A3 | attribute3
I need to get the xml data out into the following format
<rows>
<row empid="1">
<attribute1>somevalue1</attribute1>
<attribute2>somevalue2</attribute1>
</row>
<row empid="2">
<attribute1>somevalue3</attribute1>
<attribute3>somevalue4</attribute1>
</row>
</rows>
Anyone know how this can be done??

If you want to skip all of the gory details and just see an answer, look at the SQL query at the bottom of this posting.
The main challenge here is that the various SQL Server FOR XML options cannot generate the dynamic element names stipulated in the desired output. Therefore, my first answer is to consider simply returning a conventional SQL result set and having the client generate the XML. It is a very simple streaming transformation. However, this might not be an option for you, so we continue on the path of having SQL Server generate the XML.
My second thought was to use SQL Server's built-in XQuery functionality to perform the transformation, thus:
/* WARNING: the following SQL does not work */
SELECT
CAST((SELECT * FROM data FOR XML RAW) AS XML)
.query('
<rows>
{
for $empId in distinct-values(/row/#empId)
return
<row empid="{$empId}">
{
for $attr in /row[#empId = $empId]
return
attribute { "attribute" } { $attr/#attributeValue }
}
</row>
}
</rows>
')
Alas, this does not work. SQL Server complains:
Msg 9315, Level 16, State 1, Line 25
XQuery [query()]: Only constant expressions are supported for the name expression
of computed element and attribute constructors.
Apparently, the XQuery implementation suffers from the same limitation as the FOR XML features. So, my second answer is to suggest generating the XML on the client side :) But if you insist on generating the XML from SQL, then fasten your seatbelts...
The overall strategy is going to be to abandon SQL Server's native facilities for SQL generation. Instead, we are going to build up the XML document using string concatenation. If this approach is offensive, you can stop reading now :)
Let's start with generating a sample dataset to play with:
SELECT NULL AS empId INTO employee WHERE 1=0
UNION SELECT 1
UNION SELECT 2
SELECT NULL AS dataId, NULL AS empId, NULL AS attributeId, NULL AS attributeVal INTO employeeAttributes WHERE 1=0
UNION SELECT 10, 1, 'A1', 'someValue1'
UNION SELECT 20, 1, 'A2', 'someValue2'
UNION SELECT 30, 2, 'A1', 'someValue3'
UNION SELECT 40, 2, 'A3', 'someValue4 & <>!'
SELECT NULL AS attributeId, NULL AS attributeName INTO attributes WHERE 1=0
UNION SELECT 'A1', 'attribute1'
UNION SELECT 'A2', 'attribute2'
UNION SELECT 'A3', 'attribute3'
Note that I have changed the value of the last attribute in the provided example to include some XML-unfriendly characters.
Now, put together a basic SQL query to perform the necessary joins:
SELECT
e.empId
, a.attributeName
, ea.attributeVal
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
which gives this result:
empId attributeName attributeVal
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
Those funny characters in the last attribute are going to give us trouble. Let's change the query to escape them.
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
FROM cruftyData
)
SELECT * FROM data
with results:
empId attributeName attributeValXml
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
This ensures that attribute values can now be safely used in an XML document. What about attribute names? The rules for XML attribute names are more strict than those for element content. We will assume that the attributes names are valid XML identifiers. If this is not true, then some scheme will need to be devised to convert the names in the database to valid XML names. This is left as an exercise to the reader :)
The next challenge is to ensure that the attributes are grouped together for each employee, and we can tell when we are at the first or last value in a group. Here is the updated query:
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
SELECT * FROM data ORDER BY 1, 2
The only change is to add the down and up columns to the result set:
empId attributeName attributeVal down up
1 attribute1 someValue1 2 1
1 attribute2 someValue2 1 2
2 attribute1 someValue3 2 1
2 attribute3 someValue4 & <>! 1 2
We can now identify the first attribute for an employee because up will be 1. The last attribute can be identified in similar fashion using the down column.
Armed with all of this, we are now equipped to perform the nasty business of building up the XML result using string concatenation.
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT xml1, xml2, xml3
--SELECT #result = #result + 'wombat' + xmlString
FROM xmlData
ORDER BY empId, up
with the result:
xml1 xml2 xml3
<row id="1"> <attribute1>someValue1</attribute1>
<attribute2>someValue2</attribute2> </row>
<row id="2"> <attribute1>someValue3</attribute1>
<attribute3>someValue4 & <>!</attribute3> </row>
All that remains is to concatenate all of the rows together, and to add the root rows tags. Since T-SQL does not (yet) have a string concatenation aggregate function, we will resort to using a variable as an accumulator. Here is the final query, in all its hacky glory:
DECLARE #result AS NVARCHAR(MAX)
SELECT #result = '<rows>'
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT #result = #result + xml1 + xml2 + xml3
FROM xmlData
ORDER BY empId, up
SELECT #result = #result + '</rows>'
SELECT #result
The XML ends up in the #result variable. You can check that it is well-formed XML using:
SELECT CAST(#result AS XML)
The final XML looks like this:
<rows><row id="1"><attribute1>someValue1</attribute1><attribute2>someValue2</attribute2></row><row id="2"><attribute1>someValue3</attribute1><attribute3>someValue4 & <>!</attribute3></row></rows>

You can get close - but you can't get your desired output 100%.
Using this query:
SELECT
EmpID AS '#empid',
(
SELECT
a.AttributeName AS '#name',
ea.AttributeVal
FROM dbo.EmployeeAttributes ea
INNER JOIN dbo.Attributes a ON ea.AttributeId = a.AttributeId
WHERE ea.EmpID = e.EmpID
FOR XML PATH ('attribute'), TYPE
)
FROM dbo.Employee e
FOR XML PATH('row'), ROOT('rows')
you get this output:
<rows>
<row empid="1">
<attribute name="Attribute1">
<AttributeVal>SomeValue1</AttributeVal>
</attribute>
<attribute name="attribute2">
<AttributeVal>SomeValue2</AttributeVal>
</attribute>
</row>
<row empid="2">
<attribute name="Attribute1">
<AttributeVal>SomeValue3</AttributeVal>
</attribute>
<attribute name="attribute3">
<AttributeVal>SomeValue4</AttributeVal>
</attribute>
</row>
</rows>
What you cannot do is make the inner XML nodes have tag names that match the attribute name - you have to use some fixed tag name (like <attribute> in my sample), and then apply the values that are retrieved from your tables as either attributes on those XML tags (like the name= attribute in my sample) or as XML element values.
As far as I know, there is no way to use the AttributeValue as the XML tag name....

Here's an answer, but the PIVOT command limits you in that you have to know the name of your attributes in advance. With a little tweaking, you could probably do this dynamically (try searching for dynamic pivot in SQL Server 2005):
DECLARE #Employee TABLE ( empid INT )
DECLARE #EA TABLE
(
dataid INT
, empid INT
, attributeid CHAR(2)
, AttributeVal VARCHAR(100)
)
DECLARE #Attributes TABLE
(
AttributeID CHAR(2)
, AttributeName VARCHAR(100)
)
INSERT INTO #Employee
VALUES ( 1 ),
( 2 )
INSERT INTO #EA
( dataid, empid, attributeid, AttributeVal )
VALUES ( 10, 1, 'A1', 'somevalue1' )
, ( 20, 1, 'A2', 'somevalue2' )
, ( 30, 2, 'A1', 'somevalue3' )
, ( 40, 2, 'A3', 'somevalue4' )
INSERT INTO #Attributes
( AttributeID, AttributeName )
VALUES ( 'A1', 'attribute1' )
,
( 'A2', 'attribute2' )
,
( 'A3', 'attribute3' )
SELECT empID as '#empid'
, attribute1
, attribute2
, attribute3
, attribute4
FROM ( SELECT e.empid
, a.AttributeName
, ea.AttributeVal
FROM #Employee e
JOIN #EA ea ON e.empid = ea.empid
JOIN #Attributes a ON ea.attributeid = a.attributeid
) ps PIVOT
( MIN(AttributeVal) FOR AttributeName IN ( [attribute1], [attribute2], [attribute3], [attribute4] ) ) AS pvt
FOR XML PATH('row'), ROOT('rows')

Related

Display row into column in SQL

I have Two SQL Table
1st Table Name :- AttributeType
ID
Name
1
Name
2
Address
3
Amount
2nd Table Name :- AttributeValue
ID
AttributeId
Value
1
1
John
2
2
Ohio,USA
3
3
500$
I want to combine this two table and join with other table column name will be display as per value in Attribute type table (if there is 5 rows, 5 column will be display in output table)
Output would be
Output table
ID
Name
Address
Amount
1
John
Ohio,USA
500$
I don't have idea about how to write query
Can anyone help me?
Thanks in advance.
I have added some changes to the data given above. Your Table column name and value is same which is a problem when using pivoting logic. So I changed it. ID id taken indirectly using rownumber. if we use id provided in the table, it will not give you proper result.
create table AttributeType
(ID int
,[AttName] varchar(100) --Changed to [AttName]
)
insert into AttributeType values (1, 'Name')
,(2,'Address')
,(3, 'Amount')
Create table AttributeValue
(
ID int
, AttributeId int
, [Value] varchar(100)
)
insert into AttributeValue values (1,1,'John')
,(2,2, 'Ohio,USA')
,(3,3, '500$')
SELECT
ROW_NUMBER() OVER (ORDER BY [Name]) as ID ,[Name], address, Amount FROM
(
SELECT
[a].[AttName]
,[av].[Value]
FROM AttributeValue av
INNER JOIN AttributeType [a]
ON a.ID = av.AttributeId
) as [sourceTable]
PIVOT
(
MAX([sourceTable].[Value] ) FOR [sourceTable].[AttName] IN ([Name], [Address],[Amount])
) AS pivoted
Dynamic Approach
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(a.[AttName])
FROM AttributeType [a]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ROW_NUMBER() OVER (ORDER BY [Name]) as ID , ' + #cols + ' from
(
SELECT
[a].[AttName]
,[av].[Value]
FROM AttributeValue av
INNER JOIN AttributeType [a]
ON a.ID = av.AttributeId
) as [sourceTable]
pivot
(
MAX([sourceTable].[Value] ) FOR
[sourceTable].[AttName] in (' + #cols + ')
) as pivoted '
execute(#query)
We can use pivoting logic here:
SELECT
av.ID,
MAX(CASE WHEN at.Name = 'Name' THEN av.Value END) AS Name,
MAX(CASE WHEN at.Name = 'Address' THEN av.Value END) AS Address,
MAX(CASE WHEN at.Name = 'Amount' THEN av.Value END) AS Amount
FROM AttributeValue av
INNER JOIN AttributeType at
ON at.ID = av.AttributeId
GROUP BY
av.ID;
Note: There appears to be a type in your AttributeValue table. In order for this to work, all ID values for a given person should have the same value. In this case, all three sample records should have an ID value of 1.

Combine Multi Rows with COALESCE

Using SQL-Server 2012
I have the following Table:
Id Description
6192 Salzburg
6193 Salzburg
6194 Salzburg
6196 Innsbruck
6197 Innsbruck
6198 Innsbruck
6199 Innsbruck
6201 Bregenz
6202 Bregenz
6203 Bregenz
I want to Select each Distinct "Description" with all the Id's together in one string:
Description Ids
Salzburg '6192,6193,6194'
Innsbruck '6196,6197,6198'
I saw some similar code on this site [How to concatenate text from multiple rows into a single text string in SQL server?, but I couldn't figure it out yet for my purpose (don't want to use XML Path!). Here is what I have tried so far:
DECLARE #ids AS Nvarchar(MAX)
SELECT #ids = COALESCE(#ids + ',', '') + CAST(t.Id AS nvarchar(5))
FROM (SELECT tmp.Id FROM (SELECT id, [Description] FROM tblMasterPropValues WHERE IdCategory = 253 AND IsActive = 1) as tmp
WHERE [Description] = tmp.[Description]) AS t
SELECT #ids
--SELECT DISTINCT [Description], #ids AS IDs FROM tblMasterPropValues WHERE IdCategory = 253 AND IsActive = 1 AND Id IN (#ids)
I can't really get my head around it, and would appreciate any help on it.
You can try using STUFF() function
SELECT description, Ids = STUFF(
(SELECT ',' + Id
FROM tblMasterPropValues t1
WHERE t1.description = t2.description
FOR XML PATH (''))
, 1, 1, '') from tblMasterPropValues t2
group by description;
For that FOR XML PATH() is the right clause so, you can do :
SELECT DISTINCT v.description, STUFF(v1.ids, 1, 1, '''') + ''''
FROM tblMasterPropValues v CROSS APPLY
(SELECT ', '+ CAST(v1.Id AS VARCHAR(255))
FROM tblMasterPropValues v1
WHERE v1.description = v.description
FOR XML PATH('')
) v1(ids);
You can also make it by using recursive CTE
DECLARE #tblMasterPropValues TABLE (Id INT, Description VARCHAR(20))
INSERT INTO #tblMasterPropValues VALUES
(6192 , 'Salzburg'),
(6193 , 'Salzburg'),
(6194 , 'Salzburg'),
(6196 , 'Innsbruck'),
(6197 , 'Innsbruck'),
(6198 , 'Innsbruck'),
(6199 , 'Innsbruck'),
(6201 , 'Bregenz'),
(6202 , 'Bregenz'),
(6203 , 'Bregenz')
;WITH Tbl AS
(
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY Description ORDER BY Id) AS RN,
COUNT(*) OVER(PARTITION BY Description) AS CNT
FROM #tblMasterPropValues
)
, Rcr AS (
SELECT *, CAST(Id AS varchar(max)) Ids
FROM Tbl WHERE RN = 1
UNION ALL
SELECT T.*, Rcr.Ids + ',' + CAST(T.Id AS VARCHAR(10)) Ids
FROM Rcr
INNER JOIN Tbl T ON T.RN = Rcr.RN + 1 and Rcr.Description = T.Description
)
SELECT RN, Description, Ids FROM Rcr
WHERE RN = CNT
Result:
Description Ids
-------------------- -----------------------
Salzburg 6192,6193,6194
Innsbruck 6196,6197,6198,6199
Bregenz 6201,6202,6203
Try this:
DECLARE #Table TABLE(ID INT, Description VARCHAR(25))
INSERT INTO #Table
VALUES (6192,'Salzburg' )
,(6193,'Salzburg' )
,(6194,'Salzburg' )
,(6196,'Innsbruck')
,(6197,'Innsbruck')
,(6198,'Innsbruck')
,(6199,'Innsbruck')
,(6201,'Bregenz' )
,(6202,'Bregenz' )
,(6203,'Bregenz' )
Query:
SELECT DISTINCT T2.Description,
SUBSTRING(
(
SELECT ','+CAST(T1.ID AS VARCHAR) AS [text()]
FROM #Table T1
WHERE T1.Description = T2.Description
ORDER BY T1.Description
FOR XML PATH ('')
), 2, 1000) [Ids]
FROM #Table T2
Result:
Description Ids
Bregenz 6201,6202,6203
Innsbruck 6196,6197,6198,6199
Salzburg 6192,6193,6194

Replace comma separated master data with description in a column

There are 2 SQL Server tables:
Products:
Name Status Code
------------------------------------
Product 1 1001, 1003
Product 2 1001, 1005, 1006
Status:
Code Description
------------------------------------
1001 State A
1003 State B
1005 State C
1006 State D
...
I wish to get something like:
Product Status
---------------------------------------------
Product 1 State A, State B
Product 2 State A, State C, State D
You can use a query like below:
See working demo
create table Products (Name varchar(100), [Status Code] varchar(100));
insert into Products values
('Product 1', '1001, 1003')
,('Product 2', '1001, 1005, 1006');
create table [Status] (Code varchar(100), Description varchar(100));
insert into [Status] values
('1001', 'State A')
,('1003', 'State B')
,('1005', 'State C')
,('1006', 'State D')
; WITH X AS
(
SELECT
P1.Name,
S.Description
FROM
(
SELECT *,
cast('<X>'+replace(P.[Status Code],',','</X><X>')+'</X>' as XML) AS xmlprods FROM Products P
)P1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(100)') AS splitdata
FROM P1.xmlprods.nodes('X') AS fdata(D)) O
LEFT JOIN [Status] S
ON S.Code= LTRIM(RTRIM(O.splitdata ))
)
SELECT
Name,
Description= STUFF((
SELECT ',' + Description FROM x AS x2
WHERE x2.Name = x.Name
ORDER BY Name FOR XML PATH,
TYPE).value(N'.[1]',N'varchar(max)'), 1, 1, '')
FROM
X
GROUP BY Name
You can try this. I used a temporary table but you can use any means you want
SELECT p.Product , p.Status , s.Description INTO #tmp FROM (
SELECT Name Product, TRIM(value ) Status FROM tblProduct CROSS
APPLY STRING_SPLIT(TRIM(StatusCode), ',') ) p JOIN tblStatus s ON
s.Code = p.Status
select distinct Product
, stuff((
select ',' + tmp2.Description
from #tmp tmp2
where tmp2.Product = tmp1.Product
order by tmp2.Product
for xml path('')
) ,1,1,'') as StatusCode from #tmp tmp1 group by Product
Below Query will help to make any comma separated to list.
SELECT A.OtherID,
Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT OtherID,
CAST ('<M>' + REPLACE(Data, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM Table1
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a)

SQL Query using inner join

CategoryTable
Code Name
1 Food
2 Non-Food
Existing Table Consists list of category, as for example, I have two only Food and Non-Food
As challenge, I am assigning tenants with category or categories (multiple assignment, as there are tenants which are categorized as food and non-food). I i used to insert Tenant and Code to a new table creating this output
TenantAssignTable
Tenant Code
Tenant1 1,2
Tenant2 1
What I need to do, is to load the TenantAssingTable to gridview consisting the Name of the CategoryCode too like this
Desired Output
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
I used inner join in my code, but this is limited as I have a string of combined code in Code column.
Select a.tenant, a.ccode, b.name
from TenantAssignTable a inner join CategoryTable b
on a.CCode = b.code
Is there anyway to achieve this kind of output? I know that this is unusual in SQL coding but this is what is challenge as what the desired output is concerned and needs which is to have a multiple assignment of category to a single tenant.
Thanks in advance!
Think simple;
You can with LIKE and XML PATH
DECLARE #CategoryTable TABLE (Code VARCHAR(50), Name VARCHAR(50))
INSERT INTO #CategoryTable
VALUES
('1', 'Food'),
('2', 'Non-Food')
DECLARE #TenantAssignTable TABLE (Tenant VARCHAR(50), Code VARCHAR(50))
INSERT INTO #TenantAssignTable
VALUES
('Tenant1', '1,2'),
('Tenant2', '1')
SELECT
T.Tenant ,
T.Code,
STUFF(
(SELECT
',' + C.Name
FROM
#CategoryTable C
WHERE
',' + REPLACE(T.Code, ' ', '') + ',' LIKE '%,' + C.Code + ',%'
FOR XML PATH('')
), 1, 1, '') A
FROM
#TenantAssignTable T
Result:
Tenant Code A
--------------- ------------ ---------------
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
You can use some XML transformations:
DECLARE #x xml
SELECT #x = (
SELECT CAST('<t name="'+a.tenant +'"><a>'+REPLACE(a.code,',','</a><a>') +'</a></t>' as xml)
FROM TenantAssignTable a
FOR XML PATH('')
)
;WITH cte AS (
SELECT t.v.value('../#name','nvarchar(max)') as Tenant,
t.v.value('.','int') as CCode,
ct.Name
FROM #x.nodes('/t/a') as t(v)
INNER JOIN CategoryTable ct
ON ct.Code = t.v.value('.','int')
)
SELECT DISTINCT
c.Tenant,
STUFF((SELECT ','+CAST(CCode as nvarchar(10))
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as CCode,
STUFF((SELECT ','+Name
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as Name
FROM cte c
Output:
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
The first part (defining #x variable) will bring your table to this kind of XML:
<t name="Tenant1">
<a>1</a>
<a>2</a>
</t>
<t name="Tenant2">
<a>1</a>
</t>
Then in CTE part we join XML with table of categories. And after all get data from CTE with the help of FOR XML PATH.
Create Function as below which return Table from separated Value
CREATE FUNCTION [dbo].[fnSplit]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
Create Function as below which return comma separated Name
CREATE FUNCTION [dbo].[GetCommaSeperatedCategory]
(
#Codes VARCHAR(50)
)
RETURNS VARCHAR(5000)
AS
BEGIN
-- Declare the return variable here
DECLARE #Categories VARCHAR(5000)
SELECT #Categories= STUFF
(
(SELECT ',' + convert(varchar(10), Name, 120)
FROM Category
WHERE Code IN (SELECT Id FROM [dbo].[fnSplit] (#Codes,',') )
ORDER BY Code
FOR XML PATH (''))
, 1, 1, '')
RETURN #Categories
END
AND Last:
SELECT
Tenant,
Code,
(SELECT [dbo].[GetCommaSeperatedCategory] (Code)) AS Name
FROM TblTenant

Comma Separated SQL Server Result Set 'JOINED' with other Columns

I have a table say ProjectMaster:
Id ProjectName
1 A
2 B
3 C
another table ProjectMeter
Id ProjectId MeterNumber
1 1 #0001
2 1 #0002
3 1 #0003
4 2 #0004
5 2 #0005
6 3 #0006
I wish to have following output
ProjectName MeterNumbers
A #0001, #0002, #0003
B #0004, #0005
C #0006
I tried this and this, but unable to solve my problem.
I cannot use a table variable.
I have a already written Stored Procedure and it brings data from many joined tables. ProjectMaster also happens to be joined in one of these tables. Now am required to fetch data from ProjectMeter, such that, each row has concatenated ProjectMeter.MeterNumber corresponding to the ProjectId in that column.
right now, I get concatenated list of all meternumbers in all the rows.
I cannot use CURSOR, TABLE variable , Temp TABLE
( I hope still something can be done to my cause)
please help.....
Try this:
SELECT projectname, STUFF((SELECT distinct ', ' + meternumber
from projectmeter m
where p.id = m.projectid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'') MeterNumbers
from projectmaster p
See SQL Fiddle with Demo
DECLARE #ProjectMaster AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectName VARCHAR(2)
)
DECLARE #ProjectMeter AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectID INT ,
MeterNumber VARCHAR(50)
)
INSERT INTO #ProjectMaster
( ProjectName )
VALUES ( 'A' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0001' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0002' )
SELECT pMaster.ID, STUFF(( SELECT ',' + MeterNumber
FROM #ProjectMeter
FOR
XML PATH('')
), 1, 1, '') AS 'Concat Result'
FROM #ProjectMeter pMeter
INNER JOIN #ProjectMaster pMaster ON pMaster.ID = pMeter.ProjectID
GROUP BY pMaster.ID
I have used table variables here but surely you just need to drop the #'s as I have used the same table names as you have specified? Not sure if this is okay? :)
Also in MS SQL you can do it using recursive query with CTE.
Here is a SQLFiddle demo
;with t1 as (
select t.*,
cast(meternumber as varchar(max)) as m2,
0 as level
from ProjectMeter t
where not exists
(select id
from ProjectMeter l
where l.id<t.id and l.ProjectId=t.ProjectID
)
union all
select b.*,
cast(c.m2+','+b.MeterNumber as varchar(max)) as m2,
c.level+1 as level
from ProjectMeter b
inner join t1 c
on (c.id < b.id) and (b.ProjectID=c.ProjectId)
)
select pm.ProjectName as ProjectName,
t1.m2 as MeterNumbers
from t1
inner join
(select ProjectId,max(level) ml
from t1
group by ProjectId
) t2
on (t1.ProjectId=t2.ProjectID) and (t1.level=t2.ml)
left join ProjectMaster pm
on (t1.ProjectId=pm.Id)
order by t1.ProjectID