【问题标题】:How do I retrieve data from Oracle as a query result using an anonymous PL/SQL block in Python?如何使用 Python 中的匿名 PL/SQL 块从 Oracle 检索数据作为查询结果?
【发布时间】:2011-10-20 23:42:56
【问题描述】:

我能找到的所有使用 PL/SQL 的示例最终都是这样的 (this example taken from Wikipedia):

FOR RecordIndex IN (SELECT person_code FROM people_table)
LOOP
  DBMS_OUTPUT.PUT_LINE(RecordIndex.person_code);
END LOOP;

换句话说,它们都有一个共同点:当需要将数据实际输出到某个地方时,它们会将其粘贴在 DBMS_OUTPUT 中,这对于应用程序来说似乎是一个非常无用的地方来处理它。

如果我想使用 PL/SQL 功能从 Oracle 数据库中检索数据,就好像这些数据是普通 SQL 查询的结果一样,我该怎么做?例如,如果我想以与处理SELECT ... FROM ... 的结果相同的方式处理被DELETE ... RETURNING ... INTO SQL 语句删除的行?

我不想修改数据库的架构或创建任何存储过程;我只想做cursor.execute("begin; ... something; end"); results = cursor.fetchall()

我确实想用cursor.var() 创建一个变量,因为该API 不能在数据库实现之间移植。 (显然 SQL 也不能移植,但人们普遍接受的事实是,无论如何都需要为不同的数据库后端生成自定义 SQL 字符串。)

