使用pymssql时如何在不使用%s的前提下安全动态设置查询列名
问题原因
你遇到的问题核心是参数化查询的占位符仅适用于传值,不能用于传递列名/表名这类SQL标识符:
pymssql处理%s占位符时,会自动对传入的字符串类型参数做转义、包裹单引号,最终你传入的列名会变成SQL里的普通字符串,比如SELECT 'fldPayAmount' FROM ...,返回的自然是固定字符串而非列的实际值。
安全实现动态列名的方案
优先推荐白名单校验方案,安全性最高;如果需要适配列经常变动的场景,可以用标识符转义方案。
方案1:白名单校验(最安全,推荐)
提前定义该表允许查询的所有列的集合,用户传入的列必须全部在白名单内,否则直接报错,完全杜绝SQL注入风险。
# 第一步:定义允许查询的列白名单,替换为你的表实际列名 ALLOWED_PAYMENT_COLUMNS = {"fldPayId", "fldPayAmount", "fldPayDate", "fldCustomerId", "fldOrderId", "fldRemark"} # 第二步:校验用户传入的列 invalid_columns = [col for col in columns if col not in ALLOWED_PAYMENT_COLUMNS] if invalid_columns: raise ValueError(f"包含非法查询列:{','.join(invalid_columns)},允许查询的列:{','.join(ALLOWED_PAYMENT_COLUMNS)}") # 第三步:拼接查询列段(已经过校验,直接拼接无风险) select_segment = ", ".join(columns) # 第四步:写查询语句,日期参数仍然用%s占位符走参数化查询 query = f"SELECT {select_segment} FROM dbo.tblPayments WHERE %s <= fldPayDate AND fldPayDate >= %s;" # 执行时传入日期参数即可(示例) # cursor.execute(query, (start_date, end_date))
方案2:标识符转义(无需提前维护白名单)
按照SQL Server的标识符规则对列名做转义:用方括号[]包裹列名,同时把列名内部的]替换为]],确保恶意内容无法逃逸出标识符范围。
def escape_mssql_identifier(identifier: str) -> str: """转义SQL Server标识符(列名/表名)""" return f"[{identifier.replace(']', ']]')}]" # 转义所有传入的列名 escaped_columns = [escape_mssql_identifier(col) for col in columns] select_segment = ", ".join(escaped_columns) # 后续逻辑和方案1一致,日期仍然走参数化 query = f"SELECT {select_segment} FROM dbo.tblPayments WHERE %s <= fldPayDate AND fldPayDate >= %s;"
注意事项
- 日期范围参数必须继续使用参数化占位符
%s传递,不要直接拼接进SQL字符串,既可以避免SQL注入,也能避免不同地区日期格式兼容问题。 - 不要直接用
format()拼接未校验的用户输入列名,会存在SQL注入风险,比如恶意用户传入fldPayId; DROP TABLE dbo.tblPayments;--作为列名,直接拼接会导致表被删除。
内容的提问来源于stack exchange,提问作者LaZoR_Bear
相关产品推荐
相关产品推荐

