从表中获取逗号分隔的一组值,其中另一个表上的另一个参考值出现两次(或更多)

Posted

技术标签:

【中文标题】从表中获取逗号分隔的一组值,其中另一个表上的另一个参考值出现两次(或更多)【英文标题】:Get comma-separated set of values from table where another reference value on another table appears twice (or more) 【发布时间】:2021-12-06 22:01:01 【问题描述】:

假设 SQL Server 2014 中的数据库设置如下:

DECLARE @MATERIAL TABLE (ID int, CODE varchar(30));

INSERT @MATERIAL (ID, CODE) VALUES
(1, 'D3033MBBY'),
(2, 'D3033MBTY'),
(3, '011130-01'),
(4, '011130-04C'),
(5, '021002'),
(6, '021017-B'),
(7, '021134-01'),
(8, '021135-01'),
(9, '021955-01'),
(10, '3LS91101-550'),
(11, 'D3049MBRB'),
(12, 'EF0118'),
(13, 'FV8130'),
(14, 'FY7009'),
(15, 'H05802'),
(16, 'D3033MRTE');

DECLARE @SUBSTITUTE TABLE (ID int, ITEID int, SUBSTITUTECODE varchar(100));

INSERT @SUBSTITUTE (ID, ITEID, SUBSTITUTECODE) VALUES
(5232, 1, '191045762418'),
(5442, 2, '191045762418'),
(6435, 3, '5206432380030'),
(6573, 4, '5206432380030'),
(6582, 5, '5206432357131'),
(6683, 6, '5206432369486'),
(7332, 7, '5206432380610'),
(7482, 8, '5206432380818'),
(7721, 9, '5206432346029'),
(7831, 10, '5205172116350'),
(8034, 11, '191045480992'),
(8184, 12, '4061622759543'),
(8284, 13, '4062058577497'),
(8573, 14, '4064039588089'),
(9438, 15, '4064048672519'),
(9746, 16, '191045762418');

SELECT sub.SUBSTITUTECODE
FROM @SUBSTITUTE AS sub
INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
GROUP BY sub.SUBSTITUTECODE
HAVING COUNT(sub.SUBSTITUTECODE) > 1;

我想创建一个会产生以下结果集的查询:

CODES SUBSTITUTECODE
D3033MBBY,D3033MBTY,D3033MRTE 191045762418
011130-01,011130-04C 5206432380030

换句话说,我想在@MATERIAL 中获得一组以逗号分隔的CODEs,其中表@SUBSTITUTE 中的这些记录有重复的SUBSTITUTECODE 引用

间接地,我可以通过以下查询找到与那些重复的SUBSTITUTECODEs 对应的CODEs:

SELECT prod.CODE, sub.SUBSTITUTECODE
FROM @SUBSTITUTE AS sub
INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
WHERE sub.SUBSTITUTECODE IN (SELECT sub.SUBSTITUTECODE
    FROM @SUBSTITUTE AS sub
    INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
    GROUP BY sub.SUBSTITUTECODE
    HAVING COUNT(sub.SUBSTITUTECODE) > 1)

上述案例的工作小提琴可以找到here。

请注意,此方案的完整案例在 SQL Server 2014 上运行。

TIA

【问题讨论】:

我已经设法做到了这一点,正如工作小提琴中所示。我的意思是,我能够获得带有重复条目的SUBSTITUTECODEs 列表!我无法添加与每个产品对应的产品的逗号分隔值的列。 @DaleK 我在我的问题中添加了更多信息。我能够在单独的行中获得CODES 以及与它们对应的SUBSTITUTECODEs,并使用间接查询,这意味着我使用IN 子句将两个查询组合在一起的查询...但我敢打赌,还有另一种解决方案,只需使用某种类型的 JOIN 子句。 【参考方案1】:

不错的开始小提琴,谢谢!如果我们只是把你已经拥有的东西放在 CTE 中,我们可以围绕它编写一个标准的字符串聚合:

