How to concatenate many rows with same id in sql? - sql

My table contains the details like with two fields:
ID DisplayName
1 Editor
1 Reviewer
7 EIC
7 Editor
7 Reviewer
7 Editor
19 EIC
19 Editor
19 Reviewer
I want get the unique details with DisplayName like
1 Editor,Reviewer 7 EIC,Editor,Reviewer
Don't get duplicate value with ID 7
How to combine DisplayName Details? How to write the Query?

In SQL-Server you can do it in the following:
QUERY
SELECT id, displayname =
STUFF((SELECT DISTINCT ', ' + displayname
FROM #t b
WHERE b.id = a.id
FOR XML PATH('')), 1, 2, '')
FROM #t a
GROUP BY id
TEST DATA
create table #t
(
id int,
displayname nvarchar(max)
)
insert into #t values
(1 ,'Editor')
,(1 ,'Reviewer')
,(7 ,'EIC')
,(7 ,'Editor')
,(7 ,'Reviewer')
,(7 ,'Editor')
,(19,'EIC')
,(19,'Editor')
,(19,'Reviewer')
OUTPUT
id displayname
1 Editor, Reviewer
7 Editor, EIC, Reviewer
19 Editor, EIC, Reviewer

DECLARE #t TABLE
(
ID INT,
DisplayName VARCHAR(50)
)
INSERT INTO #t (ID, DisplayName)
VALUES
(1 , 'Editor'),
(1 , 'Reviewer'),
(7 , 'EIC'),
(7 , 'Editor'),
(7 , 'Reviewer'),
(7 , 'Editor'),
(19, 'EIC'),
(19, 'Editor'),
(19, 'Reviewer')
SELECT *, STUFF((
SELECT DISTINCT ', ' + DisplayName
FROM #t
WHERE ID = t.ID
FOR XML PATH('')), 1, 2, '')
FROM (
SELECT DISTINCT ID
FROM #t
) t
Output -
----------- ------------------------
1 Editor, Reviewer
7 Editor, EIC, Reviewer
19 Editor, EIC, Reviewer
My post about string aggregation:
http://www.codeproject.com/Articles/691102/String-Aggregation-in-the-World-of-SQL-Server

For MySQL:
SELECT id, GROUP_CONCAT(displayname) FROM tableName GROUP BY id
Refer: http://www.sqlines.com/mysql/functions/group_concat

SQL Server 2017+ and SQL Azure: STRING_AGG
Starting with the next version of SQL Server, we can finally concatenate across rows without having to resort to any variable or XML witchery.
STRING_AGG (Transact-SQL)
SELECT ID, STRING_AGG(DisplayName, ', ') AS DisplayNames
FROM TableName
GROUP BY ID

and in case of oracle database
select id,
listagg(displayname, ',') within group (order by displayname) as names
from test
group by id

to change the separator use
SELECT id, GROUP_CONCAT(displayname SEPARATOR ';') FROM tableName GROUP BY id
this will change separator from comma to semicolon :)

Thank you all,
SELECT Distinct
t1.ID,
MAX(STUFF(t2.x_id,1,1,'')) AS DisplayName
FROM Table t1
CROSS apply(
SELECT Distinct ', ' + SUBSTRING(t2.DisplayName,1,2)
FROM Table t2
WHERE t2.ID = t1.ID AND t2.DisplayName > ''
FOR xml PATH('')
) AS t2 (x_id)
GROUP BY
t1.ID
order by 1
GO

Related

Displaying multiple row values in one row in SQL Server [duplicate]

