Передача необязательного аргумента List из Django для фильтрации в Raw SQL - PullRequest
1 голос
/ 14 июня 2019

При использовании таких примитивных типов, как 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.

Однако, при попытке использовать аналогичный подход для передачи списка / кортежа идентификаторов, я всегда выдаю ошибку синтаксиса 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 намного сложнее, этот здесь упрощениев качестве иллюстрации
  • в крайнем случае - я мог бы изменить SQL в Python на основе аргумента, но я действительно хотел бы избежать этого.)

Ответы [ 2 ]

1 голос
/ 14 июня 2019

Из документов psycopg2 :

Примечание. Вы можете использовать список Python в качестве аргумента оператора IN, используя ЛЮБОЙ оператор PostgreSQL.

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

Кроме того, ЛЮБОЙ может также работать с пустыми списками, тогда как IN () является ошибкой синтаксиса SQL.

0 голосов
/ 18 июня 2019

Сначала у меня была идея использовать только 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})

Это также выполняет сканирование индекса для непустых списков, так что это довольно быстро.

...