unpack-tuple-within-sql语句

0lvr5msh  于 2021-06-20  发布在  Mysql
关注(0)|答案(3)|浏览(269)

我正在使用 Python 以及 PyMySQL . 我想从mysql数据库中根据项目的ID获取一些项目:

items_ids = tuple([3, 2])
sql = f"SELECT * FROM items WHERE item_id IN {items_ids};"

我正在使用格式化的字符串文本( f" " , https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep498)来计算sql语句中的元组。
但是,我想按元组指定的顺序取回项,因此首先使用 item_id = 3 然后是 item_id = 2 . 要做到这一点,我必须使用 ORDER BY FIELD 子句(另请参见:按SQLIn()子句中值的顺序排序)。
但如果我写下这样的话:

items_ids = tuple([3, 2])
sql = f"SELECT * FROM items WHERE item_id IN {items_ids} ORDER BY FIELD{(item_id,) + items_ids};"

那么 item_idORDER BY FIELD 子句被视为未声明的python变量
如果我写下这样的话:

items_ids = tuple([3, 2])
sql = f"SELECT * FROM items WHERE item_id IN {items_ids} ORDER BY FIELD{('item_id',) + items_ids};"

那么 item_idORDER BY FIELD 子句被视为字符串而不是sql变量,在本例中 ORDER BY FIELD 什么都不做。
如何计算元组 (item_id,) + items_ids 在sql语句中 item_id 作为 ORDER BY FIELD 条款?
显然,我可以在项目从数据库返回后根据 items_ids 我不太在意mysql,但我只是想知道如何做到这一点。

vuktfyat

vuktfyat1#

解决此单元素元组问题的另一种更简单的方法是检查元素的长度,方法是将其保留在列表中并将其保留为列表,而不是将其作为元组传递给cursor param:
如:

if (len(get_version_list[1])==1):
                port_id=str(port_id[0])
                port_id = '(' + "'" + port_id + "'" + ')'

            else:
                port_id=tuple(port_id)

pd.read_sql(sql=get_version_str.format(port_id,src_cd), con=conn)

通过使用上述代码,您将无法在sql中获得(item\u id,)此错误:)

vlf7wbxs

vlf7wbxs2#

解决方案 .format() 具体如下:

items_ids = tuple([3, 2])
items_placeholders = ', '.join(['{}'] * len(items_ids))

sql = "SELECT * FROM items WHERE item_id IN {} ORDER BY FIELD(item_id, {});".format(items_ids, items_placeholders).format(*items_ids)

# with `.format(items_ids, items_placeholders)` you get this: SELECT * FROM items WHERE item_id IN (3, 2) ORDER BY FIELD(item_id, {}, {});

# and then with `.format(*items_ids)` you get this: SELECT * FROM items WHERE item_id IN (3, 2) ORDER BY FIELD(item_id, 3, 2);

一个相当棘手的解决方案 f-strings 具体如下:

sql1 = f"SELECT * FROM items WHERE item_id IN {item_ids} ORDER BY FIELD(item_id, "
sql2 = f"{items_ids};"
sql = sql1 + sql2[1:]

# SELECT * FROM items WHERE item_id IN (3, 2) ORDER BY FIELD(item_id, 3, 2);

但作为 @IIija 提到,我可能会得到一个 SQL injection 因为 IN {item_ids} 不能容纳一个元素元组。
另外,使用 f-strings 在字符串中解包元组可能比使用 .format() 正如其他人之前所提到的(在python3.6中格式化的字符串文本带有元组),因为您不能使用 * 解包 f-string . 但是,也许您可以为此提出一个解决方案(使用迭代器?)来产生这个结果

sql = f"SELECT * FROM items WHERE item_id IN ({t[0]}, {t[1]}) ORDER BY FIELD(item_id, {t[0]}, {t[1]});"

尽管我现在还没有解决这个问题的办法。如果您有这种想法,欢迎您发布解决方案。

8zzbczxx

8zzbczxx3#

请不要使用f字符串或任何字符串格式将值传递给sql查询。这就是sql注入之路。现在您可能会想:“这是一个整数元组,会发生什么坏事?”首先,一个元素python元组的字符串表示形式不是有效的sql。其次,有人可能会用用户可控制的数据(而不是int的元组)来仿效这个例子(因此,让这些坏例子在线会延续这个习惯)。另外,你不得不求助于你的“狡猾”解决方案的原因是使用了错误的工具。
向sql查询传递值的正确方法是使用占位符。对于pymysql,占位符有点混乱 %s . 不要把它和手工格式化混在一起。如果必须向查询传递数量可变的值,则必须采用某种字符串生成方法,但生成的是占位符,而不是值:

item_ids = (3, 2)
item_placeholders = ', '.join(['%s'] * len(item_ids))

sql = f"""SELECT * FROM items
          WHERE item_id IN ({item_placeholders})
          ORDER BY FIELD(item_id, {item_placeholders})"""

# Produces:

# 

# SELECT * FROM items

# WHERE item_id IN (%s, %s)

# ORDER BY FIELD(item_id, %s, %s)

with conn.cursor() as cur:
    # Build the argument tuple
    cur.execute(sql, (*item_ids, *item_ids))
    res = cur.fetchall()

相关问题