我正在尝试创建一种方法,用于自动通过类似于locals()
的东西传递参数,类似于f-字符串的工作方式。
它目前是如何工作的
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()
我希望它的工作方式类似于下面的例子(显然不起作用)
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
的所有实例,并用参数值替换它们。
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,我现在有了一个解决问题的解决方案
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
发布于 2021-05-31 13:24:42
您可以通过将查询包装在sqlalchemy.text
中并将列表转换为元组来使用参数化查询。例如:
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'])
输出:
letter number
0 x 23
1 y 24
2 z 25
为了使该示例完整,使用了以下测试表:
df = pd.DataFrame({
'letter': list(string.ascii_lowercase),
'number': range(len(string.ascii_lowercase))})
df.to_sql('letters', conn, index=False)
更新:这里有一个可能的解决方法,可以让它处理列表:
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
函数(括在引号中,但不包含数字):
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
例如:
sql = """
SELECT *
FROM letters
WHERE
number in :numbers AND
letters in :letters
"""
get_query(sql,
numbers=[1, 2, 3],
letters=['A', 'B', 'C'])
输出:
SELECT *
FROM letters
WHERE
number in (1,2,3) AND
letters in ('A','B','C')
发布于 2021-05-30 17:23:24
为什么不这样做:
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
https://stackoverflow.com/questions/67718518
复制相似问题