【问题标题】:Oracle Plus (+) Joins to ANSI conversionOracle Plus (+) 连接到 ANSI 转换
【发布时间】:2017-03-15 18:15:14
【问题描述】:

我正在从 Oracle 到 SQL Datawarehouse Azure 的仓库迁移过程中遇到了这个查询的问题。

来自 Oracle 的原始查询 - 它返回 1872520 行。

SELECT
 *
FROM
 STG_REV_APPORTION_CSC_NO t1,
 STG_SEP_VL t2,
 STG_SEP_VL t3
WHERE
 t3.BUSINESS_DATE(+)    = t1.BUSINESS_DATE
AND t3.CSC_APP_NO(+)     = t1.CSC_APP_NO
AND t3.JOURNEY_NO(+)     = t1.JOURNEY_NO
AND t3.PURSE_TXN_CTR(+)  = t1.PURSE_TXN_CTR
AND t2.BUSINESS_DATE(+) = t1.BUSINESS_DATE
AND t2.CSC_APP_NO(+)    = t1.CSC_APP_NO
AND t2.JOURNEY_NO(+)    = t1.JOURNEY_NO
AND
 (
   t2.TRIP_NO(+) + 1
 )
 = t1.TRIP_NO
AND
 (
   t2.MSG_TYPE_CD(+)  = 13070
 AND t3.MSG_TYPE_CD(+) = 4357
 );

documentation 获取线索,我尝试将查询重写为 ANSI:

SELECT COUNT(*) 
 FROM STG_REV_APPORTION_CSC_NO t1
 RIGHT OUTER JOIN STG_SEP_VL t3 ON t3.BUSINESS_DATE = t1.BUSINESS_DATE
 AND t3.CSC_APP_NO     = t1.CSC_APP_NO
 AND t3.JOURNEY_NO     = t1.JOURNEY_NO
 AND t3.PURSE_TXN_CTR  = t1.PURSE_TXN_CTR
 RIGHT OUTER JOIN STG_SEP_VL t2 ON t2.BUSINESS_DATE = t1.BUSINESS_DATE
AND t2.CSC_APP_NO   = t1.CSC_APP_NO
AND t2.JOURNEY_NO    = t1.JOURNEY_NO 
AND (t2.TRIP_NO + 1)  = t1.TRIP_NO
WHERE t2.MSG_TYPE_CD = 13070 AND t3.MSG_TYPE_CD = 4357

它返回零行。 ANSI 版本应该在 oracle 实例上工作——它在那里也返回零行。

然后我尝试使用 toad 上的重构选项将 plus join 转换为 ANSI。我得到以下

SELECT *
  FROM STG_SEP_VL  T2
       RIGHT OUTER JOIN STG_REV_APPORTION_CSC_NO T1
           ON     (T2.BUSINESS_DATE = T1.BUSINESS_DATE)
              AND (T2.CSC_APP_NO = T1.CSC_APP_NO)
              AND (T2.JOURNEY_NO = T1.JOURNEY_NO)
       RIGHT OUTER JOIN STG_SEP_VL T3
           ON     (T3.PURSE_TXN_CTR = T1.PURSE_TXN_CTR)
              AND (T3.BUSINESS_DATE = T1.BUSINESS_DATE)
              AND (T3.CSC_APP_NO = T1.CSC_APP_NO)
              AND (T3.JOURNEY_NO = T1.JOURNEY_NO)
 WHERE     ( ( (T2.TRIP_NO                                             /*(+)*/
                          ) + 1) = T1.TRIP_NO)
       AND ( ( (T2.MSG_TYPE_CD                                         /*(+)*/
                              ) = 13070) AND ( (T3.MSG_TYPE_CD         /*(+)*/
                                                              ) = 4357));

现在这个查询应该在 Oracle 上运行并返回相同数量的行,然后我才能在 SQL Server 上运行它。但它没有 - 它返回零行。

我查看了这两个查询的解释计划。以下是 (+) 加入计划的样子:

此查询的 ANSI 版本如下所示:

我错过了什么吗?

【问题讨论】:

  • 所有(+) 条件必须写在ON 子句中!此查询不需要 WHERE 子句。 RIGHT JOIN STG_SEP_VL t2 ON ... AND t2.MSG_TYPE_CD = 13070。或在where:WHERE (t2.MSG_TYPE_CD = 13070 OR t2.MSG_TYPE_CD IS NULL /*(for rows not existing in t2)*/
  • 这太可怕了。应该责怪首先编写 (+) 查询的人,如果是你,对不起。我看不出错误在哪里,但我会说您的查询看起来比 Toad 重构的查询更正确,因为 AND (t2.TRIP_NO + 1) = t1.TRIP_NO 应该在 ON 子句而不是 WHERE 子句中,这对所有有一个(+)。我会在你的地方做的是分解:重写原始查询以仅使用一个表,然后是 2,然后是 3。然后使用 1 然后 2 然后 3 表将查询转换为正确的语法并比较结果
  • 不是我,谢天谢地:)

