0

我是 Oracle SQL PL/SQL 的新手,所以请多多包涵。我希望能够通过动态构建动态 SQL 语句并执行它们来修改/更新具有来自另一个数据集 (tblMODIFY_TEST) 的特定数据值的数据集 (tblDATA_TEST)。

到目前为止,我构建的示例基于使用 CURSOR (FOR/LOOP),但昨天我看到一篇文章建议更合适的解决方案是使用 REFCURSOR,但遗憾的是没有丰富的 Oracle 经验语法 我想我最好在迷路之前问问专家们。

使用示例数据集的预期最终结果将是看到以下构造的 SQL 语句正在创建和执行。

-- Example constucted SQL statments
-- UPDATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE "BR_CD" = '0123';
-- UPDATE "tblDATA_TEST" SET "is_hdgd_flg" = -1 WHERE "ORG_GRP_ID" = 654;
-- UPDATE "tblDATA_TEST" SET "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY') WHERE "data_dte" = TO_DATE('3.09.2020','DD.MM.YYYY');
-- UPDATE "tblDATA_TEST" SET "is_incl_flg" = -1 WHERE "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY');
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "ORG_ID" = 321;
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 500.50 WHERE "ORG_DESCR" = 'CLIENT E';
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "CTRY_CD" = 'UK' AND "BR_CD" = '0654' AND "ORG_ID" = 888;

下面是示例代码,假设您在名为“tblDATA_TEST”的表中保存了以下数据集,其中插入了以下示例数据记录。

CREATE TABLE "tblDATA_TEST" ("data_dte" DATE, RGN_CD VARCHAR2(5), CTRY_CD VARCHAR2(5), BR_CD VARCHAR2(5), ORG_GRP_ID NUMBER, ORG_ID NUMBER, ORG_DESCR VARCHAR2(255), OUT_AMT FLOAT, "is_ovrdue_flg" NUMBER, "is_hdgd_flg" NUMBER, "tb_incl_flg" NUMBER, "tb_excl_flg" NUMBER);
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'AMER', 'US', 0123, 987, 789, 'CLIENT A', 100.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'AMER', 'US', 0123, 654, 456, 'CLIENT B', 200.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0456, 321, 123, 'CLIENT C', 300.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0456, 654, 654, 'CLIENT D', 400.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0654, 321, 321, 'CLIENT E', 500.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0654, 321, 888, 'CLIENT F', 600.50, 0, 0, 0, 0 );

并且我希望能够通过以下修改数据表 tblMODIFY_TEST 和插入的数据记录来更新 tblDATA_TEST 表中的一些数据记录。

CREATE TABLE "tblMODIFY_TEST" ("data_dte" DATE, "mdfy_rank" NUMBER, "tbl_name" VARCHAR2(250), "fld_name" VARCHAR2(250), "fld_value" VARCHAR2(250), "reason_descr" VARCHAR2(250), "valid_dte" DATE, "criteriafld1" VARCHAR2(250 BYTE), "criteriavalue1" VARCHAR2(250), "criteriafld2" VARCHAR2(250), "criteriavalue2" VARCHAR2(250), "criteriafld3" VARCHAR2(250), "criteriavalue3" VARCHAR2(250), "criteriafld4" VARCHAR2(250), "criteriavalue4" VARCHAR2(250), "criteriafld5" VARCHAR2(250), "criteriavalue5" VARCHAR2(250));
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '1', 'tblDATA_TEST', 'tb_excl_flg', '-1',   'Test branch code (BR_CD), varchar data type', NULL ,'BR_CD', '0123', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '2', 'tblDATA_TEST', 'is_hdgd_flg', '-1',   'Test organisation group code (ORG_GRP_CD), number data type', NULL,    'ORG_GRP_CD', '654', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '3', 'tblDATA_TEST', 'data_dte',    '31.10.2020',   'Test data date (date_dte), date data type', NULL,  'data_dte', '30.09.2020', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '4', 'tblDATA_TEST', 'is_incl_flg', '-1',   'Test data date (date_dte), date data type', NULL,  'data_dte', '31.10.2020', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '5', 'tblDATA_TEST', 'OUT_AMT', '800.50',   'Test outstanding amount (OUT_AMT), float data type', NULL, 'ORG_ID', '321', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '6', 'tblDATA_TEST', 'OUT_AMT', '800.50',   'Test validation date, this should not get processed (valid_dte)', TO_DATE('31.08.2020','DD.MM.YYYY'),  'ORG_ID', '321', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '7', 'tblDATA_TEST', 'OUT_AMT', '500.50',   'Test outstanding amount (OUT_AMT), float data type',   NULL, 'ORG_DESCR', 'CLIENT E', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'),    '8', 'tblDATA_TEST', 'OUT_AMT', '800.50',   'Test outstanding amount (OUT_AMT), float data type and multiple criteria', NULL, 'CTRY_CD', 'UK', 'BR_CD', '0654', 'ORG_ID', 888, NULL, NULL, NULL, NULL );

