【问题标题】:SQL Server Left Outer Join returns either too many rows or incorrect rowsSQL Server 左外连接返回太多行或不正确的行
【发布时间】:2015-03-18 20:00:07
【问题描述】:

我有一个 SQL Server 查询,我想对 2 个表进行左连接。我需要它返回第一个表中的所有行,并且只返回其他两个表中的匹配数据。我的第一个化身创建了某种叉积,其行数超过了所需行数的 10 倍。所以我添加了一个 rowcount 函数并让它过滤所有 rowcount 值 = 1。现在它返回正确的行数,但其他 2 个表中的数据错误。

我有某些过滤规则(case 语句),我将应用于 2 个正确表的内容以确定要返回的内容(如果有的话)。我认为这可能是问题的根源,但我不确定。我尝试在 select 语句中使用过滤器代码,并将其用作查询末尾的 where 语句,甚至用作 join on 语句的一部分。我也尝试使用交叉和外部应用。一切都无济于事。

我试图在不使用循环的情况下实现这一点。是否可以?请看下面的代码。此代码生成太多行。我不会重复第二次迭代,因为它会在顶部添加一个额外的选择,并在底部添加一个 m=1 的过滤器以生成正确的行数。

这是我要加入的 2 个表。请注意,第二个表只是更大表的一小部分。

谢谢!!!

SELECT
GLENTRY.Fac + GLENTRY.Rundt + GLENTRY.Jrnllog AS UniqueID,
GLENTRY.Fac AS Fac,
(CASE WHEN 
    EntityTranslate2014.AcctEnd <> '' AND EntityTranslate2014.AcctEnd = SUBSTRING(GLENTRY.Acct,6,3)
THEN 
    EntityTranslate2014.Entity 
ELSE
    CASE WHEN
        EntityTranslate2014.AcctEnd <> 0 AND EntityTranslate2014.Acct = GLENTRY.Acct
    THEN 
        EntityTranslate2014.Entity 
    ELSE
        CASE WHEN 
            EntityTranslate2014.SSDept <> 0 AND EntityTranslate2014.SSDept = SUBSTRING(GLENTRY.Acct,1,4)
        THEN 
            EntityTranslate2014.Entity 
        ELSE
            CASE WHEN 
                EntityTranslate2014.SSDept = 0
                AND
                EntityTranslate2014.AcctEnd = ''
                AND
                EntityTranslate2014.Acct = 0 
            THEN 
                EntityTranslate2014.Entity
            ELSE
                '' 
            END
        END
    END
END)
AS NEWEntity,

(CASE WHEN AcctTranslate2014.Fac = GLENTRY.Fac OR AcctTranslate2014.Fac = '0' 
THEN 
    AcctTranslate2014.NEWDept 
ELSE 
    Null 
END)
AS Department,

(CONVERT(DATETIME,GLENTRY.Period + '/01/' + 
                        CASE WHEN 
                            CONVERT(NVARCHAR(4),GLENTRY.Yearz) = '' 
                        THEN 
                            '2014' 
                        ELSE 
                            GLENTRY.Yearz
                        END) 
AS YEARPER,

GLENTRY.Acct) AS SSAcct,
CONVERT(NVARCHAR(20),
            CASE WHEN 
                AcctTranslate2014.Fac = GLENTRY.Fac OR AcctTranslate2014.Fac = '0'
            THEN 
                AcctTranslate2014.NEWAcct 
            ELSE 
                Null 
            END)
AS NEWAccount,
GLENTRY.Rundt,
GLENTRY.Jrnlid,
GLENTRY.Amount,
GLENTRY.Dc,
GLENTRY.Ref,
GLENTRY.Refdt,
CONVERT(NVARCHAR(100),'Import of SS ' + CONVERT(VARCHAR(2),GLENTRY.Period) + '/' + CONVERT(VARCHAR(4),GLENTRY.Yearz) + ' GL activity') AS Descr,
GLENTRY.Invnr,
GLENTRY.Jrnllog,
ROW_NUMBER() OVER(PARTITION BY GLENTRY.Fac, GLENTRY.Yearz, GLENTRY.Period, GLENTRY.Pagez, GLENTRY.Acct, GLENTRY.Rundt, GLENTRY.Jrnlid,
GLENTRY.Amount, GLENTRY.Dc, GLENTRY.Ref, GLENTRY.Refdt, GLENTRY.Descr, GLENTRY.Invnr, GLENTRY.Origfac, GLENTRY.Jrnllog, GLENTRY.Seq
ORDER BY GLENTRY.Fac, GLENTRY.Yearz, GLENTRY.Period) AS m

