oracle 基于存储过程中另一个表的值更新多个列

t98cgbkg  于 2023-11-17  发布在  Oracle
关注(0)|答案(2)|浏览(182)

我试图写一个动态合并过程,能够根据另一个表的值更新多个列。我试图将查询结果存储为一个字符串,然后我可以使用它来构建我的动态查询。

create or replace PROCEDURE SC2_MERGE(
    source_table_name IN VARCHAR2,
    destination_table_name IN VARCHAR2,
    bk_column_name IN VARCHAR2 DEFAULT 'bk_hash_key',
)

AS
 merge_cmd varchar(32767);
 update_set_cmd varchar(32767);

 
  
CURSOR update_record IS
    SELECT  listagg('t.'|| column_name ||' = '|| 's.'|| column_name, ',')
  FROM all_tab_cols
 WHERE table_name = destination_table_name
    and column_name not in ('PK_HASH_KEY', 'BK_HASH_KEY', 'VALID_FROM', 'VALID_UNTIL');
    v_update_record  update_record%ROWTYPE;

BEGIN  

    
     FOR v_update_record IN update_record LOOP
              update_set_cmd:= v_update_record;
     END LOOP;
      
    
   merge_cmd:= 'MERGE INTO' || destination_table_name || ' t
                         USING ' || source_table_name || ' s
                         ON (t.' || bk_column_name || '= s.'  ||  bk_column_name || ')
                         WHEN MATCHED THEN
                                          UPDATE SET ' || update_set_cmd;

 execute IMMEDIATE merge_cmd;

END;

字符串
但是,当我尝试编译程序时,我会收到:
32/15 PL/SQL:忽略语句
32/33 PLS-00382:表达式类型错误错误:检查编译器日志
我也尝试了all_tab_cols.column_name%TYPE而不是update_record%ROWTYPE,但收到相同的错误。
游标查询的输出如下所示:
x1c 0d1x的数据
任何帮助或指出正确的方向是高度赞赏!

lsmd5eda

lsmd5eda1#

如果使用游标for循环,就不需要费心定义游标记录了--这就是游标for循环的美妙之处!
您的代码可以重写为类似于(N.B.未经测试):

CREATE OR REPLACE PROCEDURE sc2_merge(source_table_name      IN VARCHAR2,
                                      destination_table_name IN VARCHAR2,
                                      bk_column_name         IN VARCHAR2 DEFAULT 'bk_hash_key',)

 AS
  merge_cmd      VARCHAR(32767);

  CURSOR update_record IS
    SELECT listagg('t.' || column_name || ' = ' || 's.' || column_name, ',') col_list
    FROM   all_tab_cols
    WHERE  table_name = destination_table_name
    AND    column_name NOT IN ('PK_HASH_KEY', 'BK_HASH_KEY', 'VALID_FROM', 'VALID_UNTIL');

BEGIN

  FOR update_set_cmd IN update_record
  LOOP
    merge_cmd := 'MERGE INTO ' || destination_table_name || ' t
                         USING ' || source_table_name || ' s
                         ON (t.' || bk_column_name || '= s.' || bk_column_name || ')
                         WHEN MATCHED THEN
                                          UPDATE SET ' || update_set_cmd.col_list;

    EXECUTE IMMEDIATE merge_cmd;
  END LOOP;

END;
/

字符串
请注意,为了能够引用记录中的字段,我给游标中的计算列提供了一个别名(col_list)。然后要访问该字段,请使用格式<record_name>.<field_name>,即update_set_cmd.col_list
另外,我将merge语句移到了循环内部,因为您只需要循环一行。
因为是这种情况,你实际上根本不需要循环,你可以简单地选择你的列到一个变量中:

CREATE OR REPLACE PROCEDURE sc2_merge(source_table_name      IN VARCHAR2,
                                      destination_table_name IN VARCHAR2,
                                      bk_column_name         IN VARCHAR2 DEFAULT 'bk_hash_key',)

 AS
  merge_cmd  VARCHAR2(32767);
  v_col_list VARCHAR2(32767);

BEGIN

  SELECT listagg('t.' || column_name || ' = ' || 's.' || column_name, ',') col_list
  INTO   v_col_list
  FROM   all_tab_cols
  WHERE  table_name = destination_table_name
  AND    column_name NOT IN ('PK_HASH_KEY', 'BK_HASH_KEY', 'VALID_FROM', 'VALID_UNTIL');

  merge_cmd := 'MERGE INTO ' || destination_table_name || ' t
                         USING ' || source_table_name || ' s
                         ON (t.' || bk_column_name || '= s.' || bk_column_name || ')
                         WHEN MATCHED THEN
                                          UPDATE SET ' || v_col_list;

  EXECUTE IMMEDIATE merge_cmd;

END;
/


当这个隐式游标没有返回行或返回太多行时,你不需要担心错误处理,因为你正在对整个数据集使用聚合函数(即你没有group by子句)-这将总是返回一行。

mefy6pfw

mefy6pfw2#

你正在从游标中提取一个 record 到一个字符串中;这不会起作用。你需要从那个记录中提取一个字段,在这种情况下,这也意味着你需要在游标查询中对列表达式进行别名:

...
CURSOR update_record IS
    SELECT  listagg('t.'|| column_name ||' = '|| 's.'|| column_name, ',') AS cols
--------------------------------------------------------------------------^^^^^^^
  FROM all_tab_cols
...
     FOR v_update_record IN update_record LOOP
              update_set_cmd:= v_update_record.cols;
----------------------------------------------^^^^^
     END LOOP;
...

字符串
你不需要声明v_update_record,这在你的游标循环中是隐式的。
你并不真的需要一个游标,因为总是有一行你可以直接选择:

BEGIN
   SELECT  listagg('t.'|| column_name ||' = '|| 's.'|| column_name, ',')
   INTO update_set_cmd
     FROM all_tab_cols
    WHERE table_name = destination_table_name
       and column_name not in ('PK_HASH_KEY', 'BK_HASH_KEY', 'VALID_FROM', 'VALID_UNTIL');

   merge_cmd:= ...
...


无论哪种方式,一旦过程已经编译并且您尝试执行它,动态SQL将出错,因为您在INTO之后缺少一个空格:

merge_cmd:= 'MERGE INTO' || destination_table_name || ' t


需要

merge_cmd:= 'MERGE INTO ' || destination_table_name || ' t


但还没到那个地步
也请记住,因为你正在做:

WHERE table_name = destination_table_name


你为destination_table_name传入的值必须与它在数据字典中出现的大小写相同。因为你稍后使用它时不带引号,这意味着你没有使用带引号的标识符(这很好),在这种情况下,名称必须以小写形式传入-或者你可以在查询中更改它:

WHERE table_name = UPPER(destination_table_name)


如果你确实有带引号的标识符,那就不要这样做,但是你必须在动态MERGE中引用那些标识符。

相关问题