Sql 保持日期间隔不重叠并与Oracle中的触发器保持一致+;11g
假设我有以下几点:Sql 保持日期间隔不重叠并与Oracle中的触发器保持一致+;11g,sql,oracle,plsql,triggers,oracle11g,Sql,Oracle,Plsql,Triggers,Oracle11g,假设我有以下几点: CREATE TABLE test ( id NUMBER(10) , valid_from DATE , valid_to DATE, PRIMARY KEY (id, valid_from) ); INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1900'); INSERT INTO test (id, valid_from) VALUES (1,
CREATE TABLE test (
id NUMBER(10)
, valid_from DATE
, valid_to DATE,
PRIMARY KEY (id, valid_from)
);
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1900');
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1901');
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1902');
INSERT INTO test (id, valid_from) VALUES (2, '01/JAN/1903');
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-99 01-JAN-00
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
输出:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-01
1 01-JAN-02
2 01-JAN-03
1 01-JAN-00
ID VALID_FROM VALID_TO UPDATE_REQUIRED
---------- ---------- --------- ---------------
1 01-JAN-00 01-JAN-01 Y
1 01-JAN-01 01-JAN-02 Y
1 01-JAN-02 N
2 01-JAN-03 N
现在我需要一个触发器,它将保持VALID_TO字段与VALID_保持一致,如下所示:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
INNER JOIN TABLE(l_changed_ids) chg ON (chg.column_value = original.id)
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id IN (select column_value from TABLE(l_changed_ids))
select t1.id, t1.valid as from_date, t2.valid as To_date -- t1.etc, ...
from test t1
left join test t2
on t2.id = t1.id
and t2.valid =(
select Min( t3.valid )
from test t3
where t3.id = t1.id
and t3.valid > t1.valid )
order by t1.id, t1.valid desc;
我有一个查询,用于计算有效的\u,并检查是否有任何记录需要更新:
WITH original AS (
SELECT id,
valid_from,
valid_to,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY valid_from DESC) seq
FROM test
), should_be AS (
SELECT df.id,
df.valid_from AS VALID_FROM,
dt.valid_from AS VALID_TO
FROM original df
LEFT OUTER JOIN original dt ON (df.id = dt.id
AND df.seq = dt.seq + 1)
), update_req AS (
SELECT
should_be.*,
CASE WHEN original.VALID_TO = should_be.VALID_TO OR (original.VALID_TO IS NULL AND should_be.VALID_TO IS NULL) THEN 'N' ELSE 'Y' END UPDATE_REQUIRED
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
)
SELECT *
FROM update_req
ORDER BY id, valid_from
输出:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-01
1 01-JAN-02
2 01-JAN-03
1 01-JAN-00
ID VALID_FROM VALID_TO UPDATE_REQUIRED
---------- ---------- --------- ---------------
1 01-JAN-00 01-JAN-01 Y
1 01-JAN-01 01-JAN-02 Y
1 01-JAN-02 N
2 01-JAN-03 N
我在触发器中使用此查询,以确保在错误时更新有效的_TO字段:
CREATE OR REPLACE TYPE ID_COLLECTION_T AS TABLE OF NUMBER(10);
CREATE OR REPLACE TRIGGER trg_test
FOR DELETE OR INSERT OR UPDATE
ON test REFERENCING NEW AS NEW OLD AS OLD
COMPOUND TRIGGER
l_changed_ids ID_COLLECTION_T := ID_COLLECTION_T(); -- initialize
AFTER EACH ROW IS
BEGIN
-- Keep track of changed ids
CASE
WHEN INSERTING OR UPDATING THEN l_changed_ids.extend; l_changed_ids(l_changed_ids.last) := :NEW.id;
WHEN DELETING OR UPDATING THEN l_changed_ids.extend; l_changed_ids(l_changed_ids.last) := :OLD.id;
END CASE;
END
AFTER EACH ROW;
AFTER STATEMENT IS
l_existing_inconsistencies VARCHAR2(1);
BEGIN
-- first we check whether the executed statement caused any VALID_TO inconsistencies
WITH original AS (
SELECT id,
valid_from,
valid_to,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY valid_from DESC) seq
FROM test
), should_be AS (
SELECT df.id,
df.valid_from AS VALID_FROM,
dt.valid_from AS VALID_TO
FROM original df
LEFT OUTER JOIN original dt ON (df.id = dt.id
AND df.seq = dt.seq + 1)
), update_req AS (
SELECT
should_be.*,
CASE WHEN original.VALID_TO = should_be.VALID_TO OR (original.VALID_TO IS NULL AND should_be.VALID_TO IS NULL) THEN 'N' ELSE 'Y' END UPDATE_REQUIRED
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id MEMBER OF l_changed_ids -- we ONLY (!) want to search for inconsistencies for modified ids
)
SELECT CASE WHEN 'Y' IN (SELECT UPDATE_REQUIRED FROM update_req) THEN 'Y' ELSE 'N' END
INTO l_existing_inconsistencies
FROM DUAL;
-- If there are inconsistencies, then we update the table.
IF l_existing_inconsistencies = 'Y' THEN
MERGE INTO test o
USING (
WITH original AS (
SELECT id,
valid_from,
valid_to,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY valid_from DESC) seq
FROM test
), should_be AS (
SELECT df.id,
df.valid_from AS VALID_FROM,
dt.valid_from AS VALID_TO
FROM original df
LEFT OUTER JOIN original dt ON (df.id = dt.id
AND df.seq = dt.seq + 1)
)
SELECT
should_be.*,
CASE WHEN original.VALID_TO = should_be.VALID_TO OR (original.VALID_TO IS NULL AND should_be.VALID_TO IS NULL) THEN 'N' ELSE 'Y' END UPDATE_REQUIRED
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id MEMBER OF l_changed_ids -- we ONLY (!) want to search for inconsistencies for modified ids
) n
ON (o.id = n.id AND o.valid_from = n.valid_from AND n.UPDATE_REQUIRED = 'Y')
WHEN MATCHED THEN UPDATE SET o.valid_to = n.valid_to;
END IF;
END
AFTER STATEMENT;
END trg_test;
现在,触发器使插入/更新/删除的ID的数据保持一致:
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1899');
现在,我们在测试表中发现以下内容:
CREATE TABLE test (
id NUMBER(10)
, valid_from DATE
, valid_to DATE,
PRIMARY KEY (id, valid_from)
);
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1900');
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1901');
INSERT INTO test (id, valid_from) VALUES (1, '01/JAN/1902');
INSERT INTO test (id, valid_from) VALUES (2, '01/JAN/1903');
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-99 01-JAN-00
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
这里的问题是语句的成员。它会导致对其每个成员进行全表扫描。
在多个update/insert语句的情况下,可能会更改许多ID,因此l_changed_id集合很大。
我无法优化以下成员:
我试过:
-- Original query on 5mil records: 40 sec
WITH original AS (
SELECT id,
valid_from,
valid_to,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY valid_from DESC) seq
FROM test
), should_be AS (
SELECT df.id,
df.valid_from AS VALID_FROM,
dt.valid_from AS VALID_TO
FROM original df
LEFT OUTER JOIN original dt ON (df.id = dt.id
AND df.seq = dt.seq + 1)
) select * from should_be
-- TommCatt suggestion on 5mil records: 65 sec
with Date_List as (
select t1.ID, t1.Valid_from as From_Date, Min( t2.Valid_from ) as To_Date
from test t1
left join test t2
on t2.id = t1.id
and t2.valid_from > t1.valid_from
group by t1.ID, t1.Valid_from
)
select id, from_date, to_date
from Date_List
-- TommCatt suggestion on 5mil records for 12c: untested
-- a_horse_with_no_name suggestion on 5mil records: 10 sec WINNER!!
SELECT id,
valid_from,
LEAD(valid_from, 1) OVER (PARTITION BY id ORDER BY valid_from ASC) valid_to
FROM test
-- EXEC Plan for the winner:
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5106K| 63M| 22222 (1)| 00:04:27 |
| 1 | WINDOW BUFFER | | 5106K| 63M| 22222 (1)| 00:04:27 |
| 2 | INDEX FULL SCAN| SYS_C0011495 | 5106K| 63M| 22222 (1)| 00:04:27 |
---------------------------------------------------------------------------------
-- Original query on 5mil records: 40 sec
WITH original AS (
SELECT id,
valid_from,
valid_to,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY valid_from DESC) seq
FROM test
), should_be AS (
SELECT df.id,
df.valid_from AS VALID_FROM,
dt.valid_from AS VALID_TO
FROM original df
LEFT OUTER JOIN original dt ON (df.id = dt.id
AND df.seq = dt.seq + 1)
) select * from should_be
-- TommCatt suggestion on 5mil records: 65 sec
with Date_List as (
select t1.ID, t1.Valid_from as From_Date, Min( t2.Valid_from ) as To_Date
from test t1
left join test t2
on t2.id = t1.id
and t2.valid_from > t1.valid_from
group by t1.ID, t1.Valid_from
)
select id, from_date, to_date
from Date_List
-- TommCatt suggestion on 5mil records for 12c: untested
-- a_horse_with_no_name suggestion on 5mil records: 10 sec WINNER!!
SELECT id,
valid_from,
LEAD(valid_from, 1) OVER (PARTITION BY id ORDER BY valid_from ASC) valid_to
FROM test
-- EXEC Plan for the winner:
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5106K| 63M| 22222 (1)| 00:04:27 |
| 1 | WINDOW BUFFER | | 5106K| 63M| 22222 (1)| 00:04:27 |
| 2 | INDEX FULL SCAN| SYS_C0011495 | 5106K| 63M| 22222 (1)| 00:04:27 |
---------------------------------------------------------------------------------
在KScope14会议期间,我碰巧测试了SQL的性能。我在博客上写了一些关于结果的信息: 尝试将的成员替换为使用表运算符将集合“转换”为“临时表”。要么像这样:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
INNER JOIN TABLE(l_changed_ids) chg ON (chg.column_value = original.id)
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id IN (select column_value from TABLE(l_changed_ids))
select t1.id, t1.valid as from_date, t2.valid as To_date -- t1.etc, ...
from test t1
left join test t2
on t2.id = t1.id
and t2.valid =(
select Min( t3.valid )
from test t3
where t3.id = t1.id
and t3.valid > t1.valid )
order by t1.id, t1.valid desc;
或者像这样:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
INNER JOIN TABLE(l_changed_ids) chg ON (chg.column_value = original.id)
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id IN (select column_value from TABLE(l_changed_ids))
select t1.id, t1.valid as from_date, t2.valid as To_date -- t1.etc, ...
from test t1
left join test t2
on t2.id = t1.id
and t2.valid =(
select Min( t3.valid )
from test t3
where t3.id = t1.id
and t3.valid > t1.valid )
order by t1.id, t1.valid desc;
如果您对要添加基数提示的已更改ID的数量有大致了解,则可能对优化器有用:
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id IN (select /*+ cardinality(42) */ column_value from TABLE(l_changed_ids))
以上是直接在这里输入的未经测试的代码-我希望您能让它正常工作:-)
哦,对不起,我刚刚读到您的更新,您尝试了表运算符,但速度很慢…基于触发器的另一种方法是在提交时使用物化视图刷新,并在物化视图上使用约束。我在一些讨论和论坛上看到了提到的方法。此处给出了一个示例和一些讨论:
我自己还没有尝试过,但它可能值得研究一下?您可以非常轻松地通过与任何其他计划相媲美的性能来模拟解决方案。当然,维护工作将大大减少。我自己使用这个技巧,效果很好 首先,当您有一组这样的From/To字段时,您可以设置我称之为行扩展依赖项的内容。从数据完整性的角度来看,这是可怕的。每次执行DML时,必须至少执行两条语句。要插入新的有效日期记录,您必须找到“当前”记录并更新“截止日期”,然后发出插入。任何一个日期的更新只能通过两个update语句完成。而且,正如你可以清楚地看到的,保持日期序列的有效性绝对是一场噩梦 解决办法其实相当简单。只有“有效”或“有效”日期,而不是起始日期字段。完全删除“截止日期”字段。现在,让我们规定,当一个ID在valid字段中的日期变为有效时,它将保持有效,直到使用相同ID和更晚日期输入另一行。第二行中的date字段是行变为有效的日期,但也是第一行变为无效(或不再有效——我更喜欢这个术语)的时间点 一个插页。完成了 因此,重叠和缺口变得不可能。你甚至不需要检查它们。不可能 但有些人希望在他们的报告中看到“From”和“to”,对吗?那很好。“From”和“To”在结果集中很好,它们只是作为数据散发着恶臭。下面是如何从数据中获取“从”和“到”:
with
Date_List( id, from_date, to_date )as(
select t1.ID, t1.Valid as From_Date, Min( t2.Valid ) as To_Date
from test t1
left join test t2
on t2.id = t1.id
and t2.valid > t1.valid
group by t1.ID, t1.Valid
)
select id, from_date, to_date
from Date_List
order by id, From_date desc;
在cte中,你加入PK到PK——非常快。在cte之外,您可能需要再次加入表以获取其他数据,我相信您为了清晰起见省略了这些数据。这仍然会很快,因为你再次加入PK领域。当您获得Oracle-12c时,您可以这样重写它:
ID VALID_FROM VALID_TO
---------- ---------- ---------
1 01-JAN-00 01-JAN-01
1 01-JAN-01 01-JAN-02
1 01-JAN-02
2 01-JAN-03
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
INNER JOIN TABLE(l_changed_ids) chg ON (chg.column_value = original.id)
FROM should_be
INNER JOIN original ON (should_be.id = original.id AND should_be.valid_from = original.valid_from)
WHERE original.id IN (select column_value from TABLE(l_changed_ids))
select t1.id, t1.valid as from_date, t2.valid as To_date -- t1.etc, ...
from test t1
left join test t2
on t2.id = t1.id
and t2.valid =(
select Min( t3.valid )
from test t3
where t3.id = t1.id
and t3.valid > t1.valid )
order by t1.id, t1.valid desc;
使用联接和子查询在某些方面看起来更糟。然而,计时测试将显示令人印象深刻的结果。但是,即使在表中实际添加一个To_Date字段可以获得更好的性能,请记住我之前说过的话:间隙和重叠是不可能的!!!如果你尝试的话,你甚至不能把事情搞砸。你可以想象的最糟糕的情况是为同一个ID输入两次相同的日期,但是由于这些定义了PK,系统不会允许你这样做。想想你不必编写的所有触发器、约束和存储过程(无论如何也不要保持日期同步) TABLE()如果其他一切都失败了,那就太好了。的确,但据我回忆,这只是为了确保没有重叠。在我的例子中,我试图计算并具体化一个字段。由于该表有数百万行,并且计算有效_的查询很复杂(分区…),因此我认为不可能在MV上快速刷新。但是我会调查一下。您是否尝试过在CTE内部使用
lead()
来获取最新版本
?根据我的经验,这几乎总是比自动加入快。@TommCatt谢谢你的帖子。我完全同意你所说的“将有效的\u放到并且只使用有效的(\u from)
”物理方式。但这是一个拥有数百万历史数据的数据仓库,技术负责人坚持(!!)在插入/更新/删除时将计算的valid\u具体化为字段