我试图将SQLite within Python的参数替换用于IN子句。这是一个完整的运行示例,演示了:
import sqlite3
c = sqlite3.connect(":memory:")
c.execute('CREATE TABLE distro (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)')
for name in 'Ubuntu Fedora Puppy DSL SuSE'.split():
c.execute('INSERT INTO distro (name) VALUES (?)', [ name ] )
desired_ids = ["1", "2", "5", "47"]
result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' % (", ".join(desired_ids)), ())
for result in result_set:
print result
打印出来:
(1,u'Ubuntu') (2,u'Fedora') (5,u'SuSE')
正如文档所述,“你不应该使用Python的字符串操作来组装你的查询,因为这样做是不安全的;它会使你的程序容易受到SQL注入攻击,”我希望使用参数替换。 / p>
当我尝试:
result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ (", ".join(desired_ids)) ])
我得到一个空的结果集,当我尝试时:
result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ desired_ids ] )
我明白了:
InterfaceError:绑定参数0时出错 - 可能是不支持的类型。
虽然我希望对这个简化问题的任何答案都有效,但我想指出我想要执行的实际查询是在双嵌套子查询中。即:
UPDATE dir_x_user SET user_revision = user_attempted_revision
WHERE user_id IN
(SELECT user_id FROM
(SELECT user_id, MAX(revision) FROM users WHERE obfuscated_name IN
("Argl883", "Manf496", "Mook657") GROUP BY user_id
)
)
答案 0 :(得分:59)
您确实需要正确数量的?
,但这不会造成SQL注入风险:
>>> result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' %
','.join('?'*len(desired_ids)), desired_ids)
>>> print result_set.fetchall()
[(1, u'Ubuntu'), (2, u'Fedora'), (5, u'SuSE')]
答案 1 :(得分:22)
根据http://www.sqlite.org/limits.html(第9项),SQLite不能(默认情况下)处理超过999个查询参数,因此如果您有数千个,那么这里的解决方案(生成所需的占位符列表)将会失败你正在寻找的物品IN
。如果是这种情况,你需要拆分列表然后遍历它的部分并自己加入结果。
如果您的IN
子句中不需要数千个项目,那么Alex的解决方案是这样做的(并且似乎是Django的工作方式)。
答案 2 :(得分:11)
更新:这有效:
import sqlite3
c = sqlite3.connect(":memory:")
c.execute('CREATE TABLE distro (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)')
for name in 'Ubuntu Fedora Puppy DSL SuSE'.split():
c.execute('INSERT INTO distro (name) VALUES (?)', ( name,) )
desired_ids = ["1", "2", "5", "47"]
result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' % ("?," * len(desired_ids))[:-1], desired_ids)
for result in result_set:
print result
问题是你需要一个吗?对于输入列表中的每个元素。
语句("?," * len(desired_ids))[:-1]
生成一个重复的字符串“?”,然后切断最后一个逗号。因此,desired_ids中的每个元素都有一个问号。
答案 3 :(得分:3)
我总是这样做:
query = 'SELECT * FROM distro WHERE id IN (%s)' % ','.join('?' for i in desired_ids)
c.execute(query, desired_ids)
没有注入风险,因为您没有直接将来自desired_ids的字符串放入查询中。
答案 4 :(得分:0)
你可以使用非常薄的一层,比如notanorm
https://pypi.org/project/notanorm/
...然后您的代码如下所示:
import notanorm
c = notanorm.SqliteDb(":memory:")
c.query('CREATE TABLE distro (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)')
for name in 'Ubuntu Fedora Puppy DSL SuSE'.split():
c.insert('distro', name=name)
desired_ids = ["1", "2", "5", "47"]
result_set = c.select('distro', id=desired_ids)
for result in result_set:
print(result)
既易于阅读,又能让您有一天切换数据库。
{'id': 1, 'name': 'Ubuntu'}
{'id': 2, 'name': 'Fedora'}
{'id': 5, 'name': 'SuSE'}
答案 5 :(得分:-1)
如果sqlite的sql请求长度有问题,那么无限数量的问号可能会成为某种方式。