trying to concatenate a column into a comma delimited list - sql

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

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;

Use Data of 1 table into another one dynamically

I have one table category_code having data like
SELECT Item, Code, Prefix from category_codes
Item Code Prefix
Bangles BL BL
Chains CH CH
Ear rings ER ER
Sets Set ST
Rings RING RG
Yellow GOld YG YG........
I have another table item_categories having data like
select code,name from item_categories
code name
AQ.TM.PN AQ.TM.PN
BL.YG.CH.ME.PN BL.YG.CH.ME.PN
BS.CZ.ST.YG.PN BS.CZ.ST.YG.PN
CR.YG CR.YG.......
i want to update item_categories.name column corresponding to category_code.item column like
code name
BL.YG.CH.ME.PN Bangles.Yellow Gold.Chains.. . . .
Please suggest good solution for that. Thanks in advance.
First, split the code into several rows, join with the category code and then, concat the result to update the table.
Here an example, based on the data you gave
create table #category_code (item varchar(max), code varchar(max), prefix varchar(max));
create table #item_categories (code varchar(max), name varchar(max));
insert into #category_code (item, code, prefix) values ('Bangles','BL','BL'),('Chains','CH','CH'),('Ear rings','ER','ER'), ('Sets','Set','ST'),('Rings','RING','RG'), ('Yellow gold','YG','YG');
insert into #item_categories (code, name) values ('AQ.TM,PN','AQ.TM.PN'),('BL.YG.CH.ME.PN','BL.YG.CH.ME.PN'),('BS.CZ.ST.YG.PN','BS.CZ.ST.YG.PN')
;with splitted as ( -- split the codes into individual code
select row_number() over (partition by ic.code order by ic.code) as id, ic.code, x.value, cc.item
from #item_categories ic
outer apply string_split(ic.code, '.') x -- SQL Server 2016+, otherwise, use another method to split the data
left join #category_code cc on cc.code = x.value -- some values are missing in you example, but can use an inner join
)
, joined as ( -- then joined them to concat the name
select id, convert(varchar(max),code) as code, convert(varchar(max),coalesce(item + ',','')) as Item
from splitted
where id = 1
union all
select s.id, convert(varchar(max), s.code), convert(varchar(max), j.item + coalesce(s.item + ',',''))
from splitted s
inner join joined j on j.id = s.id - 1 and j.code = s.code
)
update #item_categories
set name = substring (j.item ,1,case when len(j.item) > 1 then len(j.item)-1 else 0 end)
output deleted.name, inserted.name
from #item_categories i
inner join joined j on j.code = i.code
inner join (select code, max(id)maxid from joined group by code) mj on mj.code = j.code and mj.maxid = j.id

Stored procedure that takes in a dynamic number of joins

