提问人:Maeaex1 提问时间:10/27/2023 最后编辑:Maeaex1 更新时间:10/27/2023 访问量:62
基于存储过程中另一个表的值更新多个列
Update multiple columns based on values of another table in stored procedure
问:
我正在尝试编写一个动态合并过程,该过程能够根据另一个表的值更新多个列。我尝试将查询结果存储为字符串,然后可以使用该字符串来构建动态查询。
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
游标查询的输出如下所示:
非常感谢任何帮助或指向正确方向!
答:
如果您使用的是游标 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
评论
您正在将记录从光标提取到字符串中;那行不通。您需要从该记录中获取一个字段,在这种情况下,这也意味着您需要在游标查询中为列表达式添加别名:
...
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
评论
下一个:执行从数据库游标继承的类
评论