FROM GLENTRY 
LEFT OUTER JOIN
    EntityTranslate2014 ON GLENTRY.Fac = EntityTranslate2014.Fac 
LEFT OUTER JOIN
    AcctTranslate2014 ON CONVERT(VARCHAR(8),GLENTRY.Acct) = AcctTranslate2014.SSAcct
WHERE GLENTRY.Yearz = 2014 AND GLENTRY.Period = 11


EntityTranslate2014 File

Fac Entity  Descr       AcctEnd SSDept  Acct
1   51900   Entity1     0               0
2   50901   Entity2     0               0
3   10100   Entity3     0               0
3   10500   Entity4     4016            0
3   10500   Entity4     4020            0
3   10500   Entity4     4022            0
3   10500   Entity4     4024            0
3   10500   Entity4     4028            0
3   10500   Entity4     7016            0
4   30900   Entity5     0               0
5   10300   Entity6     0               0
6   11300   Entity7     0               0
7   11100   Entity8     0               0
7   11500   Entity9     4016            0
7   11500   Entity9     4020            0
7   11500   Entity9     4022            0
7   11500   Entity9     4024            0
7   11500   Entity9     4028            0
7   11500   Entity9     7016            0
9   32909   Entity10    0               0
10  12100   Entity11    0               0
11  32901   Entity12    0               0
12  53900   Entity13    0               0
13  10200   Entity14    0               0
14  32914   Entity15    0               0
15  32915   Entity16    0               0
16  11200   Entity17    0               0
17  32917   Entity18    0               0
18  32918   Entity19    0               0
19  32919   Entity20    0               0
20  32920   Entity21    0               0
21  13100   Entity22    0               0
22  52900   Entity23    0               0
89  99900   Entity24    0               0
123 12300   Entity25    0               0
124 12200   Entity26    0               0
133 13300   Entity27    0               0
201 11201   Entity28    0               0
202 11202   Entity29    0               0
402 25402   Entity30    0               0
403 25403   Entity31    0               0
549 25430   Entity32    0               0
549 25432   Entity33    7195            0
910 50910   Entity34    0               0
911 50911   Entity35    0               0
21  13500   Entity36    4016            0
21  13500   Entity36    4020            0
21  13500   Entity36    4022            0
21  13500   Entity36    4024            0
21  13500   Entity36    4028            0
21  13500   Entity36    7016            0
16  11202   Entity37    0        002    0
16  11201   Entity37    0        001    0
16  11200   Entity38    0               30918000
16  11200   Entity38    0               31918000
16  11200   Entity38    0               32110000


AcctTranslate2014

NewAcct     SSAcct      NewDEpt Fac
10111500    111200010   000     0
10111600    111200050   000     0
10111700    111550010   000     0
10113092    111050450   000     0
10115090    111050010   000     0

【问题讨论】:

  • GLENTRY和EntityTranslate2014的关系是1对1还是1对多?如果关系是一对多,那么这就解释了为什么你的结果被重复了。如果没有更好地了解数据,就很难确定。也许您可以提供一些示例数据。
  • 左外连接听起来像是要走的路。那是您发布的代码吗?快速检查一下,外连接只引用源表,而不是可能返回 null 的两个表。
  • 我已经添加了我要加入的 2 个表。请注意,第二个表只显示了 100 行中的几行。
  • rhholt 和 Brian J - 感谢您的快速回复! GLENTRY 和 EntityTranslate2014 之间的关系可以是多对多的。这就是 select 语句中的 Case 逻辑的目的,尽管我认为它可能不够有效。我正在使用它从 EntityTranslate2014 表中返回正确的行。至于左连接,我在查询底部有 2 个左连接语句。他们不正确吗?是否有一种特殊的方法可以将 GLENTRY 左连接到 EntityTranslate2014 和 AcctTranslate2014?

标签: sql-server left-join multiple-tables


【解决方案1】:
FROM GLENTRY
LEFT OUTER JOIN EntityTranslate2014
    ON GLENTRY.Fac = EntityTranslate2014.Fac
LEFT OUTER JOIN AcctTranslate2014
    ON CONVERT(VARCHAR(8), GLENTRY.Acct) = AcctTranslate2014.SSAcct