【问题讨论】:

    标签: python sql oracle cx-oracle


    【解决方案1】:

    使用 Oracle 12c,您将能够定义一个临时 PL/SQL 函数在 SELECT 语句中使用它:

    WITH FUNCTION x(param)
      <body>
    END x;
    SELECT x(p) FROM t
    

    这是一个 SQL 语句,您可以以通常的方式从中获取行的选择。 不幸的是,Oracle 12c 还没有发布...

    【讨论】:

    • 我仍然对旧版本的 Oracle 感兴趣,但很高兴知道它即将到来。
    【解决方案2】:

    Oracle 中的“临时表”想法很糟糕。它们不像 SQL*Server 或 Sybase 中的临时表。在 Oracle 中,表是 PERMANENT;只有内容是临时的。 因此,您不应该编写需要动态创建/删除表的应用程序 - 采用这种方法会遇到各种问题。

    【讨论】:

      【解决方案3】:

      使用 OCI,您只能检索简单类型的表,而不是记录表

      使用 cx_Oracle (Python):

      cx = cx_Oracle.connect(dsn)
      cu = cx.cursor()
      dates = cu.var(cx_Oracle.DATE, 100)
      cu.execute("""
      DECLARE
        TYPE date_tab_typ IS TABLE OF DATE INDEX BY PLS_INTEGER;
        v_dates date_tab_typ;
      BEGIN
        SELECT SYSDATE-ROWNUM BULK COLLECT INTO v_dates
          FROM user_objects
          WHERE ROWNUM < 100;
        :1 := v_dates;
      END;
      """, [dates])
      
      dates = [dates.getvalue(i+1) for i in xrange(100)]
      

      我编写了一个库,它围绕存储的函数/过程生成此类匿名 PL/SQL 块,以便能够使用记录类型的数组(您需要为记录中的每列创建一个数组)- 有效,但不是很好.

      或者,如果您不能使用游标作为返回类型(因为您不能将记录生成为选择),那么您可以编写 PIPELINED 存储函数:它们像 Python 生成器一样工作,每次 PIPE 调用生成一条记录!

      【讨论】:

        【解决方案4】:

        您的要求是:

        • 我不想修改数据库的架构
        • 或创建任何存储过程 ... 特别是,我不想使用 cursor.var() 创建变量 (不幸的是,存储过程是通常的方式去做)

        您还在评论中询问了如何创建一个将在事务结束时销毁的临时表,我会给您一个示例(因为我认为这是唯一可以满足您的要求)。

        [通常需要注意的是这不是处理错误或防弹,但如果你真的想这样做,这应该让你开始]

        import cx_Oracle
        
        def main():
            block = """
                BEGIN
                    -- you could put your PL/SQL in here, 
                    -- to insert values into this table
                    INSERT INTO some_table (col1, col2) VALUES ('test', 'this');
                END;
            """
        
            conn = cx_Oracle.connect("User/password@somewhere")
            curs = conn.cursor()
            conn.begin()
            curs.execute("""CREATE GLOBAL TEMPORARY TABLE some_table (
                                col1 VARCHAR2(10), 
                                col2 VARCHAR2(10)
                            ) ON COMMIT PRESERVE ROWS""")
            curs.execute(block)
            curs.execute('SELECT * FROM some_table')
            print(curs.fetchall())
            curs.execute('TRUNCATE TABLE some_table')
            curs.execute('DROP TABLE some_table')
            conn.commit()
        
        if __name__ == '__main__':
            main()
        

        返回: [('测试', '这个')]

        Oracle Global Temporary Table Docs 在这里。

        【讨论】:

        • 我的理解是 DDL 语句(如“创建表”)在 Oracle 中隐式提交事务,因此我无法即时创建这些。我真的只是想要每个事务(或者更好的是,每个光标)状态来促进发送这些行。
        【解决方案5】:

        您可以使用匿名块中的 DBMS_SQL 包来描述您的查询

            q="""DECLARE
          c           NUMBER;
          d           NUMBER;
          col_cnt     INTEGER;
          f           BOOLEAN;
          rec_tab     DBMS_SQL.DESC_TAB;
          col_num    NUMBER;
          v_sql dbms_sql.varchar2a;
         v_sql_1 varchar2(32767);
         v_sql_2 varchar2(32767);
         v_sql_3 varchar2(32767);
         v_sql_4 varchar2(32767);
          v_type VARCHAR2(32):='';
          PROCEDURE print_rec(rec in DBMS_SQL.DESC_REC) IS
          BEGIN
            v_type:=CASE rec.col_type
                        WHEN 1 THEN 'VARCHAR2'
                        WHEN 12 THEN 'DATE'
                        WHEN 2 THEN 'NUMBER'
                    ELSE ''||rec.col_type
                    END;
            DBMS_OUTPUT.PUT_LINE(rec.col_name||':'||rec.col_max_len||':'||v_type);
          END;
        BEGIN
          v_sql(1):='%s';
          v_sql(2):='%s';
          v_sql(3):='%s';
          v_sql(4):='%s';
          v_sql(5):='%s';
          c := DBMS_SQL.OPEN_CURSOR;
          DBMS_SQL.PARSE(c, v_sql,1,5,False, DBMS_SQL.NATIVE);
          d := DBMS_SQL.EXECUTE(c);
          DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
        /*
         * Following loop could simply be for j in 1..col_cnt loop.
         * Here we are simply illustrating some of the PL/SQL table
         * features.
         */
          col_num := rec_tab.first;
          IF (col_num IS NOT NULL) THEN
            LOOP
              print_rec(rec_tab(col_num));
              col_num := rec_tab.next(col_num);
              EXIT WHEN (col_num IS NULL);
            END LOOP;
          END IF;
          DBMS_SQL.CLOSE_CURSOR(c);
        END;
        /
        """ % (qry[0:32000].replace("'","''"),qry[32000:64000].replace("'","''"),qry[64000:96000].replace("'","''"),qry[96000:128000].replace("'","''"),qry[128000:160000].replace("'","''"))
            regexp=re.compile(r'([\w\_\:\(\)\d]+)')
        

        在这一步之后,您可以创建 SQL 语句并使用 SQL*Plus 运行提取

        查看extractor.py了解更多详情。

        【讨论】:

          【解决方案6】:

          您可以通过 2 种方式做到这一点: 1.不可移植-写入全局上下文。 2. 便携-写入临时表。

          【讨论】:

          • 请举例说明。如何写入全局上下文?如何创建将在事务结束时销毁的临时表?
          • 就此而言,“全局上下文”是什么?当您说“不可移植”时,它不能移植到什么地方?我只是在这里询问甲骨文;在每个其他数据库中,我只需发出一个“选择”,结果就会显示在应用程序的光标上。
          猜你喜欢
          • 1970-01-01
          • 2014-09-30
          • 2018-07-19
          • 1970-01-01
          • 1970-01-01
          • 2011-01-10
          • 1970-01-01
          • 2014-11-28
          • 2013-12-29
          相关资源
          最近更新 更多