从 Django 传递可选列表参数以在 Raw SQL 中进行过滤
Passing Optional List argument from Django to filter with in Raw SQL
当使用 Integer 等基本类型时,我可以毫无问题地进行这样的查询:
with connection.cursor() as cursor:
cursor.execute(sql='''SELECT count(*) FROM account
WHERE %(pk)s ISNULL OR id %(pk)s''', params={'pk': 1})
如果 pk
参数等于 return 行 id = 1
或者 return 所有 行None
.
但是,当尝试使用类似的方法传递 list/tuple 的 ID 时,我总是在传递 [=50] 时产生 SQL 语法错误=] 元组,例如尝试:
with connection.cursor() as cursor:
cursor.execute(sql='''SELECT count(*) FROM account
WHERE %(ids)s ISNULL OR id IN %(ids)s''', params={'ids': (1,2,3)})
有效,但传递 ()
会产生 SQL 语法错误:
psycopg2.ProgrammingError: syntax error at or near ")"
LINE 1: SELECT count(*) FROM account WHERE () ISNULL OR id IN ()
或者如果我通过 None
我得到:
django.db.utils.ProgrammingError: syntax error at or near "NULL"
LINE 1: ...LECT count(*) FROM account WHERE NULL ISNULL OR id IN NULL
我尝试将参数放在 SQL 中 ()
- (%(ids)s)
- 但这总是会破坏一个或另一个条件。我也尝试过 pg_typeof
或转换参数,但没有结果。
备注:
- 实际的SQL要复杂得多,这里是为了说明目的而进行的简化
- 作为最后的手段 - 我可以根据参数更改 Python 中的 SQL,但我 真的 想避免这种情况。)
Note You can use a Python list as the argument of the IN operator using the PostgreSQL ANY operator.
ids = [10, 20, 30]
cur.execute("SELECT * FROM data WHERE id = ANY(%s);", (ids,))
此外,ANY 也可以处理空列表,而 IN () 是 SQL 语法错误。
起初我有一个想法,只使用 1 个参数,但将其替换为虚拟值 [-1]
,然后像
一样使用它
cursor.execute(sql='''SELECT ... WHERE -1 = any(%(ids)s) OR id = ANY(%(ids)s)''', params={'ids': ids if ids else [-1]})
但这对非空列表进行了完整table扫描,这很不幸,所以不行。
然后我想我可以在 python 中做一些预处理并发送 2 个参数而不是仅发送单个列表 - 实际列表和一个空列表布尔指示符。即
cursor.execute(sql='''SELECT ... WHERE %(empty_ids)s = TRUE OR id = ANY(%(ids)s)''', params={'empty_ids': not ids, 'ids': ids})
不是最优雅的解决方案,但它执行得很好(非空列表的索引扫描,空列表的完整 table 扫描 - 但是 returns 整个 table , 所以没关系)
最后我想出了最简单的解决方案,而且相当优雅:
cursor.execute(sql='''SELECT ... WHERE '{}' = %(ids)s OR id = ANY(%(ids)s)''', params={'ids': ids})
这个也对非空列表执行索引扫描,所以速度非常快。
当使用 Integer 等基本类型时,我可以毫无问题地进行这样的查询:
with connection.cursor() as cursor:
cursor.execute(sql='''SELECT count(*) FROM account
WHERE %(pk)s ISNULL OR id %(pk)s''', params={'pk': 1})
如果 pk
参数等于 return 行 id = 1
或者 return 所有 行None
.
但是,当尝试使用类似的方法传递 list/tuple 的 ID 时,我总是在传递 [=50] 时产生 SQL 语法错误=] 元组,例如尝试:
with connection.cursor() as cursor:
cursor.execute(sql='''SELECT count(*) FROM account
WHERE %(ids)s ISNULL OR id IN %(ids)s''', params={'ids': (1,2,3)})
有效,但传递 ()
会产生 SQL 语法错误:
psycopg2.ProgrammingError: syntax error at or near ")"
LINE 1: SELECT count(*) FROM account WHERE () ISNULL OR id IN ()
或者如果我通过 None
我得到:
django.db.utils.ProgrammingError: syntax error at or near "NULL"
LINE 1: ...LECT count(*) FROM account WHERE NULL ISNULL OR id IN NULL
我尝试将参数放在 SQL 中 ()
- (%(ids)s)
- 但这总是会破坏一个或另一个条件。我也尝试过 pg_typeof
或转换参数,但没有结果。
备注:
- 实际的SQL要复杂得多,这里是为了说明目的而进行的简化
- 作为最后的手段 - 我可以根据参数更改 Python 中的 SQL,但我 真的 想避免这种情况。)
Note You can use a Python list as the argument of the IN operator using the PostgreSQL ANY operator.
ids = [10, 20, 30]
cur.execute("SELECT * FROM data WHERE id = ANY(%s);", (ids,))
此外,ANY 也可以处理空列表,而 IN () 是 SQL 语法错误。
起初我有一个想法,只使用 1 个参数,但将其替换为虚拟值 [-1]
,然后像
cursor.execute(sql='''SELECT ... WHERE -1 = any(%(ids)s) OR id = ANY(%(ids)s)''', params={'ids': ids if ids else [-1]})
但这对非空列表进行了完整table扫描,这很不幸,所以不行。
然后我想我可以在 python 中做一些预处理并发送 2 个参数而不是仅发送单个列表 - 实际列表和一个空列表布尔指示符。即
cursor.execute(sql='''SELECT ... WHERE %(empty_ids)s = TRUE OR id = ANY(%(ids)s)''', params={'empty_ids': not ids, 'ids': ids})
不是最优雅的解决方案,但它执行得很好(非空列表的索引扫描,空列表的完整 table 扫描 - 但是 returns 整个 table , 所以没关系)
最后我想出了最简单的解决方案,而且相当优雅:
cursor.execute(sql='''SELECT ... WHERE '{}' = %(ids)s OR id = ANY(%(ids)s)''', params={'ids': ids})
这个也对非空列表执行索引扫描,所以速度非常快。