SQLite"IN"子句的参数替换

Cli*_*ore 41 python sqlite

我试图使用Python中的SQLite参数替换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
Run Code Online (Sandbox Code Playgroud)

打印出来:

(1,u'Ubuntu')(2,u'Fedora')(5,u'SuSE')

正如文档所述,"[y]你不应该使用Python的字符串操作来组装你的查询,因为这样做是不安全的;它会使你的程序容易受到SQL注入攻击,"我希望使用参数替换.

当我尝试:

result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ (", ".join(desired_ids)) ])
Run Code Online (Sandbox Code Playgroud)

我得到一个空的结果集,当我尝试时:

result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ desired_ids ] )
Run Code Online (Sandbox Code Playgroud)

我明白了:

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
        ) 
    )
Run Code Online (Sandbox Code Playgroud)

Ale*_*lli 63

你需要正确数量的?s,但这不会造成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')]
Run Code Online (Sandbox Code Playgroud)

  • 有没有一种简单的方法可以让它使用命名参数?像 `:id1 :id2 :id3` 等。我在带有一些其他命名参数的较大查询的上下文中使用它。 (4认同)
  • 几年后我会来这里,但我也需要命名参数.我只是这样做:`query ="SELECT*FROM my_table WHERE my_param =:my_param AND id IN({})".format(','.join(':{}'.format(i)for i in range( len(desired_ids)))); params = {'my_param':'foo'}; params.update({str(i):id for i,id in enumerate(desired_ids)}); result = cursor.execute(query,params)`sqlite3模块非常满意``0`,`:1`,`:2`等字符串替换参数.(堆栈溢出确实在评论中谋杀了代码格式;抱歉这很难读.) (4认同)
  • 谢谢你的回答,geekofalltrades。我真的希望这可以是一个清晰的、顶级的答案,与其他无法处理命名参数的答案相匹配。 (2认同)

cib*_*byr 23

根据http://www.sqlite.org/limits.html(第9项),SQLite不能(默认情况下)处理超过999个参数的查询,因此这里的解决方案(生成所需的占位符列表)将如果你有数以千计的物品,那就失败了IN.如果是这种情况,您将需要拆分列表然后循环其中的部分并自己加入结果.

如果您的IN子句中不需要数千个项目,那么Alex的解决方案就是实现它的方式(似乎是Django的工作方式).

  • 根据现在的同一个链接,显然这个限制已经增加到“3.32.0 之后的 SQLite 版本为 32766”。 (3认同)

Mar*_*off 12

更新:这有效:

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
Run Code Online (Sandbox Code Playgroud)

问题是你需要一个吗?对于输入列表中的每个元素.

该语句("?," * len(desired_ids))[:-1]产生一个重复的字符串"?",然后切断最后一个逗号.因此,desired_ids中的每个元素都有一个问号.


Joh*_*uhy 5

我总是最终做这样的事情:

query = 'SELECT * FROM distro WHERE id IN (%s)' % ','.join('?' for i in desired_ids)
c.execute(query, desired_ids)
Run Code Online (Sandbox Code Playgroud)

没有注入风险,因为您没有将来自 desired_ids 的字符串直接放入查询中。