请注意,由于 MODIFY 动态查询过程的目的是通用/通用,即能够更新任何表中的任何字段值,因此在 SQL 语句的构造中要使用的修改字段都是文本字段( VARCHAR),这意味着在构建动态 SQL 语句时,该过程将必须确定要更新的表/字段的字段数据类型是什么,并进一步确定潜在的字段数据类型是什么许多要使用的“标准字段”。

因此,我将一个函数“fncGET_FLD_TYP”放在一起,它可以通过查看系统表 USER_TAB_COLUMNS 来确定字段数据类型,传递表名和字段名以获取字段数据类型。

CREATE or REPLACE FUNCTION "fncGET_FLD_TYP"("tblNAME" IN VARCHAR, "fldNAME" IN VARCHAR) RETURN VARCHAR AS "fldDATA_TYPE" VARCHAR(50);
BEGIN
  SELECT DATA_TYPE INTO "fldDATA_TYPE" FROM USER_TAB_COLUMNS WHERE TABLE_NAME = "tblNAME" AND COLUMN_NAME = "fldNAME";
  RETURN "fldDATA_TYPE";
  
END;

出于模拟目的,我创建了一个名为 fncMODIFY 的函数,但我想理想情况下这将作为存储过程运行。

CREATE or REPLACE FUNCTION "fncMODIFY"
  RETURN VARCHAR DETERMINISTIC IS
  sql_stmt VARCHAR2(4000);
  fld_typ VARCHAR2(50);
  z_row NUMBER :=0;

  CURSOR c_modify IS SELECT * FROM "tblMODIFY_TEST" WHERE "valid_dte" >= "data_dte" OR "valid_dte" IS NULL ORDER BY "mdfy_rank";
   
  BEGIN  -- iterate through each of the MODIFY record set, constructing an UPDATE SQL statement on the fly and executing it, setting the table_name.field_name to the 'fld_value', (workout perhaps via a udf function the field data type & convert accordingly), based upon the criteria# fields and their values (via a function workout the field data type & convert accordingly)
    FOR r_modify IN c_modify LOOP
      sql_stmt := 'UPATE "' || r_modify."tbl_name" || '" SET "' || r_modify."fld_name" || '" = ';

      FOR Z in (SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'tblMODIFY_TEST') LOOP
        z_row := z_row + 1;
        
        CASE 
          WHEN Z.COLUMN_NAME = 'fld_name' THEN 
            SELECT "fncGET_FLD_TYP"(r_modify."tbl_name", r_modify."fld_name") INTO fld_typ FROM DUAL;
            CASE 
              WHEN fld_typ = 'DATE' THEN
                sql_stmt := sql_stmt || 'TO_DATE(<fld_value>, ''DD.MM.YYYY'')';
                
              WHEN fld_typ IN('FLOAT', 'NUMBER') THEN
                --sql_stmt := sql_stmt || 'TO_NUMBER(<fld_value>)';
                sql_stmt := sql_stmt || '<fld_value>';
                              
              ELSE --'VARCHAR2'
                sql_stmt := sql_stmt || ' ''<fld_value>''' || fld_typ ; 
                
            END CASE;
      
            sql_stmt := REPLACE(sql_stmt, '<fld_value>', r_modify."fld_value");
            

          WHEN SUBSTR(Z.COLUMN_NAME,1,11) =  'criteriafld' THEN -- AND r_modify.COLUMN_NAME IS NOT NULL THEN
            IF z_row = 8 THEN
              sql_stmt := sql_stmt || ' WHERE <criteriafld#> = ';
                
            ELSIF z_row > 8 THEN 
              sql_stmt := sql_stmt || ' AND <criteriafld#> = ';
              
            END IF;
          
            SELECT "fncGET_FLD_TYP"(r_modify."tbl_name", Z.COLUMN_NAME) INTO fld_typ FROM DUAL;
            CASE 
              WHEN fld_typ = 'DATE' THEN
                sql_stmt := sql_stmt || 'TO_DATE(<criteriavalue#>, ''DD.MM.YYYY'')';
              
              WHEN fld_typ IN ('FLOAT','NUMBER') THEN
                --sql_stmt := sql_stmt || 'TO_NUMBER(<criteriavalue#>)';
                sql_stmt := sql_stmt || '<criteriavalue#>';
                            
              ELSE -- 'VARCHAR2'
                sql_stmt := sql_stmt || '<criteriavalue#>';
              
            END CASE;

            sql_stmt := sql_stmt || fld_typ;
            --sql_stmt := REPLACE(sql_stmt, '<criteriafld#>', r_modify.criteriafldZ);
            --sql_stmt := REPLACE(sql_stmt, '<criteriavalue#>', r_modify.criteriavalueZ);

        ELSE -- DO NOTHING
          sql_stmt := sql_stmt || '';
            
        END CASE;
      END LOOP;
          
      RETURN sql_stmt || ';';
      
      -- EXECUTE.IMMEDIATELY UPDATE tbl_name SET fld_name = fld_value WHERE criteriafld1 = criterivalue1 AND criteriafld# = criterivalue# etc etc
      -- EXAMPLE MODIFY_TEST DATA
      -- UPDATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE "BR_CD" = '0123';
      -- UPDATE "tblDATA_TEST" SET "is_hdgd_flg" = -1 WHERE "ORG_GRP_ID" = 654;
      -- UPDATE "tblDATA_TEST" SET "is_incl_flg" = -1 WHERE "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY');
      -- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "ORG_ID" = 321;
      -- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 500.50 WHERE "ORG_DESCR" = 'CLIENT E';
      -- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "CTRY_CD" = 'UK' AND "BR_CD" = '0654' AND    "ORG_ID" = 888;
      
      -- EXECUTE.IMMEDIATELY sql_stmt;

    END LOOP;
  END;

