MS SQL - group by concat string [duplicate] - sql

This question already has answers here:
Simulating group_concat MySQL function in Microsoft SQL Server 2005?
(12 answers)
Closed 6 years ago.
I am new to writing SQL scripts (at least anything more than SELECT * FROM X). I have run into a problem grouping a table by one value, and then joining values from another column into a single string.
I would like to perform a group by on a temp table, and then concatenate values of a column in the group together.
The table variable (edit), #categoriesToAdd, data structure is [SubscriberId int, CategoryId int].
What I am trying to do is something like (My understanding is that CONCAT is the bit missing from MSSQL):
SELECT SubscriberId,
CONCAT(CONVERT(VARCHAR(10), CategoryId) + ', ') as categoriesAdded
FROM #categoriesToAdd
GROUP BY SubscriberId
The concatenated category IDs for each subscriber would then look something like:
0001, 0002, 0003, 0004
Thanks!

In sql server you can use FOR XML PATH
select SubscriberId,
categoriesAdded=Stuff((SELECT ',' + CAST(CategoryId as VARCHAR(255)) FROM catsToAdd t1 WHERE t1.SubscriberId=#categoriesToAdd.SubscriberId
FOR XML PATH (''))
, 1, 1, '' )
from #categoriesToAdd as catsToAdd
GROUP BY SubscriberId

Related

SQL: Not being able to use column alias in where [duplicate]

This question already has answers here:
TSQL - Use a Derived Select Column in the Where Clause
(6 answers)
Closed 1 year ago.
Given the following sql example procedure:
DECLARE #xmlvoucherSequenceList xml = '<?xml version="1.0"?><Voucher><Id Item1="6425"/></Voucher>'
select
col.value('./#Item1','int') voucherSequence
from #xmlvoucherSequenceList.nodes('/Voucher/Id') t(col)
where voucherSequence not in (
...
)
I don't understand why "voucherSequence" alias in where appear underlined in red in the sql management studio editor with "Column voucherSequence not found".
If I comment the where part and run only the select, the voucherSequence clearly appears as the column name (alias).
The weird thing is if I replace the previous query by the next one:
select
col.value('./#Item1','int') voucherSequence
from #xmlvoucherSequenceList.nodes('/Voucher/Id') t(col)
where col.value('./#Item1','int') not in (
...
)
The sql compiler won't complain and the query works fine, but I'd like to use the alias in where, that's why alias is for.
Your code looks like SQL Server. I would recommend:
select v.voucherSequence
from #xmlvoucherSequenceList.nodes('/Voucher/Id') t(col) cross apply
(values (t.col.value('./#Item1', 'int'))
) v(voucherSequence)
where v.voucherSequence not in (
...
)

STRING_AGG substitute for SQL Server 2008 [duplicate]

Can anyone help me make this query work for SQL Server 2014?
This is working on Postgresql and probably on SQL Server 2017. On Oracle it is listagg instead of string_agg.
Here is the SQL:
select
string_agg(t.id,',') AS id
from
Table t
I checked on the site some xml option should be used but I could not understand it.
In SQL Server pre-2017, you can do:
select stuff( (select ',' + cast(t.id as varchar(max))
from tabel t
for xml path ('')
), 1, 1, ''
);
The only purpose of stuff() is to remove the initial comma. The work is being done by for xml path.
Note that for some characters, the values will be escaped when using FOR XML PATH, for example:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH('')),1,1,'');
This returns the string below:
7 > 5,Salt & pepper,2
lines'
This is unlikely desired. You can get around this using TYPE and then getting the value of the XML:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'');
This returns the string below:
7 > 5,Salt & pepper,2
lines
This would replicate the behaviour of the following:
SELECT STRING_AGG(V.String,',')
FROM VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String);
Of course, there might be times where you want to group the data, which the above doesn't demonstrate. To achieve this you would need to use a correlated subquery. Take the following sample data:
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
GroupID int,
SomeCharacter char(1));
INSERT INTO dbo.MyTable (GroupID, SomeCharacter)
VALUES (1,'A'), (1,'B'), (1,'D'),
(2,'C'), (2,NULL), (2,'Z');
From this wanted the below results:
GroupID
Characters
1
A,B,D
2
C,Z
To achieve this you would need to do something like this:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID --This is your correlated join and should be on the same columns as your GROUP BY
--You "JOIN" on the columns that would have been in the PARTITION BY
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID; --I use GROUP BY rather than DISTINCT as we are technically aggregating here
So, if you were grouping on 2 columns, then you would have 2 clauses your sub query's WHERE: WHERE MT.SomeColumn = sq.SomeColumn AND MT.AnotherColumn = sq.AnotherColumn, and your outer GROUP BY would be GROUP BY MT.SomeColumn, MT.AnotherColumn.
Finally, let's add an ORDER BY into this, which you also define in the subquery. Let's, for example, assume you wanted to sort the data by the value of the ID descending in the string aggregation:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID
ORDER BY sq.ID DESC --This is identical to the ORDER BY you would have in your OVER clause
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID;
For would produce the following results:
GroupID
Characters
1
D,B,A
2
Z,C
Unsurprisingly, this will never be as efficient as a STRING_AGG, due to having the reference the table multiple times (if you need to perform multiple aggregations, then you need multiple sub queries), but a well indexed table will greatly help the RDBMS. If performance really is a problem, because you're doing multiple string aggregations in a single query, then I would either suggest you need to reconsider if you need the aggregation, or it's about time you conisidered upgrading.

