【问题标题】:Concatenate Rows into String with Many to Many Joins使用多对多连接将行连接成字符串
【发布时间】:2014-06-02 07:44:55
【问题描述】:

我有一个查询几乎适用于我的所有数据场景......除了一个。我有一个主表、一个详细表(包含一对多行项目)和许多连接到详细表的描述表(代码描述等)。我需要将所有详细记录连接成一个字符串值,以便每个主记录都有一条记录。所以如果主记录有3个细节,所有的值都需要连接成一个记录来代表主记录。

当主记录有多个详细记录但详细记录与描述表 1 到 1 不匹配时,我的问题就出现了。例如,在我的场景中,主记录有 3 个详细记录,其中只有一个详细记录有描述记录。所以从 1 到 3 到 1,Master 到 Detail 到 Description。这导致了一个问题。尝试连接记录时,代码不起作用,因为从 Detail 到 Description 连接创建的 NULL 值。我似乎可以让它工作的唯一方法是做一个 Distinct 子查询,然后在外面做我的连接逻辑。我觉得必须有更好的方法,或者我只是错过了一些东西。我在下面提供了示例代码来显示我的问题。有 3 个选择运行。第一个是来自连接的所有记录的平面结果。第二个是我原来的逻辑显示了这个缺陷。第三个是一个工作版本,我希望有人知道如何做得更好。非常感谢您对此问题的任何帮助。

DECLARE @Notification table(
    SystemID int NOT NULL,
    NotificationID int
    );

DECLARE @NotificationItems table(
    SystemID int NOT NULL,
    NotificationID VARCHAR(100),
    LineItem VARCHAR(100)
    );    

DECLARE @NotificationCauses table(
    SystemID int NOT NULL,
    NotificationID VARCHAR(100),
    LineItem VARCHAR(100),
    TestValue VARCHAR(100)
    );    

INSERT INTO @Notification 
SELECT 40,1 UNION 
SELECT 40,2 UNION 
SELECT 40,3 UNION 
SELECT 40,4
INSERT INTO @NotificationItems 
SELECT 40,1,1 UNION 
SELECT 40,1,2 UNION 
SELECT 40,1,3 UNION 
SELECT 40,2,1 UNION 
SELECT 40,2,2 UNION 
SELECT 40,3,1
INSERT INTO @NotificationCauses 
SELECT 40,1,1,'Code_A' UNION 
SELECT 40,2,1,'Code_B' UNION 
SELECT 40,2,2,'Code_C' UNION 
SELECT 40,3,1,'Code_D'

--SELECT  *
--FROM    @Notification
--SELECT  *
--FROM    @NotificationItems

SELECT *
FROM    @Notification AS n
LEFT OUTER JOIN @NotificationItems AS ni
    ON n.NotificationID = ni.NotificationID
    AND n.SystemID = ni.SystemID
LEFT OUTER JOIN @NotificationCauses AS nc
    ON ni.NotificationID = nc.NotificationID
    AND ni.SystemID = nc.SystemID
    AND ni.LineItem = nc.LineItem


SELECT DISTINCT n.SystemID, n.NotificationID
,SUBSTRING(
    (
        SELECT DISTINCT
         CASE WHEN LTRIM(RTRIM(ni1.LineItem)) <> ISNULL('','') THEN ', '+ni1.LineItem ELSE '' END AS [text()] 
        FROM @NotificationItems AS ni1
        WHERE ni1.SystemID = ni.SystemID AND ni1.NotificationID = ni.NotificationID --AND a1.LineItem = a.LineItem
        ORDER BY 1
        FOR XML PATH ('')
    ), 2, 1000) AS [LineItem]
,SUBSTRING(
    (
        SELECT DISTINCT
         CASE WHEN LTRIM(RTRIM(nc1.TestValue)) <> ISNULL('','') THEN ', '+nc1.TestValue ELSE '' END AS [text()] 
        FROM @NotificationCauses AS nc1
        WHERE nc1.SystemID = nc.SystemID AND nc1.NotificationID = nc.NotificationID --AND nc1.LineItem = nc.LineItem
        ORDER BY 1
        FOR XML PATH ('')
    ), 2, 1000) AS [TestValues]
FROM    @Notification AS n
LEFT OUTER JOIN @NotificationItems AS ni
    ON n.SystemID = ni.SystemID
    AND n.NotificationID = ni.NotificationID
