将参数传递给 DB .execute 以获取 WHERE IN... INT 列表
Posted
技术标签:
【中文标题】将参数传递给 DB .execute 以获取 WHERE IN... INT 列表【英文标题】:Passing param to DB .execute for WHERE IN... INT list 【发布时间】:2011-01-16 05:53:47 【问题描述】:使用 Python 的 DB API 规范,您可以将参数的参数传递给 execute() 方法。我的部分语句是 WHERE IN 子句,我一直在使用元组来填充 IN。例如:
params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)
但是当我遇到参数元组只是一个元组的情况时,执行失败。
ProgrammingError:错误:“)”处或附近的语法错误 第 13 行:id 在 (3,) 中的位置
我怎样才能让元组正确地使用子句?
【问题讨论】:
【参考方案1】:编辑:如果您认为此答案绕过了针对 SQL 注入攻击的内置保护措施,那您就错了;仔细看看。
使用pg8000(与 DB-API 2.0 兼容的 Pure-Python 接口到 PostgreSQL 数据库引擎)进行测试:
这是将多个参数传递给“IN”子句的推荐方式。
params = [3,2,1]
stmt = 'SELECT * FROM table WHERE id IN (%s)' % ','.join('%s' for i in params)
cursor.execute(stmt, params)
完整示例:
>>> from pg8000 import DBAPI
>>> conn = DBAPI.connect(user="a", database="d", host="localhost", password="p")
>>> c = conn.cursor()
>>> prms = [1,2,3]
>>> stmt = 'SELECT * FROM table WHERE id IN (%s)' % ','.join('%s' for i in prms)
>>> c.execute(stmt,prms)
>>> c.fetchall()
((1, u'myitem1'), (2, u'myitem2'), (3, u'myitem3'))
【讨论】:
如果我错了,请纠正我,但是您的示例不是仅将第一项传递给 IN 子句吗? > SELECT * FROM table WHERE id IN (3) 这个答案是非常错误的!在 Python 代码中自己替换参数,而不是让数据库驱动程序来做,这是一个称为“SQL 注入”的安全漏洞。想象一下,如果列表中的一项是字符串"); DROP TABLE table; --"
。
防止 SQL 注入不是您应该添加到允许 SQL 注入的代码中的东西。你很少会做对。相反,您不应该编写允许 SQL 注入的代码。
@rspeer:这不是在 Python 代码中替换参数,而是在 Python 代码中生成占位符,然后让 DB-API 对这些占位符进行替换。
如果没有理解,可能不太容易误解:['%s'] * len(params)
。【参考方案2】:
错误来自 3 之后的逗号。只需将其保留为单个值即可。
params = ((3), ... )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)
【讨论】:
是的,我知道错误发生的原因,但我没有构建元组。元组由另一个 SQL 结果填充。所以顺便说一下,单项元组保留了一个悬挂的逗号。 我还想指出,单个项目元组必须有一个尾随逗号。 啊,我误会了。那么在这种情况下,您可以使用 len() 来获取元组的长度,如果它是一个,则使用 tuple[0] 来提取不带逗号的值。【参考方案3】:这可能不能完全回答您提出的问题,但我认为它可能会解决您遇到的问题。
Python 的 DB-API 似乎没有为您提供将元组作为安全替换参数传递的方法。 bernie 接受的答案是使用 Python %
运算符进行替换,这是不安全的。
但是,您可能不必将元组作为参数传递,尤其是当您想要的元组是另一个 SQL 查询的结果时(正如您向 Daniel 指出的那样)。相反,您可以使用 SQL 子查询。
如果您希望在 IN 子句中的 ID 集是 SELECT id FROM other_table WHERE use=true
的结果,例如:
stmt = "SELECT * FROM table WHERE id IN (SELECT id FROM other_table WHERE use=true)"
db.execute(stmt)
这也可以参数化(安全方式)。如果您要选择的 ID 是具有给定 parent_id
的 ID:
stmt = "SELECT * FROM table WHERE id IN (SELECT id FROM other_table WHERE parent_id=%s)"
params = (parent_id,)
db.execute(stmt, params)
【讨论】:
【参考方案4】:接受的答案有 SQL 注入的风险;您永远不应该将用户输入直接传递给数据库。相反,使用正确数量的占位符生成一个查询,然后让 pg8000 进行转义:
params = [3,2,1]
# SELECT * from table where id in (%s,%s,%s)
stmt = 'SELECT * FROM table WHERE id IN ()'.format(','.join(['%s']*len(params)))
cursor.execute(stmt, tuple(params))
【讨论】:
这和接受的答案不一样吗?它还使用正确数量的占位符生成查询,而不是将参数直接替换到查询字符串中。【参考方案5】:使用 f-string 的解决方案。
params = [...]
stmt = f"SELECT * FROM table WHERE id IN (','.join(['%s']*len(params ),))"
db.execute(stmt, params)
如果有另一个参数占位符,它将是这样的
age = 18
params = [...]
stmt = f"SELECT * FROM table WHERE age>%s AND id IN (','.join(['%s']*len(params ),))"
db.execute(stmt, tuple([age] + params))
【讨论】:
【参考方案6】:正如问题所说,以下将失败:
params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)
在 pg8000 docs 之后,IN
可以替换为 ANY()
以获得相同的结果:
params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id = ANY(%s)"
db.execute(stmt, params)
这会将查询和参数分别发送到服务器,避免 SQL 注入攻击。
【讨论】:
以上是关于将参数传递给 DB .execute 以获取 WHERE IN... INT 列表的主要内容,如果未能解决你的问题,请参考以下文章
将参数传递给cursor.execute()时pyodbc中的UnicodeDecodeError,但在将参数直接写入字符串时不会