SQL distinct columns with a concat of unique [duplicate]

This question already has answers here:
Simulating group_concat MySQL function in Microsoft SQL Server 2005?
(12 answers)
Closed 6 years ago.
In SQL Server, I have a simple view called v_master_server_list.
Server entries have multiple group memberships, so the servers will be duplicated in the view for every group membership. I need to return only a single row with the distinct server names and for every group membership, I'd like this concatenated with some kind of delimiter like '|' or ','
I've come up with this (thanks for the link), and I had to scrub out the null entries otherwise the LEN and LEFT functions wouldn't work. Question is, how do I now return all entries (even if nothing is found in 'arms_group')?
select server, LEFT(column_names, LEN(column_names )-1) AS column_names
from cmdb.[dbo].v_master_server_list AS extern
CROSS APPLY
(
select arms_group + ','
FROM cmdb.[dbo].v_master_server_list AS intern
where extern.server = intern.server
FOR XML PATH('')
) pre_trimmed (column_names)
where arms_group is not null
GROUP BY server, column_names;
You can use for xml to Concatenate Row Values in Transact-SQL
select distinct
group_membership
, servers = stuff(
(
select distinct '|'+i.server_name
from v_master_server_list as i
where i.group_membership = o.group_membership
order by i.server_name
for xml path (''), type).value('.','varchar(max)')
,1,1,'')
from v_master_server_list as o
In Sql Server vNext, you can use string_agg()

Convert multiple rows into one with coma as separator [duplicate]

This question already has answers here:
How to create a SQL Server function to "join" multiple rows from a subquery into a single delimited field? [duplicate]
(13 answers)
Closed 7 years ago.
If I issue SELECT ID FROM TestAhmet I get this result:
1
3
5
2
4
but what I really need is one row with all the values separated by comma, like this:
1,2,3,4,5
How do I do this?
ps: I cant do this : Convert multiple rows into one with comma as separator
If id is numeric column then do like this
select stuff((select ',' +Convert(varchar(50),id)
from TestAhmet
for xml path ('')
), 1, 1, '') as users
This should work:
select stuff((select ',' + cast(id as varchar(8000))
from TestAhmet
for xml path ('')
), 1, 1, '') as users
This is a variation on the string aggregation logic often used in SQL Server. But, without a group by, you probably won't find many examples on the web.

Concatinating Rows Specific Field and Displaying the Concatinated Value in SELECT query-SQL [duplicate]

This question already has answers here:
Simulating group_concat MySQL function in Microsoft SQL Server 2005?
(12 answers)
Closed 8 years ago.
My aim is to display the person name and all the reason for his absences(concated and displaying in a string).
I am using the following query to display the EmployeeName, ReasonForAbsence. I am having problem with concatenating and Displaying all record specific column called 'AbsenceReason'. The error is Incorrect Syntax near ='. More info about error- its happening for displaying absence reason part.
SELECT
--Displaying Name,
EMP.NAME,
--Displaying Absence Reason
(
SELECT
#AbsenceReasons= #AbsenceReasons + ';' + REASONTEXT
FROM
ABSENCE
WHERE ID=EMP.ID
)
FROM
(
SELECT
*
FROM
Employees
) EMP
What have I missed?
Thank you
As Martin says this is a duplicated question but...
SELECT DISTINCT Absence.EmpId, Reasons.AllReasons
FROM Absence
CROSS APPLY ( SELECT ReasonText + ' ,'
FROM Absence EMP2
WHERE EMP2.EmpId= Absence.EmpId
FOR XML PATH('')
) Reasons ( AllReasons)