LEFT OUTER JOIN @NotificationCauses AS nc
    ON ni.SystemID = nc.SystemID
    AND ni.NotificationID = nc.NotificationID
    AND ni.LineItem = nc.LineItem


SELECT DISTINCT SystemID, NotificationID
,SUBSTRING(
    (
        SELECT DISTINCT
         CASE WHEN LTRIM(RTRIM(a1.LineItem)) <> ISNULL('','') THEN ', '+a1.LineItem ELSE '' END AS [text()] 
        FROM @NotificationItems AS a1
        WHERE a1.SystemID = a.SystemID AND a1.NotificationID = a.NotificationID --AND a1.LineItem = a.LineItem
        ORDER BY 1
        FOR XML PATH ('')
    ), 2, 1000) AS [LineItem]
,SUBSTRING(
    (
        SELECT DISTINCT
         CASE WHEN LTRIM(RTRIM(a1.TestValue)) <> ISNULL('','') THEN ', '+a1.TestValue ELSE '' END AS [text()] 
        FROM @NotificationCauses AS a1
        WHERE a1.SystemID = a.SystemID AND a1.NotificationID = a.NotificationID --AND a1.LineItem = a.LineItem
        ORDER BY 1
        FOR XML PATH ('')
    ), 2, 1000) AS [TestValues]    
FROM    
(    
SELECT DISTINCT n.NotificationID, n.SystemID, ni.LineItem, nc.TestValue
FROM    @Notification AS n
LEFT OUTER JOIN @NotificationItems AS ni
    ON n.SystemID = ni.SystemID
    AND n.NotificationID = ni.NotificationID
LEFT OUTER JOIN @NotificationCauses AS nc
    ON ni.SystemID = nc.SystemID
    AND ni.NotificationID = nc.NotificationID
    AND ni.LineItem = nc.LineItem
) AS a

【问题讨论】:

标签: sql sql-server sql-server-2008 concatenation


【解决方案1】:

我已经清除了查询,这就是结果

SELECT n.SystemID, n.NotificationID
     , SUBSTRING((SELECT COALESCE(', ' + ni.LineItem, '') [text()] 
                  FROM   NotificationItems AS ni
                  WHERE  ni.SystemID = n.SystemID 
                    AND  ni.NotificationID = n.NotificationID
                  ORDER BY 1
                  FOR XML PATH ('')
                 ), 2, 1000) AS [LineItem]
     , SUBSTRING((SELECT COALESCE(', ' + nc.TestValue, '') [text()] 
                  FROM   NotificationItems AS ni
                         INNER JOIN NotificationCauses nc
                                 ON ni.SystemID = nc.SystemID 
                                AND ni.NotificationID = nc.NotificationID 
                                AND ni.LineItem = nc.LineItem
                  WHERE ni.SystemID = n.SystemID 
                    AND ni.NotificationID = n.NotificationID
                  ORDER BY 1
                  FOR XML PATH ('')
                 ), 2, 1000) AS [TestValues]
FROM   Notification n

那些是建立的“气味”:

  • ISNULL('', ''),它真的什么都不做,替换为空白字符串''(更多内容)

  • 主select的FROM,真正使用的时候有3个表,没用的2个被删除,需要更新FROM和WHERE条件子查询

  • 子查询的DISTINCT,同样什么也不做,被剥离了

  • 子查询中的CASE 在值前添加一个逗号,如果修剪后的值不为空,这正是COALESCE(', ' + value, '') 所做的(如果需要修剪,则重新添加)

其他所有内容都只是按照我的方式格式化查询,因为如果按照你的方式格式化它会更容易阅读(比如通过重构进行审查)

Here 是提供数据的已清除查询的 SQLFiddle 演示

【讨论】:

  • 谢谢 Serpiton。您的解决方案似乎解决了我的问题,现在我看到了,它确实有意义。真正的问题,除了您所做的其他一些代码清理项之外,是我的连接逻辑在哪里以及我是如何做的。现在我看到了,将 LEFT OUTER 连接移动到子选择中并将它们转换为 INNER JOIN 非常有意义。感谢您让我免于为此而烦恼!
  • 对不起,我本想接受但错过了。再次感谢。
猜你喜欢
  • 2015-09-18
  • 1970-01-01
  • 1970-01-01
  • 2012-02-02
  • 1970-01-01
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多