I am trying to build a stored procedure that takes in a dynamic number of tables and joins them.
SELECT a.Account
FROM Database.Accounts a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 1) b ON a.Account = b.Account
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 2) c ON a.Account = c.Account
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 3) d ON a.Account = d.Account
...
/*Where the number of joins is equal to COUNT(Table_Name) FROM Database.TableStrings*/
or
first = SELECT a.Account
FROM Database.Accounts a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 1) b ON a.Account = b.Account
second = SELECT a.Account
FROM first a
JOIN (SELECT Table_Name FROM Database.TableStrings WHERE Row = 2) b ON a.Account = b.Account
...
Below is an example of the tables and the expected output:
---Database.Accounts---
Account (Integer)
------------------------
111
222
333
444
555
---Database.TableStrings---
Table_Name (String):
------------------------
'Database.HasPhoneNumber'
'Database.HasEmail'
'Database.HasBankAccount'
---Database.HasPhoneNumber---
Account (Integer)
------------------------
111
444
---Database.HasEmail---
Account (Integer)
------------------------
111
222
---Database.HasBankAccount---
Account (Integer)
------------------------
111
222
555
With the example tables, the expected output would be Account 111.
I am having a hard time visualizing how to do this in a stored procedure, if it is even possible.
you may consider using recursive queries (cte) to handle the string concatenation. and run sp_executesql to execute generated sql string query
lets generate also your table aliases using char()
declare #strSQL nvarchar(max), #strSQLjoin nvarchar(max)
set #strSQL = 'SELECT a.Account
FROM Database.Accounts a ';
set #strSQLjoin = '';
with cte as (
select 'Database.PhoneNumbers' as TableStrings
union all select 'Database.Emails'
union all select 'Database.BankAccounts'
), cte2 as (
select TableStrings, (char(row_number() over (order by TableStrings) + 97)) as tbl
from cte)
select #strSQLjoin = coalesce(#strSQLjoin
+ ' JOIN (SELECT Table_Name FROM '+TableStrings+') '+tbl+' ON a.Account = '+tbl+'.Account '
,' JOIN (SELECT Table_Name FROM '+TableStrings+') '+tbl+' ON a.Account = '+tbl+'.Account')
from cte2;
set #strSQL=#strSQL + #strSQLjoin;
print #strSQL
exec sp_executesql #strSQL
I understand that this is not exactly what you asked for. But since adding new rows to the TableStrings table already implies schema changes (creating new tables), at this point you might as well update your stored procedure accordingly instead of using dynamic SQL
If that's an option then for example your query may look like
SELECT a.Account
FROM Database.Accounts a
where exists (select 1 from Database.HasPhoneNumber e where e.Account = a.Account)
and exists (select 1 from Database.HasEmaile e where e.Account = a.Account)
and exists (select 1 from Database.HasBankAccount e where e.Account = a.Account)
...
based on your sample data and your sample query, you can achieve what you want without using dynamic query
-- sample table
declare #Accounts table
(
Account int
)
declare #TableStrings table
(
Table_Name varchar(100),
Account int
)
-- sample data
insert into #Accounts
values (111), (222), (333), (444), (555)
insert into #TableStrings
values ('Database.HasPhoneNumber', 111), ('Database.HasPhoneNumber', 444),
('Database.HasEmail', 111), ('Database.HasEmail', 222),
('Database.HasBankAccount', 111), ('Database.HasBankAccount', 222),
('Database.HasBankAccount', 555)
select a.Account
from #Accounts a
inner join #TableStrings t on a.Account = t.Account
group by a.Account
having count(*) = (select count(distinct Table_Name) from #TableStrings)

How to get Email ID(one value) for each ID for 1st table and then display Email Name from 2nd table as comma Separated in SQL Server 2012?

What i have tried
Results wanted like thisFrom One table , for each ID there can be multiple email id's based on some condition Ex
ID EmailID's
1 Mike.Foster#Mail.com
1 lilly.Foster#Mail.com
2 Michel.Josh#Mail.com
2 Nash.Ted#Mail.com
I have to get email Name from these Ids from another table, something like this
Output i Need
Email_Name
Foster.Mike,Foster.Lilly
Josh.Michel,Ted.Nash
This is what i tried.
SELECT User_Email =
STUFF((SELECT DISTINCT ', ' + User_Email
FROM table1 b
WHERE b.Component_ID= a.Component_ID
and [Role] ='Team Lead' and Functional_Group ='Product'
FOR XML PATH('')), 1, 2, '')
FROM [WFS].table1 a
GROUP BY table1
Now another table i want Email Names
Select EmailNamefrom Table2 where EmailIDs IN ( 'code for Email')
Table1 schema
ID Component_ID EmailIDs Role Functional_Group
1 1 Mike.Foster#Mail.com Team Lead Product
2 1 lilly.Foster#Mail.com Team Lead Product
3 2 Michel.Josh#Mail.com Team Lead Product
4 2 Nash.Ted#Mail.com Team Lead Product
Table 2 schema
ID EmailIDs EmailName
1 Mike.Foster#Mail.com Foster.Mike
2 lilly.Foster#Mail.com Foster.Lilly
Any suggestions are welcome.Thanks in advance
Disregard this answer as I found out that GROUP_CONCAT() is a MySQL specific function, which means it won't work in SQL Server, however, I'll let it stay for future references.
SELECT
GROUP_CONCAT(EmailName SEPARATOR ', ') as name
FROM
table1
INNER JOIN table2 ON table1.EmailIDs=table2.EmailIDs
WHERE
table1.EmailIDs=table2.EmailIDs
GROUP BY
table1.Component_ID
Output:
Foster.Mike, Foster.Lilly
Ted.Nash, Josh.Michel
Working SQL fiddle
Docs: https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html#function_group-concat
You need CTE with STUFF() :
WITH t AS (
SELECT t1.*, t2.emailname
FROM table1 t1 INNER JOIN
table2 t2
ON t2.emailids = t1.emailids
)
SELECT id, STUFF ( (SELECT DISTINCT ','+t1.emailname
FROM t t1
WHERE t1.id = t.id
FOR XML PATH ('')
), 1, 1, ''
) AS Email_Name
FROM t
GROUP BY id;
You were actually close but your SQL doesn't match your schema. This one works as you want:
SELECT Email_Name =
STUFF((SELECT DISTINCT ', ' + EmailIDs
FROM table1 b
WHERE b.Component_ID= a.Component_ID
and [Role] ='Team Lead' and Functional_Group ='Product'
FOR XML PATH('')), 1, 2, '')
FROM table1 a
GROUP BY a.Component_ID;
EDIT: I didn't understand what you are asking exactly. Might this be what you meant?
SELECT STUFF((SELECT ', ' + EmailName
FROM Table2 where EmailIDs IN ( SELECT EmailIDs
FROM table1
WHERE [Role] ='Team Lead' and Functional_Group ='Product')
FOR XML PATH('')), 1, 2, '')
Or you meant this:
SELECT DISTINCT Component_ID, emailNames
FROM table1
CROSS APPLY (SELECT STUFF((SELECT ', '+t2.EmailName
FROM table2 t2
INNER JOIN TABLE1 t1 ON t1.EmailIDs = t2.EmailIDs
WHERE t1.Component_ID = Table1.Component_ID
FOR XML PATH('')), 1, 2, '')
) t(EmailNames)
WHERE [Role] ='Team Lead' and Functional_Group ='Product'
Here is SQLFiddle Demo

SQL: Nested SELECT with multiple values in a single field

In my SQL 2005 DB, I have a table with values stored as IDs with relationships to other tables. So in my MyDBO.warranty table, I'm storing product_id instead of product_name in order to save space. The product_name is stored in MyDBO.products.
When the marketing department pulls the demographic information, the query selects the corresponding name for each ID from related tables (trimmed down for brevity):
SELECT w1.warranty_id AS "No.",
w1.created AS "Register Date"
w1.full_name AS "Name",
w1.purchase_date AS "Purchased",
(
SELECT p1.product_name
FROM WarrDBO.products p1 WITH(NOLOCK)
WHERE p1.product_id = i1.product_id
) AS "Product Purchased",
i1.accessories
FROM WarrDBO.warranty w1
LEFT OUTER JOIN WarrDBO.warranty_info i1
ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC
Now, my problem is that the "accessories" column on the warranty_info table stores several values:
No. Register Date Name Purchased Accessories
---------------------------------------------------------------------
1500 1/1/2008 Smith, John Some Product 5,7,9
1501 1/1/2008 Hancock, John Another 2,3
1502 1/1/2008 Brown, James And Another 2,9
I need to do something similar with "Accessories" that I did with "Product" and pull accessory_name from the MyDBO.accessories table using accessory_id. I'm not sure where to start, because first I'd need to extract the IDs and then somehow concatenate multiple values into a string. So each line would have "accessoryname1,accessoryname2,accessoryname3":
No. Register Date Name Purchased Accessories
---------------------------------------------------------------------
1500 1/1/2008 Smith, John Some Product Case,Bag,Padding
1501 1/1/2008 Hancock, John Another Wrap,Label
1502 1/1/2008 Brown, James And Another Wrap,Padding
How do I do this?
EDIT>> Posting my final code:
I created this function:
CREATE FUNCTION SQL_GTOInc.Split
(
#delimited varchar(50),
#delimiter varchar(1)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id INT identity(1,1), -- I use this column for numbering splitted parts
val INT
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(5)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
And updated my code accordingly:
SELECT w1.warranty_id,
i1.accessories,
(
CASE
WHEN i1.accessories <> '' AND i1.accessories <> 'NULL' AND LEN(i1.accessories) > 0 THEN
STUFF(
(
SELECT ', ' + a1.accessory
FROM MyDBO.accessories a1
INNER JOIN MyDBO.Split(i1.accessories, ',') a2
ON a1.accessory_id = a2.val
FOR XML PATH('')
), 1, 1, ''
)
ELSE ''
END
) AS "Accessories"
FROM MyDBO.warranty w1
LEFT OUTER JOIN MyDBO.warranty_info i1
ON i1.warranty_id = w1.warranty_id
You could write a table valued function that simply splits comma separated string into XML and turns XML nodes to rows.
See:
http://www.kodyaz.com/articles//t-sql-convert-split-delimeted-string-as-rows-using-xml.aspx
Join to accessories through the result of function call, and stuff the result back to comma separated list of names.
Untested code:
SELECT w1.warranty_id AS "No.",
w1.created AS "Register Date"
w1.full_name AS "Name",
w1.purchase_date AS "Purchased",
(
SELECT p1.product_name
FROM WarrDBO.products p1 WITH(NOLOCK)
WHERE p1.product_id = i1.product_id
) AS "Product Purchased",
STUFF(
(
SELECT
', ' + a.name
FROM [table-valued-function](i1.accessories) acc_list
INNER JOIN accessories a ON acc_list.id = a.id
FOR XML PATH('')
), 1, 1, ''
) AS [accessories]
FROM WarrDBO.warranty w1
LEFT OUTER JOIN WarrDBO.warranty_info i1
ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC
Nothing to do with your question. Just a note that your original query can also be written, moving the subqery to a join, as:
SELECT w1.warranty_id AS "No.",
w1.created AS "Register Date"
w1.full_name AS "Name",
w1.purchase_date AS "Purchased",
p1.product_name AS "Product Purchased",
i1.accessories
FROM WarrDBO.warranty w1
INNER JOIN WarrDBO.products p1
ON p1.product_id = i1.product_id
LEFT OUTER JOIN WarrDBO.warranty_info i1
ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC
You just need to use the FOR XML feature of SQL Server to easily cat strings:
Example from the linked blog post:
SELECT
STUFF(
(
SELECT
' ' + Description
FROM dbo.Brands
FOR XML PATH('')
), 1, 1, ''
) As concatenated_string
To parse a field that has already been stored as comma delimited you will have to write a UDF that parses the field and returns a table which can then be used with an IN predicate in your WHERE clause. Look here for starters, and here.
It seem to be a work for a concatenate aggregate function.
In SQL it can be deployed using CLR
http://www.mssqltips.com/tip.asp?tip=2022