cx_Oracle工具快速入门2-SQL执行

作者: python测试开发 | 来源:发表于2019-08-23 14:02 被阅读19次

    SQL执行

    执行SQL语句是Python应用程序与Oracle数据库通信的主要方式。 使用方法Cursor.execute()或Cursor.executemany()执行语句。 语句包括查询,数据操作语言(DML)和数据定义语言(DDL)。 还可以执行一些其他特殊语句。

    cx_Oracle可用于一次执行一个单独的语句。 读取SQL文件,请使用RunSqlScript()。SQL语句不应包含尾随分号(“;”)或正斜杠(“/”)。 这将失败:

    • 错误
    cur.execute("select * from MyTable;")
    
    
    • 正确
    
    cur.execute("select * from MyTable")
    

    SQL查询

    查询(以SELECT或WITH开头的语句)只能使用Cursor.execute()方法执行。 然后可以迭代行,或者可以使用Cursor.fetchone() , Cursor.fetchmany()或Cursor.fetchall()方法之一获取行。映射到Python类型的默认类型,并可以重载。

    使用SQL语句插入或连接用户数据,例如sql = 'SELECT * FROM mytab WHERE mycol = ' + myvar,有安全风险并影响性能。 请改用绑定变量 。 例如sql = 'SELECT * FROM mytab WHERE mycol = :mybv 。

    Fetch方法

    cur.execute可以返回游标。 可用代码迭代行,如:

    
    cur = connection.cursor()
    for row in cur.execute("select * from MyTable"):
        print(row)
    

    也可以使用Cursor.fetchone()方法一次一个地获取行:

    
    cur = connection.cursor()
    cur.execute("select * from MyTable")
    while True:
        row = cur.fetchone()
        if row is None:
            break
        print(row)
    

    如果需要批量处理行,可以使用方法Cursor.fetchmany() 。 批处理的大小由numRows参数Cursor.arraysize控制。

    
    cur = connection.cursor()
    cur.execute("select * from MyTable")
    numRows = 10
    while True:
        rows = cur.fetchmany(numRows)
        if not rows:
            break
        for row in rows:
            print(row)
    

    如果需要获取所有行,并且内存够大,则可以使用Cursor.fetchall()方法。

    cur = connection.cursor()
    cur.execute("select * from MyTable")
    rows = cur.fetchall()
    for row in rows:
        print(row)
    
    关闭游标

    游标可用于执行多个语句。 一旦不再需要它,应该通过调用close()来关闭它,以便回收数据库中的资源。 当引用它的变量超出范围(并且不保留其他引用)时,它将自动关闭。 另一种控制游标生命周期的方法是使用“with”块,这可以确保在块完成后关闭游标。 例如:

    with connection.cursor() as cursor:
        for row in cursor.execute("select * from MyTable"):
            print(row)
    

    此代码确保一旦块完成,就关闭游标并由数据库回收资源。 此外,任何在块外使用变量cursor尝试都将失败。

    性能调节

    为了获得最佳性能,应在调用Cursor.execute()之前设置cx_Oracle Cursor.arraysize值。 默认值为100.对于返回大量行的查询,增加arraysize可以提高性能,因为它减少了到数据库的往返次数。 但是,增加此值会增加所需的内存量。 系统的最佳值取决于网络速度,查询行大小和可用内存等因素。 通过试验您的应用程序可以找到合适的值。

    无论使用哪种获取方法获取行,内部所有行都是以与arraysize的值对应的批量获取的。 大小不会影响行返回应用程序的方式或时间(除了用作Cursor.fetchmany()的默认大小)。 它不限制查询返回的最小或最大行数。

    除了调整arraysize ,还要确保您的SQL语句是最佳的,并避免选择应用程序不需要的列。 对于不需要获取所有数据的查询,请使用行限制子句来减少数据库处理的行数。

    设置arraysize示例:

    cur = connection.cursor()
    cur.arraysize = 500
    for row in cur.execute("select * from MyTable"):
        print(row)
    

    增加arraysize大小特别有用的一个地方是将数据从一个数据库复制到另一个数据库:

    # setup cursors
    sourceCursor = sourceConnection.cursor()
    sourceCursor.arraysize = 1000
    targetCursor = targetConnection.cursor()
    targetCursor.arraysize = 1000
    
    # perform fetch and bulk insertion
    sourceCursor.execute("select * from MyTable")
    while True:
        rows = sourceCursor.fetchmany()
        if not rows:
            break
        targetCursor.executemany("insert into MyTable values (:1, :2)", rows)
        targetConnection.commit()
    

    如果想返回少量行,那么您应该减少arraysize的值。

    cur = connection.cursor()
    cur.arraysize = 1
    cur.execute("select * from MyTable where id = 1"):
    row = cur.fetchone()
    print(row)
    

    在cx_Oracle中,仅在第一次执行语句时检查arraysize值。 要更改重复语句的arraysize ,请创建一个新游标:

    array_sizes = (10, 100, 1000)
    for size in array_sizes:
        cursor = connection.cursor()
        cursor.arraysize = size
        start = time.time()
        cursor.execute(sql).fetchall()
        elapsed = time.time() - start
        print("Time for", size, elapsed, "seconds")
    

    参考资料

    查询列元数据

    执行查询后,可以使用Cursor.description获取列元数据(如列名和数据类型):

    cur = connection.cursor()
    cur.execute("select * from MyTable")
    for column in cur.description:
        print(column)
    

    这可能会产生如下元数据:

    ('ID', <class 'cx_Oracle.NUMBER'>, 39, None, 38, 0, 0)
    ('NAME', <class 'cx_Oracle.STRING'>, 20, 20, None, None, 1)
    
    获取数据类型

    下表提供了cx_Oracle知道如何获取的所有数据类型的列表。 中间列给出了查询元数据中返回的类型。 最后一列给出了默认返回的Python对象的类型。 可以使用输出类型处理程序更改Python类型。

    Oracle Database Type cx_Oracle Type Default Python type
    BFILE cx_Oracle.BFILE cx_Oracle.LOB
    BINARY_DOUBLE cx_Oracle.NATIVE_FLOAT float
    BINARY_FLOAT cx_Oracle.NATIVE_FLOAT float
    BLOB cx_Oracle.BLOB cx_Oracle.LOB
    CHAR cx_Oracle.FIXED_CHAR str
    CLOB cx_Oracle.CLOB cx_Oracle.LOB
    CURSOR cx_Oracle.CURSOR cx_Oracle.Cursor
    DATE cx_Oracle.DATETIME datetime.datetime
    INTERVAL DAY TO SECOND cx_Oracle.INTERVAL datetime.timedelta
    LONG cx_Oracle.LONG_STRING str
    LONG RAW cx_Oracle.LONG_BINARY bytes [4]
    NCHAR cx_Oracle.FIXED_NCHAR str [1]
    NCLOB cx_Oracle.NCLOB cx_Oracle.LOB
    NUMBER cx_Oracle.NUMBER float or int [2]
    NVARCHAR2 cx_Oracle.NCHAR str [1]
    OBJECT [5] cx_Oracle.OBJECT cx_Oracle.Object
    RAW cx_Oracle.BINARY bytes [4]
    ROWID cx_Oracle.ROWID str
    TIMESTAMP cx_Oracle.TIMESTAMP datetime.datetime
    TIMESTAMP WITH LOCAL TIME ZONE cx_Oracle.TIMESTAMP datetime.datetime [3]
    TIMESTAMP WITH TIME ZONE cx_Oracle.TIMESTAMP datetime.datetime [3]
    UROWID cx_Oracle.ROWID str
    VARCHAR2 cx_Oracle.STRING str
    使用输出类型处理程序更改提取的数据类型

    有时,必须更改从Oracle数据库类型到Python类型的默认转换,以防止数据丢失或符合Python应用程序的用途。 在这种情况下,可以指定输出类型处理程序。

    可以在connection或cursor上指定输出类型处理程序。 如果在游标上指定,则仅在该特定游标上更改获取类型处理。 如果在连接上指定,则由该连接创建的所有游标将更改其获取类型处理。

    输出类型处理程序应该是具有以下签名的函数:

    handler(cursor, name, defaultType, size, precision, scale)
    

    参数与Cursor.description的查询列元数据Cursor.description 。 对于要获取的每个列,将调用该函数一次。 该函数应返回一个变量对象 (通常通过调用Cursor.var() )或值None 。 值None表示应使用默认类型。

    输出处理程序的示例以获取数字精度和提取LOB作为字符串和字节显示 。

    提取数值精度

    使用输出类型处理程序的一个原因是确保在获取某些数字时不会丢失数字精度。 Oracle数据库使用十进制数字,这些数字无法无缝转换为二维数字表示形式,如Python浮点数。 此外,Oracle数字的范围超过了浮点数的范围。 Python具有没有这些限制的十进制对象,并且cx_Oracle知道如何执行Oracle数字和Python十进制值之间的转换。

    以下代码示例演示了此问题:

    cur = connection.cursor()
    cur.execute("create table test_float (X number(5, 3))")
    cur.execute("insert into test_float values (7.1)")
    connection.commit()
    cur.execute("select * from test_float")
    val, = cur.fetchone()
    print(val, "* 3 =", val * 3)
    

    这显示7.1 * 3 = 21.299999999999997

    使用Python decimal 对象不会损失精度:

    import decimal
    
    def NumberToDecimal(cursor, name, defaultType, size, precision, scale):
       if defaultType == cx_Oracle.NUMBER:
           return cursor.var(decimal.Decimal, arraysize=cursor.arraysize)
    
    cur = connection.cursor()
    cur.outputtypehandler = NumberToDecimal
    cur.execute("select * from test_float")
    val, = cur.fetchone()
    print(val, "* 3 =", val * 3)
    

    显示7.1 * 3 = 21.3

    使用Oracle编号的字符串表示调用Python decimal.Decimal转换器。 decimal.Decimal的输出在输出元组中返回。

    使用Outconverters更改查询结果

    cx_Oracle“outconverters”可以与输出类型处理程序一起使用来更改返回的数据。

    例如,要使查询返回空字符串而不是NULL:

    def OutConverter(value):
        if value is None:
            return ''
        return value
    
    def OutputTypeHandler(cursor, name, defaultType, size, precision, scale):
        if defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
            return cursor.var(str, size, cur.arraysize, outconverter=OutConverter)
    
    connection.outputtypehandler = OutputTypeHandler
    
    滚动游标

    可滚动游标使应用程序能够向后,向前移动,跳过行以及移动到查询结果集中的特定行。 结果集缓存在数据库服务器上,直到光标关闭。 相反,常规游标仅限于向前移动。

    通过在创建光标时设置参数scrollable=True来创建scrollable=True游标。 Cursor.scroll()方法用于移动到结果集中的不同位置。

    例如:

    cursor = connection.cursor(scrollable=True)
    cursor.execute("select * from ChildTable order by ChildId")
    
    cursor.scroll(mode="last")
    print("LAST ROW:", cursor.fetchone())
    
    cursor.scroll(mode="first")
    print("FIRST ROW:", cursor.fetchone())
    
    cursor.scroll(8, mode="absolute")
    print("ROW 8:", cursor.fetchone())
    
    cursor.scroll(6)
    print("SKIP 6 ROWS:", cursor.fetchone())
    
    cursor.scroll(-4)
    print("SKIP BACK 4 ROWS:", cursor.fetchone())
    
    限制行

    查询数据通常分为一组或多组:

    给出查询必须处理的行数的上限,这有助于提高数据库的可伸缩性。
    执行“Web分页”,允许从一组行移动到下一个或以前的行,按需设置。
    用于获取连续小集中的所有数据以进行批处理。 发生这种情况是因为记录数量太大,Python无法一次处理。
    后者可以通过一次执行SQL查询调用Cursor.fetchmany()来处理。

    本节讨论“Web分页”和限制最大行数。 对于每个“页面”结果,执行SQL查询以从表中获取适当的行集。 由于查询可能不止一次执行,因此请确保对行号和行限制使用绑定变量 。

    Oracle Database 12c SQL引入了OFFSET / FETCH子句,类似于MySQL的LIMIT关键字。 在Python中,您可以使用以下方法获取一组行:

    myoffset = 0       // do not skip any rows (start at row 1)
    mymaxnumrows = 20  // get 20 rows
    
    sql =
      """SELECT last_name
         FROM employees
         ORDER BY last_name
         OFFSET :offset ROWS FETCH NEXT :maxnumrows ROWS ONLY"""
    
    cur = connection.cursor()
    for row in cur.execute(sql, offset=myoffset, maxnumrows=mymaxnumrows):
        print(row)
    

    在事先不知道SQL查询的应用程序中,此方法有时涉及将OFFSET子句附加到“真实”用户查询。 要非常小心,以避免SQL注入安全问题。

    对于Oracle Database 11g及更早版本,有几种方法可以限制返回的行数。 旧的规范分页查询是:

     SELECT *
    FROM ( SELECT a .* , ROWNUM AS rnum
          FROM ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          WHERE ROWNUM <= MAX_ROW )
    WHERE rnum >= MIN_ROW
    

    这里, MIN_ROW是第一行的行号, MAX_ROW是要返回的最后一行的行号。 例如:

     SELECT *
    FROM ( SELECT a .* , ROWNUM AS rnum
          FROM ( SELECT last_name FROM employees ORDER BY last_name ) a
          WHERE ROWNUM <= 20 )
    WHERE rnum >= 1
    

    这总是有一个“额外”列,这里称为RNUM。

    Oracle Database 11g的替代和首选查询语法使用分析ROW_NUMBER()函数。 例如,要获取第1到第20个名称,查询是:

     SELECT last_name FROM
    ( SELECT last_name ,
            ROW_NUMBER () OVER ( ORDER BY last_name ) AS myr
            FROM employees )
    WHERE myr BETWEEN 1 and 20
    

    确保对上限和下限值使用绑定变量 。

    查询损坏的数据

    如果在您选择数据时查询失败并显示错误“codec无法解码字节”,则:

    检查你的字符集是否正确。 查看客户端和数据库字符集 。 如果合适,请考虑使用UTF-8:

    connection = cx_Oracle.connect("hr", userpwd, "dbhost.example.com/orclpdb1",
            encoding="UTF-8", nencoding="UTF-8")
    

    如果数据确实已损坏,您可以将选项传递给cx_Oracle使用的内部decode() ,以允许它被选中并防止整个查询失败。 通过创建outputtypehandler并设置encodingErrors 。 例如,替换字符列中的损坏字符:

    def OutputTypeHandler(cursor, name, defaultType, size, precision, scale):
        if defaultType == cx_Oracle.STRING:
            return cursor.var(defaultType, size, arraysize=cursor.arraysize,
                    encodingErrors="replace")
    
    cursor.outputtypehandler = OutputTypeHandler
    
    cursor.execute("select column1, column2 from SomeTableWithBadData")
    

    可以为encodingErrors选择其他编解码器行为,请参阅错误处理程序 。

    INSERT和UPDATE语句

    可以使用cx_Oracle轻松执行SQL数据操作语言语句(DML),如INSERT和UPDATE。 例如:

    cur = connection.cursor()
    cur.execute("insert into MyTable values (:idbv, :nmbv)", [1, "Fredico"])
    

    不要将用户数据连接或插入到SQL语句中。 请参阅使用绑定变量 。

    有关提交和回滚数据更改的最佳实践,请参阅事务管理 。

    处理多个数据值时,请使用executemany()来提高性能。 请参阅批处理语句执行和批量加载

    插入NULL

    Oracle需要一个类型,即使是空值。 传递值None时,cx_Oracle假定类型为STRING。 如果这不是所需的类型,您可以显式设置它。 例如,要插入空Oracle Spatial SDO_GEOMETRY对象:

    typeObj = connection.gettype("SDO_GEOMETRY")
    cur = connection.cursor()
    cur.setinputsizes(typeObj)
    cur.execute("insert into sometable values (:1)", [None])
    

    相关文章

      网友评论

        本文标题:cx_Oracle工具快速入门2-SQL执行

        本文链接:https://www.haomeiwen.com/subject/oodnsctx.html