为什么 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())
两者都工作得很好但是是不好的做法
文档建议对具有可变输入的查询使用 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,)
当在 INSERT
和 SELECT
语句中使用字符串格式时,日期被计算为算术表达式,导致存储和检索错误的值。像这样的错误很烦人,但使用字符串格式化也会使您的应用程序受到 SQL 注入攻击,这可能会产生更严重的后果。
假设我有一个数据库 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())
两者都工作得很好但是是不好的做法
文档建议对具有可变输入的查询使用 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,)
当在 INSERT
和 SELECT
语句中使用字符串格式时,日期被计算为算术表达式,导致存储和检索错误的值。像这样的错误很烦人,但使用字符串格式化也会使您的应用程序受到 SQL 注入攻击,这可能会产生更严重的后果。