【问题标题】:stored procedure with dynamic column name具有动态列名的存储过程
【发布时间】:2015-12-12 17:54:32
【问题描述】:

我正在尝试打印我们不知道列名的多表行。我从一天开始就在尝试这个,我想出了这个程序,但出现错误“必须声明 emp_dummy_col”。请帮帮我。 谢谢。

create or replace procedure sp_display
as
   CURSOR cur_emp is select EMP_ID,EMP_NAME,DEPT_IT,DOJ,LOCATION from employee;
   emp_rows cur_emp%rowtype;
   type emp_table is table of emp_rows%type;
   emp_dummy_table emp_table;
   CURSOR cur_col is select column_name from user_tab_cols where table_name ='EMPLOYEE';
   emp_row cur_col%rowtype;
   type emp_table1 is table of emp_row%type;
   emp_dummy_col emp_table1;
begin
   open cur_emp;
      fetch cur_emp bulk collect into emp_dummy_table;
   close cur_emp;
   open cur_col;
      fetch cur_col bulk COLLECT into emp_dummy_col; 
   close cur_col;
   for i in 1..emp_dummy_table.count
   loop
      for j in 1..emp_dummy_col.count
      loop
         DBMS_OUTPUT.PUT_LINE(emp_dummy_table(i).emp_dummy_col(j));
      end loop;
   end loop;
end;

【问题讨论】:

  • 你真正想做什么?动态访问记录中的字段会很痛苦——您必须在字符串变量中动态构建整个 PL/SQL 块。我的猜测是,无论你真正想要完成什么,使用动态 SQL 比使用动态 PL/SQL 更容易。
  • 所以可以做到。谢谢
  • 您知道主 cur_emp 查询中的列 - 您明确选择了 EMP_ID 和其他四个。为什么不明确地提及它们呢?如果您没有选择其他列,这些列将包含在您的 cur_col 查询中,但 emp_dummy_table 中将没有匹配字段,那么您希望这些列会发生什么?真的不清楚你为什么要这样做,而不仅仅是在 SQL 中查询表。
  • 好的,@Alex Poole 明白了,但我不会选择特定列,而是将其设为 *,如果我想动态更改表名怎么办?上面的程序只是一个实验。

标签: plsql oracle11g


【解决方案1】:

正如 Justin Cave 所说,您可以使用包 DBMS_SQL 完成大量工作。

以下只是一个简单的例子。

SET SERVEROUTPUT ON
SET FEEDBACK OFF
CLEAR

DECLARE
  -- The SQL Statement
  V_SQL VARCHAR2(4000) := 'select EMP_ID,EMP_NAME,DEPT_IT,DOJ,LOCATION from employee';
  -- The cursor (number) variable
  V_CURSOR INTEGER;
  -- Variable to hold the return value of DBMS_SQL.EXECUTE (Number of processed rows)
  V_NUM_ROWS NUMBER;
  -- Variable to hold the return value of DBMS_SQL.DESCRIBE_COLUMNS
  V_COL_CNT INTEGER;
  -- Column description table. Outcome from DBMS_SQL.DESCRIBE_COLUMNS
  V_DESC DBMS_SQL.DESC_TAB;
  -- Variable for the current column number when iterating over the column description collection
  V_COL_NUM NUMBER;
  -- (generic) Variable for a VARCHAR2-Column
  V_VARC_COL VARCHAR2(4000);
  -- (generic) Variable for a NUMBER-Column
  V_NUM_COL NUMBER;
