为什么 sqlite3 DB_API qmark 和命名样式在 "select where" 查询中不起作用?

Why do sqlite3 DB_API qmark and named style not work in "select where" queries?

假设我有一个数据库 table users 有一行:

ID = 0, name = 'myName'

我可以使用以下任一方式获取 ID 值(假设我已连接):

cursor.execute("""SELECT ID FROM users WHERE %s = %s""" % ('name', 'myName'))
print(cursor.fetchone())

cursor.execute("""SELECT ID FROM users WHERE {} = {}""".format('name', 'myName'))
print(cursor.fetchone())

根据 the documentation.

两者都工作得很好但是是不好的做法

文档建议对具有可变输入的查询使用 qmark 或命名样式。在 SELECT * FROM myDatabase WHERE ? = ? 查询中使用建议的样式时会出现问题。


qmark风格:

cursor.execute("""SELECT ID FROM users WHERE ? = ?""", ('name', 'myName'))
print(cursor.fetchone())

命名样式

cursor.execute("""SELECT ID FROM users WHERE :column = :cell""", {'column': 'name', 'cell':'myName'})
print(cursor.fetchone())

使用后一种样式会导致返回 None。当在 INSERT 的上下文中使用 qmark 或命名样式时(例如在文档中的示例中使用的),它确实按预期工作。

(为了澄清,使用 qmark 样式,为每个 ? 或整个 ? = ? 添加括号不会改变结果。对每个 ? 使用括号并使用额外的由于给定的参数过多,参数导致 execute() 出现故障。)

是什么导致了这种行为?是否可以在 SELECT...WHERE ? = ? 查询中使用 qmark 或命名样式?

参数替换用于 values,而不是 identifiers(列名和表名等)。 RDBMS 对引用值和 identifiers 有不同的规则。使用标识符的参数替换占位符会导致标识符被错误引用,例如

cur.execute('SELECT * FROM tbl WHERE ? = ?', ('col1', 42))

最终成为

SELECT * FROM tbl WHERE 'col1' = 42

请注意 col1 周围的单引号,这导致它被评估为字符串,而不是列名。

如果您想在查询中使用动态标识符和值,则对标识符使用字符串格式,对值使用参数替换。例如,对标识符使用双引号

cur.execute('SELECT * FROM tbl WHERE "{}" = ?'.format('col1'), (42,))

这是导致错误的字符串格式化示例

>>> conn = sqlite3.connect(':memory:')
>>> conn.execute('create table tbl (col1 date)')
<sqlite3.Cursor object at 0x7f56abcf1ce0>
>>> cur = conn.cursor()
>>> cur.execute('INSERT INTO tbl (col1) VALUES(?)', ('2021-05-01',))
<sqlite3.Cursor object at 0x7f56abc8f030>
>>> cur.execute('INSERT INTO tbl (col1) VALUES(%s)' % '2021-05-01')
<sqlite3.Cursor object at 0x7f56abc8f030>
>>> conn.commit()
>>> cur.execute('SELECT col1 FROM tbl WHERE %s = %s' % ('col1', '2021-05-01'))
<sqlite3.Cursor object at 0x7f56abc8f030>
>>> for row in cur:print(row)
... 
(2015,)

当在 INSERTSELECT 语句中使用字符串格式时,日期被计算为算术表达式,导致存储和检索错误的值。像这样的错误很烦人,但使用字符串格式化也会使您的应用程序受到 SQL 注入攻击,这可能会产生更严重的后果。