【问题标题】:Filtering SQL query based on parameters with more than one value根据具有多个值的参数过滤 SQL 查询
【发布时间】:2019-02-18 01:53:17
【问题描述】:

我正在尝试构建一个需要两个参数(2列)过滤的SQL,并且第二列需要匹配多个值。

下面是我迄今为止构建的 SQL(感谢 Martijn Pieters 的帮助)

import psycopg2
import pandas as pd
import datetime

# Connecting to db

con = psycopg2.connect(db_details)
cur = con.cursor()
cur.execute("select * from sales limit 10")
rows = cur.fetchall()

params = {'earliest': datetime.datetime.today() - datetime.timedelta(days=7),
      'store_name': 'store_1', 'store_2'}

df = pd.read_sql("""
     select store_name,count(*) from sales 
     where created_at >= %(earliest)s
     and store_name = %(store_name)s""",
 params=params, con=con)

上面的 SQL 有一个用于 where 子句的日期参数,我又添加了一个参数,即 store_name,其中行匹配两个值之一。

想知道如何将这个附加参数添加到现有查询中。

我尝试创建一个参数(类似于日期过滤器)并将其传递给现有查询,但是当我给它两个值时出现语法错误:

    'store_name': 'store_1', 'store_2'}
                                      ^
SyntaxError: invalid syntax

指向params 字段。

【问题讨论】:

  • 您有一个简单的语法错误,因为您确实需要至少更好地学习该语言的基础知识。当您第一次需要正确行走时,您正在尝试跑步。如果你也学习了更多关于 SQL 的基础知识,那也会很有帮助;您不能只为单个参数传入两个值,因为 SQL 将使用不同类型的运算符来测试这些值,而不是 store_name = ... 来测试单个值,您必须使用 store_name in (<value1>, <value2>, ..., <valueN>)。我希望你的问题真的只关注后一个问题。
  • 将来,您确实需要开始包含错误输出的问题,因为尝试定义store_name 至少有人试图回答您的问题会看到这是一个columnname IN (value, value, value) 测试,而不是一个直接的单值columnname = value 测试。带有参数的 SQL IN 测试需要 Python 程序员额外的工作,因为现在您必须为这些参数生成 SQL 文本。
  • 如果你只是设置了'store_name': 'store_1'(所以是一个单一的字符串值),代码会照原样工作。所以这里真正的问题不是定义参数,而是定义多个值的参数。
  • 几乎是Python/psycopg2 WHERE IN statement的骗子,至少就整个练习的目标而言。

标签: python sql python-3.x postgresql


【解决方案1】:

你有两个问题:

  • 您使用了无效的 Python 语法;字典中的逗号分隔键值对,因此 'store_2' 字符串将是另一个键值对,但缺少 : value 部分。如果你想定义一个包含多个字符串的值,你必须在那里使用一个元组或一个列表,你是否明确使用(...) 或[...] 将该语法与key: value, key: value 符号分开:

    params = {
        'earliest': datetime.datetime.today() - datetime.timedelta(days=7),
        'store_name': ('store_1', 'store_2'),  # tuple with two values
    }
    
  • 一般来说,SQL 参数只能使用单个值。 store_name 参数只能被赋予一个值,而不是一个值序列。这是因为 SQL 参数是 SQL 查询和要在该查询中使用的动态值之间的桥梁,其参数旨在充当每个单个动态值的占位符。

    也就是说,psycopg2 库 specifically supports tuples,然而这是大多数 Python 数据库库的例外。

    接下来,如果您想过滤匹配 'store_1' 或 'store_2' 的行,正确的 SQL 语法是使用两个 store_name = ... 测试,它们之间有 OR 和括号(以保持该部分分开从与AND 连接的date 测试到商店名称测试),或使用store_name IN ('store_1', 'store_2')。 IN 测试将列名与(...) 括号中列出的多个值进行比较。

鉴于您在此处使用psycopg2,您可以使用引用元组值的store_name 键,但您确实需要使用IN 进行查询:

params = {
    'earliest': datetime.datetime.today() - datetime.timedelta(days=7),
    'store_name': ('store_1', 'store_2')
}

df = pd.read_sql("""
     SELECT store_name, count(*) FROM sales 
     WHERE created_at >= %(earliest)s
     AND store_name IN %(store_name)s""",
     params=params, con=con)

另外说明:pd.read_sql() 函数[明确指出使用 DBAPI 连接时仅支持 sqlite](如果是 DBAPI2 对象,则仅支持 sqlite3):

如果是 DBAPI2 对象,则仅支持 sqlite3。

您正在使用这样的对象;大多数 Python 数据库适配器都是 DBAPI2 库; DBAPI2 是一个Python standard for such libraries。

你真的应该改用SQLAlchemy connection string。您的代码碰巧可以工作,因为您从不尝试将任何数据写回数据库,并且 psycopg 连接和游标对象在很大程度上与 sqlite3 库版本兼容,但您可能会遇到问题。

【讨论】:

  • 在 psycopg 的情况下,可以使用单个占位符传递多个值。元组和列表有特殊的适配,其中元组适配对 IN 谓词和 ROW 构造函数很有用。
  • @IljaEverilä:这对我来说实际上是个新闻!和indeed, this is documented。这在这里确实有帮助。
  • 一些 MySQL 驱动程序也具有类似的功能,尽管我认为它们要么被模糊记录,要么根本没有。
  • @IljaEverilä:这暴露了一个不同的问题:传入 DBAPI2 连接可能可以工作,但实际上只支持 SQLite3,并且很可能存在类型转换问题。
【解决方案2】:

我不明白为什么这不起作用:

params = {'earliest': datetime.datetime.today() - datetime.timedelta(days=7),
          'store_name': '<put what you want here>'}

df = pd.read_sql("""
         select store_name,count(*) from sales 
         where created_at >= %(earliest)s
         and store_name = %(store_name)s""",
     params=params, con=con)

因为你想要两个商店,所以这有点复杂。

这应该有效:

params = {'earliest': datetime.datetime.today() - datetime.timedelta(days=7),
          'store_names': ','.join(('store_1', 'store_2'))}

df = pd.read_sql("""
         select store_name,count(*) from sales 
         where created_at >= %(earliest)s
         and store_name in (%(store_names)s)""",
     params=params, con=con)

【讨论】:

  • 谢谢我在参数中传递的问题超过了 store_name。请您帮忙。
  • @KevinNash 您能否编辑您的问题并显示您尝试过的代码和错误消息?
  • 我已经用我所做的更改以及它引发的语法错误更新了我的初始消息。如果我只传递一个商店名称,它就可以正常工作
  • IN 不能使用单个参数。您需要为每个值生成单独的参数。这是因为 SQL 参数只能处理 一个 值,并且您不希望该值包含逗号。
  • 您要求的是store_name 列的值为'store_1,store_2' 的行,而不是store_name 值为'store_1' 或 @ 的行987654328@.
猜你喜欢
  • 2017-10-30
  • 2020-08-22
  • 1970-01-01
  • 2019-05-27
  • 1970-01-01
  • 2016-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多