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

Update multiple columns based on values of another table in stored procedure

提问人:Maeaex1 提问时间:10/27/2023 最后编辑:Maeaex1 更新时间:10/27/2023 访问量:62

问:

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

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%TYPEupdate_record%ROWTYPE

游标查询的输出如下所示:

enter image description here

非常感谢任何帮助或指向正确方向!

Oracle 存储过程 SQL 更新 游标 oracle19c

评论


答:

3赞 Boneist 10/27/2023 #1

如果您使用的是游标 for 循环,则无需费心定义游标记录 - 这就是游标 for 循环的美妙之处!

您的代码可以重写为(注意:未经测试):

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

评论

0赞 Maeaex1 10/27/2023
棒!使用后一种代码建议,它就像一个魅力!
3赞 Alex Poole 10/27/2023 #2

您正在将记录从光标提取到字符串中;那行不通。您需要从该记录中获取一个字段,在这种情况下,这也意味着您需要在游标查询中为列表达式添加别名:

...
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

评论

0赞 Maeaex1 10/27/2023
非常感谢!UPPER 的改进至关重要!!