2016-08-17 5 views
0

Я даже не знаю, если это возможно, - так что любая идея будет приветствоваться:Определение различий в 2 ряда 2 таблицы на основе значений столбцов (без предварительного знания имен столбцов)

Я хочу функцию, которая будет возвращает строку (varchar2) представление различий в значениях столбцов из 2 конкретных таблиц.

Так что это задача будет

разности находят между 2 строки, которые принадлежат к 2 таблицы (что происходит иметь ту же структуру).

Рассмотрите следующую ситуацию.

В таблице А (A_rowid, col1, col2, col3, COL4, col5 ..., Coln) и значения (ID1, знач1, знач2, ​​val3, .., valn)

Таблица B (B_rowid, col1 , col2, col3, col4, col5 ..., coln) и значения (id2, val1, val2 ', val3, .., valn')

* A_rowid - уникальный ключ tableA, B_rowid - уникальный ключ таблицы B

fnction diff(A_rowid number, B_rowid number) returns varchar2 is 
begin 
--do something 
end; 

Все столбцы таблиц можно рассматривать как VARCHAR2.

Таким образом,

ожидается выход будет ->

Null, если никакой разницы не обнаружено

или

дифф: col2: val2-> val2 ', coln: valn-> valn'

Что важно здесь то, что я хотел бы сделать это без жесткого кодирования названий столбцов

(имена таблиц жёстко, хотя).

например. если и когда мы добавляем дополнительные столбцы в наши таблицы - функция все равно должна работать.

+0

Что вы считаете «A_rowid» и «B_rowid»? –

+0

Возможно, самый простой способ - выбрать «выбрать» из таблицы_A MINUS select * из таблицы_B' –

+0

сравнить столбцы столбцов также есть? –

ответ

2

Вы можете использовать этот один:

FUNCTION diff(A_rowid NUMBER, B_rowid NUMBER) RETURN VARCHAR2 IS 
    CURSOR TabColumns IS 
    SELECT COLUMN_NAME, COLUMN_ID 
    FROM USER_TAB_COLUMNS 
    WHERE TABLE_NAME = 'TABLE_A' 
     AND COLUMN_NAME <> 'A_ROWID' 
    ORDER BY COLUMN_ID; 

    sqlstr VARCHAR2(1000); 
    val_a VARCHAR2(4000); 
    val_b VARCHAR2(4000); 
    res VARCHAR2(30000); 
BEGIN 

    FOR aCol IN TabColumns LOOP 
    BEGIN 
     sqlstr := 'SELECT a.'||aCol.COLUMN_NAME||', b.'||aCol.COLUMN_NAME; 
     sqlstr := sqlstr ||' FROM TABLE_A a CROSS JOIN TABLE_B b '; 
     sqlstr := sqlstr || ' WHERE A_rowid = :aRow AND B_rowid = :bRow '; 
     sqlstr := sqlstr || ' AND LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||') '; 
     sqlstr := sqlstr || ' AND COALESCE(a.'||aCol.COLUMN_NAME||', b.'||aCol.COLUMN_NAME||') IS NOT NULL '; 
     EXECUTE IMMEDIATE sqlstr INTO val_a, val_b USING A_rowid, B_rowid; 
     res := res ||', '||aCol.COLUMN_NAME||':'||val_a||'->'||val_b; 
    EXCEPTION 
     WHEN NO_DATA_FOUND THEN 
      NULL; 
    END; 
    END LOOP; 

    RETURN REGEXP_REPLACE(res, '^, ', 'diff:'); 

END; 

Примечание, функция LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||') требуется в случае NULL значений.

Условие a.COLUMN_NAME <> b.COLUMN_NAME ничего не возвращает, если одно из значений NULL.

LNNVL(a.COLUMN_NAME = b.COLUMN_NAME) эквивалентно

(a.COLUMN_NAME <> b.COLUMN_NAME 
    OR (a.COLUMN_NAME IS NULL AND b.COLUMN_NAME IS NOT NULL) 
    OR (a.COLUMN_NAME IS NOT NULL AND b.COLUMN_NAME IS NULL)) 

Однако использование функции выше, только если вы не обеспокоены производительности.Более продвинутое решение было бы это:

FUNCTION diff(A_rowid NUMBER, B_rowid NUMBER) RETURN VARCHAR2 IS 

    CURSOR TabColumns IS 
    SELECT COLUMN_NAME, COLUMN_ID 
    FROM USER_TAB_COLUMNS 
    WHERE TABLE_NAME = 'TABLE_A' 
     AND COLUMN_NAME <> 'A_ROWID' 
    ORDER BY COLUMN_ID; 

    sqlstr VARCHAR2(10000); 
    val_a VARCHAR2(4000); 
    val_b VARCHAR2(4000); 
    res VARCHAR2(30000); 

    cur INTEGER; 
    p INTEGER; 
    res INTEGER; 

