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 в истории запросов.