RawSQL中LIKE通配符%和_必须手动转义,否则直接生效导致意外交配;正确做法是用%s占位并传入已加通配符的参数值,或改用ORM原生表达式。

RawSQL 中的 LIKE 通配符必须手动转义
RawSQL 不会自动处理 % 和 _,它们在 LIKE 子句中直接作为通配符生效。用户输入若含这些字符,会导致意外交配或全表扫描。
常见错误是把搜索词拼进字符串里:
annotate(score=RawSQL(f"CASE WHEN title LIKE '%{search}%' THEN 1 ELSE 0 END"))
正确做法是用 %s 占位,并在 params 中传入已加通配符的值:
RawSQL("CASE WHEN title LIKE %s THEN 1 ELSE 0 END", params=[f"%{search}%"])
- 如果
search是hello%,不转义就会匹配所有以hello开头的记录,而非字面量hello% - 更稳妥的方式是绕过 RawSQL,改用 ORM 原生表达式:
Case(When(title__icontains=search, then=1), output_field=IntegerField()) - 真要保留原生逻辑,可调用
connection.ops.prep_for_like_query(search)获取转义后字符串
RawSQL 的 params 只绑定占位符,不保护整个 SQL 字符串
params 参数只替换 SQL 字符串中明确写出的 %s,其余部分(包括字段名、函数名、操作符)完全不经过参数化。一旦这些结构部分来自用户输入,就等于开门揖盗。
例如以下写法看似用了 params,实则危险:
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
RawSQL(f"SELECT {user_field} FROM myapp_article WHERE status = %s", params=[status])
这里 {user_field} 是字符串拼接,params 根本管不到它。
- 字段名、表名、
ORDER BY、GROUP BY等必须走白名单校验,不能依赖params - 若需动态字段,先查
MyModel._meta.get_field(user_field)确认存在且非敏感字段(如_meta、__dict__) - 函数名(如
UPPER()、JSON_EXTRACT())也须限定在预设列表内
RawSQL 与 annotate() / filter() 混用时,参数顺序极易出错
在 annotate() 或 filter(RawSQL(...)) 中使用多个 %s 时,params 必须严格按 SQL 中出现顺序提供值,错一位就可能把状态码当 ID 更新、把用户名当时间戳解析。
比如这个查询:
annotate(score=RawSQL("CASE WHEN created_at > %s AND status = %s THEN %s ELSE 0 END", params=[cutoff, status, weight]))
- 三个
%s必须对应[cutoff, status, weight],不能写成[status, cutoff, weight] - 推荐拆成多个独立表达式,或改用
Case()+When()组合,避免手数占位符 - 调试时可用
str(queryset.query)查看最终生成的 SQL,确认参数位置是否对齐
RawSQL 不校验主键字段,ORM 实例化可能静默失败
RawSQL 本身不强制要求返回主键,但若用于 annotate() 或 values() 之外的上下文(如直接赋给模型字段),缺少主键会导致实例无法构建或抛 InvalidQuery。
- 只要涉及模型映射(如
Person.objects.raw(...)),主键字段必须显式出现在 SELECT 列表中 - 在
annotate()中虽不依赖主键,但若后续链式调用.first()或.get(),底层仍会尝试构造模型实例 - 最易被忽略的是嵌套子查询场景:外层 RawSQL 引用内层结果时,内层若没选主键,外层可能拿到空对象或报错

















