【问题标题】:Python - Passing datetime parameters into a SQL CommandPython - 将日期时间参数传递给 SQL 命令
【发布时间】:2019-08-23 17:01:37
【问题描述】:

我正在尝试在 Python 中做这样的事情,

SQLCommand = ("Delete From %s where [Date] >= %s and [Date] <= %s", (calendar_table_name, required_starting_date, required_ending_date))

cursor.execute(SQLCommand)

calendar_table_name 是一个string 变量

required_starting_date 是一个datetime 变量

required_ending_date 是一个datetime 变量

尝试这样做会给我一个错误:

要执行的第一个参数必须是字符串或 unicode 查询。

试过这个,它给了我同样的错误:

SQLCommand = ("Delete From " +  calendar_table_name + " where [Date] >= %s and [Date] <= %s", ( required_starting_date, required_ending_date))

cursor.execute(SQLCommand)

编辑:

type(required_ending_date)

Out[103]: pandas._libs.tslibs.timestamps.Timestamp


type(required_starting_date)

Out[103]: pandas._libs.tslibs.timestamps.Timestamp

这对我来说适用于 SSMS,

  delete from [table_test] where [Date] >= '2007-01-01' and [Date] <= '2021-01-01';

更新:- 这是我正在尝试的代码

Delete_SQLCommand =  f"Delete FROM [{calendar_table_name}] WHERE [Date]>=? And [Date]<=?"
params = (required_starting_date, required_ending_date)

required_starting_date 和 required_ending_date 是“时间戳”格式

calendar_tbl_connection = pyodbc.connect(driver=driver, server=required_server, database=database_name,
                     trusted_connection='yes')   
calendar_tbl_cursor = calendar_tbl_connection.cursor()
calendar_tbl_cursor.execute(Delete_SQLCommand,params)
calendar_tbl_connection.commit
calendar_tbl_connection.close()

【问题讨论】:

  • 您不能参数化表名。 From %s where 在这种情况下无效。您需要使用字符串格式来制作动态表名,然后为查询的其余部分传递参数
  • 你还没有向我们展示这个 sql 命令是如何使用的。给我们打电话给execute()
  • 对,所以编辑建议您传递 datetime 对象而不是字符串,并且该列配置为采用 TEXT 值(或类似值),或者您的连接库不会转换成 ISO 格式
  • @tgikal 这就是您获得 SQL 注入攻击的方式。不要那样做。
  • 使 SQLCommand 成为字符串部分,即 SQLCommand = "Delete From ..." 。然后使用参数调用执行:cursor.execute(SQLCommand, (arg1, arg2))

标签: python sql-server pandas pyodbc


【解决方案1】:

pyodbc 可以将 pandas 的 Timestamp 值作为正确的参数化查询的输入来处理:

# test data
calendar_table_name = "#calendar_table"
crsr.execute(f"CREATE TABLE [{calendar_table_name}] ([Date] date)")
crsr.execute(f"INSERT INTO [{calendar_table_name}] VALUES ('2019-08-22'),('2019-08-24')")
df = pd.DataFrame(
    [(datetime(2019, 8, 23, 0, 0), datetime(2019, 8, 25, 0, 0))],
    columns=['required_starting_date', 'required_ending_date'])
required_starting_date = df.iloc[0][0]
required_ending_date = df.iloc[0][1]
print(type(required_starting_date))  # <class 'pandas._libs.tslibs.timestamps.Timestamp'>

# test
sql = f"DELETE FROM [{calendar_table_name}] WHERE [Date]>=? AND [Date]<=?"
params = (required_starting_date, required_ending_date)
crsr.execute(sql, params)
cnxn.commit()

#verify
rows = crsr.execute(f"SELECT * FROM [{calendar_table_name}]").fetchall()
print(rows)  # [(datetime.date(2019, 8, 22), )]

