SQLAlchemy и пустой оператор IN
Я узнал, что SQLAlchemy переводит
db.query(...).filter(A.id.in_(ids))
в
SELECT ...
FROM a
WHERE a.id != a.id
если ids
пусто. Это приводит к последовательному сканированию в таблице a
, что, очевидно, катастрофично для производительности.
Первый вопрос: почему? Почему не просто 1 = 0
или что-то, что не требует последовательного сканирования?
Второй, более важный: есть ли обычно обходное решение (кроме if
рядом с каждым in_
)?
Я думаю, что in_
не может быть легко переопределено для охвата всех случаев, не вызывая этой проблемы, но я не могу быть первым, кто столкнулся с ним, и может быть какое-то решение, охватывающее простые, распространенные случаи использования in_
.
ИЗМЕНИТЬ
SQLAlchemy регистрирует предупреждение каждый раз, когда это происходит:
"The IN-predicate on 'foo.bar' was invoked with an empty sequence. This results in a contradiction, which nonetheless can be expensive to evaluate. Consider alternative strategies for improved performance."
Ответы
Ответ 1
Я использую:
if len(ids) > 0:
db.query(...).where(A.id.in_(ids))
else:
db.query(...).where(False)
Я попробовал .limit(0)
вместо .where(false)
без успеха. В пустых запросах есть несколько заглавных различий, которые разрушали другие вещи по конвейеру. Этот обходной путь, хотя и может быть быстрее, по крайней мере позволяет избежать упомянутого предупреждения.
Ответ 2
Знайте, о чем вы просите:
- Только если значение
A.id
сопоставимо, любое сравнение действительно может быть выполнено. Невысокое значение не сравнимо ни с чем, все сравнения приведут к несуществующему значению, которое, в свою очередь, оценивается как False. То есть, если A.id
равно NULL
, тогда A.ID == anything
является False, а A.ID != anything
также является False: A.ID == A.ID || A.ID != A.ID
является False, если A.id
- NULL
.
-
IN
-clause с пустой последовательностью спрашивает, является ли это значение частью пустого списка. Не существующее значение является частью списка, даже пустого.
- Следовательно, то, о чем вы просите, это некоторые изменения
IS NOT NULL
и того, что является частью ничего. Это условие, для которого необходимо проверить. Ненужное значение не является чем-то; только некоторое значение, которое не является NULL
, может не быть членом пустого списка...
- Поскольку sqlalchemy умна в том, что это, вероятно, не так, как вы хотите выразить это условие, оно дает предупреждение. Вероятно, вы должны отказаться от
IN
-clause, если последовательность пуста.
Для конкретного примера это sqlfiddle
Для более философского подхода см. Какова природа пустоты
Ответ 3
Когда я столкнулся с этим, это произошло потому, что я использовал тип Enum для одного из столбцов таблицы базы данных. Когда я изменил его на String, проблема исчезла. Это не настоящее решение, так как я предпочел бы Enum, но он действительно избежал проблемы.
Ответ 4
Чтобы ответить на вопрос OP о "почему", вот FAQ в записи (что всегда удивительно сложно найти):
Почему .col.in_([])
создает col != col
? Почему бы не 1=0
?
Небольшое введение в проблему. Оператор IN
в SQL, учитывая список элементов для сравнения с столбцом, как правило, не принять пустой список, то есть в то время как верно:
column IN (1, 2, 3)
его недействительно сказать:
column IN ()
SQLAlchemy Operators.in_()
оператор, заданный пустым списком, выдает это выражение:
column != column
Начиная с версии 0.6, он также выдает предупреждение с указанием что будет проведена менее эффективная операция сравнения. Эта выражение является единственным, которое является агностиком базы данных и производит правильные результаты.
Например, наивный подход "просто оценивается как ложный, сравнивая 1 = 0 или 1!= 1", неправильно обрабатывает нули. Выражение как:
NOT column != column
не возвращает строку, если column IS NULL
, но выражение, которое не учитывает столбец, например:
NOT 1=0
вернет строку.
Как показано в этом сообщении, вы можете использовать функцию ANY, чтобы избежать этого, поскольку он синтаксически действителен даже для пустого списка (но не поддерживается на SQLite, по-видимому). Это, вероятно, быстрее и для больших списков, поскольку для построения запроса он меньше строит строку.
Проблема производительности с оператором in_
была недавно исправлена , и исправление, вероятно, будет в SQLAlchemy 1.2.0.
Ответ 5
использовать подзапрос, если ids
пуст, будет (никогда не исполняется).
Пример:
subquery = db.query(SomeTable.id).filter(...).subquery()
db.query(...).filter(A.id.in_(subquery))
в
SELECT ...
FROM a
WHERE a.id IN (SELECT ...)