Celesta: setIn (вложенные фильтры)

22 views
Skip to first unread message

Ivan Ponomarev

unread,
Jun 11, 2017, 5:19:24 PM6/11/17
to curs-group

Уважаемые коллеги,

в класс BasicCursor Celesta добавился новый метод фильтрации: setIn, который позволяет установить фильтр с вложенным запросом по указанному набору полей.

Для чего он нужен?

Хорошо знакомый метод setFilter позволяет фильтровать записи, некоторое поле которых принимает любое значение из заранее заданного набора. К примеру,

myCursor.setFilter('city_id', "'MSK'|'LON'")
отфильтровывает записи, поле «код города» которых принимает значение MSK или LON. Вызов
myCursor.setFilter('city_id', "'M'%")
отфильтровывает записи, код города в которых начинается с латинской буквы M.

Но функциональности setFilter бывает недостаточно: что если необходимо отфильтровать записи, относящиеся к городам, полное название которых на русском языке начинается с буквы «Ф»?

Одним из способов решения такой задачи может быть следующее: отфильтровать справочник городов по city.setFilter('name', "'Ф'%"), далее выгрузить из базы данных в память полный набор идентификаторов таких городов, объединить их в строку фильтра через вертикальную черту, и использовать это как фильтр на другом курсоре. Ясно, что такой подход плох, если попадающих в фильтр записей слишком много: это породит обмен лишними данными по сети и слишком длинный SQL-запрос к интересующей нас таблице.

Именно для такого случая и можно теперь применять метод setIn.

Как им пользоваться?

Общая схема работы с setIn такова:

  • устанавливаются фильтры на фильтрующем курсоре,
  • устанавливается связь полей между фильтруемым и фильтрующим курсором.

Связь полей задается при помощи вспомогательного класса FieldsLookup, принимающего в метод конструктора два курсора, по которым ищется пересечение. Первым аргументом должен быть указан фильтруемый, а вторым — фильтрующий курсор. Создание объекта и аккумулирование пар столбцов с последующей установкой фильтра происходит следующим образом:

from ru.curs.celesta.dbutils.filter.value import FieldsLookup
from _filters_orm import FilteringCursor, FilteredCursor
 
a = FilteringCursor(context)
a.setRange('foo', 'bar')
b = FilteredCursor(context)
lookup = FieldsLookup(a, b).add("a1", "b1").add("a2", "b2")
a.setIn(lookup)


Для данного примера в PostgreSQL, например, для доступа к строкам курсора a будет сгенерировано следующее sql выражение:

SELECT ... FROM Filtered WHERE ( a1, a2 ) IN (SELECT b1, b2 FROM Filtering WHERE Filtering.foo = 'bar' )
Ограничения

У данного фильтра имеется набор ограничений, несоблюдение которых приведёт к выбрасыванию исключения во время выполнения методов FieldsLookup.add или BasicCursor.setIn:

  • Типы данных у каждой пары сопоставляемых полей должны в точности совпадать.
  • В каждой из таблиц должен существовать индекс, включающий в себя все столбцы из набора сопоставляемых столбцов: для примера выше для таблицы Filtering должен иметься индекс I1(a1, a2,..), для Filtered - I2(b1, b2,...).
  • Соответствующие индексы должны содержать сопоставляемые столбцы в своём начале. Для нашего примера, если имеются индексы I1(a1, a2,..), I2(b1, b2,...), следующий код вызовет исключение на последней строчке, т. к. поля a2, b2 находятся не в начале индексов I1 и I2:
lookup = FieldsLookup(a, b).add("a2", "b2")
a.setIn(lookup)

Возможность доступна в trunk-версии. Wiki-документация обновлена.


С уважением,

Иван Головко, Иван Пономарёв
Reply all
Reply to author
Forward
0 new messages