2009년 7월 21일 화요일

Oracle Package - Update Cascade

Oracle에서는 MS-SQL과 달리, UPDATE CASCADE를 지원하지 않는다.

그러므로 이를 Trigger나 Package등으로 설정을 하여야 하는데

이를 간단하게 사용할 수 있는 Package가 있다.

 

 

PL/SQL로 다음의 Package를 생성한다.

 

CREATE OR REPLACE PACKAGE UPDATE_CASCADE
AS

    PROCEDURE ON_TABLE( P_TABLE_NAME      IN VARCHAR2,
                        P_PRESERVE_ROWID  IN BOOLEAN DEFAULT TRUE,
                        P_USE_DBMS_OUTPUT IN BOOLEAN DEFAULT FALSE );

END UPDATE_CASCADE;
/

CREATE OR REPLACE PACKAGE BODY UPDATE_CASCADE
AS

TYPE CNAMEARRAY IS TABLE OF USER_CONS_COLUMNS.COLUMN_NAME%TYPE
                   INDEX BY BINARY_INTEGER;

SQL_STMT           VARCHAR2(32000);
USE_DBMS_OUTPUT    BOOLEAN DEFAULT FALSE;
PRESERVE_ROWID     BOOLEAN DEFAULT TRUE;

FUNCTION Q( S IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
    RETURN '"'||S||'"';
END Q;

FUNCTION PKG_NAME( S IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
    RETURN Q( 'U' || S || 'P' );
END PKG_NAME;


FUNCTION VIEW_NAME( S IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
    RETURN Q( 'U' || S || 'V' );
END VIEW_NAME;

FUNCTION TRIGGER_NAME( S IN VARCHAR2, S2 IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
    RETURN Q( 'U' || S || S2 );
END TRIGGER_NAME;


FUNCTION STRIP( S IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
    RETURN LTRIM(RTRIM(S));
END STRIP;

PROCEDURE ADD( S IN VARCHAR2 )
IS
BEGIN
    IF ( USE_DBMS_OUTPUT ) THEN
        DBMS_OUTPUT.PUT_LINE( CHR(9) || S );
    ELSE
        SQL_STMT := SQL_STMT || CHR(10) || S;
    END IF;
END ADD;

 

PROCEDURE EXECUTE_IMMEDIATE
AS
    EXEC_CURSOR     INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
    ROWS_PROCESSED  NUMBER  DEFAULT 0;
BEGIN
    IF ( USE_DBMS_OUTPUT ) THEN
        DBMS_OUTPUT.PUT_LINE( '/' );
    ELSE
        DBMS_SQL.PARSE(EXEC_CURSOR, SQL_STMT, DBMS_SQL.NATIVE );
        ROWS_PROCESSED := DBMS_SQL.EXECUTE(EXEC_CURSOR);
        DBMS_SQL.CLOSE_CURSOR( EXEC_CURSOR );
        DBMS_OUTPUT.PUT_LINE(
        SUBSTR( SQL_STMT, 2, INSTR( SQL_STMT,CHR(10),2)-2 ) );
        SQL_STMT := NULL;
    END IF;
EXCEPTION
    WHEN OTHERS THEN
      IF DBMS_SQL.IS_OPEN(EXEC_CURSOR) THEN
        DBMS_SQL.CLOSE_CURSOR(EXEC_CURSOR);
      END IF;
      RAISE;
END;

PROCEDURE GET_PKEY_NAMES
( P_TABLE_NAME      IN OUT USER_CONSTRAINTS.TABLE_NAME%TYPE,
  P_PKEY_NAMES         OUT CNAMEARRAY,
  P_CONSTRAINT_NAME IN OUT USER_CONSTRAINTS.CONSTRAINT_NAME%TYPE )
IS
BEGIN
    SELECT TABLE_NAME, CONSTRAINT_NAME
      INTO P_TABLE_NAME, P_CONSTRAINT_NAME
      FROM USER_CONSTRAINTS
     WHERE ( TABLE_NAME = P_TABLE_NAME
             OR TABLE_NAME = UPPER(P_TABLE_NAME) )
            AND CONSTRAINT_TYPE = 'P' ;

    FOR X IN ( SELECT COLUMN_NAME , POSITION
                 FROM USER_CONS_COLUMNS
                WHERE CONSTRAINT_NAME = P_CONSTRAINT_NAME
                ORDER BY POSITION ) LOOP
        P_PKEY_NAMES( X.POSITION ) := X.COLUMN_NAME;
    END LOOP;
END GET_PKEY_NAMES;


PROCEDURE WRITE_SPEC( P_TABLE_NAME IN USER_CONSTRAINTS.TABLE_NAME%TYPE,
                      P_PKEY_NAMES IN CNAMEARRAY )
IS
    L_COMMA     CHAR(1) DEFAULT ' ';
BEGIN
    ADD( 'CREATE OR REPLACE PACKAGE ' || PKG_NAME(P_TABLE_NAME) );
    ADD( 'AS' );
    ADD( '--' );
    ADD( '    ROWCNT    NUMBER DEFAULT 0;' );
    ADD( '    INTRIGGER BOOLEAN DEFAULT FALSE;' );
    ADD( '--' );

    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '    TYPE C' || STRIP(I) || '_TYPE IS TABLE OF ' ||
                    Q(P_TABLE_NAME) || '.' || Q(P_PKEY_NAMES(I)) ||
                 '%TYPE INDEX BY BINARY_INTEGER;' );
            ADD( '--' );
            ADD( '    EMPTY_C' || STRIP(I) || ' C' || STRIP(I) || '_TYPE;' );
            ADD( '    OLD_C' || STRIP(I) || '   C' || STRIP(I) || '_TYPE;' );
            ADD( '    NEW_C' || STRIP(I) || '   C' || STRIP(I) || '_TYPE;' );
            ADD( '--' );
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;

    ADD( '--' );
    ADD( '    PROCEDURE RESET;' );
    ADD( '--' );
    ADD( '    PROCEDURE DO_CASCADE;' );
    ADD( '--' );

    ADD( '    PROCEDURE ADD_ENTRY' );
    ADD( '    ( ' );
    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '        ' || L_COMMA || 'P_OLD_C' || STRIP(I) || ' IN ' ||
                  Q(P_TABLE_NAME) || '.' || Q(P_PKEY_NAMES(I)) || '%TYPE ' );
            L_COMMA := ',';
            ADD( '        ,P_NEW_C' || STRIP(I) || ' IN OUT ' ||
                  Q(P_TABLE_NAME) || '.' || Q(P_PKEY_NAMES(I)) || '%TYPE ' );
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( '     );' );
    ADD( '--' );
    ADD( 'END ' || PKG_NAME(P_TABLE_NAME) || ';' );

END WRITE_SPEC;


PROCEDURE WRITE_BODY
( P_TABLE_NAME         IN USER_CONSTRAINTS.TABLE_NAME%TYPE,
  P_PKEY_NAMES      IN CNAMEARRAY,
  P_CONSTRAINT_NAME    IN USER_CONSTRAINTS.CONSTRAINT_NAME%TYPE )
IS
    L_COL_CNT         NUMBER DEFAULT 0;
    L_COMMA         CHAR(1) DEFAULT ' ';
    L_PKEY_STR      VARCHAR2(2000);
    L_PKEY_NAME_STR VARCHAR2(2000);
    L_OTHER_COL_STR VARCHAR2(2000);
BEGIN
    ADD( 'CREATE OR REPLACE PACKAGE BODY ' || PKG_NAME(P_TABLE_NAME) );
    ADD( 'AS' );
    ADD( '--' );

    ADD( '    PROCEDURE RESET ' );
    ADD( '    IS' );
    ADD( '    BEGIN' );
    ADD( '--' );
    ADD( '        IF ( INTRIGGER ) THEN RETURN; END IF;' );
    ADD( '--' );
    ADD( '        ROWCNT := 0;' );
    FOR I IN 1 .. 16 LOOP
        BEGIN
           IF (P_PKEY_NAMES(I) = P_PKEY_NAMES(I)) THEN
              L_COL_CNT := L_COL_CNT+1;
           END IF;
           ADD( '        OLD_C' || STRIP(I) || ' := EMPTY_C' || STRIP(I) || ';' );
           ADD( '        NEW_C' || STRIP(I) || ' := EMPTY_C' || STRIP(I) || ';' );
        EXCEPTION
           WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( '    END RESET;' );
    ADD( '--' );

    ADD( '    PROCEDURE ADD_ENTRY ' );
    ADD( '    ( ' );
    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '        ' || L_COMMA || 'P_OLD_C' || STRIP(I) || ' IN ' ||
                Q(P_TABLE_NAME) || '.' || Q(P_PKEY_NAMES(I)) || '%TYPE ' );
            L_COMMA := ',';
            ADD( '        ,P_NEW_C' || STRIP(I) || ' IN OUT ' ||
                  Q(P_TABLE_NAME) || '.' || Q(P_PKEY_NAMES(I)) || '%TYPE ' );
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( '     )' );
    ADD( '    IS' );
    ADD( '    BEGIN' );
    ADD( '--' );
    ADD( '        IF ( INTRIGGER ) THEN RETURN; END IF;' );
    ADD( '--' );
    ADD( '        IF ( ' );
    FOR I IN 1 .. L_COL_CNT LOOP
        IF ( I <> 1 ) THEN
            ADD( '            OR' );
        END IF;
        ADD( '             P_OLD_C' || STRIP(I) || ' <> ' ||
             'P_NEW_C' || STRIP(I) );
    END LOOP;
    ADD( '         ) THEN ' );
    ADD( '        ROWCNT := ROWCNT + 1;' );

    FOR I IN 1 .. L_COL_CNT LOOP
        ADD( '        OLD_C' || STRIP(I) ||
             '( ROWCNT ) := P_OLD_C' || STRIP(I) || ';' );
        ADD( '        NEW_C' || STRIP(I) ||
             '( ROWCNT ) := P_NEW_C' || STRIP(I) || ';' );
        ADD( '        P_NEW_C' || STRIP(I) ||
             ' := P_OLD_C' || STRIP(I) || ';' );
    END LOOP;

    ADD( '        END IF;' );
    ADD( '    END ADD_ENTRY;' );


    ADD( '--' );

    L_COMMA := ' ';
    FOR I IN 1 .. L_COL_CNT LOOP
        L_PKEY_STR      := L_PKEY_STR||L_COMMA||'$$_C' || STRIP(I) || '(I)';
        L_PKEY_NAME_STR := L_PKEY_NAME_STR || L_COMMA || Q(P_PKEY_NAMES(I));
        L_COMMA := ',';
    END LOOP;

    FOR X IN ( SELECT COLUMN_NAME
                 FROM USER_TAB_COLUMNS
                WHERE TABLE_NAME = P_TABLE_NAME
                  AND COLUMN_NAME NOT IN
                    ( SELECT COLUMN_NAME
                        FROM USER_CONS_COLUMNS
                       WHERE CONSTRAINT_NAME = P_CONSTRAINT_NAME )
                ORDER BY COLUMN_ID )
    LOOP
        L_OTHER_COL_STR := L_OTHER_COL_STR || ',' || Q(X.COLUMN_NAME);
    END LOOP;

    ADD( '    PROCEDURE DO_CASCADE' );
    ADD( '    IS' );
    ADD( '    BEGIN' );
    ADD( '--' );
    ADD( '        IF ( INTRIGGER ) THEN RETURN; END IF;' );
    ADD( '        INTRIGGER := TRUE;' );
    ADD( '--' );
    ADD( '        FOR I IN 1 .. ROWCNT LOOP' );
    ADD( '            INSERT INTO ' || P_TABLE_NAME || ' ( ' );
    ADD( '            ' || L_PKEY_NAME_STR );
    ADD( '            ' || L_OTHER_COL_STR || ') SELECT ');
    ADD( '            ' || REPLACE( L_PKEY_STR, '$$', 'NEW' ) );
    ADD( '            ' || L_OTHER_COL_STR );

    ADD( '            FROM ' || Q(P_TABLE_NAME) || ' A' );
    ADD( '            WHERE (' || L_PKEY_NAME_STR || ' ) = ' );
    ADD( '                  ( SELECT ' || REPLACE(L_PKEY_STR,'$$','OLD') );
    ADD( '                      FROM DUAL );' );
    ADD( '--' );
    IF ( PRESERVE_ROWID ) THEN
       ADD( '            UPDATE ' || Q(P_TABLE_NAME) || ' SET ' );
       ADD( '            ( ' || L_PKEY_NAME_STR || ' ) = ' );
       ADD( '            ( SELECT ' );
       FOR I IN 1 .. L_COL_CNT LOOP
          IF ( I <> 1 ) THEN ADD( '                  ,' ); END IF;
          ADD( '                 DECODE( ' || Q(P_PKEY_NAMES(I)) ||
          REPLACE(', OLD_C$$(I), NEW_C$$(I), OLD_C$$(I) )', '$$',STRIP(I)) );
       END LOOP;
       ADD( '              FROM DUAL )' );
       ADD( '            WHERE ( ' || L_PKEY_NAME_STR || ' ) =' );
       ADD( '                  ( SELECT ' || REPLACE(L_PKEY_STR,'$$','NEW') );
       ADD( '                      FROM DUAL )' );
       ADD( '               OR ( ' || L_PKEY_NAME_STR || ' ) =' );
       ADD( '                  ( SELECT ' || REPLACE(L_PKEY_STR,'$$','OLD') );
       ADD( '                      FROM DUAL );' );
    END IF;

    FOR X IN ( SELECT TABLE_NAME, CONSTRAINT_NAME
                 FROM USER_CONSTRAINTS
                WHERE R_CONSTRAINT_NAME = P_CONSTRAINT_NAME
                  AND CONSTRAINT_TYPE = 'R' ) LOOP

        L_COMMA := ' ';
        L_OTHER_COL_STR := '';
        FOR Y IN ( SELECT COLUMN_NAME
                     FROM USER_CONS_COLUMNS
                    WHERE CONSTRAINT_NAME = X.CONSTRAINT_NAME
                    ORDER BY POSITION ) LOOP
            L_OTHER_COL_STR := L_OTHER_COL_STR || L_COMMA || Q(Y.COLUMN_NAME);
            L_COMMA := ',';
        END LOOP;

        ADD( '--' );
        ADD( '            UPDATE ' || Q( X.TABLE_NAME ) || ' SET ');
        ADD( '            ( ' || L_OTHER_COL_STR || ' ) = ' );
        ADD( '            ( SELECT  ' || REPLACE( L_PKEY_STR, '$$', 'NEW' ) );
        ADD( '                FROM DUAL )' );
        ADD( '            WHERE ( ' || L_OTHER_COL_STR || ' ) = ' );
        ADD( '                  ( SELECT  ' ||
                                REPLACE( L_PKEY_STR, '$$', 'OLD' ) );
        ADD( '                    FROM DUAL );' );

    END LOOP;

    ADD( '--' );
    ADD( '            DELETE FROM ' || Q(P_TABLE_NAME)  );
    ADD( '             WHERE ( ' || L_PKEY_NAME_STR || ' ) = ' );
    ADD( '                   ( SELECT ' ||
    REPLACE( L_PKEY_STR, '$$', 'OLD' ) );
    ADD( '                       FROM DUAL);' );
    ADD( '        END LOOP;' );

    ADD( '--' );
    ADD( '        INTRIGGER := FALSE;' );
    ADD( '        RESET;' );
    ADD( '   EXCEPTION' );
    ADD( '       WHEN OTHERS THEN' );
    ADD( '          INTRIGGER := FALSE;' );
    ADD( '          RESET;' );
    ADD( '          RAISE;' );
    ADD( '    END DO_CASCADE;' );
    ADD( '--' );
    ADD( 'END ' || PKG_NAME( P_TABLE_NAME ) || ';' );

END WRITE_BODY;


PROCEDURE WRITE_BU_TRIGGER( P_TABLE_NAME IN USER_CONSTRAINTS.TABLE_NAME%TYPE,
                            P_PKEY_NAMES IN CNAMEARRAY )
IS
    L_COMMA CHAR(1) DEFAULT ' ';
BEGIN
    ADD( 'CREATE OR REPLACE TRIGGER ' || TRIGGER_NAME( P_TABLE_NAME, '1' ) );
    ADD( 'BEFORE UPDATE OF ' );
    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '   ' || L_COMMA || Q(P_PKEY_NAMES(I)) );
            L_COMMA := ',';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( 'ON ' || Q(P_TABLE_NAME) );
    ADD( 'BEGIN ' || PKG_NAME(P_TABLE_NAME) || '.RESET; END;' );
END WRITE_BU_TRIGGER;


PROCEDURE WRITE_BUFER_TRIGGER
( P_TABLE_NAME IN USER_CONSTRAINTS.TABLE_NAME%TYPE,
  P_PKEY_NAMES IN CNAMEARRAY )
IS
    L_COMMA   CHAR(1) DEFAULT ' ';
BEGIN
    ADD( 'CREATE OR REPLACE TRIGGER '||TRIGGER_NAME( P_TABLE_NAME, '2' ) );
    ADD( 'BEFORE UPDATE OF ' );

    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '   ' || L_COMMA || Q(P_PKEY_NAMES(I)) );
            L_COMMA := ',';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( 'ON ' || Q(P_TABLE_NAME) );
    ADD( 'FOR EACH ROW' );
    ADD( 'BEGIN ' );
    ADD( '   ' || PKG_NAME(P_TABLE_NAME) || '.ADD_ENTRY(' );

    L_COMMA := ' ';
    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '      ' || L_COMMA || ':OLD.' || Q(P_PKEY_NAMES(I)) );
            ADD( '      ,:NEW.' || Q(P_PKEY_NAMES(I)) );
            L_COMMA := ',';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( '      );' );
    ADD( 'END;' );