您可以使用以下 SQL 语句查看每个数据表中的内容

  SELECT * FROM "tblDATA_TEST"; 
  SELECT * FROM "tblMODIFY_TEST";                                                                                                          -- Content of the tblMODIFY_TEST table
  SELECT * FROM "tblMODIFY_TEST" WHERE "valid_dte" >= "data_dte" OR "valid_dte" IS NULL ORDER BY "mdfy_rank";                         -- We only want to process modify updates that are still valid, i.e. where the validation date is in the future, or is NULL

并且此 SQL 语句调用函数 fncMODIFY,该函数尚未执行任何操作,因为我仍在努力获取正确的语法,以便能够传递 fld_name/criteriafld# 字段值以便能够构造 WHERE 条件正确。

  SELECT "fncMODIFY" FROM DUAL;                                                                                                       -- Call the fuction

因此当前输出为:

  UPATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#>;

但希望是获得上面的示例 SQL 语句并实际执行它们。

任何帮助/指导将不胜感激,并在此先感谢您。

4

1 回答 1

0
  1. 您没有<criteriafield#>用实际的字段名称和值替换和值,因此它看起来不像您希望的那样。
  2. 你没有要求EXECUTE IMMEDIATE所以它没有被解雇,也没有任何反应。
  3. 您可以完全避免 PL/SQL(也可以避免您的数据类型函数)并通过在 all_tab_cols 上连接的单个 SQL 语句构建您需要的一切。然后将其提供给 PL/SQLEXECUTE IMMEDIATE并更新该表。如果您将其构建为 PL/SQL 块BEGIN...ENDEXECUTE IMMEDIATE对其执行操作,则无需将其拆分为多个语句,并且可以为记录目的创建可重现的文本(记录的代码块可以通过 SQLPlus 执行和调试)。

