我正在使用 sqlite3 模块运行 python,并且想要搜索与列表匹配的行。 sql 应该看起来像
SELECT column FROM table WHERE column IN (1, 2, 3, 4, 5)
在Python中,我们建议使用
?
参数进行值替换,所以这应该看起来像
val_num = [ 1, 2, 3, 4, 5 ]
val_str = ', '.join( [ str(v) for v in val_num ] )
db = sqlite3.connect( filename )
sql = '''SELECT column FROM table WHERE column IN (?)'''
cur = db.cursor()
cur.execute( sql, (val_str,) )
print( cur.fetch_all() )
cur.close()
这将返回一个空列表,
[]
但是,如果我手动将值替换到sql语句中(不建议这样做),它会按预期工作
val_num = [ 1, 2, 3, 4, 5 ]
val_str = ', '.join( [ str(int(v)) for v in val_num ] )
db = sqlite3.connect( filename )
sql = '''SELECT column FROM table WHERE column IN ({})'''.format( val_str )
cur = db.cursor()
cur.execute( sql, (val_str,) )
print( cur.fetch_all() )
cur.close()
返回
[(1,), (2,), (3,), (4,), (5,)]
如何使用 API 语法执行此语句而不是手动替换值?
占位符机制用于传递单个文字值,而不是列表。所以这个:
SELECT column FROM table WHERE column IN (?)
...生成一个查询,其中所有值都填充在同一个文字字符串中,例如:
SELECT column FROM table WHERE column IN ('1, 2, 3, 4, 5')
where
谓词相当于:column = '1, 2, 3, 4, 5'
,这显然不是你想要的。
列表中的每个值都需要有一个占位符 (
?
)。这是一种方法:
sql = 'SELECT column FROM table WHERE column IN ({0})'.format(', '.join('?' for _ in val_num));
cur.execute(sql, val_num);
尝试使用这种方法进行
IN
操作:
val_num = [ 1, 2, 3, 4, 5 ]
query_str = '''
SELECT column FROM table
where
column in (
''' + ','.join('%s' for i in range(len(val_num))) + ' ) '
cursor.execute(query_str, params=tuple(val_num))
rows = cursor.fetchall()
这是伪代码,没有给出创建
cursor
对象的语句。只需尝试更改您的 query-string
和 execute
行(看到此示例)-您就会让它工作。
如果目标是使用服务器端参数替换,您将需要构建查询以获得您想要检查的变量的准确数量的
?
。以下应该可以实现这一点。
val_num = [1, 2, 3, 4, 5]
qs = ", ".join("?" * len(val_num))
query = f"SELECT column FROM table WHERE column IN {qs}"
cur.execute(sql, val_str)
总结:
sqlite3
不直接从列表中获取数据,但您可以使用python准备查询字符串。做这件事有很多种方法。最简单的方法是使用 f 字符串直接替换,但这样应用程序将容易受到 SQL 注入攻击。使用问号作为占位符,或使用命名占位符,是将查询传递到 sqlite3 的安全方法,但它需要更多的工作。另外,您可以使用您拥有的列表创建一个新的 SQL 表,并对其运行常规 SQL 查询(后者未在此处显示)。
使用两个列表的示例
让我用一个简单的例子来解释一下。考虑下面名为
preferences
的 SQL 表,其中包含一些著名数学家的名字。
id | 颜色 | 数字 | 名字 |
---|---|---|---|
1 | 蓝色 | 3 | 牛顿 |
2 | 红色 | 2 | 欧拉 |
3 | 黄色 | 2 | 高斯 |
4 | 黄色 | 2 | 拉马努金 |
5 | 黄色 | 3 | 欧几里德 |
假设您想要检索最喜欢的颜色是蓝色或黄色、最喜欢的数字是 2 或 1 的数学家的名字。
手动,SQLITE 查询将如下所示:
SELECT id, name FROM preferences
WHERE number IN (1, 2)
AND color IN ('YELLOW', 'BLUE')
现在让我们看看执行此操作的不同方法。
0。直接使用占位符是不行的
直接将列表作为占位符传递的简单方法是行不通的。在下面的代码中,它引发了一个异常。
1。仅使用 f 字符串的解决方案(不安全)
使用字符串格式,您可以将值作为元组传递并直接替换查询。如果外部用户可以与数据库交互,则不建议这样做,因为数据库容易受到 SQL 注入攻击。
2。使用 f 字符串创建问号 (
?
) 占位符的解决方案(安全)
这种方法实现起来非常简单。缺点是查询字符串结尾有很多问号,这使得人们很难阅读。
3.使用命名占位符和 f 字符串的解决方案(安全) 这里的想法是引入一个从列表创建自动占位符的函数。假设列表是
L=[x0, x1, x2]
。然后我们将占位符命名为 :x_0
、:x_1
和 :x_2
,
该函数还应返回
values
对应的字典,例如{"x_0": x0, "x_1": x1, "x_2": x2}
。
使用此函数,您现在可以准备查询字符串以使用命名占位符
python 3.10代码
import sqlite3
from sqlite3 import OperationalError
def run_sqlite_query(query: str, values: dict | None =None, verbose: bool = True) -> tuple | None:
try:
cnn = sqlite3.connect("my_toy_database.db")
cur = cnn.cursor()
if verbose:
print("\nQuery:", query)
print("Values:\n", values)
if values:
cur.execute(query, values)
else:
cur.execute(query)
records = cur.fetchall()
if verbose:
print("\nRecords:\n", records)
return records
except OperationalError as e:
print("\nOperationalError raised.\n")
finally:
cnn.close()
def get_placeholders(L: list[any], basename: str = "x") -> tuple[str, dict]:
"""Helper function to prepare named placedholers and value dict for method 3"""
placeholders = ", ".join([f":{basename}_{i}" for i, x in enumerate(L)])
values = {f"{basename}_{i}": x for i, x in enumerate(L)}
return f"({placeholders})", values
def invalid_method_direct_placeholders():
# This query won't work with lists as expected:
query = """
SELECT id, name FROM preferences
WHERE number IN :desired_numbers
AND color IN :desired_colors
"""
values = {"desired_numbers": tuple(desired_numbers), "desired_colors": tuple(desired_colors)}
records = run_sqlite_query(query, values)
return records
def method1_fstrings_only__insecure():
# This query works but is vulnerable to SQL injection attacks
query = f"""
SELECT id FROM preferences
WHERE number IN {tuple(desired_numbers)}
AND color IN {tuple(desired_colors)}
"""
records = run_sqlite_query(query)
return records
def method2_fstrings_and_question_mark_placeholders():
query = f"""
SELECT id, name FROM preferences
WHERE number IN ({','.join(['?']*len(desired_numbers))})
AND color IN ({','.join(['?']*len(desired_colors))})
"""
values = desired_numbers + desired_colors
records = run_sqlite_query(query, values)
return records
def method3_fstrings_plus_named_placeholders():
# Prepare placeholder for each list
num_placeholders, num_values = get_placeholders(desired_numbers, "number")
color_placeholders, color_values = get_placeholders(desired_colors, "color")
print(f"{num_placeholders = }")
print(f"{num_values = }")
print(f"{color_placeholders = }")
print(f"{color_values = }")
# Merge into a single ditionary with values
values = {**color_values, **num_values}
# Using f-strings to generate a query string:
query = f"""
SELECT id, name FROM preferences
WHERE number IN {num_placeholders}
AND color IN {color_placeholders}
"""
records = run_sqlite_query(query, values)
return records
if __name__ == "__main__":
# Initial lists
desired_numbers = [2, 1]
desired_colors = ['YELLOW', 'BLUE']
sep = "\n" + "*"*30 + "\n"
print(sep, "0. Records from invalid method:", sep)
records = invalid_method_direct_placeholders()
print(sep, "1. Records from method 1:", sep)
records = method1_fstrings_only__insecure()
print(sep, "2. Records from method 2:", sep)
records = method2_fstrings_and_question_mark_placeholders()
print(sep, "3. Records from method 3:", sep)
records = method3_fstrings_plus_named_placeholders()
以下是运行给定脚本的结果:
******************************
0. Records from invalid method:
******************************
Query:
SELECT id, name FROM preferences
WHERE number IN :desired_numbers
AND color IN :desired_colors
Values:
{'desired_numbers': (2, 1), 'desired_colors': ('YELLOW', 'BLUE')}
OperationalError raised.
******************************
1. Records from method 1:
******************************
Query:
SELECT id, name FROM preferences
WHERE number IN (2, 1)
AND color IN ('YELLOW', 'BLUE')
Values:
None
Records:
[(3, 'GAUSS'), (4, 'RAMANUJAN')]
******************************
2. Records from method 2:
******************************
Query:
SELECT id, name FROM preferences
WHERE number IN (?,?)
AND color IN (?,?)
Values:
[2, 1, 'YELLOW', 'BLUE']
Records:
[(3, 'GAUSS'), (4, 'RAMANUJAN')]
******************************
3. Records from method 3:
******************************
num_placeholders = '(:number_0, :number_1)'
num_values = {'number_0': 2, 'number_1': 1}
color_placeholders = '(:color_0, :color_1)'
color_values = {'color_0': 'YELLOW', 'color_1': 'BLUE'}
Query:
SELECT id, name FROM preferences
WHERE number IN (:number_0, :number_1)
AND color IN (:color_0, :color_1)
Values:
{'color_0': 'YELLOW', 'color_1': 'BLUE', 'number_0': 2, 'number_1': 1}
Records:
[(3, 'GAUSS'), (4, 'RAMANUJAN')]