【问题标题】:Column Transpositions into Oracle (pl/sql, sql)列转置到 Oracle (pl/sql, sql)
【发布时间】:2018-08-07 22:17:33
【问题描述】:

我正在做转置,并查看某些列是否存在与其他表完全相同的值。示例:

tableA: id = 10, col = abc , value = 10

tableB: id = 10, abc = 10

我有以下代码:

declare
  TYPE  t_my_list is record(id VARCHAR2(4000),col VARCHAR2(4000),val VARCHAR2(4000));
  Type list_3STR is table of t_my_list;

  v_stmt VARCHAR2(32000) := 'SELECT id, col, val FROM userA.tableA';
  v_lstmt VARCHAR2(32000);
  v_ret list_3STR := list_3STR();      
  cDel   number; 
begin
   EXECUTE IMMEDIATE v_stmt BULK COLLECT INTO v_ret;  

    for i in v_ret.first..v_ret.last loop
    --DBMS_OUTPUT.PUT_LINE('ID: '||v_ret (i).id||', COL: '||v_ret (i).col||', VAL: '||v_ret (i).val);

        v_lstmt := 'SELECT count(*) FROM userB.tableB WHERE NVL(cast('||v_ret (i).col||' as Varchar2(100)), ''<null>'') in ('''||v_ret (i).val||''', ''<null>'') and ID = '''||v_ret (i).id||''' ';
        DBMS_OUTPUT.PUT_LINE(v_lstmt);
        EXECUTE IMMEDIATE v_lstmt INTO cDel;

         If cDel > 0 Then
          DBMS_OUTPUT.PUT_LINE('delete row from userA.tableA')
         End if;

    end loop;

DBMS_OUTPUT.PUT_LINE('v_ret = '||v_ret.count);   
end;

我必须考虑5个案例

userA.tableA 到 userB.tableB

  1. NULL 到 NULL = 1 --DELETE 值
  2. NULL 到 DATA = 0 --NOT DELETE
  3. 相同的 DATA 到 DATA = 1 --DELETE
  4. 不同的数据到不同的数据 = 0 --NOT DELETE
  5. DATA 为 NULL = 0 = --NOT DELETE

我的代码适用于案例 1 到 4。如何解决第 5 个问题?

更新:案例示例:

1. 
id = 10, col = test, val = null
id = 10, test = null

2.
id = 10, col = test, val = null
id = 10, test = 99

3.
id = 10, col = test, val = 99
id = 10, test = 99

4.
id = 10, col = test, val = 5
id = 10, test = 99

5.
id = 10, col = test, val = 4
id = 10, test = null

【问题讨论】:

  • 动态SQL有原因吗?是否有一些限制,阻止写一个删除语句并在“where”部分指定逻辑?
  • 另外,请提供与每个场景匹配的示例输入数据,并提供您期望的输出。
  • @4est 您不能使用游标,因为这是您公司的标准?怎么回事?这就像说你只能用鼻子打字来完成你的工作。 *皱眉* 如果是出于性能原因,因为 10g 循环游标已优化为在幕后执行 bulk collect ... limit 100。此外,在不使用限制子句的情况下进行批量收集 - 正如您在示例代码中所做的那样 - 会占用大量内存;我想这应该是你的标准的一部分,不要在没有限制条款的情况下批量收集?
  • @4est - 你的意思是删除,而不是删除?通常不应即时创建或删除表。但是如果它们被删除,你会从你的包中得到一个运行时错误;如果它们被删除并重新创建,包将在下次调用时自动重新编译。 (另外,您可以在不使用批量收集的情况下使用带游标的动态 SQL...)
  • 同样在your previous question 中,我展示了使用is null 而不是nvl(),这适用于场景2 和5。你为什么回到nvl() 和魔法值?

标签: sql oracle plsql null transpose


【解决方案1】:

我会这样做(如果我绝对必须这样做的话)的方式是找出需要与表 b 中的哪些列进行比较,然后您可以计算出表 a 中的值可以用来与这些列进行比较。

一旦有了(通过查询 user_tables(或必要时的 all_tables/dba_tables)来检索相关列),您就可以生成一个连接子句。

join 子句需要检查两列是否为空或两列是否具有相同的非空值。

一旦你有了它,你就可以在合并语句中使用它来删除与连接条件匹配的行。为此,我们首先更新匹配的行(我们需要这样做以便在下一步中删除这些行才能看到这些行),然后删除它们。

这是一个有效的测试用例:

设置:

create table a (id integer, col varchar2(30), val number, constraint a_pk primary key (id, col));

create table b (id integer, abc number, test number, xyz number, constraint b_pk primary key (id));

insert into a (id, col, val)
select 10, 'test', null from dual union all
select 11, 'test', null from dual union all
select 12, 'test', 99 from dual union all
select 13, 'test', 5 from dual union all
select 14, 'test', 4 from dual union all
select 10, 'abc', 1 from dual union all
select 10, 'xyz', 7 from dual union all
select 11, 'abc', 4 from dual union all
select 11, 'xyz', 6 from dual union all
select 12, 'abc', 12 from dual union all
select 12, 'efg', 30 from dual union all
select 13, 'abc', 3 from dual union all
select 13, 'xyz', 5 from dual union all
select 14, 'abc', 8 from dual union all
select 14, 'xyz', 9 from dual;

insert into b (id, abc, test, xyz)
select 10, 1, null, 7 from dual union all
select 11, 4, 99, 8 from dual union all
select 12, 11, 99, 30 from dual union all
select 13, 1, 5, 5 from dual union all
select 14, 1, null, 7 from dual;

commit;

我们希望保留在表 a 中的行

select a.*
from   a tgt
        full outer join b src on (tgt.id = src.id
      and (1 = 0
           or (upper(tgt.col) = 'ABC' and (tgt.val = src.ABC or (tgt.val is null and src.ABC is null)))
           or (upper(tgt.col) = 'TEST' and (tgt.val = src.TEST or (tgt.val is null and src.TEST is null)))
           or (upper(tgt.col) = 'XYZ' and (tgt.val = src.XYZ or (tgt.val is null and src.XYZ is null)))))
where tgt.id is not null and src.id is NULL
ORDER BY a.id, a.col;

ID COL  VAL
-- ---- ---
11 test    
11 xyz    6
12 abc   12
12 efg   30
13 abc    3
14 abc    8
14 test   4
14 xyz    9

运行代码

set serveroutput on

declare
  v_sql clob;
begin
  v_sql := 'merge into a tgt' || chr(10) ||
           '  using  b src' || chr(10) ||
           '  on (tgt.id = src.id' || chr(10) ||
           '      and (1 = 0';

  -- Generate the join conditions
  for rec in (select '           or (upper(tgt.col) = '''||column_name||''' and (tgt.val = src.'||column_name||' or (tgt.val is null and src.'||column_name||' is null)))' join_condition
              from   user_tab_columns
              where  table_name = 'B'
              and    column_name != 'ID')
  loop
    v_sql := v_sql || chr(10) || rec.join_condition;
  end loop;

  v_sql := v_sql || '))' || chr(10) ||
                    'when matched then' || chr(10) || -- we only care about rows that match on the join clause
                    '  update set tgt.col = tgt.col' || chr(10) || -- we need to physically update those rows, or the delete clause won't see them.
                    '  delete where 1=1'; -- we need to have the where clause here, but we're deleting all rows that were updated in the previous step, hence 1=1 which is always true.

  dbms_output.put_line (v_sql||';');

  execute immediate v_sql;
end;
/

dbms_output 语句

merge into a tgt
  using  b src
  on (tgt.id = src.id
      and (1 = 0
           or (upper(tgt.col) = 'ABC' and (tgt.val = src.ABC or (tgt.val is null and src.ABC is null)))
           or (upper(tgt.col) = 'TEST' and (tgt.val = src.TEST or (tgt.val is null and src.TEST is null)))
           or (upper(tgt.col) = 'XYZ' and (tgt.val = src.XYZ or (tgt.val is null and src.XYZ is null)))))
when matched then
  update set tgt.col = tgt.col
  delete where 1=1;

检查我们是否保留了预期的列

select *
from   a
order by id, col;

ID COL  VAL
-- ---- ---
11 test    
11 xyz    6
12 abc   12
12 efg   30
13 abc    3
14 abc    8
14 test   4
14 xyz    9

但是,如果您对此事有任何选择,我会认真、认真地要求您重新考虑这个设计。

在表 a 中拥有键值对更新表 b 是一种非常奇怪的方式,而且正如您所发现的,它使最简单的事情变得非常棘手。

【讨论】:

  • 感谢@Boneist 的精彩解释和示例
  • 只是补充一下,我建议在单个合并语句中执行此操作,以便您只查询表 b 一次,而不是查询表 a 中的每一行。您应该修改 user_tab_columns 上的查询以仅选择与检查相关的那些列。您可能还需要修改查询以针对不同的列数据类型执行不同的操作,例如您可能需要使用适当的格式掩码在 a.val 列周围抛出一个 to_date,以便正确地将值与表 b 列进行比较。
猜你喜欢
  • 2014-08-24
  • 2015-09-16
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
  • 1970-01-01
  • 2017-12-18
  • 1970-01-01
  • 2012-10-26
相关资源
最近更新 更多