从 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 或转换参数,但没有结果。

备注:

来自psycopg2 docs

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})

这个也对非空列表执行索引扫描,所以速度非常快。