BEGIN

  -- Open a cursor
  V_CURSOR := DBMS_SQL.OPEN_CURSOR;
  -- Parse the SQL-Query (without any bindings etc.)
  DBMS_SQL.PARSE(V_CURSOR, V_SQL, DBMS_SQL.NATIVE);

  -- Execute the parsed query and return the number of processed rows. 
  -- May be 0 or undefined on SELECT- and DDL-Statements and should be ignored
  V_NUM_ROWS := DBMS_SQL.EXECUTE(V_CURSOR);

  -- Get the columns description table
  DBMS_SQL.DESCRIBE_COLUMNS(V_CURSOR, V_COL_CNT, V_DESC);

  V_COL_NUM := V_DESC.FIRST;

  IF (V_COL_NUM IS NOT NULL) THEN
    LOOP
      -- Assign variables to column types
      CASE V_DESC(V_COL_NUM).COL_TYPE
        WHEN DBMS_SQL.NUMBER_TYPE THEN
          DBMS_SQL.DEFINE_COLUMN(V_CURSOR, V_COL_NUM, V_NUM_COL);
        WHEN DBMS_SQL.VARCHAR2_TYPE THEN
          DBMS_SQL.DEFINE_COLUMN(V_CURSOR, V_COL_NUM, V_VARC_COL, 4000);
          -- more branches if other column types are expected/supported (Date, LOBs etc.)
        ELSE
          NULL;
      END CASE;
      V_COL_NUM := V_DESC.NEXT(V_COL_NUM);
      EXIT WHEN(V_COL_NUM IS NULL);
    END LOOP;

    -- Fetch the rows
    V_NUM_ROWS := DBMS_SQL.FETCH_ROWS(V_CURSOR);
    IF (V_NUM_ROWS > 0) THEN
      LOOP
        -- Column handling
        FOR L_I IN V_DESC.FIRST .. V_DESC.LAST LOOP
          CASE V_DESC(L_I).COL_TYPE
          -- NUMBER column type value handler
            WHEN DBMS_SQL.NUMBER_TYPE THEN
              DBMS_SQL.COLUMN_VALUE(V_CURSOR, L_I, V_NUM_COL);
              V_VARC_COL := TO_CHAR(V_NUM_COL);
              -- VARCHAR2 column type value handler
            WHEN DBMS_SQL.VARCHAR2_TYPE THEN
              DBMS_SQL.COLUMN_VALUE(V_CURSOR, L_I, V_VARC_COL);
              -- more branches if other column types are expected/supported (Date, LOBs etc.)
            ELSE
              --Handling for unsupported column types (if necessary)
              NULL;
          END CASE;
          DBMS_OUTPUT.PUT(V_DESC(L_I).COL_NAME || '" = "' || V_VARC_COL || '"');
          IF (L_I < V_DESC.COUNT) THEN
            DBMS_OUTPUT.PUT('  |  ');
          END IF;
        END LOOP;
        DBMS_OUTPUT.PUT_LINE('');
        -- Fetch the next row
        V_NUM_ROWS := DBMS_SQL.FETCH_ROWS(V_CURSOR);
        -- Exit loop if no more row is fetched
        EXIT WHEN V_NUM_ROWS = 0;
      END LOOP;
    END IF;
  END IF;
  -- Close the cursor (important!)
  DBMS_SQL.CLOSE_CURSOR(V_CURSOR);

EXCEPTION
  WHEN OTHERS THEN
    -- Close the cursor (important!) if any error occurs
    IF (DBMS_SQL.IS_OPEN(V_CURSOR)) THEN
      DBMS_SQL.CLOSE_CURSOR(V_CURSOR);
    END IF;
    -- Rethrow the exception
    RAISE;
END;
/

关于 DBMS_SQL 的详细/进一步信息:

!!安全警告!!

使用这种动态 SQL 容易受到 SQL 注入和其他技术的攻击!

【讨论】:

    【解决方案2】:

    嗯,您拥有的代码示例并没有真正按照您的要求进行。它希望您告诉列,请参阅:

    从员工中选择 EMP_ID、EMP_NAME、DEPT_IT、DOJ、LOCATION;

    1)您可以像这样获取表的未知列(我在此示例中使用表 NEWS)

    begin
        for rec in
               (select column_name from user_tab_cols
               where table_name='NEWS') loop
            dbms_output.put_line(rec.column_name);
        end loop;
    end;
    

    2) 如果你想打印出数据,那在 PLSQL 中是非常讨厌的,见Cursor For Loop with dynamic SQL-Statement

    3) 如果您擅长妥协,请尝试这种方法。我根据从 user_tab_cols 中学到的字段构建动态 SQL 语句。动态 SQL 将每列的字符串值连接成一行,最后我在此上获取一个游标。

    为方便起见,我还收集了 header 变量中的列名。

    declare
        statement varchar2(4000);
        header varchar2(4000);
        TYPE TCur IS REF CURSOR;
        cur TCur;
        TYPE TRec IS RECORD (
          line varchar2(4000)
        );
        rec TRec;
    begin
    
        for rec in
               (select column_name from user_tab_cols
               where table_name='NEWS') loop
            if statement is not null then
                statement:=statement||'||'',''||';
                header:=header||',';
            end if;
            statement:=statement||rec.column_name;
            header:=header||rec.column_name;
        end loop;
        statement:='select '||statement||' as line from '||'NEWS';
        dbms_output.put_line(header);
        open cur for statement;
        loop
            fetch cur into rec;
            exit when cur%notfound;
            dbms_output.put_line(rec.line);
        end loop;
        close cur;
    end;
    

    给出如下:

    NEWS_ID,NEWS
    0,Some new PLSQL hack has been done
    1,And the day is not over yet!
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 2017-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多