Related
Table1
ID
Notes
ReasonID
1
Test1
[11,12]
2
Test2
[13,14]
Table 2
Reasonid
Name
11
Other1
12
Other2
13
Other3
14
Other4
Result should look like this, where Notes column from Table1 should concat with Name column from Table2.
ID
Final_Notes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
If you use SQL Server 2017+, you may try to parse the ReasonID column as JSON, use an appropriate JOIN and then aggregate with STRING_AGG().
Sample data:
SELECT *
INTO Table1
FROM (VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]')
) t (ID, Notes, ReasonID)
SELECT *
INTO Table2
FROM (VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4')
) t (ReasonID, Name)
Statement:
SELECT
ID,
FinalNotes = CONCAT(
Notes,
',',
(
SELECT STRING_AGG(t2.Name, ',') WITHIN GROUP (ORDER BY CONVERT(int, j.[key]))
FROM OPENJSON(ReasonID) j
-- Important, JOIN with possible implicit conversion
JOIN Table2 t2 ON j.[value] = t2.ReasonID
)
)
FROM Table1
Result:
ID
FinalNotes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
db<>fiddle
Please try the following solution.
It will work starting from SQL Server 2012 onwards.
It is using the following:
XML/XQuery to tokenize comma separated list of values.
FOR XML PATH to compose FinalNotes comma separated list.
SQL
-- DDL and sample data population, start
DECLARE #Table1 TABLE(ID INT, Notes VARCHAR(60), ReasonID VARCHAR(60));
INSERT INTO #Table1(ID, Notes, ReasonID) VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(Reasonid INT, Name VARCHAR(60));
INSERT INTO #Table2(Reasonid, Name) VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID, Notes, Name
FROM #Table1 AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(REPLACE(ReasonID,'[',''),']',''), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
CROSS APPLY c.nodes('/root/r/text()') AS t2(x)
INNER JOIN #Table2 AS t3 ON t3.Reasonid = x.value('.', 'INT')
)
SELECT ID, CONCAT(Notes
, (SELECT #separator + c.Name AS [text()]
FROM rs AS c
WHERE c.ID = p.ID
FOR XML PATH(''))) AS FinalNotes
FROM rs AS p
GROUP BY ID, Notes;
Output
+----+---------------------+
| ID | FinalNotes |
+----+---------------------+
| 1 | Test1,Other1,Other2 |
| 2 | Test2,Other3,Other4 |
+----+---------------------+
use SUBSTRING(string, 2, LEN(string)-2) for deleting [] and Parsename to split based on comma and join and concat as follows
Your data
DECLARE #Table1 TABLE(
ID INTEGER NOT NULL,
Notes VARCHAR(60) NOT NULL,
ReasonID VARCHAR(60) NOT NULL
);
INSERT INTO #Table1(ID, Notes, ReasonID)
VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(
Reasonid INTEGER NOT NULL,
Name VARCHAR(60) NOT NULL
);
INSERT INTO #Table2(Reasonid, Name)
VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
your query
SELECT id,
Concat(notes, ',', T2.name, ',', T3.name) FinalNotes
FROM (SELECT id,
notes,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 2) R1,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 1) R2
FROM #table1) T1
join #table2 T2
ON T1.R1 = T2.reasonid
join #table2 T3
ON T1.R2 = T3.reasonid
by using XML
DROP TABLE IF EXISTS #t -- temporary table
select t1.ID,t1.Notes, Name into #t -- temporary table
from
(
SELECT A.ID,a.Notes,
Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT ID, Notes,
CAST ('<M>' + REPLACE(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2) , ',', '</M><M>') + '</M>' AS XML) AS String
FROM #Table1) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) t1
join #Table2 t2 on t1.String=t2.Reasonid
---XML Path
SELECT ID,concat(notes,',',
STUFF((SELECT ', ' + CAST(name AS VARCHAR(10)) [text()]
FROM #t t1
WHERE t1.ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ')) FinalNotes
FROM #t t
GROUP BY ID,notes
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
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.
I've tried flexing my Google-fu to no avail so here I am! Unfortunately I cannot change anything about these tables as they are coming out of an application that I have to report out of.
In SQL Server 2008, I'm trying to replace multiple values in one text string column (Table 1) with the value from another table (Table 2).
Thanks in advance!!
Table 1
id value
-------------
1 a1, a2, a3
2 a2, a3
3 a4
Table 2
id value
---------
a1 Value1
a2 Value2
a3 Value3
a4 Value4
Desired Output
id value
-----------------------------
1 Value1, Value2, Value3
2 Value2, Value3
3 Value4
I'm sorry for this solution in advance :) It does what you need though:
create table TableA(
id int,
string varchar(255)
)
create table table2(
id varchar , text varchar(255)
)
insert into tableA values(1,'a,b,c,d')
insert into tableA values(2,'e,f')
insert into table2 values('a', 'value1')
insert into table2 values('b', 'value2')
insert into table2 values('c', 'value3')
insert into table2 values('d', 'value4')
insert into table2 values('e', 'value5')
insert into table2 values('f', 'value6')
select id, left(myConcat,len(myConcat)-1) from (
select c.id, replace(replace(CAST(CAST('<i'+stuff((select * from(
SELECT A.[id] ,
Split.a.value('.', 'VARCHAR(1000)') AS String
FROM (SELECT [id],
CAST ('<M>' + REPLACE([string], ',', '</M><M>') + '</M>' AS XML) AS String
FROM TableA) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) a
inner join table2 b on a.String = b.id
where a.id = c.id
FOR XML PATH ('')
),1,2,'') AS XML).query('/text') AS VARCHAR(1000)),'<text>',''),'</text>',',') myConcat
from TableA c
group by c.id
) d
Using the DelimitedSplit8K found at http://www.sqlservercentral.com/articles/Tally+Table/72993/ as suggested by #user1221684 you might come up with something like this. Working with delimited data like this is a pain. First you have to parse the string so you can join it to the other table and then ruin by stuffing it back into a denormalized form.
Make sure that if you use this that you understand that function and what this code is doing. This is not entry level t-sql and it will be YOU supporting this at 3am when it breaks in production, not me.
if OBJECT_ID('tempdb..#table1') is not null
drop table #table1;
create table #table1
(
id int,
value varchar(50)
);
insert #table1
select 1, 'a1, a2, a3' union all
select 2, 'a2, a3' union all
select 3, 'a4';
if OBJECT_ID('tempdb..#table2') is not null
drop table #table2;
create table #table2
(
id varchar(50),
value varchar(50)
);
insert #table2
select 'a1', 'Value1' union all
select 'a2', 'Value2' union all
select 'a3', 'Value3' union all
select 'a4', 'Value4';
with parsedValues as
(
select t1.id
, t1.value
, LTRIM(x.item) as item
from #table1 t1
cross apply dbo.DelimitedSplit8K(t1.value, ',') x
)
, swappedVals as
(
select pv.id
, t2.value
from parsedValues pv
join #table2 t2 on t2.id = pv.item
)
select id
, STUFF((select ',' + value
from swappedVals sv2
where sv2.id = sv.id
order by sv2.value --need to make sure to order here so the results are in the right order
for XML path('')), 1, 1, '') as MyValues
from swappedVals sv
group by id
;
This site has a delimited text split function http://www.sqlservercentral.com/articles/Tally+Table/72993/
Use that function to split your values out into a temp table. Replace the values in your temp table with the new values. Then use STUFF..FOR XML to combine the records back together and update your table.
One query with a few cte's should be able to handle all of this after you add the function to your database.
Example using Sql Fiddle
Use this:
DECLARE #t TABLE(id int,value varchar(255))
INSERT INTO #t (id,value)
VALUES(1,'a1'),(2,'a2'),(3,'a3')....
SELECT *,STUFF((SELECT DISTINCT ','+value FROM #t WHERE id=t.id)
FOR XML PATH('')),1,2,' ')
FROM (SELECT DISTINCT ID FROM #t) t
DISTINCT in the case of same id, otherwise let it go
Let's say I have three tables:
table1 fields:
memberid | name
table2 fields:
interestId | interestName
table3 (used to make a relation between member and interest) fields:
memberid | interestId
and now I know I can user inner join to select one member's all interests.
But how can I cascade all the interests in a single row???
For example, I can select this result:
memberid name interstId interestName
1 dennis 1 play basketball
1 dennis 2 music
1 dennis 3 moive
but the result i want to get is:
memberid name interests
1 dennis play basketball, music, moive
How can I write the SQL query?
Thanks in advance!
In SQL Server 2005 onwards, You can use XML Path() to concatenate values. It appears to be very performant too.
EDIT : Have tested the following and works
SELECT
t1.memberid,
t1.[name],
ISNULL(STUFF(
(
SELECT
', ' + t2.interestName
FROM
table2 t2
INNER JOIN
table3 t3
ON
t2.interestId = t3.interestId
WHERE
t3.memberid = t1.memberid
FOR XML PATH('')
), 1, 2, ''
), 'None') As interests
FROM
table1 t1
GROUP BY
t1.memberid,
t1.[name]
Example code:
DECLARE #table1 TABLE ( memberid INT IDENTITY(1,1), name VARCHAR(25) )
INSERT INTO #table1 VALUES('dennis');
INSERT INTO #table1 VALUES('mary');
INSERT INTO #table1 VALUES('bill');
DECLARE #table2 TABLE ( interestId INT IDENTITY(1,1), interestName VARCHAR(25) )
INSERT INTO #table2 VALUES('play basketball');
INSERT INTO #table2 VALUES('music');
INSERT INTO #table2 VALUES('movie');
INSERT INTO #table2 VALUES('play hockey');
INSERT INTO #table2 VALUES('wine tasting');
INSERT INTO #table2 VALUES('cheese rolling');
DECLARE #table3 TABLE ( memberid INT, interestId INT )
INSERT INTO #table3 VALUES(1,1);
INSERT INTO #table3 VALUES(1,2);
INSERT INTO #table3 VALUES(1,3);
INSERT INTO #table3 VALUES(2,2);
INSERT INTO #table3 VALUES(2,4);
INSERT INTO #table3 VALUES(2,6);
INSERT INTO #table3 VALUES(3,1);
INSERT INTO #table3 VALUES(3,5);
INSERT INTO #table3 VALUES(3,6);
SELECT
t1.memberid,
t1.[name],
ISNULL(STUFF(
(
SELECT
', ' + t2.interestName
FROM
#table2 t2
INNER JOIN
#table3 t3
ON
t2.interestId = t3.interestId
WHERE
t3.memberid = t1.memberid
FOR XML PATH('')
), 1, 2, ''
), 'None') As interests
FROM
#table1 t1
GROUP BY
t1.memberid,
t1.[name]
Results
memberid name interests
----------- -----------------------------------------------------------------------
1 dennis play basketball, music, movie
2 mary music, play hockey, cheese rolling
3 bill play basketball, wine tasting, cheese rolling
It depends on the DB you are using. Take a look at this question: Show a one to many relationship as 2 columns - 1 unique row (ID & comma separated list)
since you didn't specify your database, I can advice to take a look at left (right) joins.
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
GROUP_CONCAT
just saw the comment on the dbms system, but it would work with mysql.
SELECT t1.memberid, t1.name,
STUFF(
( SELECT ', ' + interestName
FROM table2 t2
inner join table3 as t3
on t2.interestId = t3.interestId and t3.memberid = t1.memberid
FOR XML PATH('') //use to merge the interests
), 1, 2, ''
) As interests
FROM table1
This works
Depends on particular database. Maybe it will help you (using T-SQL and MS SQL Server) for a known memberid:
declare #result varchar(8000)
set #result = ''
declare #memberid int
set #memberid = 1
select #result = str(#memberid) + ' ' + (select name from table1 where memberid = #memberid) + ' '
select #result = #result + str(interestid) + ' ' + interest
from
(
select table2.interestid, table2.interestname
from table3
inner join table2 on table2.interestid = table3.interestid
where table3.memberid = #memberid
) t1
select left(#result, LEN(#result) - 1)