This question already has answers here:
Simulating group_concat MySQL function in Microsoft SQL Server 2005?
(12 answers)
Concatenate one field after GROUP BY
(1 answer)
Closed 8 years ago.
I have a table as follow:
ID User Activity PageURL
1 Me act1 ab
2 Me act1 cd
3 You act2 xy
4 You act2 st
I want to group by User and Activity such that I end up with something like:
User Activity PageURL
Me act1 ab, cd
You act2 xy, st
As you can see, the column PageURL is combined together separated by a comma based on the group by.
Would really appreciate any pointers and advice.
SELECT
[User], Activity,
STUFF(
(SELECT DISTINCT ',' + PageURL
FROM TableName
WHERE [User] = a.[User] AND Activity = a.Activity
FOR XML PATH (''))
, 1, 1, '') AS URLList
FROM TableName AS a
GROUP BY [User], Activity
SQLFiddle Demo
A good question. Should tell you it took some time to crack this one. Here is my result.
DECLARE #TABLE TABLE
(
ID INT,
USERS VARCHAR(10),
ACTIVITY VARCHAR(10),
PAGEURL VARCHAR(10)
)
INSERT INTO #TABLE
VALUES (1, 'Me', 'act1', 'ab'),
(2, 'Me', 'act1', 'cd'),
(3, 'You', 'act2', 'xy'),
(4, 'You', 'act2', 'st')
SELECT T1.USERS, T1.ACTIVITY,
STUFF(
(
SELECT ',' + T2.PAGEURL
FROM #TABLE T2
WHERE T1.USERS = T2.USERS
FOR XML PATH ('')
),1,1,'')
FROM #TABLE T1
GROUP BY T1.USERS, T1.ACTIVITY

Selecting data against numeric values saved as comma separated string