END WRITE_BUFER_TRIGGER;

PROCEDURE WRITE_AU_TRIGGER( P_TABLE_NAME IN USER_CONSTRAINTS.TABLE_NAME%TYPE,
                            P_PKEY_NAMES IN CNAMEARRAY )
IS
    L_COMMA  CHAR(1) DEFAULT ' ';
BEGIN
    ADD( 'CREATE OR REPLACE TRIGGER ' || TRIGGER_NAME( P_TABLE_NAME, '3' ) );
    ADD( 'AFTER UPDATE OF ' );
    FOR I IN 1 .. 16 LOOP
        BEGIN
            ADD( '   ' || L_COMMA || Q(P_PKEY_NAMES(I)) );
            L_COMMA := ',';
        EXCEPTION
            WHEN NO_DATA_FOUND THEN EXIT;
        END;
    END LOOP;
    ADD( 'ON ' || Q(P_TABLE_NAME) );
    ADD( 'BEGIN ' || PKG_NAME(P_TABLE_NAME) || '.DO_CASCADE; END;' );
END WRITE_AU_TRIGGER;

PROCEDURE ON_TABLE( P_TABLE_NAME      IN VARCHAR2,
                    P_PRESERVE_ROWID  IN BOOLEAN DEFAULT TRUE,
                    P_USE_DBMS_OUTPUT IN BOOLEAN DEFAULT FALSE )
