Using XML PATH to concatenate multiple fields - sql

I'm having a bit of trouble implementing the XML PATH method of concatenating multiple rows. So, given the following table, Test.
ID Type
1 boy
2 boy
3 boy
1 girl
2 girl
3 man
3 woman
The query is:
SELECT DISTINCT a.ID,
(
SELECT b.Type + ','
FROM Test as b
WHERE a.Type = b.Type
for XML PATH ('')
)
FROM Test as a
but instead of returning:
ID Type
1 boy,girl,man,
2 boy,girl,
3 boy,girl,woman
it instead returns this:
ID Type
1 boy,boy,boy,
1 girl,girl,
2 boy,boy,boy,
2 girl,girl,
3 boy,boy,boy,
3 man,
3 woman,
What's going on?

You're joining on the wrong field.
Try
SELECT DISTINCT a.ID,
(
SELECT b.Type + ','
FROM Test as b
WHERE a.ID = b.ID
for XML PATH ('')
)
FROM Test as a

Instead of using DISTINCT prefer using GROUP BY something like this...
SELECT a.ID,
STUFF((SELECT ', ' + [Type] [text()]
FROM Test
WHERE ID = a.[ID]
for XML PATH (''),TYPE).
value('.','NVARCHAR(MAX)'),1,2,'') AS [Type]
FROM Test as a
GROUP BY a.ID

Related

SQL Server - Concatenate - Stuff doesn't work

