在 python + sqlite3 中搜索 IN 列表

问题描述 投票:0回答:4

我正在使用 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 语法执行此语句而不是手动替换值?

python sqlite
4个回答
1
投票

占位符机制用于传递单个文字值,而不是列表。所以这个:

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);

0
投票

尝试使用这种方法进行

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
行(看到此示例)-您就会让它工作。


0
投票

如果目标是使用服务器端参数替换,您将需要构建查询以获得您想要检查的变量的准确数量的

?
。以下应该可以实现这一点。

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)

0
投票

总结:

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')]
© www.soinside.com 2019 - 2024. All rights reserved.