IS
    L_TABLE_NAME         USER_CONSTRAINTS.TABLE_NAME%TYPE DEFAULT P_TABLE_NAME;
    L_CONSTRAINT_NAME   USER_CONSTRAINTS.CONSTRAINT_NAME%TYPE;
    L_PKEY_NAMES        CNAMEARRAY;
    L_COMMA                CHAR(1) DEFAULT ' ';
BEGIN
    USE_DBMS_OUTPUT := P_USE_DBMS_OUTPUT;
    PRESERVE_ROWID  := P_PRESERVE_ROWID;

    GET_PKEY_NAMES( L_TABLE_NAME, L_PKEY_NAMES, L_CONSTRAINT_NAME );

    SQL_STMT := NULL;
    WRITE_SPEC( L_TABLE_NAME, L_PKEY_NAMES );
    EXECUTE_IMMEDIATE;
    WRITE_BODY( L_TABLE_NAME, L_PKEY_NAMES, L_CONSTRAINT_NAME );
    EXECUTE_IMMEDIATE;
    WRITE_BU_TRIGGER( L_TABLE_NAME, L_PKEY_NAMES );
    EXECUTE_IMMEDIATE;
    WRITE_BUFER_TRIGGER( L_TABLE_NAME, L_PKEY_NAMES );
    EXECUTE_IMMEDIATE;
    WRITE_AU_TRIGGER( L_TABLE_NAME, L_PKEY_NAMES );
    EXECUTE_IMMEDIATE;


END ON_TABLE;

END UPDATE_CASCADE;
/

 

SQL상에서 UPDATE CASCADE할 Table명을 포함하여 실행하면 끝!

만일, TBL_MEMBER의 PK를 참조하는 다른 FK들에 대하여 UPDATE CASCADE를 한다면,

EXEC UPDATE_CASCADE.ON_TABLE('TBL_MEMBER');

라고 입력하면 된다.

이 Package 외에도 여러방법이 있으므로, 참조해 보도록 한다.

출처는 http://asktom.oracle.com/tkyte/update_cascade/

펼쳐두기..


 

댓글 없음:

댓글 쓰기