在SQL语句中解压缩元组

时间:2018-05-31 13:11:53

标签: python mysql

我正在使用PythonPyMySQL。我想根据他们的ID从MySQL数据库中获取一些项目:

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子句(另请参见此处:Ordering by the order of values in a SQL IN() clause)。

但如果我写这样的话:

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

然后item_id子句中的ORDER 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_id子句中的ORDER BY FIELD被视为字符串而不是SQL变量,在这种情况下ORDER BY FIELD不执行任何操作。

如何通过在(item_id,) + items_ids子句中将item_id维护为SQL变量来评估SQL语句中的元组ORDER BY FIELD

显然我可以根据items_ids从数据库返回后对项目进行排序,而不必费心地使用MySQL,但我只是想知道如何做到这一点。

3 个答案:

答案 0 :(得分:2)

请不要使用f字符串或任何字符串格式将值传递给SQL查询。这是前往SQL injection的道路。现在你可能会想:"它是一个整数元组,可能发生什么坏事?"首先,单元素Python元组的字符串表示不是有效的SQL。其次,有人可能会使用除了int元组之外的用户可控数据来跟踪这个例子(因此在网上有这些不好的例子会延续这种习惯)。也是你必须诉诸你"狡猾"解决方案是使用错误的工具来完成工作。

将值传递给SQL查询的正确方法是use placeholders。在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()

答案 1 :(得分:1)

.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()更难(Formatted string literals in Python 3.6 with tuples),因为您无法使用*解压缩f-string内的元组。但是,也许你可能想出一个解决方案(使用迭代器?)来产生这个

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

即使我现在还没有解决这个问题。如果您有这样的解决方案,欢迎您发布此类解决方案。

答案 2 :(得分:-1)

解决此单个元素元组问题的另一种更简单的方法是通过将元素保留在列表中并将其保留为列表而不是将其作为元组传递给游标参数来检查元素的长度:

例如:

 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_id,)此错误:)