【问题标题】:SQL Alternative for 'OR' in where clause when using outer join使用外连接时,where 子句中“OR”的 SQL 替代方法
【发布时间】:2015-10-26 18:35:40
【问题描述】:

我有以下疑问:

select * from
from assignments dah, employees emp 
where 
    dah.person_id=emp.person_id(+)
and 
(dah.effective_end_date between emp.date_from(+) and emp.date_to(+)
and dah.effective_end_date between emp.valid_from(+) and emp.valid_to(+))
or   
(dah.effective_start_date between emp.date_from(+) and emp.date_to(+)
and dah.effective_start_date between emp.valid_from(+) and emp.valid_to(+))

我收到以下消息:“OR 或 IN 的操作数中不允许外连接运算符 (+)”。我知道使用带有内部连接的 2 个联合是一种解决方案,但我不能使用它,因为我实际上有很多代码(我提供的代码只是一个示例)。

编辑:我需要通过 oracle 语法完成此操作,因为我使用数据仓库,而我们的 ETL 不完全支持显式语法。也许有些东西我没有看到,这可以用不同的方式写吗?

编辑 nr.2:也许日期重叠逻辑可以在不使用 OR 和 oracle 语法的情况下以某种方式实现?

【问题讨论】:

  • 你真的应该学会在 where 子句中使用显式的 LEFT JOIN 而不是隐式连接。甚至 Oracle 也建议停止使用 (+) 语法

标签: sql oracle select join


【解决方案1】:

如果您将已弃用的外连接运算符 ((+)) 转换为显式外连接,它应该可以工作:

SELECT          *
FROM            assignments dah
LEFT OUTER JOIN employees emp ON
                dah.person_id = emp.person_id AND
                ((dah.effective_end_date BETWEEN emp.date_from AND 
                                                 emp.date_to AND 
                  dah.effective_end_date BETWEEN emp.valid_from AND 
                                                 emp.valid_to) OR
                (dah.effective_start_date BETWEEN emp.date_from AND 
                                                  emp.date_to AND
                 dah.effective_start_date BETWEEN emp.valid_from AND 
                                                  emp.valid_to)
                )

【讨论】:

  • 这应该是一个右连接。另外,您还有一个额外的逗号。
【解决方案2】:

使用明确的left join 语法:

select *
from employees emp left join
     assignments dah 
     on dah.person_id = emp.person_id and
        ((dah.effective_end_date between emp.date_from and emp.date_to and
          dah.effective_end_date between emp.valid_from and emp.valid_to
         ) or
         (dah.effective_start_date between emp.date_from and emp.date_to and
          dah.effective_start_date between emp.valid_from and emp.valid_to
         )
        );

一个简单的规则是永远不要在from 子句中使用逗号。始终使用显式 join 语法。

注意:从技术上讲,您的外连接语法将具有 other 顺序的表:

from assignments dah left join
     employees emp 
     on . . .

我故意交换了它们。 left join 保留第一个表中的所有行,即使是那些没有匹配项的行。 + 语法更难遵循。 + 位于将获得 NULL 值的一侧。但是,对我来说,不匹配的行似乎不太可能出现在 assignments 表中。

如果你有正确的外键关系,那么所有的分配都应该有一个正确的人。但是,我可能不理解您的数据,您可能想要反转您的表格以实现您真正想要做的事情。

编辑:

至于重叠,我倾向于使用更简单的:

     on dah.person_id = emp.person_id and
        (dah.effective_end_date >= emp.date_from and
         dah.effective_start_date <= emp.date_to 
        )

如果您愿意,您甚至可以使用古老的 + 符号来编写此代码。另请注意:这些不做完全相同的事情。这将检测一个周期完全嵌入另一个周期的重叠。

【讨论】:

  • 谢谢,但我需要使用 oracle 语法... 可以不使用 OR 来编写日期重叠吗?我在网上做了一些研究,没有找到任何东西。
  • @user3014914 。 . .我不确定“Oracle 语法”是什么意思。 Oracle 从版本 9i 开始支持这种 ANSI 标准语法。以下是使用 2002 年的“新”语法的参考:asktom.oracle.com/pls/asktom/…
  • 关于重叠逻辑 - 例如 date_from effective_start_date 的时间呢?在这种情况下,我需要收集数据。 Date_from 可以是 Effective_start_date。
【解决方案3】:

您需要像这样使用显式 LEFT JOIN:

SELECT * FROM
    assignments dah LEFT JOIN 
    employees emp ON dah.person_id=emp.person_id
AND 
(dah.effective_end_date between emp.date_from and emp.date_to
and dah.effective_end_date between emp.valid_from and emp.valid_to)
or   
(dah.effective_start_date between emp.date_from and emp.date_to
and dah.effective_start_date between emp.valid_from and emp.valid_to)

