如何使用Python中的匿名PL / SQL块从Oracle检索数据作为查询结果?

时间:2011-10-20 23:42:57

标签: python sql oracle cx-oracle

使用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查询的结果一样,我该怎么做?例如,如果我想处理DELETE ... RETURNING ... INTO SQL语句删除的行,就像我处理SELECT ... FROM ...的结果一样?

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

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

6 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:1)

Oracle中的“临时表”想法很糟糕。它们与SQL * Server或Sybase中的临时表不同。在Oracle中,表是PERMANENT;只有内容是暂时的。 所以你不应该编写一个需要动态创建/删除表的应用程序 - 在这种方法之后你会遇到各种各样的问题。

答案 2 :(得分:1)

使用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调用产生一条记录!

答案 3 :(得分:0)

您的要求是:

  • 我不想修改数据库的架构
  • 或创建任何存储过程 ... 特别是,我不想使用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()

返回: [('test','this')]

Oracle Global Temporary Table Docs此处。

答案 4 :(得分:0)

您可以在匿名块中使用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了解更多详情。

答案 5 :(得分:-1)

你可以通过两种方式实现: 1.不便携 - 写入全球背景。 2.便携式 - 写入临时表。