【讨论】:

  • 您在测试中拥有的 Select 语句有效。但是,如果我将查询更改为删除,那么它会失败。抱歉不能早点回复这个问题。
  • 失败怎么办?你有例外吗?
  • 不,它只是不会删除它。查询运行,但数据没有被删除。
  • 你还记得commit()吗?
  • 如果您使用 SSMS 检查删除结果,请仔细检查您正在连接到 Python 应用正在处理的同一个 SQL Server 实例。
【解决方案2】:

您没有说明您使用哪个库进行 SQL 访问,但这里有一个使用 psycopg 的安全示例。

from psycopg2 import sql

cmd = sql.SQL("delete from {} where date >= %s and date <= %s")
table_name = sql.Identifier(calendar_table_name)
cur.execute(
    cmd.format(table_name),
    [required_starting_date, required_ending_date]
)

请注意,这不是 str.format 被调用,而是SQL.format。该库确保calendar_table_name 是正确的列名,SQL.format 确保它在之前正确地合并到您的命令模板中,以便生成有效的参数化查询。


如果没有适当的库支持,您将需要进行 一些 类的动态查询生成。它应该是 restricted 排序,但越受限越好。最安全的方法是从硬编码查询的查找表开始:

queries = {
  'name1': 'delete from name1 where ... ',
  'name2': 'delete from name2 where ...',
}

这样,您不能为任意表名构造查询,只能选择预先构造的查询。

第二种方法是将构造函数包装在一个首先检查有效表名的函数中。例如,

def generate_query(table_name):
    if table_name not in ['name1', 'name2', ...]:
        raise ValueError("Invalid table name")

    return "delete from {} where ...".format(table_name)

【讨论】:

  • 我正在使用pyodbc
  • @Siddarth:这可能仍然有效,python 有一个标准化的数据库连接器 api。如果 pyodbc 跟随它,cursor.execute 应该与 pyscopg2 (postgres) 的工作方式大致相同。
  • @HåkenLid psycopg2.sql 提供的类不是 DBAPI 的一部分。
  • 好的。因此,即使 cursor.execute 方法工作正常,pyodbc 中可能缺少您答案中的 sql.Identifiersql.SQL 的等效项?我想可以只使用字符串格式。但是你必须小心表名变量是安全的。我找到了一个相关的问题,但那里的最高答案看起来可能会暴露一个 sql 注入漏洞:Passing table name as a parameter in pyodbc.
  • @HåkenLid 您可以获取数据库架构的参考列表,并在执行前根据该列表检查任何变量输入(“这个‘表’是否真的存在?”)。
【解决方案3】:

这段代码有 3 个(至少)不同的问题:

  1. 您使用的是 pandas 时间戳类型,而不是预期的 python 日期时间类型。 Roganosh answer explains that
  2. 您将 sql 标识符(表名)与 sql 值(日期)混合在一起。您只能将值作为参数传递给cursor.executesee chepner's answer.
  3. 您使用不正确的参数调用 cursor.execute

cursor.execute 需要两个参数。由于您的SQLCommand 变量是一个元组,因此您可以在调用cursor.execute 时使用* 将查询字符串和变量解包为两个参数。

SQLCommand = (
    "DELETE FROM table_name WHERE date >= %s", 
    (datetime.date(2019, 08, 23),) 
)

cursor.execute(*SQLCommand)

请注意,您不能将 sql 标识符(例如表名)作为参数传递给 cursor.execute 方法。 The Python Database API Specification 未指定如何使用动态标识符(例如列名或表名)构造查询。

【讨论】:

  • 这仅适用于查询参数,不适用于表名。 DBAPI 本身不支持完全动态生成 SQL 命令。
  • @chepner:你是对的。问题中的代码有两个问题。我的回答解决了为什么他们会收到错误“要执行的第一个参数必须是字符串或 unicode 查询”。在这种情况下。我已经编辑了答案以更清楚地说明参数(cursor.execute 的第二个参数必须是值,而不是表名等标识符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-27
  • 2021-09-21
  • 1970-01-01
  • 2017-10-08
相关资源
最近更新 更多