1

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?

Tab Alleman
  • 31,483
  • 7
  • 36
  • 52
  • https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2017 – GSerg Nov 30 '18 at 17:39
  • See split-string and STUFF... split string to join the first table with the second, and then the STUFF pattern to concatenate things back. – pmbAustin Nov 30 '18 at 17:40
  • Possible duplicate of [join comma delimited data column](https://stackoverflow.com/questions/16507239/join-comma-delimited-data-column) – GSerg Nov 30 '18 at 17:41
  • Which sql server version you use? – Helgato Nov 30 '18 at 17:45
  • sql server 2017 – ThirdParty Auth Nov 30 '18 at 17:50
  • 2
    I am shocked, especially given the people who posted answers, that nobody mentioned just how truly horrible storing delimited data is. Here is a great discussion of just how bad it really is. https://stackoverflow.com/a/3653574/3813116 – Sean Lange Nov 30 '18 at 19:41
  • 1
    @SeanLange In my defense, I've said it so many times, I am shocked people just keep storing data this way and then need to deal with it and don't find the dozens and dozens of prior discussions about it... :-) – Aaron Bertrand Nov 30 '18 at 19:50
  • @AaronBertrand to true. Couldn't even begin to count the number of times I have warned against the horrors of such a terrible approach to data. – Sean Lange Nov 30 '18 at 20:15
  • @SeanLange thanks for your concern but I am not saving data like that. This column is a derived data. Let me little bit explain originally my table has a number e.g. 14337 that is bit representation coming from device. I have a sql script which converts this to binary (10000000000111) finds the ON bits (1,12,13,14) and each ON bit has a specific description which needs to be saved against original number 14337 sent by device. Now think the number of combinations for 16 bits. Cheers mate! – ThirdParty Auth Nov 30 '18 at 20:38
  • 1
    Ummm...ok. Not sure how storing delimited data isn't storing delimited data. But whatever. – Sean Lange Nov 30 '18 at 21:37

4 Answers4

2

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
Jason A. Long
  • 4,382
  • 1
  • 12
  • 17
  • 3
    STRING_SPLIT doesn't guarantee output order though. "The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string. " so for a given row, things may be out of the order you expect. – user1443098 Nov 30 '18 at 18:44
  • 1
    On the other hand, the expected order isn't clear. It might not matter, it might be expected to be the same as the order in the original list, or it might expect to be reordered in numerical order. The only thing we can rule out is that the result column does not need to list the items in alphabetical order. – Aaron Bertrand Nov 30 '18 at 18:46
  • @user1443098 - Yea... It's a shame too. STRING_SPLIT has been one of the most requested functions for as long as I can remember.The omission of an "ItemNumber" column is border line inexcusable... Especially considering that all the best home grown splitters included an ItemNumber to get a guaranteed sort. – Jason A. Long Nov 30 '18 at 19:19
  • 1
    You can trick it using `charindex()` (see my answer) but this breaks down quickly if the list contains duplicates. – Aaron Bertrand Nov 30 '18 at 19:49
  • @JasonA.Long that's a big miss on Microsoft's part. IMHO – user1443098 Nov 30 '18 at 20:00
  • @AaronBertrand - Cool idea. I was just taking a look at that. Play with it a little once I'm off the company dime. Quick question... You wouldn't happen to have any code laying around that would naturally cause STRING_SPLIT to return not in the original order, without using an ORDER BY clause? For a function that doesn't have a "guaranteed" sort order, it's pretty consistent about returning in the original order. I have an idea I want to test, but I need a "naturally wrong order" to see if it's actually effective. – Jason A. Long Nov 30 '18 at 20:46
  • 1
    On its own? No. The documentation being rewritten by Conor Cunningham to explicitly guarantee that the rows could come back in any order ("[The order is not guaranteed to match the order of the substrings in the input string.](https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql)") is more than enough for me to not trust it. As soon as you introduce other data outside of the original list, well, [all bets are **definitely** off](https://i.stack.imgur.com/eCTIT.png). – Aaron Bertrand Nov 30 '18 at 20:52
  • @AaronBertrand - Once again, good info. Thank you. I know that order isn't "guaranteed" (nothing without an explicit ORDER BY clause is) but I suspect that it's "not guaranteed" in the same way the Quirky Updates are "not guaranteed"... Which is to say, if you follow a few rules, they work, even without the guarantee. I'm updating my answer to show you what I want to test... – Jason A. Long Nov 30 '18 at 21:09
  • 1
    I read the new wording as "guaranteed to not be guaranteed" - so, trusting it to "work" is, IMHO, not wise, potentially misleading, and maybe even a little dangerous. I wouldn't advocate it at all when the only proof you have that it works is that you haven't broken it yet. People trusted order by in a view for years, and then SQL Server 2005 came along and broke many, many applications. – Aaron Bertrand Nov 30 '18 at 21:12
  • 1
    `ORDER BY (SELECT NULL)` is by definition non-deterministic so I think again you are relying on an observance that carries no guarantee whatsoever. – Aaron Bertrand Nov 30 '18 at 21:17
  • Again... I know... But it just has a bad habit of working correctly every single time. I really want to find a scenario where it breaks so I can throw it away. – Jason A. Long Nov 30 '18 at 21:22
  • If the guarantee from Conor Cunningham at Microsoft that it can break isn't enough for you, and [this evidence that it definitely breaks as soon as you do anything meaningful with the output](https://i.stack.imgur.com/eCTIT.png), I'm not sure what to tell you. :-) – Aaron Bertrand Nov 30 '18 at 21:23
  • @AaronBertrand - I'm not trying to be combative or obstinate. I know the no ORDER BY means no "guarantee"... I'm also not trying to convince you or anyone else to throw this into a business critical query. I'm just playing with the code and trying to figure out what will and what won't break it. And before you say it's definitively broken by a join... [Have a look at the 3rd query...](https://drive.google.com/file/d/1ShOFxpfP2Qbs_g8koFAwlEkJDaTwZ-FK/view?usp=sharing) – Jason A. Long Nov 30 '18 at 22:56
  • Yeah, I don't think this is getting anywhere. I don't understand the purpose of demonstrating some specific scenarios where you get the order that you want. Sometimes when I don't wear my seatbelt I don't get ejected through my windshield, that doesn't mean I'm going to skip the seatbelt if I'm not going far. – Aaron Bertrand Nov 30 '18 at 23:08
1

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

John Cappelletti
  • 79,615
  • 7
  • 44
  • 66
1

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;
Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
0

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

Ilyes
  • 14,640
  • 4
  • 29
  • 55