【问题标题】:Selecting data against numeric values saved as comma separated string根据保存为逗号分隔字符串的数值选择数据
【发布时间】:2018-11-30 17:37:45
【问题描述】:

我有两个 sql 表,正在寻找一个 sql 查询来从 Table2.ValueDescription 列中针对 Table1.ValueID 列中的每个数值选择数据并将结果保存在 Table3 中

表1:

ID  ValueID
1   1,12,14
2   3,5,15
3   2,6,13,16

表2:

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

预期结果:

表3:

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

使用 SQL Server Management Studio

这是我尝试过的查询

 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

我在这里缺少什么?

【问题讨论】:

  • 查看拆分字符串和 STUFF...拆分字符串以将第一个表与第二个表连接起来,然后使用 STUFF 模式将事物串联起来。
  • 我很震惊,特别是考虑到发布答案的人,没有人提到存储分隔数据是多么可怕。这是一个关于它到底有多糟糕的精彩讨论。 stackoverflow.com/a/3653574/3813116
  • @SeanLange 在我的辩护中,我已经说过很多次了,我很震惊人们只是继续以这种方式存储数据,然后需要处理它,却找不到几十个之前的讨论... :-)
  • 嗯...好的。不确定如何存储分隔数据不存储分隔数据。但无论如何。

标签: sql sql-server sql-server-2017


【解决方案1】:

如果您确实使用 SQL Server 2017,则可以同时使用 STRING_SPLIT 和 STRING_AGG 函数。它们使语法非常简单。

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;

结果...

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

编辑:

-

-============================================================================
-- 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

【讨论】:

  • STRING_SPLIT 不保证输出顺序。 “输出行可能是任何顺序。不保证该顺序与输入字符串中子字符串的顺序相匹配。”所以对于给定的行,事情可能不符合您的预期。
  • 另一方面,预期的顺序不明确。这可能无关紧要,它可能与原始列表中的顺序相同,或者它可能希望按数字顺序重新排序。我们唯一可以排除的是结果列不需要按字母顺序列出项目。
  • 您可以使用charindex() 欺骗它(请参阅我的答案),但如果列表包含重复项,这会很快崩溃。
  • 单独使用?不。Conor Cunningham 重写的文档以明确保证行可以以任何顺序返回(“The order is not guaranteed to match the order of the substrings in the input string.”),这足以让我不信任它。只要您在原始列表之外引入其他数据,all bets are definitely off
  • 我将新措辞读作“保证不被保证” - 因此,相信它“工作”是,恕我直言,不明智,可能会误导,甚至可能有点危险。当你唯一能证明它有效的证据是你还没有破坏它时,我根本不会提倡它。多年来人们一直信任视图,然后 SQL Server 2005 出现并破坏了许多应用程序。
【解决方案2】:

这将保持正确的顺序

示例

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

退货

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

糟糕——刚刚看到你是 2017 年

【讨论】:

  • 太棒了!太棒了!约翰卡佩莱蒂你刚刚救了我的命!
  • 乐于助人。但既然你是 2017 年,请看一下 string_split 和 string_aggr
  • 肯定会看一下,但已经创建了 Table3 以及所有可能的组合。 :)
  • @ThirdPartyAuth 我会放弃 table3 ...根据您的样本量,组合的数量可能会达到天文数字 :)
【解决方案3】:

它并没有那么漂亮,但是 SQL Server 2017 中的新内置函数确实使这更容易理解,并且仍然可以尊重原始列表的顺序(好吧,我什至无法分辨如果您打算按列表中的位置或按数字顺序排序,因为它们是相同的),那么只要它都是整数并且没有重复:

;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;

如果顺序无关紧要,并且您确定Table1 中的ValueID 列表中不能有重复或非整数,则要简单得多:

;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;

【讨论】:

    【解决方案4】:

    你可以这样做

    SELECT *,
           STUFF(
             (
               SELECT ',' + ValueDescription
               FROM T2
               WHERE ',' + T1.ValueID + ',' LIKE '%,' + CAST(T2.ValueID AS VARCHAR) + ',%'
               FOR XML PATH('')
             ),
             1, 1, ''
           ) ValueDescription
    FROM T1;
    

    返回:

    +----+-----------+-------------------------------+
    | 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-11
      • 1970-01-01
      • 2017-11-27
      • 2020-08-02
      • 1970-01-01
      相关资源
      最近更新 更多