标签: oracle outer-join ansi


【解决方案1】:

没有样本数据很难确定,但我认为应该归咎于 where 子句。

在 where 子句中包含来自 t2 和 t3 的字段会否定外连接的效果,除非您也允许空值 (t2.MSG_TYPE_CD = 13070 OR 2.MSG_TYPE_CD IS NULL)。将这些过滤器移动到连接中允许不匹配的记录进入结果。

SELECT 
    COUNT(*) 
FROM 
    STG_REV_APPORTION_CSC_NO t1
        RIGHT OUTER JOIN STG_SEP_VL t3      ON  t3.BUSINESS_DATE    = t1.BUSINESS_DATE
                                            AND t3.CSC_APP_NO       = t1.CSC_APP_NO
                                            AND t3.JOURNEY_NO       = t1.JOURNEY_NO
                                            AND t3.PURSE_TXN_CTR    = t1.PURSE_TXN_CTR
                                            AND t3.MSG_TYPE_CD      = 4357
        RIGHT OUTER JOIN STG_SEP_VL t2      ON  t2.BUSINESS_DATE    = t1.BUSINESS_DATE
                                            AND t2.CSC_APP_NO       = t1.CSC_APP_NO
                                            AND t2.JOURNEY_NO       = t1.JOURNEY_NO 
                                            AND (t2.TRIP_NO + 1)    = t1.TRIP_NO
                                            AND t2.MSG_TYPE_CD      = 13070 
;

我不是 100% 相信这个查询是正确的。我怀疑应该用左外连接替换右外连接。这将返回 t1 中的每条记录,并且仅返回 t2 和 t3 中匹配的记录。

【讨论】:

    【解决方案2】:

    很难找到这种不匹配的确切原因,但我认为您已经交换了表 STG_SEP_VL 中 PURSE_TXN_CTR 列的连接条件。

    SELECT *
    FROM STG_REV_APPORTION_CSC_NO t1
    RIGHT
    JOIN STG_SEP_VL t2
    ON  t2.BUSINESS_DATE = t1.BUSINESS_DATE
    AND t2.CSC_APP_NO    = t1.CSC_APP_NO
    AND t2.JOURNEY_NO    = t1.JOURNEY_NO
    AND  ( t2.TRIP_NO + 1 ) = t1.TRIP_NO
    RIGHT
    JOIN  STG_SEP_VL t3
    ON  t3.BUSINESS_DATE    = t1.BUSINESS_DATE
    AND t3.CSC_APP_NO     = t1.CSC_APP_NO
    AND t3.JOURNEY_NO     = t1.JOURNEY_NO
    AND t3.PURSE_TXN_CTR  = t1.PURSE_TXN_CTR
    WHERE ( t2.MSG_TYPE_CD  = 13070 AND t3.MSG_TYPE_CD = 4357 );
    

    【讨论】:

      【解决方案3】:

      这是我想出的:

      SELECT *
        FROM stg_rev_apportion_csc_no t1 
        LEFT JOIN stg_sep_vl t3
               ON t1.business_date = t3.business_date AND
                  t1.csc_app_no = t3.csc_app_no       AND
                  t1.journey_no = t3.journey_no       AND
                  t1.purse_txn_ctr = t3.purse_txn_no  AND
                  4357 = t3.msg_type_cd
        LEFT JOIN stg_sep_vl t2
               ON t1.business_date = t2.business_date AND
                  t1.csc_app_no = t2.csc_app_no       AND
                  t1.journey_no = t2.journey_no       AND
                  t1.trip_no = t2.trip_no + 1         AND
                  13070 = t2.msg_type_cd;
      

      表 t2 和 t3 与 t1 外连接,因此您可以先列出 t1 并执行左连接,或者先列出 t2 和 t3 并执行右连接。

      【讨论】:

      • 太棒了!该查询得出了 Oracle 上的确切行数。它在 SQL Server 上提供了更多结果 - 我现在将调试该问题 :)
      • @RKKuppala - 所以,要明确一点:迈克(在对原始问题的第一条评论中)有正确的差异原因。原始查询中的外连接条件t2.MSG_TYPE_CD(+) = 13070 不能被“外连接”和不带 (+) 的 WHERE 条件替换。这样做(你没有行的原因)是计算所有连接,并且只有在应用 WHERE 条件之后。现在 WHERE 条件确实需要特定的硬编码值,因此它(或t3 上的另一个)将拒绝来自外部连接的所有行。 DCookie 的解决方案避免了这种情况。
      • 谢谢,@mathguy 我现在明白了,你的解释很有帮助。
      猜你喜欢
      • 2014-07-08
      • 1970-01-01
      • 2017-04-08
      • 2015-11-06
      • 1970-01-01
      • 2013-07-26
      • 1970-01-01
      • 2010-11-15
      • 2020-06-29
      相关资源
      最近更新 更多