【讨论】:

    【解决方案4】:

    如果不能使用 ANSI LEFT OUTER JOIN 语法,请只使用这个:

    首先:您在查询中缺少括号 - 除了最初的 JOIN 之外,您可以将 x between min(+) AND max(+) 重写为 (min is NULL OR x &gt;= min) AND (max is NULL OR x &lt;= max)

    SELECT *
    FROM assignments dah, employees emp 
    WHERE
        dah.person_id = emp.person_id(+)
    AND 
    (
          (emp.date_from IS NULL OR dah.effective_start_date >= emp.date_from)
      AND (emp.date_to IS NULL OR dah.effective_start_date <= emp.date_to)
      AND (emp.valid_from IS NULL OR dah.effective_start_date >= emp.valid_from)
      AND (emp.valid_to IS NULL OR dah.effective_start_date <= emp.valid_to)
    
      OR
    
          (emp.date_from IS NULL OR dah.effective_end_date >= emp.date_from)
      AND (emp.date_to IS NULL OR dah.effective_end_date <= emp.date_to)
      AND (emp.valid_from IS NULL OR dah.effective_end_date >= emp.valid_from)
      AND (emp.valid_to IS NULL OR dah.effective_end_date <= emp.valid_to)
    )
    

    我认为这会选择您想要的 - 与所有行的左连接,其中 start_date 或 end_date 在两个日期之间。

    您希望所有行都来自具有 id 和右开始日期的 LEFT JOIN 或仅具有结束日期的行,没有任何 id 可以加入...您的查询基本上是这样的:WHERE ( id1=id2(+) AND ...) OR ( ... ) 因为 AND比 OR 的结合力更强。

    如果您知道emp.date_fromemp.date_to 都有效或为NULL

    因此,如果从不存在只有 date_from 为 NULL,但 date_to 有效的情况,则可以大大缩短语句:

    SELECT *
    FROM assignments dah, employees emp 
    WHERE
        dah.person_id = emp.person_id(+)
    AND 
    (
         emp.date_from IS NULL
      OR dah.effective_start_date BETWEEN emp.date_from AND emp.date_to
         AND dah.effective_start_date BETWEEN emp.valid_from AND emp.valid_to
      OR dah.effective_end_date BETWEEN emp.date_from AND emp.date_to
         AND dah.effective_end_date BETWEEN emp.valid_from AND emp.valid_to
    )
    

    【讨论】:

    • 那不是还是会得到 ORA-01719 吗?
    • @AlexPoole 感谢您的信息 - 我对其进行了调整并更换了额外的 WHERE-Parts,现在它可以工作了!
    【解决方案5】:

    由于您必须使用旧式外连接语法,所以这里有一种方法(简化,因为您没有向我们提供示例数据和/或表创建脚本):

    with assignments as (select 1 assignment_id, 1 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 2 assignment_id, 1 person_id, to_date('02/08/2015', 'dd/mm/yyyy') start_date, to_date('04/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 3 assignment_id, 1 person_id, to_date('06/08/2015', 'dd/mm/yyyy') start_date, to_date('10/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 4 assignment_id, 2 person_id, to_date('02/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual),
           employees as (select 1 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 3 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual)
    select *
    from   assignments dah,
           employees emp
    where  dah.person_id = emp.person_id (+)
    and    dah.start_date <= emp.end_date (+)
    and    dah.end_date >= emp.start_date (+);
    
    ASSIGNMENT_ID  PERSON_ID START_DATE END_DATE   PERSON_ID_1 START_DATE_1 END_DATE_1
    ------------- ---------- ---------- ---------- ----------- ------------ ----------
                2          1 02/08/2015 04/08/2015           1 01/08/2015   03/08/2015
                1          1 01/08/2015 03/08/2015           1 01/08/2015   03/08/2015
                3          1 06/08/2015 10/08/2015                                    
                4          2 02/08/2015 03/08/2015          
    

    你确定你的外连接是正确的吗?您确定您实际上不是在追求以下内容吗?:

    with assignments as (select 1 assignment_id, 1 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 2 assignment_id, 1 person_id, to_date('02/08/2015', 'dd/mm/yyyy') start_date, to_date('04/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 3 assignment_id, 1 person_id, to_date('06/08/2015', 'dd/mm/yyyy') start_date, to_date('10/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 4 assignment_id, 2 person_id, to_date('02/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual),
           employees as (select 1 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual union all
                         select 3 person_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('03/08/2015', 'dd/mm/yyyy') end_date from dual)
    select *
    from   assignments dah,
           employees emp
    where  dah.person_id (+) = emp.person_id
    and    dah.start_date (+) <= emp.end_date
    and    dah.end_date (+) >= emp.start_date;
    
    ASSIGNMENT_ID  PERSON_ID START_DATE END_DATE   PERSON_ID_1 START_DATE_1 END_DATE_1
    ------------- ---------- ---------- ---------- ----------- ------------ ----------
                1          1 01/08/2015 03/08/2015           1 01/08/2015   03/08/2015
                2          1 02/08/2015 04/08/2015           1 01/08/2015   03/08/2015
                                                             3 01/08/2015   03/08/2015
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多