使用外连接时where子句中的'OR'的SQL替代方法

时间:2015-08-04 11:17:46

标签: sql oracle select join

我有以下查询:

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语法以某种方式实现?

5 个答案:

答案 0 :(得分:5)

使用明确的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语法。

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

from assignments dah left join
     employees emp 
     on . . .

我是故意交换它们的。 left join保留第一个表中的所有行,即使那些没有匹配的行也是如此。 +语法更难以遵循。 +会获得NULL值。但是,对我来说,不匹配的行似乎不太可能在赋值表中。

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

编辑:

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

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

如果愿意,您甚至可以使用古老的+表示法来编写。另请注意:这些并不完全相同。这将检测一个周期完全嵌入另一个周期的重叠。

答案 1 :(得分:4)

如果将已弃用的外连接运算符((+))转换为显式外连接,它应该有效:

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 :(得分:2)

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

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

答案 3 :(得分:1)

请仅使用此项,如果您不能使用ANSI LEFT OUTER JOIN语法:

首先:您的查询中缺少括号 - 除了最初的JOIN之外,您可以将x between min(+) AND max(+)重写为(min is NULL OR x >= min) AND (max is NULL OR x <= 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在两个日期之间。

您希望所有行都来自LEFT JOIN的id和右开始日期OR行只有end-date,没有任何id加入...你的查询基本上是这样的:WHERE ( id1=id2(+) AND ...) OR ( ... )因为AND比OR更强大。

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

因此,如果从来没有只有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
)

答案 4 :(得分:0)

你需要使用这样的显式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)