从Django传递Optional List参数以在Raw SQL中进行过滤

时间:2019-06-14 10:08:34

标签: sql django postgresql sql-in rawsql

在使用诸如Integer之类的基本类型时,我可以毫无问题地进行如下查询:

with connection.cursor() as cursor:
    cursor.execute(sql='''SELECT count(*) FROM account 
        WHERE %(pk)s ISNULL OR id %(pk)s''', params={'pk': 1})

如果id = 1参数等于pk,则将返回None的行,或者返回所有行。

但是,当尝试使用类似的方法传递ID的 list / tuple 时,我总是在传递空/无元组时产生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,但我确实希望避免这种情况。)

2 个答案:

答案 0 :(得分:1)

来自psycopg2 docs

  

注意您可以使用PostgreSQL ANY运算符将Python列表用作IN运算符的参数。

ids = [10, 20, 30]
cur.execute("SELECT * FROM data WHERE id = ANY(%s);", (ids,))

此外,ANY也可以使用空列表,而IN()是SQL语法错误。

答案 1 :(得分:0)

起初我有一个想法,只使用1个参数,但是用一个伪值[-1]代替它,然后像使用它一样

cursor.execute(sql='''SELECT ... WHERE -1 = any(%(ids)s) OR id = ANY(%(ids)s)''', params={'ids': ids if ids else [-1]})

但是这样做对非空列表进行了全表扫描,这很不幸,所以不行。

然后我想我可以在python中做一些预处理,然后发送2个参数,而不仅仅是单个列表-实际列表和一个空列表布尔指示符。那是

cursor.execute(sql='''SELECT ... WHERE %(empty_ids)s = TRUE OR id = ANY(%(ids)s)''', params={'empty_ids': not ids, 'ids': ids})

这不是最优雅的解决方案,但是它的性能很好(对非空列表进行索引扫描,对空列表进行全表扫描-但是无论如何都会返回整个表,所以可以)

然后最终,我想出了最简单的解决方案,也很优雅:

cursor.execute(sql='''SELECT ... WHERE '{}' = %(ids)s OR id = ANY(%(ids)s)''', params={'ids': ids})

该程序还对非空列表执行索引扫描,因此速度非常快。