I have this query in SQL Server and I want to concatenate using stuff the 'Products' column, but I don't know why this doesn't work...
SELECT BrandsProvider.id, BrandsProvider.Name,
(SELECT (STUFF((
SELECT ','+ CONVERT(NVARCHAR(MAX), sku)
FROM items it2
WHERE it2.sku = it.sku
FOR XML PATH('')),
COUNT('ID'), 1, ''))) AS 'Products'
FROM items it
INNER JOIN BrandsProvider ON it.IdBrandProduct = BrandsProvider.id
And the result is:
Id Name Products
--------------------------------
1 BRAND EXAMPLE PR344
1 BRAND EXAMPLE PR345
And I want this:
Id Name Products
--------------------------------
1 BRAND EXAMPLE PR344, PR345
Also I used SELECT DISTINCT in the query but the result is the same...
So, where can be the mistake?
SELECT b.id
, b.Name
, STUFF((SELECT ', ' + CAST(i.sku AS VARCHAR(10)) [text()]
FROM items i
WHERE i.IdBrandProduct = b.id
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,'') Products
FROM BrandsProvider b
GROUP BY b.id
, b.Name
SQL Server 2017 and Later Versions
If you are working on SQL Server 2017 or later versions, you can use built-in SQL Server Function STRING_AGG to create the comma delimited list:
SELECT b.id
, b.Name
, STRING_AGG(i.sku, ', ') AS Products
FROM BrandsProvider b
INNER JOIN items i ON i.IdBrandProduct = b.id
GROUP BY b.id
, b.Name;

SQL Combine multiple rows into one column and get name from Ids

Consider a database table connectouser holding two IDs:
connectouser
compID coopID
1 1
1 2
1 3
2 1
2 2
Consider another database table coop holding two IDs:
coop
ID Name
1 ABC
2 DEF
3 GHJ
I want to the following output :
Result
compID coopname
1 ABC,DEF,GHJ
2 ABC,DEF
Can anyone please help me on this.
The question was tagged MySQL for this answer.
This is a group by and group_concat():
select cu.compId, group_concat(co.name order by co.id) as coopnames
from connectouser cu join
coop co
on cu.coopID = co.ID
group by cu.compId;
In SQL Server, you can do:
select cu.compId,
stuff( (select ',' + co.name
from coop co
where cu.coopID = co.ID
order by co.id
for xml path ('')
), 1, 1, ''
) as names
from connectouser cu;
The most recent version of SQL Server supports string_agg(), which is a much better approach.

trying to concatenate a column into a comma delimited list

i have 3 tables, 1 for products and one for categories the products are assigned to. what IM trying to do is concatenate the column called stCategoryName to a single column in a comma delimited list.
Basically I have the products table containing the primary key for each product and im trying to figure out how to concatenate all the stcategoryName column next to each product so i can have a simplified return
what im trying to get is the following.
stProductID stCategoryName
123 category1,category2,category3
SELECT
dbo.StoreItemTracking.StCategoryID,
dbo.StoreItemTracking.StProductID,
dbo.StoreItemTracking.viewOrder,
dbo.StoreCategories.StCategoryName,
dbo.Store_Products.PartNumber
FROM
dbo.StoreItemTracking
INNER JOIN dbo.StoreCategories
ON dbo.StoreItemTracking.StCategoryID = dbo.StoreCategories.StCategoryID
INNER JOIN dbo.Store_Products
ON dbo.StoreItemTracking.StProductID = dbo.Store_Products.ID
Im stuck as to how to concatenate a column where the query contains 3 tables to select from.
any help greatly appreciated
Look at using coalesce to turn category into a CSV:
See example:
DECLARE #EmployeeList varchar(100)
SELECT #EmployeeList = COALESCE(#EmployeeList + ', ', '')
+ CAST(Emp_UniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1
SELECT #EmployeeList
You can also use CTE's or Subqueries. See:
http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=createacommadelimitedlist
Another nice and easy example:
http://www.codeproject.com/Articles/21082/Concatenate-Field-Values-in-One-String-Using-CTE-i
This:
FId FName
--- ----
2 A
4 B
5 C
6 D
8 E
with:
;WITH ABC (FId, FName) AS
(
SELECT 1, CAST('' AS VARCHAR(8000))
UNION ALL
SELECT B.FId + 1, B.FName + A.FName + ', '
FROM (And the above query will return
SELECT Row_Number() OVER (ORDER BY FId) AS RN, FName FROM tblTest) A
INNER JOIN ABC B ON A.RN = B.FId
)
SELECT TOP 1 FName FROM ABC ORDER BY FId DESC
becomes:
FName
----------------------------
A, B, C, D, E,
Don't understand how your products and categories are connected but in general I do like this to create comma separated lists.
SELECT table1.Id
,Csv
FROM table1
CROSS APPLY (
-- Double select so we can have an alias for the csv column
SELECT (SELECT ',' + table2.Name
FROM table2
WHERE table2.Id = table1.Id
FOR XML PATH('')
) AS RawCsv
) AS CA1
CROSS APPLY (
-- Trim the first comma
SELECT RIGHT(RawCsv, LEN(RawCsv) - 1) AS Csv
) AS CA2

How should I modify this SQL statement?

My SQL Server view
SELECT
geo.HyperLinks.CatID, geo.Tags.Tag, geo.HyperLinks.HyperLinksID
FROM
geo.HyperLinks LEFT OUTER JOIN
geo.Tags INNER JOIN
geo.TagsList ON geo.Tags.TagID = geo.TagsList.TagID ON geo.HyperLinks.HyperLinksID = geo.TagsList.HyperLinksID WHERE HyperLinksID = 1
returns these...
HyperLinksID CatID Tags
1 2 Sport
1 2 Tennis
1 2 Golf
How should I modify the above to have results like
HyperLinksID CatID TagsInOneRowSeperatedWithSpaceCharacter
1 2 Sport Tennis Golf
UPDATE: As Brad suggested I came up here...
DECLARE #TagList varchar(100)
SELECT #TagList = COALESCE(#TagList + ', ', '') + CAST(TagID AS nvarchar(100))
FROM TagsList
WHERE HyperLinksID = 1
SELECT #TagList
Now the result looks like
HyperLinksID CatID TagsInOneRowSeperatedWithSpaceCharacter
1 2 ID_OF_Sport ID_OF_Tennis ID_OF_Golf
And of course I have to combine the contents from the#TagList variable and the original SELECT statement...
Which means that I'll have to wait for the holy SO bounty :(
If SQL, try this post:
Concatenating Row Values
If you want to try your hand at CLR code, there are examples of creating a custom aggregate function for concatenation, again, for MS SQL.
This post is pretty exhaustive with lots of ways to accomplish your goal.
Using the approach from here to avoid any issues if your tag names contain special XML characters:.
;With HyperLinks As
(
SELECT 1 AS HyperLinksID, 2 AS CatID
),
TagsList AS
(
SELECT 1 AS TagId, 1 AS HyperLinksID UNION ALL
SELECT 2 AS TagId, 1 AS HyperLinksID UNION ALL
SELECT 3 AS TagId, 1 AS HyperLinksID
)
,
Tags AS
(
SELECT 1 AS TagId, 'Sport' as Tag UNION ALL
SELECT 2 AS TagId, 'Tennis' as Tag UNION ALL
SELECT 3 AS TagId, 'Golf' as Tag
)
SELECT HyperLinksID,
CatID ,
(SELECT mydata
FROM ( SELECT Tag AS [data()]
FROM Tags t
JOIN TagsList tl
ON t.TagId = tl.TagId
WHERE tl.HyperLinksID = h.HyperLinksID
ORDER BY t.TagId
FOR XML PATH(''), TYPE
) AS d ( mydata ) FOR XML RAW,
TYPE
)
.value( '/row[1]/mydata[1]', 'varchar(max)' ) TagsInOneRowSeperatedWithSpaceCharacter
FROM HyperLinks h
Edit: As KM points out in the comments this method actually automatically adds spaces so I've removed the manually added spaces. For delimiters other than spaces such as commas Peter's answer seems more appropriate.
If you know your data will not contain any problematic characters then a simpler (probably more performant) version is
SELECT CatID ,
HyperLinksID,
stuff(
( SELECT ' ' + Tag
FROM Tags t
JOIN TagsList tl
ON t.TagId = tl.TagId
WHERE tl.HyperLinksID = h.HyperLinksID
ORDER BY t.TagId
FOR XML PATH('')
), 1, 1, '') TagsInOneRowSeperatedWithSpaceCharacter
FROM HyperLinks h
Use FOR XML in a correlated subquery. For a space-delimited list:
SELECT h.HyperLinksID, h.CatID
, TagList = (
SELECT t.Tag AS [data()]
FROM geo.TagList l
JOIN geo.Tags t ON l.TagId = t.TagId
WHERE l.HyperLinksID = h.HyperLinksID
ORDER BY t.Tag
FOR XML PATH(''), TYPE
).value('.','NVARCHAR(MAX)')
FROM geo.HyperLinks AS h
WHERE h.HyperLinksID = 1
For any other delimiter:
SELECT h.HyperLinksID, h.CatID
, TagList = STUFF((
SELECT ', '+t.Tag
FROM geo.TagList l
JOIN geo.Tags t ON l.TagId = t.TagId
WHERE l.HyperLinksID = h.HyperLinksID
ORDER BY t.Tag
FOR XML PATH(''), TYPE
).value('.','NVARCHAR(MAX)')
,1,2,'')
FROM geo.HyperLinks AS h
WHERE h.HyperLinksID = 1
The subquery creates a delimited list, and then STUFF(...,1,2,'') removes the leading ,. TYPE).value() gets around most common problems w/ special characters in XML.

How do I Query an Adjacency relation (in SQL) to get a Leaf by providing a Path?

Greetings, fellow coders!
I have this table that contains categories and subcategories (actually I don't, but let's stick to a classic example):
Id ParentId Name
1 NULL A
2 1 B
3 2 C
4 3 D
5 NULL B
6 5 D
Is there a way for me to get category "D" (id 4) by querying the table for the full path? (see pseudocode below)
SELECT * FROM Categories WHERE FullPath = "A/B/C/D"
// Result:
Id ParentId Name
4 3 D
I know that it's possible to use left joins to get the full path, but how do I write queries to get the leaf node by providing a path?
EDIT (Solution):
Help the help from both van and Eric, this is what I did:
with p as
(
select
c.*,
cast(c.Name as varchar(1024)) as NamePath
from
Categories c
where
ParentCategoryId is null
union all
select
c.*,
cast(p.NamePath + '/' + c.Name as varchar(1024)) as NamePath
from
Categories c
inner join p on
c.ParentCategoryId = p.CategoryId
)
select Id, Name
from p
where NamePath = 'A/B/C/D'
Thanks guys, both your answers were very helpful! I wish that I was able to mark them both as the solution.
This time I will simply go for the one with the leasts ammount of points (that is van).
With the help of CTE:
WITH CategoriesWithPath (id, parentid, name, path) AS
(
SELECT c.*, cast(c.name AS VARCHAR(1024)) AS "path"
FROM #Categories c
WHERE parentid is null
UNION ALL
SELECT c.*, cast(p."path" + '/' + c.name AS VARCHAR(1024)) AS "path"
FROM #Categories c
INNER JOIN CategoriesWithPath p ON c.parentid = p.id
)
SELECT id, parentid, name
FROM CategoriesWithPath
WHERE "path" = 'a/b/c/d'
But I would create a view using this CTA, so that you can easily just execute a SELECT from this view based on "path" filter.
Here's the SQL Server (2005+) approach, using a Common Table Expression (CTE):
declare #leaf varchar(10)
declare #fullpath varchar(50)
set #leaf = 'D'
set #fullpath = '/A/B/C/D'
with p as
(
select
*,
'/' + name as path
from
categories
where
leaf = #leaf
union all
select
c.*,
'/' + name + p.path as path
from
categories c
inner join p on
c.id = p.parentid
)
select
*
from
p
where
path = #fullpath
Although not directly answering your question, if you have access to SQL Server 2008 you may want to consider using the Hierarchid data type.
I dont see the point of doing this as you already have built the path and know what "D" is. Cant you just split the pathe by '\' and use the last item to query on.