1

しばらくして、ゆっくりと変化するディメンション タイプ 2 を履歴化するための非常に高速なワンパス マージ ステートメントを考え出しました。

一意の制約のないテーブルで完全に機能します。

ほとんどの場合、一意の制約を持つテーブルでも機能します。しかし、ときどき (通常は履歴の変更が大きい場合)、エラー ORA-00001、一意の制約違反が発生します。もちろん、動作する 2 パスの方法があることは知っていますが、それらは遅くなります。

私の唯一の推測は、Oracle が UPDATE の前に INSERT を実行して、TH_Valid_To_Date を一時的に複製することがあるということです。

(主キーを保持しながら)それを回避する方法はありますか?

ソース テーブル:

CREATE TABLE TESTS 
  (
     T_Key_1                        NUMBER(38,0)    DEFAULT -1 NOT NULL ENABLE /*  */
    ,T_Key_2                        NUMBER(38,0)    DEFAULT -1 NOT NULL ENABLE /*  */
    ,Text_Value                     VARCHAR2(100)    /*  */
    ,Number_Value                   NUMBER(38,0)    DEFAULT -1 NOT NULL ENABLE /*  */
    ,Amount                         NUMBER           /*  */

    ,CONSTRAINT T_PK PRIMARY KEY (T_Key_1, T_Key_2)  /* Primární klíč */    
  )
;

履歴表:

CREATE TABLE TEST_HISTORY 
  (
     T_Key_1                        NUMBER(38,0)    DEFAULT -1 NOT NULL
    ,T_Key_2                        NUMBER(38,0)    DEFAULT -1 NOT NULL
    ,Text_Value                     VARCHAR2(100)    
    ,Number_Value                   NUMBER(38,0)    DEFAULT -1 NOT NULL
    ,Amount                         NUMBER          
    ,TH_Valid_From_Date             DATE            DEFAULT to_date('1000-01-01','yyyy-mm-dd') NOT NULL /* SCD2 - Start of validity of record. */
    ,TH_Valid_To_Date               DATE            DEFAULT to_date('3000-01-01','yyyy-mm-dd') NOT NULL /* SCD2 - End of validity of record. */

    ,CONSTRAINT TH_PK PRIMARY KEY (T_Key_1, T_Key_2, TH_Valid_To_Date) using index local 
  )
/** Physical Options **************************************************************************************************/
partition by range (TH_Valid_To_Date) interval (NUMTOYMINTERVAL (1, 'MONTH'))
(partition P_10000000 values less than (TO_DATE ('01-01-1000', 'DD-MM-YYYY')))
ENABLE ROW MOVEMENT 
;

マージ:

MERGE INTO (SELECT * FROM TEST_HISTORY WHERE TH_Valid_To_Date = to_date('3000-01-01','yyyy-mm-dd'))             Hst /*change only current records which are identified by TH_Valid_To_Date = to_date('3000-01-01','yyyy-mm-dd') */
   USING (
      SELECT * FROM (
         SELECT NVL(Src.T_Key_1, Dst.T_Key_1)                                AS T_Key_1
            ,NVL(Src.T_Key_2, Dst.T_Key_2)                                   AS T_Key_2
            ,Src.Text_Value
            ,Src.Number_Value
            ,Src.Amount
            ,CASE WHEN Src.T_Key_1 is null                                   THEN 'D' /*delete*/
                  WHEN Dst.T_Key_1 is null                                   THEN 'I' /*insert*/
                  WHEN (Src.Text_Value=Dst.Text_Value OR (Src.Text_Value is null AND Dst.Text_Value is null))
                       AND (Src.Number_Value=Dst.Number_Value OR (Src.Number_Value is null AND Dst.Number_Value is null))
                       AND (Src.Amount=Dst.Amount OR (Src.Amount is null AND Dst.Amount is null))
                                                                             THEN 'X' /*no change*/
                                                                             ELSE 'U' /*update*/ END  AS Operation  
         FROM TESTS                                         Src
         FULL JOIN (SELECT * FROM TEST_HISTORY WHERE TH_Valid_To_Date = to_date('3000-01-01','yyyy-mm-dd'))     Dst
            ON (Src.T_Key_1 = Dst.T_Key_1 AND Src.T_Key_2 = Dst.T_Key_2)
      )
      INNER JOIN (SELECT LEVEL AS duplication FROM DUAL CONNECT BY LEVEL BETWEEN 1 AND 2) ON (duplication=1 OR Operation='U') /*need to duplicate update records so that they can go to both matched and not matched parts*/
      WHERE Operation<>'X'
   )                                                                          Act
   ON (Act.T_Key_1 = Hst.T_Key_1 AND Act.T_Key_2 = Hst.T_Key_2 AND Act.duplication=1 AND Act.operation<>'I')

WHEN MATCHED THEN UPDATE
   SET
      TH_Valid_To_Date                              = p_Load_Date - 1,
   WHERE Hst.TH_Valid_To_Date                       = to_date('3000-01-01','yyyy-mm-dd')

WHEN NOT MATCHED THEN INSERT /*+ append */
   (
       T_Key_1                       
      ,T_Key_2                       
      ,Text_Value                    
      ,Number_Value                  
      ,Amount                        
      ,TH_Valid_From_Date             /*Auditní sloupec*/
      ,TH_Valid_To_Date               /*Auditní sloupec*/
   ) VALUES (
       Act.T_Key_1                       
      ,Act.T_Key_2                       
      ,Act.Text_Value                    
      ,Act.Number_Value                  
      ,Act.Amount                        
      ,p_Load_Date                    /*Auditní sloupec*/
      ,to_date('3000-01-01','yyyy-mm-dd') /*Auditní sloupec*/
   )
;
4

1 に答える 1