根据列值区分2行2个表(不知道列名称)

时间:2016-08-17 06:02:23

标签: oracle plsql

我甚至不知道是否可能 - 所以任何想法都会受到欢迎:

我想要一个函数,它将返回一个字符串(varchar2)表示2个特定表的列值的差异。

所以它的任务将是

  

找到属于2个表的2行之间的差异(会发生这种情况   具有相同的结构)。

请考虑以下情况。

表A(A_rowid,col1,col2,col3,col4,col5 ...,coln)和值(id1,val1,val2,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

因此,

预期输出 - >

如果没有找到差异,

diff:col2:val2-> val2',coln:valn-> valn'

重要的是,我希望在没有硬编码列名的情况下

(虽然表名是硬编码的)。

e.g。如果我们在表中添加其他列 - 函数应该仍然有效。

2 个答案:

答案 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;

注意,如果是NULL值,则需要函数LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||')

当其中一个值为NULL时,条件a.COLUMN_NAME <> b.COLUMN_NAME会返回任何内容。

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;

答案 1 :(得分: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数据格式转换为char,这样就可以给我们提供完美的字符串输出。