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 INTO
和 FORALL
来减少过程中的上下文切换:
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
| 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 )
所以数据是这样的:
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 INTO
和 FORALL
来减少过程中的上下文切换:
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
| 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 )