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

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])
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容