I have two sql tables and looking for a sql query to select data against each numeric value in Table1.ValueID column from Table2.ValueDescription column and save result in Table3
Table1:
ID ValueID
1 1,12,14
2 3,5,15
3 2,6,13,16
Table2:
ValueID ValueDescription
1 Motor
2 Low
3 Failed
4 New Install
5 New Item
6 Max Value
7 AC Current
8 DC Current
9 Not Reached
10 NA
11 Cutoff
12 Manual
13 Automatic
14 Device Not Found
15 Halt
16 Renew
Expected Result:
Table3:
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Using SQL Server Management Studio
Here is the query I tried
SELECT Table1.ValueID,
Stuff((SELECT ',' + CAST(Table2.Description AS VARCHAR(100))
FROM Table2
WHERE Table1.ValueID LIKE Table2.ValueID
FOR Xml Path('')),1,1,'')
FROM Table1
what I am missing here?
If in fact you really using SQL Server 2017, you can use both the STRING_SPLIT and the STRING_AGG functions. They make for a very easy syntax.
IF OBJECT_ID('tempdb..#Table1', 'U') IS NOT NULL
DROP TABLE #Table1;
CREATE TABLE #Table1 (
ID INT NOT NULL PRIMARY KEY,
ValueID VARCHAR(50) NOT NULL
);
INSERT #Table1 (ID, ValueID) VALUES
(1, '1,12,14'),
(2, '3,5,15'),
(3, '2,6,13,16');
IF OBJECT_ID('tempdb..#Table2', 'U') IS NOT NULL
DROP TABLE #Table2;
CREATE TABLE #Table2 (
ValueID INT NOT NULL PRIMARY KEY,
ValueDescription VARCHAR(50) NOT NULL
);
INSERT #Table2(ValueID, ValueDescription) VALUES
(1, 'Motor'),
(2, 'Low'),
(3, 'Failed'),
(4, 'New Install'),
(5, 'New Item'),
(6, 'Max Value'),
(7, 'AC Current'),
(8, 'DC Current'),
(9, 'Not Reached'),
(10, 'NA'),
(11, 'Cutoff'),
(12, 'Manual'),
(13, 'Automatic'),
(14, 'Device Not Found'),
(15, 'Halt'),
(16, 'Renew');
--SELECT * FROM #Table1 t1;
--SELECT * FROM #Table2 t2;
--========================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',')
FROM
STRING_SPLIT(t1.ValueID, ',') ss
JOIN #Table2 t2
ON CONVERT(INT, ss.value) = t2.ValueID
) csv;
The results...
ID ValueID Result
----------- -------------- -----------------------------------
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Edit:
-
-============================================================================
-- This is an idea that I've been kicking around for a little while now.
-- It's based on the SUSPICION that, when left to it's own devices. STRING_SPLIT
-- will always retun rows in the original order and attaching a row_number()
-- to the output, right out of the gate, will effectively serve as an "ItemNumber.
--============================================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',') WITHIN GROUP (ORDER BY rs.rn DESC) -- sort in the descending order for no real eason...
FROM (
SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
ValueID = CONVERT(INT, ss.value)
FROM
STRING_SPLIT(t1.ValueID, ',') ss
) rs
JOIN #Table2 t2
ON rs.ValueID = t2.ValueID
) csv;
ID ValueID Result
----------- ------------- --------------------------------
1 1,12,14 Device Not Found,Manual,Motor
2 3,5,15 Halt,New Item,Failed
3 2,6,13,16 Renew,Automatic,Max Value,Low
This will keep the proper sequence
Example
Select A.*
,B.*
From Table1 A
Cross Apply (
Select Result = Stuff((Select ',' +B2.ValueDescription
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(A.ValueID,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Join Table2 B2 on B1.RetVal=B2.ValueID
Order by RetSeq
For XML Path ('')),1,1,'')
) B
Returns
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Oops -- Just saw you are 2017
It's not that much prettier but the new built-in functions in SQL Server 2017 do make this a little easier to follow, and can still be made to respect the order of the original list (well, I can't even tell if you intended to order by location in the list or by numerical order, since those are the same), then provided it is all integers and there are no duplicates:
;WITH explode(ID, ValueID, value, i) AS
(
SELECT t1.ID,
t1.ValueID,
TRY_CONVERT(int,f.value),
CHARINDEX(',' + f.value + ',', ',' + t1.ValueID + ',')
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID,
-- guarantee respect original order:
Result = STRING_AGG(t2.ValueDescription,',') WITHIN GROUP (ORDER BY x.i)
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
If order doesn't matter, and you are sure there can be no duplicates or non-integers in the ValueID list in Table1, it is much simpler:
;WITH explode(ID, ValueID, value) AS
(
SELECT t1.ID, t1.ValueID, f.value
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID, STRING_AGG(t2.ValueDescription,',')
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
You can do like
SELECT *,
STUFF(
(
SELECT ',' + ValueDescription
FROM T2
WHERE ',' + T1.ValueID + ',' LIKE '%,' + CAST(T2.ValueID AS VARCHAR) + ',%'
FOR XML PATH('')
),
1, 1, ''
) ValueDescription
FROM T1;
Returns:
+----+-----------+-------------------------------+
| ID | ValueID | ValueDescription |
+----+-----------+-------------------------------+
| 1 | 1,12,14 | Motor,Manual,Device Not Found |
| 2 | 3,5,15 | Failed,New Item,Halt |
| 3 | 2,6,13,16 | Low,Max Value,Automatic,Renew |
+----+-----------+-------------------------------+
Demo

SQL - string combine based on id

Need suggestion to split string in table 1, match its Ids with table 2 and concatenate the values.
Table - 1
Id Tbl1Col
1 2
2 2,4
3
4 6
5 3
Table - 2
Id Tbl2Col
1 E
2 F
3 M
4 U
5 P
6 C
7 N
8 G
Query -
SELECT T2.Tbl2Col
FROM Table1 AS T1
LEFT JOIN Table2 AS T2 WHERE T1.Tbl1Col= T2.Id
WHERE T1.Id = #Id
Now If #Id = 1, Output is F -- works fine
Now If #Id = 2, Output should be FU -- should not be F,U
Yuck! But you can use LIKE:
SELECT T2.Tbl2Col
FROM Table1 T1 LEFT JOIN
Table2 T2
WHERE ',' + T1.Tbl1Col + ',' LIKE '%,' + CAST(T2.Id as VARCHAR(255)) + ',%'
WHERE T1.Id = #Id;
You have a lousy data format, so this cannot make use of indexes. You should really have a separate table, with one row per Table1.id and Table2.id. Such a table is called a junction table or an association table.
create table dbo.Table01 (
Id int
, Col varchar(100)
);
create table dbo.Table02 (
Id int
, Col varchar(100)
);
insert into dbo.Table01 (Id, Col)
values (1, '2'), (2, '2, 4');
insert into dbo.Table02 (Id, Col)
values (1, 'E'), (2, 'F'), (4, 'U');
select
t.Id
, replace(STRING_AGG (t02.Col, ','), ',', '') as StringAgg
from dbo.Table01 t
cross apply string_split (t.Col, ',') as ss
inner join dbo.Table02 t02 on ss.value = t02.Id
group by t.id
Follow the next approach:-
1) Turning a Comma Separated string into individual rows via using CROSS APPLY with XML
2) Join the two tables with left join.
3) Concatenate many rows with same id via using STUFF & FOR XML
4) Use Replace function for removing comma.
Demo:-
declare #MyTable table (id int , Tbl1Col varchar(10))
insert into #MyTable values (1,'2'),(2,'2,4'),(3,''),(4,'6'),(5,'3')
declare #MyTable2 table (id int , Tbl2Col varchar(10))
insert into #MyTable2 values (1,'E'),(2,'F'),(3,'M'),(4,'U'),(5,'P'),(6,'C'),(7,'N'),(8,'G')
select a.id , Tbl2Col
into #TestTable
from
(
SELECT A.id,
Split.a.value('.', 'VARCHAR(100)') AS Tbl1Col
FROM
(
SELECT id,
CAST ('<M>' + REPLACE(Tbl1Col, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #MyTable
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a) ) a
left join #MyTable2 b
on a.Tbl1Col = b.id
order by a.id
SELECT id, Tbl2Col =
Replace(STUFF((SELECT DISTINCT ', ' + Tbl2Col
FROM #TestTable b
WHERE b.id = a.id
FOR XML PATH('')), 1, 2, ''),',','')
FROM #TestTable a
GROUP BY id
Output:-
1 F
2 F U
3 NULL
4 C
5 M
References:-
Turning a Comma Separated string into individual rows
How to concatenate many rows with same id in sql?
Finally:-
Don't use this approach, and normalize your database instead , just use it as fun/training/trying .... etc code.

SQL Cross Tab Function

Hi Dear All My friends,
I want to ask one thing about sql cross tab function.Currently, I am using sql 2008 express version and my table structure is like below.
UserID Str_Value
1 A
1 B
1 C
2 A
2 B
3 D
3 E
I want to get like this .
UserID Str_Value
1 A,B,C
2 A,B
3 D,E
I don't want to use cursor.Is there any function for that one?
Please give me the right way.I really appreciate it.
Thanks.
Best Regards,
Chong
Hope this helps. You can comment ORDER BY T1.Str_Value if not needed and set the nvarchar(500) size as required
SELECT DISTINCT T1.UserId,
Stuff(
(SELECT N', ' + T2.Str_Value
FROM t T2
WHERE T2.userId = T1.userid
ORDER BY T2.Str_Value
FOR XML PATH(''),TYPE).value('text()[1]','nvarchar(500)'),1,2,N'')
AS Str_Value
FROM t T1
SELECT UserId, LEFT(Str_Value, LEN(Str_Value) - 1) AS Str_Value
FROM YourTable AS extern
CROSS APPLY
(
SELECT Str_Value + ','
FROM YourTable AS intern
WHERE extern.UserId = intern.UserId
FOR XML PATH('')
) pre_trimmed (Str_Value)
GROUP BY UserId, Str_Value
Try this:
SELECT DISTINCT
t1.UserID,
Values = SUBSTRING((SELECT ( ', ' + t2.Str_Value)
FROM dbo.Users t2
ORDER BY
t2.Str_Value
FOR XML PATH( '' )
), 3, 4000 )FROM dbo.Users t1
GROUP BY t1.UserID
create table #temp
(
userid int,
str_value varchar(1)
)
insert into #temp values (1, 'A')
insert into #temp values (1, 'B')
insert into #temp values (1, 'C')
insert into #temp values (2, 'A')
insert into #temp values (2, 'B')
insert into #temp values (3, 'D')
insert into #temp values (3, 'E')
select userid, left(x.str_value, len(x.str_value) -1) as str_value
from #temp t
cross apply
(
select str_value + ','
FROM #temp t1
where t.userid = t1.userid
for xml path('')
) x (str_value)
group by userid, x.str_value
drop table #temp

How to concatenate all strings from a certain column for each group

Suppose I have this table [Table1]
Name Mark
------- ------
ABC 10
DEF 10
GHI 10
JKL 20
MNO 20
PQR 30
What should be my SQL statement to retrieve a record that looks like this:
(group by [mark]).
I have done the 1 and 2 columns but don't know how to accomplish the third column (concat the [name] with the same [mark])
mark count names
---- ----- -----------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
I'm using Microsoft SQL.
Please help. Thanks
If MS SQL 2005 or higher.
declare #t table([name] varchar(max), mark int)
insert #t values ('ABC', 10), ('DEF', 10), ('GHI', 10),
('JKL', 20), ('MNO', 20), ('PQR', 30)
select t.mark, COUNT(*) [count]
,STUFF((
select ',' + [name]
from #t t1
where t1.mark = t.mark
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, '') [values]
from #t t
group by t.mark
Output:
mark count values
----------- ----------- --------------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
Here's a performance-related answer!
http://jerrytech.blogspot.com/2010/04/tsql-concatenate-strings-1-2-3-and.html
Using XML functions in a large query is a performance killer.
Using a CTE is a performance superstar.
Check out the link, it will explain how.
I admit the work to accomplish it is more.
But the result is milliseconds over millions of rows.
polishchuks solution is more elegant, but this is basically the same thing, we just deal with the trailing comma differently.
CREATE TABLE #Marks(Name nchar(3), Mark int)
INSERT INTO #Marks
SELECT 'ABC', 10 UNION ALL
SELECT 'DEF', 10 UNION ALL
SELECT 'GHI', 10 UNION ALL
SELECT 'JKL', 20 UNION ALL
SELECT 'MNO', 20 UNION ALL
SELECT 'PQR', 30
SELECT
mark,
[count],
CASE WHEN Len(Names) > 0 THEN LEFT(Names, LEN(Names) -1) ELSE '' END names
FROM
(
SELECT
Mark,
COUNT(Mark) AS [count],
(
SELECT DISTINCT
Name + ', '
FROM
#Marks M1
WHERE M1.Mark = M2.Mark
FOR XML PATH('')
) Names
FROM #Marks M2
GROUP BY Mark
) M
Loosely based on Itzik Ben-Gan, Inside Microsoft SQL Server 2005: T-SQL Programming, p. 215:
IF OBJECT_ID('dbo.Table1') IS NOT NULL
DROP TABLE dbo.Table1 ;
GO
CREATE TABLE dbo.Table1 ( Name VARCHAR(10), Mark INT ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'ABC', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'DEF', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'GHI', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'JKL', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'MNO', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'PQR', 30 ) ;
WITH DelimitedNames AS
(
SELECT Mark, T2.Count,
( SELECT Name + ',' AS [text()]
FROM dbo.Table1 AS T1
WHERE T1.Mark = T2.Mark
ORDER BY T1.Mark
FOR XML PATH('')) AS Names
FROM ( SELECT Mark, COUNT(*) AS Count FROM dbo.Table1 GROUP BY Mark ) AS T2
)
SELECT Mark, Count, LEFT(Names, LEN(NAMES) - 1) AS Names
FROM DelimitedNames ;