EntityTranslate2014 和 AcctTranslate2014 之间没有定义关系。如果任一表中有多个记录对应GLENTRY 中的相应记录,则这两个表将有效地相互交叉连接。例如,如果单个 GLENTRY 连接到 EntityTranslate2014 中的两条记录和 AcctTranslate2014 中的两条记录,那么您将获得 4 条记录,每个可能的组合都有一条记录。这就是JOIN 的定义方式。

如果您知道这正在发生并且您知道实际上没有任何关系,并且您只想让GLENTRY 中的每条记录EntityTranslate2014 中的第一条记录匹配AcctTranslate2014 中的第一条记录,以及EntityTranslate2014 中的第二条记录以匹配AcctTranslate2014 中的第二条记录,依此类推,您可以执行我所听说的“ZIP JOIN”:

FROM GLENTRY
LEFT OUTER JOIN (
        SELECT *,
            ROW_NUMBER() OVER (PARTITION BY Fac ORDER BY <SomeField>) row_order
        FROM EntityTranslate2014) ET2014
    ON GLENTRY.Fac = ET2014.Fac
LEFT OUTER JOIN (
        SELECT *,
            ROW_NUMBER() OVER (PARTITION BY SSAcct ORDER BY <SomeField>) row_order
        FROM AcctTranslate2014) AT2014
    ON CONVERT(VARCHAR(8), GLENTRY.Acct) = AT2014.SSAcct
    AND AT2014.row_order = ET2014.row_order

显然,不要使用SELECT *;这只是一个例子。所以,这样做的目的是为每个 OUTER 表分配 some 顺序,并将它们相互匹配。因此,每个 OUTER 表中的每条记录只会出现一次。如果不匹配,则另一个表中的记录将显示为NULLs。

另一种常见的替代方法是简单地返回每个 OUTER 表的第一条记录,这样可以保证您只有一个 GLENTRY 记录,因为每个 OUTER 表只为每个键字段提供一个或零个记录。

如果对您的目的不起作用,那么您可能需要使用两个查询,并且需要在您的应用程序中进行匹配。

【讨论】:

  • 感谢您的帮助!我刚刚上传了一些我想要实现的伪代码。没有应用程序来按摩数据。此查询将由 SSIS 作为更大包的一部分调用。
【解决方案2】:

您可以使用关联子查询“循环”遍历一个表中的每一行,并将其与另一表中的行进行比较,而无需实际编写循环逻辑(在 SQL 中应该避免这种情况)。根据 where 谓词,将内部查询中的行与外部查询中的每一行进行比较。如果 WHERE 子句查询中将返回超过 1 条记录,相关子查询还允许您按 TOP 1 过滤 WHERE 子句(如下面的代码所示)。这只是一个简短的示例,因为我没有时间完整地写出代码,但它可能会有所帮助。

SELECT Entity
  FROM EntityTranslate2014 ent
 WHERE ent.AcctEnd <> ''
   AND AcctEnd = (SELECT TOP 1 SUBSTRING(glt.Acct,6,3) 
                    FROM Glentry glt 
                   WHERE glt.fac = ent.fac
                     AND glt.Year = 2014
                     AND glt.Period = 11
                   ORDER BY glt.Fac, glt.Yearz, glt.Period)

在此示例中,EntityTranslate2014 表中的每条记录都与基于谓词 Glentry.Fac = EntityTraslate2014.fac 的内部查询的结果进行比较。希望对您有所帮助。

有关相关子查询的更多信息,请查看以下链接。

https://technet.microsoft.com/en-us/library/ms187638%28v=sql.105%29.aspx

