oracle sql - 查找日期(start/end 列)重叠的条目

oracle sql - finding entries with dates (start/end column) overlap

所以数据是这样的:

ID | START_DATE       | END_DATE         |  UID  | CANCELED
-------------------------------------------------
44 | 2015-10-20 22:30 | 2015-10-20 23:10 | 'one' |
52 | 2015-10-20 23:00 | 2015-10-20 23:30 | 'one' |
66 | 2015-10-21 13:00 | 2015-10-20 13:30 | 'two' | 

这些条目超过 100k。

我们可以看到第二个条目的 start_date 与第一个条目的 end_date 重叠。当日期重叠时,ID 较低的条目应在 'CANCELED' 列中标记为 true。

我尝试了一些查询,但它们需要很长时间,所以我不确定它们是否有效。另外我想涵盖所有重叠的案例,所以这似乎也减慢了速度。

我是负责 inserting/updating 这些条目的人 pl/sql

update table set column = 'value' where ID = '44';
   if sql%rowcount = 0 
       then insert values(...)
   end if

所以我也许可以在这一步中做到这一点。但是所有表都是 updated/inserted 使用一个大 pl/sql 动态创建的,其中所有行都得到更新或插入新行,所以这似乎又一次变慢了。

在所有 sql 'dialects' oracle 中,我有机会使用过的是最神秘的。想法?

编辑:忘记了一个重要的细节,还有一列(UID)要匹配,上面更新

我认为以下更新应该有效:

update tbl
   set cancelled = 'TRUE'
 where t_id in (select t_id
                  from tbl t
                 where exists (select 1
                          from tbl x
                         where x.t_id > t.t_id
                           and x.start_date <= t.end_date));

Fiddle: http://sqlfiddle.com/#!4/06447/1/0

如果 table 非常大,您最好使用 CTAS(create table as select)查询创建一个新的 table,您可以在其中可以使用 nologging 选项,让您避免必须写入撤消日志。当您像现在这样执行更新时,您正在将更改写入 Oracle 的撤消日志,以便在提交事务之前,您可以选择回滚。这增加了开销。因此,没有日志记录的 CTAS 查询可能 运行 更快。这是该方法的一种方法:

create table new_table nologging as
with sub as
 (select t_id,
         start_date,
         end_date,
         'TRUE' as cancelled
    from tbl t
   where exists (select 1
            from tbl x
           where x.t_id > t.t_id
             and x.start_date <= t.end_date))
select *
  from sub
union all
select   t.*
      from tbl t
 left join sub s
        on t.t_id = s.t_id
      where s.t_id is null;

Fiddle: http://sqlfiddle.com/#!4/c6a29/1

我将从这个查询开始:

update table t
    set cancelled = true
    where exists (select 1
                  from table t2
                  where t.end_date > t2.start_date and
                        t.uid = t2.uid and
                        t.id < t2.id
                 )

table(uid, start_date, id) 上的索引可能会有所帮助。

注意:当您创建 table 时,这可能 更容易 ,因为您可以使用 lag().

这将在没有动态查询或相关子查询的情况下实现这一目的,但它会为 with 子句消耗一些内存:

   MERGE INTO Table1 
   USING 
   (
   with q0 as( 
   select rownum fid, id, start_date from(
   select id, start_date from table1 
   union all 
   select 999999 id, null start_date from dual
   order by id
   )
   ), q1 as (
   select rownum fid, id, end_date from(
   select -1 id, null end_date from dual
   union all 
   select id, end_date from table1
   order by id
   )
   )
   select q0.fid, q1.id, q0.start_date, q1.END_DATE, case when (q0.start_date < q1.END_DATE) then 1 else 0 end canceled
   from q0
   join q1
   on (q0.fid = q1.fid)
   ) ta ON (ta.id = Table1.id)
WHEN MATCHED THEN UPDATE 
    SET Table1.canceled = ta.canceled;

带有别名 ta 的内部 with select 语句将产生此结果:

"FID"|"ID"|"START_DATE"     |"END_DATE"       |"CANCELED"
---------------------------------------------------------
1    |-1  |20/10/15 22:30:00|                 |0
2    |44  |20/10/15 23:00:00|20/10/15 23:10:00|1
3    |52  |21/10/15 13:00:00|20/10/15 23:30:00|0
4    |66  |                 |20/10/15 13:30:00|0

然后它在 merge v 中使用,没有任何相关查询。使用 SQLDeveloper 测试并运行良好。

您可以使用 BULK COLLECT INTOFORALL 来减少过程中的上下文切换:

SQL Fiddle

Oracle 11g R2 架构设置:

CREATE TABLE test ( ID, START_DATE, END_DATE, CANCELED ) AS 
          SELECT 44, TO_DATE( '2015-10-20 22:30', 'YYYY-MM-DD HH24:MI' ), TO_DATE( '2015-10-20 23:10', 'YYYY-MM-DD HH24:MI' ), 'N' FROM DUAL
UNION ALL SELECT 52, TO_DATE( '2015-10-20 23:00', 'YYYY-MM-DD HH24:MI' ), TO_DATE( '2015-10-20 23:30', 'YYYY-MM-DD HH24:MI' ), 'N' FROM DUAL
UNION ALL SELECT 66, TO_DATE( '2015-10-21 13:00', 'YYYY-MM-DD HH24:MI' ), TO_DATE( '2015-10-21 12:30', 'YYYY-MM-DD HH24:MI' ), 'N' FROM DUAL
/

CREATE PROCEDURE updateCancelled
AS
  TYPE ids_t IS TABLE OF test.id%TYPE INDEX BY PLS_INTEGER;
  t_ids   ids_t;
BEGIN
  SELECT ID
  BULK COLLECT INTO t_ids
  FROM   (
          SELECT ID,
                 END_DATE,
                 LEAD( START_DATE ) OVER ( ORDER BY START_DATE ) AS NEXT_START_DATE
          FROM   TEST )
  WHERE  END_DATE > NEXT_START_DATE;

  FORALL i IN 1 .. t_ids.COUNT
    UPDATE TEST
    SET    CANCELED = 'Y'
    WHERE  ID = t_ids(i);
END;
/

BEGIN
  updateCancelled();
END;
/

查询 1:

SELECT * FROM TEST

Results:

| ID |                START_DATE |                  END_DATE | CANCELED |
|----|---------------------------|---------------------------|----------|
| 44 | October, 20 2015 22:30:00 | October, 20 2015 23:10:00 |        Y |
| 52 | October, 20 2015 23:00:00 | October, 20 2015 23:30:00 |        N |
| 66 | October, 21 2015 13:00:00 | October, 21 2015 12:30:00 |        N |

或作为单个 SQL 语句:

UPDATE TEST
SET    CANCELED = 'R'
WHERE  ID IN ( SELECT ID
               FROM ( SELECT ID,
                             END_DATE,
                             LEAD( START_DATE )
                               OVER ( ORDER BY START_DATE )
                               AS NEXT_START_DATE
                      FROM TEST )
               WHERE END_DATE > NEXT_START_DATE )