另外值得一提的是,您的动态 SQL 不处理 SQL 注入。

UPD:添加了没有游标和其他逐行处理内容的代码。最后,您将获得一个 PL/SQL 块,您需要将其传递给EXECUTE IMMEDIATE并更新您的表。

需要提及的几点:

  • 我没有使用带引号的标识符,因为它们会让你的生活在一天内变成一场噩梦。所以修改表的所有内容都tblMODIFY_TEST需要更新为大写。
  • 不要使用日期、时间戳和数字的区域格式,使用 ISO 格式,因为它是确定性的,任何人都可以读取,而无需猜测月份位置和组分隔符。
  • 对值实施一些检查以避免 SQL 注入。

您的数据是动态 SQL 问题的一个很好的例子,因为它已经包含无效的列名。

with unp as (
  select
    /*Turn criteria to rows, because in one day you can need more than 5*/
    mdfy_rank,
    tbl_name,
    fld_name,
    fld_value,
    criterianum,
    criteriafield,
    criteriavalue
  from tblmodify_test
  unpivot(
    (criteriafield, criteriavalue) for criterianum in (
      (criteriafld1, criteriavalue1) as 1,
      (criteriafld2, criteriavalue2) as 2,
      (criteriafld3, criteriavalue3) as 3,
      (criteriafld4, criteriavalue4) as 4,
      (criteriafld5, criteriavalue5) as 5
    )
  ) p
  where valid_dte >= data_dte or valid_dte is null
)
, prep as (
  select
    unp.tbl_name,
    unp.mdfy_rank,
    unp.fld_name,
    /*Build formatted field value*/
    case
      when atc_val.data_type like '%CHAR%'
      then '''' || fld_value || ''''
      when atc_val.data_type = 'DATE'
      then 'to_date(''' || fld_value || ''', ''dd.mm.yyyy'')'
      when atc_val.data_type in ('NUMBER', 'FLOAT', 'BINARY_FLOAT', 'BINARY_DOUBLE')
      then fld_value
    end as fld_value,
    atc_val.data_type as value_datatype,
    /*Build where clause*/
    listagg(criteriafield || ' = :wb' || criterianum, ' and ') within group (order by criterianum) as where_clause,
    /*Build formatted bind variables to get rid of infinite sequences of quotes*/
    listagg(
        /*Add quotes*/
        case
          when atc_where.data_type like '%CHAR%'
          then '''' || criteriavalue || ''''
          when atc_where.data_type = 'DATE'
          then 'to_date(''' || criteriavalue || ''', ''dd.mm.yyyy'')'
          when atc_where.data_type in ('NUMBER', 'FLOAT', 'BINARY_FLOAT', 'BINARY_DOUBLE')
          then criteriavalue
        end
      , ', ') within group (order by criterianum) as where_bind_vars
  from unp
    left join all_tab_cols atc_val
      on upper(unp.tbl_name) = atc_val.table_name
        and upper(unp.fld_name) = atc_val.column_name
    /*Check validity of columns and tables*/
    left join all_tab_cols atc_where
      on upper(unp.tbl_name) = atc_where.table_name
        and upper(unp.criteriafield) = atc_where.column_name
  group by
    tbl_name,
    mdfy_rank,
    fld_name,
    fld_value,
    atc_val.data_type
  order by
    tbl_name,
    mdfy_rank
)
select
  /*
  tbl_name,
  mdfy_rank,
  fld_name,
  fld_value,
  */
  /*Build final PL/SQL block of updates*/
  'BEGIN' || chr(10) || chr(9) ||
  listagg(
    'execute immediate ''update ' || upper(tbl_name)
      || ' set ' || fld_name || ' = :vb where '
      || where_clause || ''' using '
      || fld_value || ', ' || where_bind_vars || ';'
    , chr(10) || chr(9)
  ) within group(order by mdfy_rank)
  || chr(10) || 'END;' as stmt
from prep
于 2020-10-29T10:23:15.627 回答