【讨论】:

    【解决方案3】:

    更新...

    您可以使用ROW_NUMBER 对连接结果中的序列进行计数,并使用ORDER BY 对它们进行排序,以便您想要的行在该序列中排在第一位。

    感谢您发布循环构造,我想我能够用一系列 CTE(别名为 (...))来近似它的功能。我不确定您的所有表/字段名称是否正确,但这应该接近功能性。我的一个主要猜测是应该如何确定“第一个”实体,您可能需要在 CTE 中的行号函数中调整 order by。

    WITH ENTFIND AS (
        SELECT  
            EntityTranslate2014.Fac,
            EntityTranslate2014.Acct,
            EntityTranslate2014.AcctEnd,
            EntityTranslate2014.Entity,
            EntityTranslate2014.SSDept,
            EntityTranslate2014.Entity,
            ROW_NUMBER() OVER (PARTITION BY Fac, Acct, AcctEnd ORDER BY Fac, Acct, AcctEnd, Entity) as E1Sort
        FROM
            EntityTranslate2014
    ),
    E2 AS (
        SELECT
            *,
            ROW_NUMBER() OVER (PARTITION BY Fac, Acct, AcctEnd ORDER BY Fac, Acct, AcctEnd, Entity) as E2Sort
        FROM
            ENTFIND
        WHERE
            AcctEnd <> 0
    ),
    E3 AS (
        SELECT
            *,
            ROW_NUMBER() OVER (PARTITION BY Fac, Acct, AcctEnd ORDER BY Fac, Acct, AcctEnd, Entity) as E3Sort
        FROM
            ENTFIND
        WHERE
            SSDept <> 0
    ),
    E4 AS (
        SELECT
            *,
            ROW_NUMBER() OVER (PARTITION BY Fac, Acct, AcctEnd ORDER BY Fac, Acct, AcctEnd, Entity) as E4Sort
        FROM
            ENTFIND
        WHERE
            Acct = 0
            AND
            AcctEnd = ''
            AND
            SSDept = 0
    )
    SELECT
        GLENTRY.Fac + GLENTRY.Rundt + GLENTRY.Jrnllog AS UniqueID,
        GLENTRY.Fac AS Fac,,
        E1.Entity,
        E2.Entity,
        E3.Entity,
        E4.Entity,
        COALESCE(E1.Entity,E2.Entity,E3.Entity,E4.Entity) as 1stFoundEntity,
    (CASE WHEN 
        EntityTranslate2014.AcctEnd <> '' AND EntityTranslate2014.AcctEnd = SUBSTRING(GLENTRY.Acct,6,3)
    THEN 
        EntityTranslate2014.Entity 
    ELSE
        CASE WHEN
            EntityTranslate2014.AcctEnd <> 0 AND EntityTranslate2014.Acct = GLENTRY.Acct
        THEN 
            EntityTranslate2014.Entity 
        ELSE
            CASE WHEN 
                EntityTranslate2014.SSDept <> 0 AND EntityTranslate2014.SSDept = SUBSTRING(GLENTRY.Acct,1,4)
            THEN 
                EntityTranslate2014.Entity 
            ELSE
                CASE WHEN 
                    EntityTranslate2014.SSDept = 0
                    AND
                    EntityTranslate2014.AcctEnd = ''
                    AND
                    EntityTranslate2014.Acct = 0 
                THEN 
                    EntityTranslate2014.Entity
                ELSE
                    '' 
                END
            END
        END
    END)
    AS NEWEntity,
    
    (CASE WHEN AcctTranslate2014.Fac = GLENTRY.Fac OR AcctTranslate2014.Fac = '0' 
    THEN 
        AcctTranslate2014.NEWDept 
    ELSE 
        Null 
    END)
    AS Department,
    
    (CONVERT(DATETIME,GLENTRY.Period + '/01/' + 
                            CASE WHEN 
                                CONVERT(NVARCHAR(4),GLENTRY.Yearz) = '' 
                            THEN 
                                '2014' 
                            ELSE 
                                GLENTRY.Yearz
                            END) 
    AS YEARPER,
    
    GLENTRY.Acct) AS SSAcct,
    CONVERT(NVARCHAR(20),
                CASE WHEN 
                    AcctTranslate2014.Fac = GLENTRY.Fac OR AcctTranslate2014.Fac = '0'
                THEN 
                    AcctTranslate2014.NEWAcct 
                ELSE 
                    Null 
                END) AS NEWAccount,
        GLENTRY.Rundt,
        GLENTRY.Jrnlid,
        GLENTRY.Amount,
        GLENTRY.Dc,
        GLENTRY.Ref,
        GLENTRY.Refdt,
        CONVERT(NVARCHAR(100),'Import of SS ' + CONVERT(VARCHAR(2),GLENTRY.Period) + '/' + CONVERT(VARCHAR(4),GLENTRY.Yearz) + ' GL activity') AS Descr,
        GLENTRY.Invnr,
        GLENTRY.Jrnllog,
        ROW_NUMBER() OVER(PARTITION BY GLENTRY.Fac, GLENTRY.Yearz, GLENTRY.Period, GLENTRY.Pagez, GLENTRY.Acct,         GLENTRY.Rundt, GLENTRY.Jrnlid,
            GLENTRY.Amount, GLENTRY.Dc, GLENTRY.Ref, GLENTRY.Refdt, GLENTRY.Descr, GLENTRY.Invnr, GLENTRY.Origfac,          GLENTRY.Jrnllog, GLENTRY.Seq
        ORDER BY GLENTRY.Fac, GLENTRY.Yearz, GLENTRY.Period) AS m
    FROM
        GLENTRY
        LEFT JOIN 
            ENTFIND E1 ON GLENTRY.Fac = E1.Fac
                    AND SUBSTRING(GLENTRY.Acct,6,3) = E1.AcctEnd
                        AND E1Sort = 1
        LEFT JOIN 
            E2 ON GLENTRY.Fac = E2.Fac
                    AND GLENTRY.Acct = E2.Acct
                        AND E2.E2Sort = 1
        LEFT JOIN 
            E3 ON GLENTRY.Fac = E3.Fac
                    AND SUBSTRING(GLENTRY.Acct,1,4) = E3.SSDept
                        AND E3.E3Sort = 1
        LEFT JOIN 
            E4 ON GLENTRY.Fac = E4.Fac
                    AND E4.E4Sort = 1
        LEFT OUTER JOIN
                EntityTranslate2014 ON GLENTRY.Fac = EntityTranslate2014.Fac 
        LEFT OUTER JOIN
                AcctTranslate2014 ON CONVERT(VARCHAR(8),GLENTRY.Acct) = AcctTranslate2014.SSAcct
    WHERE 
        GLENTRY.Yearz = 2014 AND GLENTRY.Period = 11
    

    【讨论】:

    • 谢谢,但我仍然需要筛选正确的行。每次都不会是同一个数字。这就是我使用 Case 逻辑的原因,尽管我猜错了。
    【解决方案4】:

    我要感谢大家在我试图让我的查询发挥作用时提出的想法。我最终得到了以下解决方案。注意,为简洁起见,我只包含了用于确定实体字段的代码。它不漂亮,我确信它是最佳的,但它确实有效。

    再次感谢大家的帮助!!

    CASE WHEN 
        (SELECT 
            BET.Entity 
        FROM 
            BET 
        WHERE 
            GL.Fac = BET.Fac 
            AND 
            BET.AcctEnd <> '' 
            AND 
            BET.AcctEnd = SUBSTRING(CONVERT(NVARCHAR(8),GL.Acct),6,3)) 
            IS NOT Null
    THEN
        (SELECT 
            BET.Entity 
        FROM 
            SOSViews.dbo.BI360EntityTranslate2014 BET 
        WHERE 
            GL.Fac = BET.Fac AND BET.AcctEnd <> '' 
            AND 
            BET.AcctEnd = SUBSTRING(CONVERT(NVARCHAR(8),GL.Acct),6,3)) 
    ELSE
        CASE WHEN 
            (SELECT 
                BET.Entity 
            FROM 
                SOSViews.dbo.BI360EntityTranslate2014 BET 
            WHERE 
                GL.Fac = BET.Fac 
                AND 
                BET.SOSDept <> 0
                AND 
                BET.SOSDept = SUBSTRING(CONVERT(NVARCHAR(8),GL.Acct),1,4)) 
                IS NOT Null
        THEN
            (SELECT 
                BET.Entity 
            FROM 
                BET 
            WHERE 
                GL.Fac = BET.Fac 
                AND 
                BET.SOSDept <> 0
                AND 
                BET.SOSDept = SUBSTRING(CONVERT(NVARCHAR(8),GL.Acct),1,4)) 
        ELSE
            CASE WHEN 
                (SELECT 
                    BET.Entity 
                FROM 
                    BET 
                WHERE 
                    GL.Fac = BET.Fac 
                    AND 
                    BET.SOSDept = 0 AND BET.AcctEnd = '' AND BET.Acct = 0) 
                    IS NOT Null
            THEN
                (SELECT 
                    BET.Entity 
                FROM 
                    BET 
                WHERE 
                    GL.Fac = BET.Fac 
                    AND 
                    BET.SOSDept = 0 AND BET.AcctEnd = '' AND BET.Acct = 0) 
            ELSE
                'No Entity Translation'
            END
        END
    END)
    AS Entity,
    

    【讨论】:

      【解决方案5】:

      注意:虽然它可能不直接适用于OP的问题,但有时如果连接中的行太多,就像在错误的列上连接一样简单。这只是发生在我身上......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-28
        • 2011-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-22
        相关资源
        最近更新 更多