BEGIN 

    sqlstr := 'SELECT ' 
    FOR aCol IN TabColumns LOOP 
     sqlstr := ' a.'||aCol.COLUMN_NAME||'_A, b.'||aCol.COLUMN_NAME||'_B, '; 
    END LOOP; 
    sqlstr := REGEXP_REPLACE(sqlstr, ', $', ' FROM TABLE_A a CROSS JOIN TABLE_B b '); 
    sqlstr := sqlstr || ' WHERE A_rowid = :aRow AND B_rowid = :bRow '; 

    cur := DBMS_SQL.OPEN_CURSOR; 
    DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE); 
    DBMS_SQL.BIND_VARIABLE (cur, ':aRow', A_rowid); 
    DBMS_SQL.BIND_VARIABLE (cur, ':bRow', B_rowid); 
    p := 1; 
    FOR aCol IN TabColumns LOOP 
     DBMS_SQL.DEFINE_COLUMN(cur, p, aCol.COLUMN_NAME||'_A', 4000); 
     DBMS_SQL.DEFINE_COLUMN(cur, p+1, aCol.COLUMN_NAME||'_B', 4000); 
     p := p + 2; 
    END LOOP; 
    res := DBMS_SQL.EXECUTE_AND_FETCH(cur, TRUE); 

    p := 1; 
    FOR aCol IN TabColumns LOOP 
     DBMS_SQL.COLUMN_VALUE(cur, p, val_a); 
     DBMS_SQL.COLUMN_VALUE(cur, p+1, val_b); 
     p := p + 2; 
     IF val_a <> val_b OR (val_a IS NULL AND val_b IS NOT NULL) OR (val_a IS NOT NULL AND val_b IS NULL) THEN 
      res := res ||', '||aCol.COLUMN_NAME||':'||val_a||'->'||val_b; 
     END IF; 
    END LOOP; 
    DBMS_SQL.CLOSE_CURSOR(cur); 

    RETURN REGEXP_REPLACE(res, '^, ', 'diff:'); 

END; 
+0

'If' по-прежнему необходимо в первом решении - иначе пустые столбцы отображаются в результате (' lnnvl (null = null) 'is' true') – Plirkee

+1

Да, я пропустил это. Разумеется, другое решение с «COALESCE» - но дополнительным «IF». –

1

Попробуйте эту функцию

FUNCTION getdiff(arowid varchar, browid varchar) RETURN CLOB IS 
    v_line clob; 
    v_col_cnt INTEGER; 
    v_ind  NUMBER; 
    rec_tab dbms_sql.desc_tab; 
    v_cursor NUMBER; 
    v_sql  clob; 
    V_FIRST clob;V_SECOND CLOB; 
    V_FINAL CLOB; 
begin 

    V_SQL := Q'$ select * from(select * from Table1 where rowid=:arowid)a, 
       (select * from Table2 where rowid=:browid)b $'; 

    V_CURSOR := DBMS_SQL.OPEN_CURSOR; 
    DBMS_SQL.PARSE(V_CURSOR, V_SQL, DBMS_SQL.NATIVE); 

    DBMS_SQL.BIND_VARIABLE (V_CURSOR, ':arowid', arowid); 
    DBMS_SQL.BIND_VARIABLE (V_CURSOR, ':browid', browid); 

    DBMS_SQL.DESCRIBE_COLUMNS(V_CURSOR, V_COL_CNT, REC_TAB); 
    FOR V_POS IN 1 .. REC_TAB.LAST LOOP 
    V_LINE := REC_TAB(V_POS).COL_NAME; 
    DBMS_SQL.DEFINE_COLUMN(V_CURSOR, V_POS, V_LINE); 
    END LOOP; 
    V_IND := DBMS_SQL.EXECUTE(V_CURSOR); 

    LOOP 
    V_IND := DBMS_SQL.FETCH_ROWS(V_CURSOR); 
    EXIT WHEN V_IND = 0; 
    FOR V_COL_SEQ IN 1 .. REC_TAB.COUNT LOOP 
     if v_col_seq <=V_COL_CNT/2 then 
     DBMS_SQL.COLUMN_VALUE(V_CURSOR, V_COL_SEQ, V_LINE); 
     V_FIRST := V_LINE; 
     DBMS_SQL.COLUMN_VALUE(V_CURSOR, V_COL_SEQ+3, V_LINE); 
     V_SECOND := V_LINE; 
     IF V_FIRST <> V_SECOND THEN 
      V_FINAL := V_FINAL || rec_tab(v_col_seq).col_name || ':' || V_FIRST ||'->'||V_SECOND || ','; 
     END IF; 
     end if; 
    END LOOP; 
    END LOOP; 
    RETURN V_FINAL; 
end; 

выхода getdiff функции находится в формате CLOB, потому что предел varchar2 типа данных является 32767 поэтому после ограничения охвата функция дает нам ошибка.

Использование:

select to_char(getdiff('AAAjOuAAEAAA697AAC','AAAjOuAAEAAA697AAk')) from dual; 

здесь to_char функция используется для формата данных новообращенного Clob обугливаться, так то будет дать нам идеальный выход строки.

+0

Большое спасибо! Хорошее прикосновение с Clob - я проверю это позже. Дело в том, что я уже принял один ответ 2 часа назад - так что не могу принять оба ;-(В любом случае это тоже должно быть полезно. Еще раз спасибо. – Plirkee

+0

Не думаю, что вы можете использовать значение VARCHAR для ROWID. например, 'DBMS_SQL.BIND_VARIABLE (V_CURSOR, ': arowid', CHARTOROWID (arowid)),' в этом случае. –

+0

@WernfriedDomscheit: да, мы не можем, но в этом случае я рассматриваю rowid как char, так что в любом случае мы можем попробовать оба , –