From 1499538df86283cde226dfa6fedc7298d5e1978d Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Tue, 1 Sep 2026 17:29:21 +0300 Subject: [PATCH 1/3] Rewrite query builder article --- pages/orm/query-builder.md | 257 ++++++++++++++++++++++++++++++------- 1 file changed, 210 insertions(+), 47 deletions(-) diff --git a/pages/orm/query-builder.md b/pages/orm/query-builder.md index 0563f41..bf4eeb1 100644 --- a/pages/orm/query-builder.md +++ b/pages/orm/query-builder.md @@ -3,33 +3,62 @@ title: Построитель запросов description: 'Построитель запросов. ORM Bitrix Framework: ключевые концепции, примеры и рекомендации.' --- -Методы выборки `getList` и `getRow` сразу выполняют запросы и возвращают результаты. Они подходят для простых запросов, но неудобны, если параметры неизвестны заранее или нужна сложная логика. +Методы выборки `getList` и `getRow` сразу выполняют запросы и возвращают результаты, поэтому они хорошо подходят для простых запросов, но когда все параметры запроса заранее неизвестны или нужна сложная логика начинаются сложности. -**Гибкость с объектом Query.** Для гибкой настройки запросов используйте объект `Bitrix\Main\ORM\Query\Query`. Он накапливает параметры для запроса. Это полезно, когда параметры неизвестны заранее и формируются программно. +{% note info %} -Пример с `getList` +Все примеры ниже используют условную сущность `BookTable` с полями `ID`, `TITLE`, `ISBN`, `AUTHOR_ID`, `YEAR`, `PRICE`. -```php -// получение данных через getList -$result = BookTable::getList([ - 'select' => ['ISBN', 'TITLE', 'PUBLISH_DATE'], - 'filter' => ['=ID' => 1] -]); -``` +{% endnote %} -Пример с `Bitrix\Main\ORM\Query\Query` +Для гибкой настройки, построитель запросов использует объект `Bitrix\Main\ORM\Query\Query` - он накапливает параметры для запроса до его выполнения. -```php -use Bitrix\Main\ORM\Query\Query; +Посмотрите, как можно выразить один и тот же запрос к `BookTable` на получение конкретной книги с использованием разных подходов: -// аналогичный запрос через Query -$q = new Query(BookTable::getEntity()); -$q->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']); -$q->setFilter(['=ID' => 1]); -$result = $q->exec(); -``` +{% list tabs %} + +- Пример с getList + + Получение данных через getList + + ```php + $result = BookTable::getList([ + 'select' => ['ISBN', 'TITLE', 'PUBLISH_DATE'], + 'filter' => ['=ID' => 1] + ]); + ``` + +- С использованием Query + + ```php + use Bitrix\Main\ORM\Query\Query; + + $q = new Query(BookTable::getEntity()); + $q->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']); + $q->setFilter(['=ID' => 1]); + + $result = $q->exec(); + ``` + +- С использованием текучего синтаксиса + ```php + $q = BookTable::query() + ->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']) + ->where('ID', 1) + ; + + $result = $q->exec(); + ``` + +{% endlist %} + +Объект `Query` — ключевой элемент для выборки данных. Именно он используется внутри `getList`/`getRow`. + +{% note warning %} + +В современном Bitrix Framework рекомендуется получать объект `Query` через статический метод `::query()` [соответствующей DataManager таблицы](*recomend_query), поскольку `Query` - это общий класс запроса и каждый DataManager-наследник вправе расширять его для своих технических нужд. -Объект `Query` — ключевой элемент для выборки данных и используется внутри `getList`. Однако переопределение методов `getList` может быть ограничено: метод может сработать при вызове, но не через `Query`. +{% endnote %} ## Постепенное добавление параметров @@ -38,7 +67,7 @@ $result = $q->exec(); ```php use Bitrix\Main\ORM\Query\Query; -$query = new Query(BookTable::getEntity()); +$query = BookTable::query(); attachSelect($query); attachOthers($query); $result = $query->exec(); @@ -71,7 +100,7 @@ function attachOthers(Query $query): void } ``` -**Создание объекта Query**. Используем `new Query(BookTable::getEntity())` для создания нового объекта `Query`, связанного с сущностью `BookTable`. Это будет основой для построения запроса. +**Создание объекта Query**. Используем `BookTable::query()` для создания нового объекта `Query`, связанного с сущностью `BookTable`. Это будет основой для построения запроса. **Добавление полей в запрос**. Функция `attachSelect` добавляет поля, которые нужно выбрать из базы данных. @@ -90,54 +119,188 @@ function attachOthers(Query $query): void Объект `Query` позволяет строить запрос без его выполнения. Это полезно для подзапросов или получения текста запроса: ```php -use Bitrix\Main\ORM\Query\Query; +use Bitrix\Main\Type\Date; + +$q = BookTable::query() + ->setSelect(['ID']) + ->setFilter([ + '=PUBLISH_DATE' => new Date('2014-12-13', 'Y-m-d') + ]) +; -$q = new Query(BookTable::getEntity()); -$q->setSelect(['ID']); -$q->setFilter(['=PUBLISH_DATE' => new Type\Date('2014-12-13', 'Y-m-d')]); $sql = $q->getQuery(); file_put_contents('/tmp/today_books.sql', $sql); -// Запрос "SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-31'" будет сохранен в файл, но не выполнен. +// Запрос "SELECT ID FROM b_book WHERE PUBLISH_DATE='2014-12-13'" будет сохранен в файл, но не выполнен. ``` ## Методы Query -**select, group** +В данном разделе собраны примеры использования методов `Query`. + +### select, group + +- `setSelect`, `setGroup` — задаёт список полей, полностью заменяя предыдущие. +- `addSelect`, `addGroup` — добавляет новые поля к существующему списку. +- `getSelect`, `getGroup` — возвращает массив полей. -- `setSelect`, `setGroup` — задает список полей, полностью заменяя предыдущие +```php +$query = BookTable::query(); + +$query->setSelect(['ID', 'TITLE']); // список полей: ID, TITLE +$query->addSelect('PRICE'); // добавили PRICE к списку -- `addSelect`, `addGroup` — добавляет новые поля к существующему списку +print_r($query->getSelect()); +// ['ID', 'TITLE', 'PRICE'] + +$query->setSelect(['ID', 'ISBN']); // предыдущий список заменён +print_r($query->getSelect()); +// ['ID', 'ISBN'] +``` -- `getSelect`, `getGroup` — возвращает массив полей +```php +$query = BookTable::query() + ->setSelect(['AUTHOR_ID', 'YEAR']) +; -**distinct** +$query->setGroup('AUTHOR_ID'); // принимает строку или массив +$query->addGroup('YEAR'); // добавили YEAR к AUTHOR_ID -- `setDistinct` — устанавливает флаг `DISTINCT`, чтобы убрать дубликаты строк из результата +print_r($query->getGroup()); +// ['AUTHOR_ID', 'YEAR'] +``` -- `hasDistinct` — возвращает `true`, если флаг `DISTINCT` установлен или указан внутри выражения `ExpressionField`, добавленного в выборку +### distinct -**filter** +- `setDistinct` — устанавливает флаг `DISTINCT`, чтобы убрать дубликаты строк из результата. +- `hasDistinct` — возвращает `true`, если флаг `DISTINCT` установлен или указан внутри выражения `ExpressionField`, добавленного в выборку. -- `setFilter` — устанавливает фильтр +```php +// Получить уникальных авторов книг +$query = BookTable::query() + ->setSelect(['AUTHOR_ID']) + ->setDistinct() +; -- `addFilter` — добавляет параметр фильтра +$books = $query->fetchAll(); +// SQL: SELECT DISTINCT AUTHOR_ID FROM b_book -- `getFilter` — возвращает фильтр +if ($query->hasDistinct()) { + // ... +} +``` -**order** +```php +use Bitrix\Main\ORM\Fields\ExpressionField; -- `setOrder` — устанавливает порядок сортировки +// DISTINCT внутри выражения тоже делает выборку уникальной +$query = BookTable::query() + ->registerRuntimeField( + new ExpressionField('AUTHORS_CNT', 'COUNT(DISTINCT %s)', ['AUTHOR_ID']) + ) + ->setSelect(['AUTHORS_CNT']); -- `addOrder` — добавляет поле для сортировки +$query->hasDistinct(); // true, хотя setDistinct() не вызывали +``` -- `getOrder` — возвращает порядок сортировки +### filter -**limit/offset** +- `setFilter` — устанавливает фильтр. +- `addFilter` — добавляет параметр фильтра. +- `getFilter` — возвращает фильтр. -- `setLimit`, `setOffset` — устанавливает значение +```php +$query = BookTable::query(); -- `getLimit`, `getOffset` — возвращает значение +$query->setFilter(['>=PRICE' => 500]); // цена от 500 +$query->addFilter('AUTHOR_ID', 10); // добавили условие по автору -**runtime fields** +print_r($query->getFilter()); +// ['>=PRICE' => 500, 'AUTHOR_ID' => 10] +``` + +`setFilter` заменяет фильтр целиком, так же как и `setSelect` заменяет список полей. + + +{% note note %} + +Методы `setFilter` / `addFilter` работают со старым массивом фильтра. Для нового кода предпочтительны fluent-условия `where*()` и `Query::filter()`: + +```php +$books = BookTable::query() + ->setSelect(['ID', 'TITLE']) + ->where('AUTHOR_ID', 10) + ->where('PRICE', '>=', 500) + ->fetchAll() +; +``` + +{% endnote %} + + +### order + +- `setOrder` — устанавливает порядок сортировки. +- `addOrder` — добавляет поле для сортировки. +- `getOrder` — возвращает порядок сортировки. + +```php +$query = BookTable::query(); + +$query->setOrder(['TITLE' => 'ASC']); // сначала по названию +$query->addOrder('YEAR', 'DESC'); // затем свежие издания раньше + +print_r($query->getOrder()); +// ['TITLE' => 'ASC', 'YEAR' => 'DESC'] +``` + +### limit/offset + +- `setLimit`, `setOffset` — устанавливают значение. +- `getLimit`, `getOffset` — возвращают значение. + +```php +// Третья страница каталога: по 20 книг на страницу +$query = BookTable::query() + ->setSelect(['ID', 'TITLE']) + ->setLimit(20) + ->setOffset(40); // пропустить первые 40 записей + +$query->getLimit(); // 20 +$query->getOffset(); // 40 +``` + +### runtime fields + +- `registerRuntimeField` — регистрирует временное поле. + +Временное поле существует только внутри запроса: его вычисляет SQL, а в карту сущности оно не добавляется. В `registerRuntimeField` передавайте объект поля, например `ExpressionField`: + +```php +use Bitrix\Main\ORM\Fields\ExpressionField; + +$books = BookTable::query() + ->registerRuntimeField( + new ExpressionField('PRICE_WITH_VAT', '%s * 1.2', ['PRICE']) + ) + ->setSelect(['ID', 'TITLE', 'PRICE_WITH_VAT']) + ->fetchAll() +; +``` + +Runtime-поля можно использовать и в фильтре, и в сортировке: + +```php +use Bitrix\Main\ORM\Fields\ExpressionField; + +$books = BookTable::query() + ->registerRuntimeField( + new ExpressionField('PRICE_WITH_VAT', '%s * 1.2', ['PRICE']) + ) + ->setSelect(['ID', 'TITLE', 'PRICE_WITH_VAT']) + ->where('PRICE_WITH_VAT', '>', 1000) + ->setOrder(['PRICE_WITH_VAT' => 'DESC']) + ->fetchAll() +; +``` -- `registerRuntimeField` — регистрирует временное поле +[*recomend_query]: посмотрите на `BookTable::query()` на вкладке "С использованием текучего синтаксиса" \ No newline at end of file From 6293128d1f1a0bb8493c9756534c79bd00788073 Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Fri, 11 Sep 2026 16:43:45 +0300 Subject: [PATCH 2/3] Changes after conversations with reviewer --- pages/orm/query-builder.md | 290 ++++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 133 deletions(-) diff --git a/pages/orm/query-builder.md b/pages/orm/query-builder.md index bf4eeb1..228e84c 100644 --- a/pages/orm/query-builder.md +++ b/pages/orm/query-builder.md @@ -3,60 +3,57 @@ title: Построитель запросов description: 'Построитель запросов. ORM Bitrix Framework: ключевые концепции, примеры и рекомендации.' --- -Методы выборки `getList` и `getRow` сразу выполняют запросы и возвращают результаты, поэтому они хорошо подходят для простых запросов, но когда все параметры запроса заранее неизвестны или нужна сложная логика начинаются сложности. +Методы `getList` и `getRow` сразу выполняют запрос и возвращают результат. Такой вызов подходит, когда состав полей и условия фильтрации известны заранее. -{% note info %} +Если параметры запроса формируются программно, используйте построитель запросов — объект `Bitrix\Main\ORM\Query\Query`. Построитель накапливает параметры и выполняет запрос по вызову метода `exec`. -Все примеры ниже используют условную сущность `BookTable` с полями `ID`, `TITLE`, `ISBN`, `AUTHOR_ID`, `YEAR`, `PRICE`. +Сравните три способа собрать один и тот же запрос на получение книги по идентификатору. Все примеры статьи используют класс `BookTable` — его описание смотрите в статье [Операции с сущностями](./entity-operations.md). -{% endnote %} +**Метод getList** -Для гибкой настройки, построитель запросов использует объект `Bitrix\Main\ORM\Query\Query` - он накапливает параметры для запроса до его выполнения. +Все параметры запроса передают одним массивом. -Посмотрите, как можно выразить один и тот же запрос к `BookTable` на получение конкретной книги с использованием разных подходов: +```php +$result = BookTable::getList([ + 'select' => ['ISBN', 'TITLE', 'PUBLISH_DATE'], + 'filter' => ['=ID' => 1] +]); +``` -{% list tabs %} +**Объект Query** -- Пример с getList - - Получение данных через getList +Тот же запрос через построитель. Каждый параметр задает отдельный метод, а выполняет запрос метод `exec`. - ```php - $result = BookTable::getList([ - 'select' => ['ISBN', 'TITLE', 'PUBLISH_DATE'], - 'filter' => ['=ID' => 1] - ]); - ``` +```php +use Bitrix\Main\ORM\Query\Query; -- С использованием Query +$query = new Query(BookTable::getEntity()); +$query->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']); +$query->setFilter(['=ID' => 1]); - ```php - use Bitrix\Main\ORM\Query\Query; +$result = $query->exec(); +``` - $q = new Query(BookTable::getEntity()); - $q->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']); - $q->setFilter(['=ID' => 1]); +**Цепочка вызовов** - $result = $q->exec(); - ``` +Тот же запрос в короткой записи. Методы построителя возвращают сам объект, поэтому вызовы можно объединить в цепочку. -- С использованием текучего синтаксиса - ```php - $q = BookTable::query() - ->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']) - ->where('ID', 1) - ; +```php +$query = BookTable::query() + ->setSelect(['ISBN', 'TITLE', 'PUBLISH_DATE']) + ->where('ID', 1) +; - $result = $q->exec(); - ``` +$result = $query->exec(); +``` -{% endlist %} +Объект `Query` — основа выборки данных. Метод `getList` создает такой же объект и заполняет его переданным массивом параметров. О составе этого массива рассказывает статья [Выборка данных](./querying-data.md). -Объект `Query` — ключевой элемент для выборки данных. Именно он используется внутри `getList`/`getRow`. +Метод `exec` возвращает объект `Bitrix\Main\ORM\Query\Result` — у него вызывают `fetch` для одной строки или `fetchAll` для всех. У построителя есть короткие псевдонимы `fetch`, `fetchAll`, `fetchObject` и `fetchCollection`: каждый выполняет запрос и сразу возвращает данные, поэтому вызывать `exec` отдельно не нужно. -{% note warning %} +{% note warning "" %} -В современном Bitrix Framework рекомендуется получать объект `Query` через статический метод `::query()` [соответствующей DataManager таблицы](*recomend_query), поскольку `Query` - это общий класс запроса и каждый DataManager-наследник вправе расширять его для своих технических нужд. +Создавайте объект `Query` методом `query()` нужной таблицы, а не через `new Query()`. Метод `query()` возвращает класс запроса, указанный в методе `getQueryClass()` таблицы. Например, модуль информационных блоков подставляет свой класс запроса. Вызов `new Query()` всегда создает базовый класс, поэтому доработки таблицы теряются. {% endnote %} @@ -100,207 +97,234 @@ function attachOthers(Query $query): void } ``` -**Создание объекта Query**. Используем `BookTable::query()` для создания нового объекта `Query`, связанного с сущностью `BookTable`. Это будет основой для построения запроса. +**Создание объекта Query**. Метод `BookTable::query()` создает объект `Query`, связанный с таблицей книг. Объект становится основой для построения запроса. **Добавление полей в запрос**. Функция `attachSelect` добавляет поля, которые нужно выбрать из базы данных. -- `addSelect('ID')` добавляет поле `ID` в список выбираемых полей +- `addSelect('ID')` добавляет поле `ID` в список выбираемых полей. -- Условие внутри функции добавляет поле `ISBN`, если оно необходимо +- Условие внутри функции добавляет поле `ISBN`, если оно необходимо. **Добавление фильтров и сортировки**. Функция `attachOthers` добавляет фильтры и сортировку. -- `setFilter` устанавливает условия фильтрации данных +- `setFilter` устанавливает условия фильтрации данных. -- `setOrder` задает порядок сортировки результатов +- `setOrder` задает порядок сортировки результатов. ## Запрос без выполнения -Объект `Query` позволяет строить запрос без его выполнения. Это полезно для подзапросов или получения текста запроса: +Объект `Query` позволяет построить запрос и не выполнять его. Метод `getQuery` возвращает текст запроса — он нужен для отладки или для встраивания в подзапрос. ```php use Bitrix\Main\Type\Date; -$q = BookTable::query() +$query = BookTable::query() ->setSelect(['ID']) ->setFilter([ '=PUBLISH_DATE' => new Date('2014-12-13', 'Y-m-d') ]) ; -$sql = $q->getQuery(); +$sql = $query->getQuery(); file_put_contents('/tmp/today_books.sql', $sql); -// Запрос "SELECT ID FROM b_book WHERE PUBLISH_DATE='2014-12-13'" будет сохранен в файл, но не выполнен. +// в файл попадет текст SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-13', сам запрос не выполнится ``` ## Методы Query -В данном разделе собраны примеры использования методов `Query`. +Методы объекта `Query` задают параметры запроса. Префикс в названии показывает, что делает метод. -### select, group +- `set` заменяет ранее заданное значение. -- `setSelect`, `setGroup` — задаёт список полей, полностью заменяя предыдущие. -- `addSelect`, `addGroup` — добавляет новые поля к существующему списку. -- `getSelect`, `getGroup` — возвращает массив полей. +- `add` дополняет его. -```php -$query = BookTable::query(); +- `get` возвращает текущее значение. -$query->setSelect(['ID', 'TITLE']); // список полей: ID, TITLE -$query->addSelect('PRICE'); // добавили PRICE к списку +{% note warning "" %} -print_r($query->getSelect()); -// ['ID', 'TITLE', 'PRICE'] +Если запрос обращается к полю, которого нет в объекте, ORM выбрасывает `Bitrix\Main\ArgumentException` с сообщением о том, что такого поля у объекта нет. Тем же исключением метод `addOrder` отвечает на направление сортировки, отличное от `ASC` и `DESC`. -$query->setSelect(['ID', 'ISBN']); // предыдущий список заменён -print_r($query->getSelect()); -// ['ID', 'ISBN'] -``` +{% endnote %} + +### Select и Group + +- `setSelect`, `setGroup` — задают список полей, полностью заменяя предыдущие. +- `addSelect`, `addGroup` — добавляют новые поля к существующему списку. +- `getSelect`, `getGroup` — возвращают массив полей. + +Метод `setSelect` принимает массив, а `setGroup` и `addGroup` — строку с одним полем или массив полей. Вторым аргументом `addSelect` задают псевдоним поля: вызов `addSelect('PUBLISH_DATE', 'PUBLICATION')` вернет значение под ключом `PUBLICATION`. + +В примере запрос выбирает три поля, последнее из них добавляет отдельный вызов. ```php -$query = BookTable::query() - ->setSelect(['AUTHOR_ID', 'YEAR']) +$books = BookTable::query() + ->setSelect(['ID', 'TITLE']) + ->addSelect('PUBLISH_DATE') + ->fetchAll() ; +// SELECT ID, TITLE, PUBLISH_DATE FROM my_book +``` + +Чтобы посчитать книги по датам выхода добавьте группировку. Поле `CNT` описывает объект `ExpressionField` — о таких полях рассказывает раздел [Runtime-поля](#runtime-polya). -$query->setGroup('AUTHOR_ID'); // принимает строку или массив -$query->addGroup('YEAR'); // добавили YEAR к AUTHOR_ID +```php +use Bitrix\Main\ORM\Fields\ExpressionField; -print_r($query->getGroup()); -// ['AUTHOR_ID', 'YEAR'] +$stat = BookTable::query() + ->registerRuntimeField(new ExpressionField('CNT', 'COUNT(*)')) + ->setSelect(['PUBLISH_DATE', 'CNT']) + ->setGroup('PUBLISH_DATE') + ->fetchAll() +; +// SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE ``` -### distinct +### Distinct + +- `setDistinct` — устанавливает флаг `DISTINCT` SQL-запроса, чтобы убрать дубликаты строк из результата. Без аргумента ставит флаг, вызов `setDistinct(false)` его снимает. -- `setDistinct` — устанавливает флаг `DISTINCT`, чтобы убрать дубликаты строк из результата. - `hasDistinct` — возвращает `true`, если флаг `DISTINCT` установлен или указан внутри выражения `ExpressionField`, добавленного в выборку. +Чтобы получить даты выхода книг без повторов, установите флаг `DISTINCT`. + ```php -// Получить уникальных авторов книг -$query = BookTable::query() - ->setSelect(['AUTHOR_ID']) +$dates = BookTable::query() + ->setSelect(['PUBLISH_DATE']) ->setDistinct() + ->fetchAll() ; +// SELECT DISTINCT PUBLISH_DATE FROM my_book +``` -$books = $query->fetchAll(); -// SQL: SELECT DISTINCT AUTHOR_ID FROM b_book +{% note info "" %} -if ($query->hasDistinct()) { - // ... -} -``` +Метод `hasDistinct` разбирает выражения выборки, а они формируются в момент построения запроса. Вызывайте метод после `exec`, `fetchAll` или `getQuery`. У неисполненного запроса метод учитывает только флаг, заданный через `setDistinct`. -```php -use Bitrix\Main\ORM\Fields\ExpressionField; +Если `DISTINCT` уже задан внутри выражения, метод снимает собственный флаг запроса, чтобы `DISTINCT` не попал в SQL дважды. -// DISTINCT внутри выражения тоже делает выборку уникальной -$query = BookTable::query() - ->registerRuntimeField( - new ExpressionField('AUTHORS_CNT', 'COUNT(DISTINCT %s)', ['AUTHOR_ID']) - ) - ->setSelect(['AUTHORS_CNT']); +{% endnote %} -$query->hasDistinct(); // true, хотя setDistinct() не вызывали -``` -### filter +### Filter + +- `setFilter` — устанавливает фильтр и заменяет предыдущий. Принимает массив условий. + +- `addFilter` — добавляет одно условие к текущему фильтру. Первым аргументом принимает имя поля с префиксом оператора, вторым — значение. -- `setFilter` — устанавливает фильтр. -- `addFilter` — добавляет параметр фильтра. -- `getFilter` — возвращает фильтр. +- `getFilter` — возвращает текущий фильтр. + +Основное условие отбирает книги с начала 2014 года, а второй вызов уточняет отбор по ISBN, если он задан. ```php -$query = BookTable::query(); +use Bitrix\Main\Type\Date; -$query->setFilter(['>=PRICE' => 500]); // цена от 500 -$query->addFilter('AUTHOR_ID', 10); // добавили условие по автору +$query = BookTable::query()->setSelect(['ID', 'TITLE']); +$query->setFilter(['>=PUBLISH_DATE' => new Date('2014-01-01', 'Y-m-d')]); -print_r($query->getFilter()); -// ['>=PRICE' => 500, 'AUTHOR_ID' => 10] -``` +if (/* задан отбор по ISBN */) +{ + $query->addFilter('=ISBN', '978-0321127426'); +} -`setFilter` заменяет фильтр целиком, так же как и `setSelect` заменяет список полей. +$books = $query->fetchAll(); +``` +{% note tip "" %} -{% note note %} +Метод `setFilter` принимает массив условий — тот же формат, что и ключ `filter` в методе `getList`. -Методы `setFilter` / `addFilter` работают со старым массивом фильтра. Для нового кода предпочтительны fluent-условия `where*()` и `Query::filter()`: +Для новых запросов удобнее методы `where*`: они принимают поле, оператор и значение отдельными аргументами. Об операторах и вложенных условиях рассказывает статья [Выборка данных](./querying-data.md). ```php +use Bitrix\Main\Type\Date; + $books = BookTable::query() ->setSelect(['ID', 'TITLE']) - ->where('AUTHOR_ID', 10) - ->where('PRICE', '>=', 500) + ->where('ISBN', '978-0321127426') + ->where('PUBLISH_DATE', '>=', new Date('2014-01-01', 'Y-m-d')) ->fetchAll() ; ``` {% endnote %} +### Order -### order +- `setOrder` — задает порядок сортировки и заменяет предыдущий. Принимает массив вида `['ID' => 'DESC']` или строку с одним полем — тогда сортировка идет по возрастанию. -- `setOrder` — устанавливает порядок сортировки. -- `addOrder` — добавляет поле для сортировки. -- `getOrder` — возвращает порядок сортировки. +- `addOrder` — добавляет поле сортировки к текущему порядку. Второй аргумент по умолчанию равен `ASC`, допустимы только значения `ASC` и `DESC`. -```php -$query = BookTable::query(); +- `getOrder` — возвращает текущий порядок сортировки. -$query->setOrder(['TITLE' => 'ASC']); // сначала по названию -$query->addOrder('YEAR', 'DESC'); // затем свежие издания раньше +Две сортировки работают по порядку: сначала свежие книги, внутри одной даты — по названию. -print_r($query->getOrder()); -// ['TITLE' => 'ASC', 'YEAR' => 'DESC'] +```php +$books = BookTable::query() + ->setSelect(['ID', 'TITLE']) + ->setOrder(['PUBLISH_DATE' => 'DESC']) + ->addOrder('TITLE', 'ASC') + ->fetchAll() +; +// SELECT ID, TITLE FROM my_book ORDER BY PUBLISH_DATE DESC, TITLE ASC ``` -### limit/offset +### Limit и Offset + +- `setLimit`, `setOffset` — задают количество записей и смещение от начала выборки. Принимают целое число или `null`. -- `setLimit`, `setOffset` — устанавливают значение. -- `getLimit`, `getOffset` — возвращают значение. +- `getLimit`, `getOffset` — возвращают заданные значения. + +Для постраничного вывода задайте размер страницы и смещение. В примере это третья страница каталога по 20 книг. ```php -// Третья страница каталога: по 20 книг на страницу -$query = BookTable::query() - ->setSelect(['ID', 'TITLE']) - ->setLimit(20) - ->setOffset(40); // пропустить первые 40 записей +$pageSize = 20; +$page = 3; -$query->getLimit(); // 20 -$query->getOffset(); // 40 +$books = BookTable::query() + ->setSelect(['ID', 'TITLE']) + ->setOrder(['PUBLISH_DATE' => 'DESC']) + ->setLimit($pageSize) + ->setOffset(($page - 1) * $pageSize) + ->fetchAll() +; ``` -### runtime fields +### Runtime-поля + +- `registerRuntimeField` — регистрирует временное поле запроса. + +Метод `registerRuntimeField` добавляет поле к таблице так же, как если бы его описали в методе `getMap`, но действует такое поле только внутри текущего запроса. -- `registerRuntimeField` — регистрирует временное поле. +В следующем запросе поле нужно зарегистрировать заново. В метод передавайте объект поля, чаще всего `ExpressionField` — о нем рассказывает статья [Ключевые концепции ORM](./orm-concepts.md). -Временное поле существует только внутри запроса: его вычисляет SQL, а в карту сущности оно не добавляется. В `registerRuntimeField` передавайте объект поля, например `ExpressionField`: +Здесь выражение считает возраст книги в днях — хранить это значение в таблице не нужно. ```php use Bitrix\Main\ORM\Fields\ExpressionField; $books = BookTable::query() ->registerRuntimeField( - new ExpressionField('PRICE_WITH_VAT', '%s * 1.2', ['PRICE']) + new ExpressionField('AGE_DAYS', 'DATEDIFF(NOW(), %s)', ['PUBLISH_DATE']) ) - ->setSelect(['ID', 'TITLE', 'PRICE_WITH_VAT']) + ->setSelect(['ID', 'TITLE', 'AGE_DAYS']) ->fetchAll() ; ``` -Runtime-поля можно использовать и в фильтре, и в сортировке: +Пример отбирает книги старше года и выводит самые старые первыми. ```php use Bitrix\Main\ORM\Fields\ExpressionField; $books = BookTable::query() ->registerRuntimeField( - new ExpressionField('PRICE_WITH_VAT', '%s * 1.2', ['PRICE']) + new ExpressionField('AGE_DAYS', 'DATEDIFF(NOW(), %s)', ['PUBLISH_DATE']) ) - ->setSelect(['ID', 'TITLE', 'PRICE_WITH_VAT']) - ->where('PRICE_WITH_VAT', '>', 1000) - ->setOrder(['PRICE_WITH_VAT' => 'DESC']) + ->setSelect(['ID', 'TITLE', 'AGE_DAYS']) + ->where('AGE_DAYS', '>', 365) + ->setOrder(['AGE_DAYS' => 'DESC']) ->fetchAll() ; ``` -[*recomend_query]: посмотрите на `BookTable::query()` на вкладке "С использованием текучего синтаксиса" \ No newline at end of file +Зарегистрированное поле доступно в выборке, фильтре и сортировке одного запроса, поэтому повторно регистрировать его внутри запроса не нужно. \ No newline at end of file From abf239e400e57f88b76f2fe4ad74983044d2bff5 Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Fri, 11 Sep 2026 16:58:21 +0300 Subject: [PATCH 3/3] Apply writer-skill for clear text --- pages/orm/query-builder.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/pages/orm/query-builder.md b/pages/orm/query-builder.md index 228e84c..b1392a0 100644 --- a/pages/orm/query-builder.md +++ b/pages/orm/query-builder.md @@ -59,7 +59,7 @@ $result = $query->exec(); ## Постепенное добавление параметров -Если вы не знаете заранее, какие поля выбрать или какие фильтры применить, используйте объект `Query` для постепенного добавления параметров. +Если вы не знаете заранее, какие поля выбрать или какие фильтры применить, добавляйте их в объект `Query` по ходу программы. ```php use Bitrix\Main\ORM\Query\Query; @@ -97,19 +97,7 @@ function attachOthers(Query $query): void } ``` -**Создание объекта Query**. Метод `BookTable::query()` создает объект `Query`, связанный с таблицей книг. Объект становится основой для построения запроса. - -**Добавление полей в запрос**. Функция `attachSelect` добавляет поля, которые нужно выбрать из базы данных. - -- `addSelect('ID')` добавляет поле `ID` в список выбираемых полей. - -- Условие внутри функции добавляет поле `ISBN`, если оно необходимо. - -**Добавление фильтров и сортировки**. Функция `attachOthers` добавляет фильтры и сортировку. - -- `setFilter` устанавливает условия фильтрации данных. - -- `setOrder` задает порядок сортировки результатов. +Параметры запроса разнесены по отдельным функциям, чтобы вынести логику сбора из основного кода. Метод `exec` выполнит запрос, когда обе функции добавят свои параметры. ## Запрос без выполнения @@ -142,14 +130,16 @@ file_put_contents('/tmp/today_books.sql', $sql); {% note warning "" %} -Если запрос обращается к полю, которого нет в объекте, ORM выбрасывает `Bitrix\Main\ArgumentException` с сообщением о том, что такого поля у объекта нет. Тем же исключением метод `addOrder` отвечает на направление сортировки, отличное от `ASC` и `DESC`. +Если запрос обращается к несуществующему полю, ORM выбрасывает `Bitrix\Main\ArgumentException`. То же исключение выбрасывает `addOrder` при направлении сортировки, отличном от `ASC` и `DESC`. {% endnote %} ### Select и Group - `setSelect`, `setGroup` — задают список полей, полностью заменяя предыдущие. + - `addSelect`, `addGroup` — добавляют новые поля к существующему списку. + - `getSelect`, `getGroup` — возвращают массив полей. Метод `setSelect` принимает массив, а `setGroup` и `addGroup` — строку с одним полем или массив полей. Вторым аргументом `addSelect` задают псевдоним поля: вызов `addSelect('PUBLISH_DATE', 'PUBLICATION')` вернет значение под ключом `PUBLICATION`. @@ -165,7 +155,7 @@ $books = BookTable::query() // SELECT ID, TITLE, PUBLISH_DATE FROM my_book ``` -Чтобы посчитать книги по датам выхода добавьте группировку. Поле `CNT` описывает объект `ExpressionField` — о таких полях рассказывает раздел [Runtime-поля](#runtime-polya). +Чтобы посчитать книги по датам выхода, добавьте группировку. Поле `CNT` описывает объект `ExpressionField` — о таких полях рассказывает раздел [Runtime-поля](#runtime-polya). ```php use Bitrix\Main\ORM\Fields\ExpressionField; @@ -181,7 +171,7 @@ $stat = BookTable::query() ### Distinct -- `setDistinct` — устанавливает флаг `DISTINCT` SQL-запроса, чтобы убрать дубликаты строк из результата. Без аргумента ставит флаг, вызов `setDistinct(false)` его снимает. +- `setDistinct` — устанавливает флаг `DISTINCT` SQL-запроса, чтобы убрать дубликаты строк. Без аргумента ставит флаг, вызов `setDistinct(false)` его снимает. - `hasDistinct` — возвращает `true`, если флаг `DISTINCT` установлен или указан внутри выражения `ExpressionField`, добавленного в выборку. @@ -204,7 +194,6 @@ $dates = BookTable::query() {% endnote %} - ### Filter - `setFilter` — устанавливает фильтр и заменяет предыдущий. Принимает массив условий. @@ -327,4 +316,4 @@ $books = BookTable::query() ; ``` -Зарегистрированное поле доступно в выборке, фильтре и сортировке одного запроса, поэтому повторно регистрировать его внутри запроса не нужно. \ No newline at end of file +Зарегистрированное поле доступно в выборке, фильтре и сортировке текущего запроса — регистрировать его повторно не нужно. \ No newline at end of file