Reputation: 3841
I don't even know if it is possible - so any idea will be welcome:
I want a function, which will return a string(varchar2
) representation of the differences in values of columns of 2 specific tables.
So it's task will be to
find difference between 2 rows that belong to 2 tables (which happens to have the same structure).
Consider the following scenario.
Table A (A_rowid,col1,col2,col3,col4,col5...,coln) and values (id1,val1,val2,val3,..,valn)
Table B (B_rowid,col1,col2,col3,col4,col5...,coln) and values (id2,val1,val2',val3,..,valn')
*A_rowid - unique key of tableA, B_rowid - unique key of table B
fnction diff(A_rowid number, B_rowid number) returns varchar2 is
begin
--do something
end;
All columns of the tables could be treated as Varchar2.
Thus,
The expected output would be -->
Null if no difference is found
or
diff: col2:val2->val2', coln:valn->valn'
What is important here is that I would like to do that without hard coding column names
(table names are hard coded though).
e.g. if and when we add additional columns to our tables - function should still work.
Upvotes: 0
Views: 74
Reputation: 59563
You can use this one:
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;
Note, function LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||')
is needed in case of NULL values.
Condition a.COLUMN_NAME <> b.COLUMN_NAME
does return anything when one of the values is NULL.
LNNVL(a.COLUMN_NAME = b.COLUMN_NAME)
is equivalent to
( 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) )
However, use function above only if you are not concerned about performance. The more advanced solution would be this one:
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;
Upvotes: 2
Reputation: 1286
Try this function
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;
output of getdiff function is in clob format because limit of varchar2 datatype is 32767 so after reach limit function give us error.
Use:
select to_char(getdiff('AAAjOuAAEAAA697AAC','AAAjOuAAEAAA697AAk')) from dual;
here to_char function is used for convert clob data format to char,so thats give us perfect string output.
Upvotes: 1