2025, Dec 11 15:02
Последовательные фильтры в Snowpark: OR и AND без сюрпризов
Разбираем, почему цепочка filter в Snowpark с OR и AND даёт неожиданные строки. Приоритет операторов, скобки, выражения столбцов и проверка через query_history.
Последовательные фильтры в Snowpark: почему ваш OR приводит к неожиданным строкам
Цепочка из нескольких вызовов filter для DataFrame в Snowpark может давать неожиданные результаты, когда смешиваются OR и AND. Частая ошибка — думать, что второй фильтр дополнительно сузит результат первого, тогда как фактический порядок вычислений формирует другой предикат, чем вы имели в виду.
Пример проблемы
Ниже приведён небольшой пример: создаём крошечный DataFrame и последовательно применяем два фильтра. Интуитивно кажется, что не останется ни одной строки, ведь второе условие требует X = 0 после OR:
import snowflake.snowpark
sp_sess = snowflake.snowpark.context.get_active_session()
with sp_sess.query_history(True) as qh:
frame = sp_sess.sql("SELECT 1 AS X, 2 AS Y")
frame.filter("X = 1 OR Y = 3") \
.filter("X = 0") \
.show()
qh.queries[1]
Что происходит на самом деле
В результате предикат разбирается как X = 1 OR Y = 3 AND X = 0. У оператора AND приоритет выше, чем у OR, поэтому фактическая логика — X = 1 OR (Y = 3 AND X = 0). Кроме того, условия, заданные цепочкой filter, соединяются по AND, поэтому второй фильтр не «оборачивает» первый OR; он просто добавляется как ещё одна конъюнкция.
Если заглянуть в скомпилированный SQL через snowflake.snowpark.Session.query_history, вы увидите совмещённое условие, отражающее такой приоритет операторов и соединение AND между последовательными фильтрами.
Как исправить
Явно сгруппируйте OR с помощью скобок, чтобы при добавлении второго условия итоговый фильтр читался именно так, как задумано:
frame.filter("(X = 1 OR Y = 3)") \
.filter("X = 0") \
.show()
Это гарантирует, что конечный предикат будет (X = 1 OR Y = 3) AND X = 0, а не X = 1 OR (Y = 3 AND X = 0).
Альтернатива: выражения вместо строкового SQL
Ту же логику можно собрать на выражениях столбцов — группировка станет явной без парсинга строки:
import snowflake.snowpark
from snowflake.snowpark.functions import col
sp_sess = snowflake.snowpark.context.get_active_session()
with sp_sess.query_history(True) as qh:
ds = sp_sess.sql("SELECT 1 AS X, 2 AS Y")
ds.filter((col("X") == 1) | (col("Y") == 3)) \
.filter(col("X") == 0) \
.show()
qh.queries[1]
Почему это важно
В конвейерах данных и аналитическом коде тонкие ошибки группировки предикатов приводят к некорректным результатам, которые сложно заметить на маленьких выборках и дорого отлаживать позже. Понимание того, что AND связывается сильнее, чем OR, а цепочки фильтров объединяются через AND, помогает избежать расхождений между задумкой и фактическим исполнением движка. Просмотр запроса через Session.query_history делает это поведение прозрачным при диагностике.
Выводы
Когда объединяете фильтры с OR, всегда ставьте скобки, чтобы зафиксировать ожидаемую группировку. Если хотите большей явности, используйте выражения столбцов — так приоритеты операторов и группировка будут очевидны уже на уровне кода. И при сомнениях проверяйте итоговый предикат, заглядывая в скомпилированный SQL в истории запросов.