2016-12-21 1 views
1

오라클 12에서 기본 키를 위반합니다.SCD2 한 패스 병합 내가 치수 유형 2.</p> <p>그것은 고유 제한 조건없이 테이블에 완벽하게 작동 변경 천천히 historize하는 이럴 매우 빠른 원 - 패스 병합 문을 생각 몇 시간 후

대부분 고유 한 제약 조건이있는 테이블에서도 작동합니다. 하지만 때로는 (보통 역사가 바뀌면) ORA-00001 오류가 발생합니다. 이는 고유 한 제약 조건 위반입니다. 물론 두 가지 방법이 있지만 작동은 느리다는 것을 알고 있습니다.

내 생각에 오라클은 때때로 TH_Valid_To_Date를 복제하는 INSERT UPDATE를 잠깐 동안 수행합니다.

아이디어를 피하는 방법 (기본 키를 유지하면서)?

소스 테이블 :

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*/ 
    ) 
; 

답변

0

나는이 같은 문제로 자신을 실행 : 당신은 둘 삽입 할 수 있고 레코드를 업데이트합니다. Oracle은 조인을 처음 수행 할 때 레코드가 삽입되거나 업데이트 될지 여부를 결정합니다. 다음은 이것을 보여주는 간단한 테스트 사례입니다.

-- This is the table into which we will be merging data 

CREATE TABLE merge_table 
(
    mt_col1 INTEGER NOT NULL 
    , mt_col2 INTEGER 
); 

-- Make mt_col1 unique 
ALTER TABLE merge_table ADD (
    UNIQUE (mt_col1) 
    USING INDEX); 

-- This is the table from which we will be drawing the data 

CREATE TABLE datasource_table 
(
    ds_col1 INTEGER 
    , ds_col2 INTEGER 
); 

-- Load up the data source with 10 rows (1-10) 

INSERT INTO datasource_table (
      ds_col1, ds_col2 
      ) 
    SELECT ROWNUM r, ROWNUM r 
     FROM all_objects 
    WHERE ROWNUM < 11; 

-- Create a duplicate record 

INSERT INTO datasource_table (
      ds_col1, ds_col2 
      ) 
    VALUES (1, 1); 

-- Create a record to be updated 

INSERT INTO merge_table (
      mt_col1, mt_col2 
      ) 
    VALUES (2, 2); 

COMMIT; 

-- This merge will fail with a unique constraint violation because 
-- an mt_col1 value of 1 does not exist. The datasource table contains 
-- two entries for 1, so the merge will try to insert two mt_col1=1 records, 
-- violating the unique constraint. 

MERGE INTO merge_table dt 
    USING (SELECT ds_col1, ds_col2 
       FROM datasource_table) a 
     ON (a.ds_col1 = dt.mt_col1) 
WHEN MATCHED 
THEN 
    UPDATE SET mt_col2 = a.ds_col2 + 10 
WHEN NOT MATCHED 
THEN 
    INSERT  (
       mt_col1, mt_col2 
       ) 
     VALUES (ds_col1, ds_col2); 

-- ORA-00001: unique constraint (SYS_C0013990) violated 

-- Delete one of the duplicate records and the merge succeeds 

DELETE FROM datasource_table 
     WHERE ds_col1 = 1 
     AND ROWNUM < 2; 

-- Merge is now successful 

MERGE INTO merge_table dt 
    USING (SELECT ds_col1, ds_col2 
       FROM datasource_table) a 
     ON (a.ds_col1 = dt.mt_col1) 
WHEN MATCHED 
THEN 
    UPDATE SET mt_col2 = a.ds_col2 + 10 
WHEN NOT MATCHED 
THEN 
    INSERT  (
       mt_col1, mt_col2 
       ) 
     VALUES (ds_col1, ds_col2); 

-- 10 rows updated 
+0

브라이언, 귀하의 사례가 다른 것 같습니다. 원본 테이블에 고유 한 레코드가 있습니다. 병합 테이블의 고유성은 기본 키에 추가 된 TH_Valid_To_Date 열에 의해 보장됩니다. – Redford

+0

병합 전에 표를로드 할 때 코드를 추가하십시오. 테스트 데이터를 보지 않고는 더 이상 지원할 수 없습니다. –

+0

나는 그렇게 할 수 없다. 위에 표시된 테스트는 실제 사례의 단순화입니다. 실제 TESTS 테이블에는 14M 개의 행이 있고 TEST_HISTORY에는 53M 개의 행이 있습니다. 매일 병합 성명을 통해 변경된 기록 테이블에로드 된 레코드는 보통 50,000 개 미만입니다. 그것은 잘 작동합니다. 그러나 가끔씩 모든 14M 행이 크게 변경되고 기본 키 위반이 발생합니다. – Redford