首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何将传递到pandas.read_sql的参数自动化?

如何将传递到pandas.read_sql的参数自动化?
EN

Stack Overflow用户
提问于 2021-05-27 08:30:52
回答 2查看 381关注 0票数 4

我正在尝试创建一种方法,用于自动通过类似于locals()的东西传递参数,类似于f-字符串的工作方式。

它目前是如何工作的

代码语言:javascript
运行
复制
import pandas as pd

def my_func(conn, string_id, date, integer_ids):
    sql = f"""    
    select * from TABLE a
    where STRING_ID = '{string_id}'
    and DATE = {date}
    and INTEGER_ID in ({','.join(map(str, integer_ids))})"""
    df = pd.read_sql(sql, conn)
    return df

但是,这种方法意味着我不能将SQL复制粘贴到或类似的地方,然后在那里运行它。因此,我想要一种使用参数代替的方法。

这似乎有两个问题

locals()

  1. 参数必须是文字,因此不可能传递列表
  2. ,我需要手动创建字典,不能简单地传递类似于
  3. 的内容。

我希望它的工作方式类似于下面的例子(显然不起作用)

代码语言:javascript
运行
复制
import pandas as pd

def my_func(conn, string_id, date, integer_ids):
    sql = """    
    select * from TABLE
    where STRING_ID = :string_id
    and DATE = :date
    and INTEGER_ID in :integer_ids"""
    df = pd.read_sql(sql, conn, params=locals())
    return df

编辑:测试之后,也许我可以使用regex来查找:param的所有实例,并用参数值替换它们。

代码语言:javascript
运行
复制
import re
pattern = '[\n\r].*:\s*([^\n\r]*)'
matches = re.findall(pattern,sql)
for match in matches:
    sql = sql.replace(':'+match, eval(match))

它不是很漂亮,它引入了与对象类型相关的问题。例如,string_id应该由'封装,日期也需要转换为string对象

最终编辑:多亏了perl,我现在有了一个解决问题的解决方案

代码语言:javascript
运行
复制
def read_sql(sql, conn, params):
    # Finds all words following a ":" sign in the sql
    for p in re.findall(':(\w+)', sql):
        if isinstance(params.get(p), (tuple, list)):
            ext_params = {f'{p}_{i:03d}': p_i for i, p_i in enumerate(params.get(p))}
            sql = sql.replace(f':{p}', f"(:{', :'.join(ext_params)})")
            params.update(ext_params)

    sql_text = sqlalchemy.text(sql)
    return pd.read_sql(sql_text, conn, params=params)


def my_func(conn, string_id, date, integer_ids):
    sql = """    
    select * from TABLE
    where STRING_ID = :string_id
    and DATE = :date
    and INTEGER_ID in :integer_ids"""
    df = read_sql(sql, conn, locals())
    return df
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-05-31 13:24:42

您可以通过将查询包装在sqlalchemy.text中并将列表转换为元组来使用参数化查询。例如:

代码语言:javascript
运行
复制
def my_func(conn, min_number, letters):
    # convert lists to tuples
    letters = tuple(letters)
    
    # wrap sql in sqlalchemy.text
    sql = sqlalchemy.text("""    
        SELECT *
        FROM letters
        WHERE
            number >= :min_number AND
            letter in :letters""")
    
    # read and return the resulting dataframe
    df = pd.read_sql(sql, conn, params=locals())
    return df

my_func(conn, 10, ['a', 'b', 'c', 'x', 'y', 'z'])

输出:

代码语言:javascript
运行
复制
  letter  number
0      x      23
1      y      24
2      z      25

为了使该示例完整,使用了以下测试表:

代码语言:javascript
运行
复制
df = pd.DataFrame({
    'letter': list(string.ascii_lowercase),
    'number': range(len(string.ascii_lowercase))})
df.to_sql('letters', conn, index=False)

更新:这里有一个可能的解决方法,可以让它处理列表:

代码语言:javascript
运行
复制
def get_query(sql, **kwargs):
    for k, v in kwargs.items():
        vs = "','".join(v)
        sql = sql.replace(f':{k}', f"('{vs}')")
    return sql

def my_func(conn, min_number, letters):
    sql_template = """    
        SELECT *
        FROM letters
        WHERE
            number >= :min_number AND
            letter in :letters
    """
    # pass list variables to `get_query` function as named parameters
    # to get parameters replaced with ('value1', 'value2', ..., 'valueN')
    sql = sqlalchemy.text(
        get_query(sql_template, letters=letters))
    
    df = pd.read_sql(sql, conn, params=locals())
    return df

my_func(conn, 10, ['a', 'b', 'c', 'x', 'y', 'z'])

更新2:这里是用于字符串和数字的get_query函数(括在引号中,但不包含数字):

代码语言:javascript
运行
复制
def get_query(sql, **kwargs):
    # enclose in quotes strings, but not numbers
    def q(x):
        q = '' if isinstance(x, (int, float)) else "'"
        return f'{q}{x}{q}'
    
    # replace with values
    for k, v in kwargs.items():
        sql = sql.replace(f':{k}', f"({','.join([q(x) for x in v])})")

    return sql

例如:

代码语言:javascript
运行
复制
sql = """    
SELECT *
FROM letters
WHERE
    number in :numbers AND
    letters in :letters
"""

get_query(sql,
          numbers=[1, 2, 3],
          letters=['A', 'B', 'C'])

输出:

代码语言:javascript
运行
复制
SELECT *
FROM letters
WHERE
    number in (1,2,3) AND
    letters in ('A','B','C')
票数 2
EN

Stack Overflow用户

发布于 2021-05-30 17:23:24

为什么不这样做:

代码语言:javascript
运行
复制
import pandas as pd

def my_func(conn, string_id, date, integer_ids):
    sql = """    
    select * from RISK
    where STRING_ID = %s
    and DATE = %s
    and INTEGER_ID in %s"""
    df = pd.read_sql(sql, conn, (string_id, date,integer_ids))
    return df
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67718518

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档