如何在Python中使用变量来编写SQL语句?

133

我有下面这段 Python 代码:

cursor.execute("INSERT INTO table VALUES var1, var2, var3,")

其中var1是整数,var2var3是字符串。

我该如何在Python代码中将这些变量名写入而不让它们成为查询文本的一部分?

6个回答

157
cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

需要注意的是,参数被作为一个元组 (a, b, c) 传递。如果你只传递一个参数,该元组需要以逗号结尾,如:(a,)

数据库API会对变量进行适当的转义和引用。请小心不要使用字符串格式化操作符(%),因为:

  1. 它不会进行任何转义或引用操作。
  2. 它容易导致无法控制的字符串格式攻击,例如 SQL注入

11
请再仔细阅读一遍。不安全的是使用字符串格式化运算符“%”。实际上,我在答案中已经这么说了。 - Ayman Hourieh
我的错..我想象了一个%而不是,在字符串和变量之间..由于各种原因,我无法撤销我的投票..我个人希望在描述中提到像不安全/攻击等词语,其中你说不要使用%.. - Kashyap
你可以更新一下你的回答吗?我使用了“?”而不是“%s”,因为后者会抛出一个sqlite3错误。 - user3137329
3
因为回答中表示不要使用“%”但又使用了三次,“被点踩”。更多解释会更好。 - eric
10
答案中指出不要使用“%”运算符来格式化字符串。这些字符串中的“%”直接由“cursor.execute”使用,由于它知道它正在生成SQL语句,因此可以做更多的保护工作。请注意,不要改变原文的含义。 - Mark Ransom
显示剩余4条评论

89

Python的不同DB-API实现允许使用不同的占位符,因此您需要找出您正在使用哪个占位符——例如,在使用MySQLdb时可能为:

cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

或者(例如使用Python标准库中的sqlite3):

cursor.execute("INSERT INTO table VALUES (?, ?, ?)", (var1, var2, var3))
或者其他方式(在 VALUES 后,你可以使用 (:1, :2, :3) 或 "命名风格" (:fee, :fie, :fo)(%(fee)s, %(fie)s, %(fo)s),在第二个参数中传递字典而不是映射给 execute)。查看您正在使用的 DB API 模块中的paramstyle字符串常量,并查看 http://www.python.org/dev/peps/pep-0249/ 上的 paramstyle,以了解所有参数传递样式!


68
许多方法。在实际代码中不要使用最明显的方法(使用%%s),它容易受到攻击
这里是从sqlite3的pydoc复制粘贴的内容:

......要注意,不要使用Python的字符串操作来组装查询,因为它们容易受到SQL注入攻击的影响。例如,攻击者可以简单地关闭单引号并注入OR TRUE以选择所有行:

# Never do this -- insecure!
symbol = input()

sql = "SELECT * FROM stocks WHERE symbol = '%s'" % symbol
print(sql)

cur.execute(sql)

如果您需要更多示例:

# Multiple values single statement/execution
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', ('RHAT', 'MSO'))
print c.fetchall()
c.execute('SELECT * FROM stocks WHERE symbol IN (?, ?)', ('RHAT', 'MSO'))
print c.fetchall()
# This also works, though ones above are better as a habit as it's inline with syntax of executemany().. but your choice.
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', 'RHAT', 'MSO')
print c.fetchall()
# Insert a single item
c.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', ('2006-03-28', 'BUY', 'IBM', 1000, 45.00))

12
一些DB-API的实现实际上使用%s作为它们的变量,尤其是PostgreSQL的psycopg2。这不应与在字符串替换中使用%操作符的%s混淆(尽管很容易出现混淆)。为了可移植性,如果我们能够有一个定义明确的标准方式来指定DB-API的SQL参数就太好了。 - ThatAintWorking

28

http://www.amk.ca/python/writing/DB-API.html

在将变量的值附加到语句中时要小心:
想象一下一个用户给自己取名为';DROP TABLE Users;'-- 这就是为什么在使用cursor.execute时需要使用SQL转义,而Python会在你以适当的方式使用它时提供这种转义。示例在URL中:

cursor.execute("insert into Attendees values (?, ?, ?)", (name, seminar, paid))

14
实际上,这不是SQL转义。 这是变量绑定,它更简单直接。 解析后,值被绑定到SQL语句中,使其免疫任何注入攻击。 - S.Lott
1
无论是 SQL 转义还是变量绑定,都取决于你的数据库服务器/DB-API 驱动程序的好坏。我见过一些真实世界中广泛部署的生产数据库,它们的 DB-API 驱动程序只做转义,而不是在传输数据和代码时将其分离。不用说,我对那些所谓的“数据库”没有多少尊重。 - Charles Duffy

7

对于缺乏经验的Python用户来说,提供单个值的语法可能会让人感到困惑。

给定查询

INSERT INTO mytable (fruit) VALUES (%s)

通常情况下,传递给cursor.execute的值必须被包装在一个有序序列中,例如元组列表,即使该值本身是单例,因此我们必须提供一个单元素元组,就像这样:(value,)

cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple',))

传递单个字符串
cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple'))

如果出现以下情况,将会导致错误,具体错误类型取决于DB-API连接器:

  • psycopg2:

    TypeError: 在字符串格式化期间未转换所有参数

  • sqlite3

    sqlite3.ProgrammingError: 提供的绑定数量不正确。当前语句使用1个,但提供了5个

  • mysql.connector

    mysql.connector.errors.ProgrammingError: 1064 (42000): SQL语法存在错误;


* pymysql连接器可以处理单个字符串参数而不出错。但是,最好将字符串包装在元组中,即使它只是一个单独的字符串,因为

  • 如果您切换连接器包,您不需要更改代码
  • 您保持查询参数始终是对象序列而不是单个对象的一致性心理模型。

0

将您的数据加载为自动归一化表,我建议使用这个库来推断模式、类型化数据,并支持模式演化 https://pypi.org/project/dlt/

您甚至可以使用这个库在结构化数据之后执行 upsert 操作,下面是一个示例,其中我们使用 JSON 中的 ID 来更新生成的目标 SQL 表

data = [{'id': 1, 'name': 'John'}]

# open connection
pipe = dlt.pipeline(destination='postgres',
                    dataset_name='raw_data')

# Upsert/merge: Update old records, insert new
# Capture the outcome in load info
load_info = pipe.run(data,
                      write_disposition="merge",
                      primary_key="id",
                      table_name="users")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接