;WITH subs AS 
(
  SELECT prod.CODE, sub.SUBSTITUTECODE
  FROM @SUBSTITUTE AS sub
  INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
  WHERE sub.SUBSTITUTECODE IN (SELECT sub.SUBSTITUTECODE
    FROM @SUBSTITUTE AS sub
    INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
    GROUP BY sub.SUBSTITUTECODE
    HAVING COUNT(sub.SUBSTITUTECODE) > 1)
)
SELECT CODES = STUFF((SELECT ',' + CODE 
  FROM subs AS s2 WHERE s2.SUBSTITUTECODE = subs.SUBSTITUTECODE
  FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)'),1,1,''),
    SUBSTITUTECODE FROM subs
  GROUP BY SUBSTITUTECODE;
示例db<>fiddle

但是我们可以稍微简化一下这段代码,最重要的是避免引用两个表两次,像这样:

;WITH subs AS
(
  SELECT s.ITEID, s.SUBSTITUTECODE, m.CODE, 
    c = COUNT(*) OVER (PARTITION BY s.SUBSTITUTECODE)
  FROM @SUBSTITUTE AS s
  INNER JOIN @MATERIAL AS m
  ON m.ID = s.ITEID
)
SELECT CODES = STUFF((SELECT ',' + CODE
  FROM subs AS s2 WHERE s2.SUBSTITUTECODE = subs.SUBSTITUTECODE
  FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)'),1,1,''),
    SUBSTITUTECODE
  FROM subs 
  WHERE c > 1 
  GROUP BY SUBSTITUTECODE;
示例db<>fiddle

请注意,在更现代的 SQL Server 版本(2017+)上,STRING_AGG() 使这更容易:

SELECT CODES = STRING_AGG(m.CODE, ','), s.SUBSTITUTECODE
  FROM @SUBSTITUTE AS s
  INNER JOIN @MATERIAL AS m
  ON m.ID = s.ITEID
  GROUP BY s.SUBSTITUTECODE
  HAVING COUNT(*) > 1;
示例db<>fiddle

【讨论】:

我想我会采用您的解决方案(简化的解决方案),因为对于不像您这样专业水平的人来说,它更清楚地了解发生了什么,而不是盲目地复制您的解决方案并将其粘贴到他们的真实情况中。非常感谢您! 是的,很遗憾我无法控制 SQL Server 的运行版本。我只是为了让他们的桌面商品应用程序和新网站之间运行同步服务而建立这个桥梁! @Faye 没问题,我认为从概念上讲,将 STUFF() 中的所有内容都放在脑海中会容易得多,因为“这是一个将字符串连接在一起的表达式”。我已经记住了语法,但我并没有真正考虑过底层机制。 CTE 获取连接的行并提供计数基于具有重复项的列,因此我们知道如何过滤。然后如果你删除STUFF()里面发生的事情(连接所有匹配这个子代码的材料代码),它只是从计数> 1的CTE中选择。 text()[1].[1] 快,例如参见 dba.stackexchange.com/a/193323/220697 @Charlieface 确实如此,但其他改进(如升级)会产生更大的影响。 :-) 也祝你好运教这种肌肉记忆使用text() 而不是.。您不妨让我重新开始使用 Windows。 :-)【参考方案2】:

由于您使用的是 SQL Server 2014,因此您不能使用STRING_AGG()

这是使用FOR XML PATH的解决方案

WITH CTE AS
(
    SELECT prod.CODE, sub.SUBSTITUTECODE, 
           c = COUNT(*) OVER (PARTITION BY sub.SUBSTITUTECODE)
    FROM   @SUBSTITUTE AS sub
           INNER JOIN @MATERIAL AS prod ON prod.ID = sub.ITEID
),
CTE2 AS
(
    SELECT *
    FROM   CTE
    WHERE  c > 1
)
SELECT STUFF((SELECT ',' + CODE 
              FROM CTE2 x 
              WHERE x.SUBSTITUTECODE = c.SUBSTITUTECODE 
              FOR XML PATH('')), 1, 1, ''),
       SUBSTITUTECODE
FROM   CTE2 c
GROUP BY SUBSTITUTECODE

【讨论】:

这很好用!我只是想知道是否有任何更简单的方法来编写查询而不使用两个临时表... @FayeD。什么临时表?您存储数据的两个表变量?两个 CTE(比您现在的查询要简单得多)?还有什么? 是的,临时表是指使用 WITH 表达式定义的两个 CTE。 @Faye 如果没有像子查询或 CTE 这样的表表达式,您将很难做到这一点。我认为查询中的 CTE 数量不是您应该用来评估它的清晰或有效程度的东西。 :-)

以上是关于从表中获取逗号分隔的一组值,其中另一个表上的另一个参考值出现两次(或更多)的主要内容,如果未能解决你的问题,请参考以下文章

从表中选择数值,其中数值以逗号分隔的字符串。

基于同一表上的另一个查询过滤记录的 SQL

从表中选择行,其中具有相同 id 的另一个表中的行在另一列中具有特定值

一个表中的一条记录显示在另一个表上的所有记录中

MySQL - 如何选择表中的行,其中 id 值位于另一个表中的逗号分隔字段中?

同一组连接表上的 Mysql 联合