在 PL/SQL 过程中打开动态表名的游标

Posted

技术标签:

【中文标题】在 PL/SQL 过程中打开动态表名的游标【英文标题】:Open cursor for dynamic table name in PL/SQL procedure 【发布时间】:2018-08-20 10:31:32 【问题描述】:

我想创建一个使用游标的过程,这对于任意表都是一样的。我现在的样子是这样的:

create or replace
  PROCEDURE
    some_name(
      p_talbe_name IN VARCHAR2,
      p_chunk_size IN NUMBER,
      p_row_limit IN NUMBER
    ) AS

  CURSOR v_cur IS
     SELECT common_column,
       ora_hash(substr(common_column, 1, 15), p_chunk_size) as chunk_number
     -- Here it can find the table!
     FROM p_table_name;

  TYPE t_sample IS TALBE OF v_cur%rowtype;
  v_sample t_sample;
BEGIN
  OPEN v_cur;
  LOOP FETCH v_cur BULK COLLECT INTO v_sample LIMIT p_row_limit;
    FORALL i IN v_sample.first .. v_sample.last
    INSERT INTO chunks VALUES v_sample(i);
    COMMIT;
    EXIT WHEN v_cur%notfound;
  END LOOP;
  CLOSE v_cur;
END;

问题是它找不到我想要参数化的名为p_table_name 的表。问题是我需要基于存在于所有预期表中的common_column 的散列创建块。如何处理这个问题?也许有等效的 oracle 代码可以做同样的事情?然后我需要同样的查询效率。谢谢!

【问题讨论】:

为什么在将数据插入表之前循环并将数据拉入数组,而不仅仅是动态插入选择?这是某种家庭作业,您在实现目标的方式上受到人为限制,还是有其他原因? @Boneist 不,我对解决方案没有任何限制。我只需要一个高效的。这段代码实际上是更大的 sql 的一部分,我试图通过提取可重用的过程来重构它。 【参考方案1】:

我会将其作为一个单独的 insert-as-select 语句来执行,只是因为您传入 table_name 的事实而变得复杂,因此我们需要使用动态 sql。

我会这样做:

CREATE OR REPLACE PROCEDURE some_name(p_table_name IN VARCHAR2,
                                      p_chunk_size IN NUMBER,
                                      p_row_limit  IN NUMBER) AS

  v_table_name VARCHAR2(32); -- 30 characters for the tablename, 2 for doublequotes in case of case sensitive names, e.g. "table_name"

  v_insert_sql CLOB;
BEGIN
  -- Sanitise the passed in table_name, to ensure it meets the rules for being an identifier name. This is to avoid SQL injection in the dynamic SQL
  -- statement we'll be using later.
  v_table_name := DBMS_ASSERT.ENQUOTE_LITERAL(p_table_name);

  v_insert_sql := 'insert into chunks (common_column_name, chunk_number)'||CHR(10)|| -- replace the column names with the actual names of your chunks table columns.
                  'select common_column,'||CHR(10)||
                  '       ora_hash(substr(common_column, 1, 15), :p_chunk_size) AS chunk_number'||CHR(10)||
                  'from   '||v_table_name||CHR(10)||
                  'where  rownum <= :p_row_limit';

  -- Used for debug purposes, so you can see the definition of the statement that's going to be run.
  -- Remove before putting the code in production / convert to proper logging code:
  dbms_output.put_line(v_insert_sql);

  -- Now run the statement:
  EXECUTE IMMEDIATE v_insert_sql USING p_chunk_size, p_row_limit;

  -- I've included the p_row_limit in the above statement, since I'm not sure if your original code loops through all the rows once it processes the
  -- first p_row_limit rows. If you need to insert all rows from the p_table_name into the chunks table, remove the predicate from the insert sql and the extra bind variable passed into the execute immediate.
END some_name;
/

通过使用单个 insert-as-select 语句,您正在使用最有效的工作方式。执行批量收集(您正在使用)会耗尽内存(将数据存储在数组中)并导致 PL/SQL 和 SQL 引擎之间的额外上下文切换,而 insert-as-select 语句可以避免。

【讨论】:

您确定这个insert into 语句的行为方式与带有bulk insert 的游标相同吗? 非常确定,但您可以自行测试,因为您没有提供完整的测试用例供我们使用。注:我可能读错了您的原始代码;您可能正在遍历所有行,在这种情况下,您不需要动态 insert-as-select 中的谓词,也不需要 p_row_limit 参数。 近两三年才遇到过;我认为它非常漂亮 *:-) @Boneist 我发现我们需要 row_limit,因为我们已经用完了UNDO 空间。这就是为什么需要带限制的光标的原因。您认为我们还有其他选择吗? 这是定期运行的东西吗?如果是这样,我强烈建议相应地调整撤消表空间的大小,而不是尝试解决它。​​

以上是关于在 PL/SQL 过程中打开动态表名的游标的主要内容,如果未能解决你的问题,请参考以下文章

存储过程的 PL/SQL 动态表名

PL/SQL:clob 字符串中的动态查询。如何打开游标?

mysql 存储过程 动态表名

PL/SQL - 动态访问光标数据

PL SQL - 使用动态 SQL 生成删除语句

PL/SQL 动态表名