Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/73.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 保持日期间隔不重叠并与Oracle中的触发器保持一致+;11g_Sql_Oracle_Plsql_Triggers_Oracle11g - Fatal编程技术网

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集合很大。 我无法优化以下成员:

我试过:

  • 对于许多单次插入/更新,使用TABLE()强制转换集合的速度非常慢
  • 从循环中更新不一致的行是个坏主意,因为对于n-bulk语句,这可能会递归地重新激活触发器n次。但是(!)使用循环非常快,因为每次都使用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 |
        ---------------------------------------------------------------------------------                      
    
  • 是否有其他触发方法
  • 触发器必须适用于单个和批量update/delete/insert语句。因此,如果存在不一致性,可能只会递归执行一个额外的update语句
  • 触发器根据id锁定行(这是后面的要求,但现在考虑一下可能会很有趣)
  • 更新1:计算有效期的一些性能分析:

        -- 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具体化为
    字段