Проект представляет собой ООП конструктор запросов к MySql.
Мне нужно было написать пару селектов в проекте на чистом PHP, но руки отказывались делать это по старинке. Подключать полноценную ORM библиотеку не хотелось, и я решил написать простой компоновщик запросов. Далее, из спортивного интереса, я решил реализовать поддержку всех основных операций для формирования SELECT запросов.
Реализована поддержка следующих операций:
- указать поля для выборки, указать им алиасы
- агрегатные функции (min, max, sum, avg, count), и возможность расширения
- указать сортировку, можно несколько
- сгруппировать, можно по нескольким полям
- фильтрация по рантайм полям (having)
- присоединение таблиц, можно несколько (join)
- фильтрация по полям присоединенных таблиц
- сортировка по полям присоединенных таблиц
- объединение запросов (union), можно несколько
- поддержка логических операций NOT, OR, OR NOT
- человекопонятное форматирование условий в блоке WHERE (переносы строк, скобки, отступы)
- поддержка подзапросов в операторе IN
- Склонировать репозиторий
- Настроить подключение к БД, чтобы иметь возможность сразу выполнить запрос.
Для этого создать файл с конфигом для подключения к БД
/lib/conf/db.php
, в котором объявить параметры подключения:
$db = '';
$user = '';
$pass = '';
Формировать запросы можно и без подключения к БД.
Для начала работы нужно подключить автозагрузчик классов проекта.
<?php
require_once $_SERVER["DOCUMENT_ROOT"] . "/lib/init.php";
Можно сформировать запрос и посмотреть что получилось.
<?php
$selectBuilder = new \Core\Db\Query\SelectQueryBuilder("b_iblock_element_property");
$result = $selectBuilder->getSql();
При настроенном подключении к БД можно создавать объект конструктора через объект подключения к БД. В таком случае, у конструктора сразу же заполняется ссылка на подключение к БД. И тогда можно сформировать запрос, сразу выполнить его и посмотреть на результат - в одной цепочке вызовов.
Далее в примерах будет использоваться именно этот способ создания объекта конструктора и выполнения запроса.
<?php
$connection = \Core\Db\DbManager::getConnection();
$selectBuilder = $connection->selectFrom("b_iblock_element_property");
$result = $selectBuilder->fetchAll();
Можно и явно получить итоговый запрос и выполнить его через объект подключения к БД. Но мне первый способ нравится больше.
<?php
$connection = \Core\Db\DbManager::getConnection();
$selectBuilder = new \Core\Db\Query\SelectQueryBuilder("b_iblock_element_property");
$result = $connection->query( $selectBuilder->getSql() );
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element_property");
$result = $selectBuilder->fetchAll();
Будет сформирован запрос
SELECT *
FROM `b_iblock_element_property`
Вторым параметром можно указать алиас
<?php
$selectBuilder
->addSelectField("NAME", "IBLOCK_NAME")
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->addSelectField("ID")
->addSelectField("NAME", "IBLOCK_NAME")
->addSelectField("CODE", "IBLOCK_CODE")
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock`.`ID`, `b_iblock`.`NAME` AS `IBLOCK_NAME`, `b_iblock`.`CODE` AS `IBLOCK_CODE`
FROM `b_iblock`
<?php
$selectBuilder
->distinct()
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->distinct()
->addSelectField("ID")
->addSelectField("NAME", "IBLOCK_NAME")
->addSelectField("CODE", "IBLOCK_CODE")
->fetchAll();
Будет сформирован запрос
SELECT DISTINCT `b_iblock`.`ID`, `b_iblock`.`NAME` AS `IBLOCK_NAME`, `b_iblock`.`CODE` AS `IBLOCK_CODE`
FROM `b_iblock`
Функция добавляет поле в селект. Можно выбрать только уникальные значение - задается вторым параметром. Третим параметром задается алиас, по умолчанию - название агрегатной функции.
<?php
$selectBuilder
->min("sort")
->max("sort")
->avg("sort")
->count("sort", false, "total_count")
->count("sort", true, "unique_count")
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->min("sort")
->max("sort")
->avg("sort")
->count("sort", false, "total_count")
->count("sort", true, "unique_count")
->fetchAll();
Будет сформирован запрос
SELECT MIN( `b_iblock`.`sort` ) AS `MIN`, MAX( `b_iblock`.`sort` ) AS `MAX`, AVG( `b_iblock`.`sort` ) AS `AVG`, COUNT( `b_iblock`.`sort` ) AS `total_count`, COUNT( DISTINCT `b_iblock`.`sort` ) AS `unique_count`
FROM `b_iblock`
Вторым параметром можно указать обратное направление сортировки.
<?php
$selectBuilder
->addOrderbyField("sort", false)
->addOrderbyField("name")
Чтобы отсортировать результат по алиасу
<?php
$selectBuilder
->addOrderbyAlias("COUNT", false)
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->addSelectField("ID")
->addSelectField("sort")
->addSelectField("NAME")
->addSelectField("CODE")
->addOrderbyField("sort", false)
->addOrderbyField("name")
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock`.`ID`, `b_iblock`.`sort`, `b_iblock`.`NAME`, `b_iblock`.`CODE`
FROM `b_iblock`
ORDER BY `b_iblock`.`sort` DESC, `b_iblock`.`name` ASC
<?php
$selectBuilder
->setLimit(1)
->setOffset(2)
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->addSelectField("ID")
->addSelectField("sort")
->addSelectField("NAME")
->addSelectField("CODE")
->addOrderbyField("sort", false)
->addOrderbyField("name")
->setLimit(1)
->setOffset(2)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock`.`ID`, `b_iblock`.`sort`, `b_iblock`.`NAME`, `b_iblock`.`CODE`
FROM `b_iblock`
ORDER BY `b_iblock`.`sort` DESC, `b_iblock`.`name` ASC
LIMIT 1 OFFSET 2
Обычно группировка нужна для подсчета количества
<?php
$selectBuilder
->addGroupBy("IBLOCK_PROPERTY_ID")
->addGroupBy("IBLOCK_ELEMENT_ID")
->count("ID", false, "COUNT")
Так можно посчитать сколько свойств имеет каждый элемент инфоблока. Получим первые 10 элементов с самым большим количеством свойств.
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element_property");
$result = $selectBuilder
->addSelectField("IBLOCK_ELEMENT_ID")
->addSelectField("IBLOCK_PROPERTY_ID")
->count("ID", false, "COUNT")
->addGroupBy("IBLOCK_PROPERTY_ID")
->addGroupBy("IBLOCK_ELEMENT_ID")
->addOrderbyAlias("COUNT", false)
->setLimit(10)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`, `b_iblock_element_property`.`IBLOCK_PROPERTY_ID`, COUNT( `b_iblock_element_property`.`ID` ) AS `COUNT` , `b_iblock_element_property`.`IBLOCK_PROPERTY_ID`, `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`
FROM `b_iblock_element_property`
GROUP BY `b_iblock_element_property`.`IBLOCK_PROPERTY_ID`, `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`
ORDER BY `COUNT` DESC
LIMIT 10
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element_property");
$selectBuilder
->addSelectField("ID")
->join(
$selectBuilder->joinQuery("b_iblock_element_property", "iblock_element_id", "b_iblock_element_property", "IBLOCK_ELEMENT_ID", "INNER", "joined_table_alias")
)
Будет сформирован запрос
SELECT `b_iblock_element_property`.`ID`, `joined_table_alias`.*
FROM `b_iblock_element_property`
INNER JOIN `b_iblock_element_property` AS `joined_table_alias` ON `joined_table_alias`.`iblock_element_id` = `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element_property");
$result = $selectBuilder
->whereEqual("IBLOCK_PROPERTY_ID", 31)
->whereEqual("VALUE", 20)
->join(
$selectBuilder->joinQuery("b_iblock_element_property", "iblock_element_id", "b_iblock_element_property", "IBLOCK_ELEMENT_ID", "INNER", "slice_by_offer_prop")
->addSelectField("IBLOCK_ELEMENT_ID", "PRODUCT_ID")
->setWhere(
$connection->selectFrom("slice_by_offer_prop")
->whereEqual("IBLOCK_PROPERTY_ID", 28)
)
)
->fetchAll();
Будет сформирован запрос
SELECT `slice_by_offer_prop`.`IBLOCK_ELEMENT_ID` AS `PRODUCT_ID`
FROM `b_iblock_element_property`
INNER JOIN `b_iblock_element_property` AS `slice_by_offer_prop` ON `slice_by_offer_prop`.`iblock_element_id` = `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`
WHERE (
`b_iblock_element_property`.`IBLOCK_PROPERTY_ID` = '31'
) AND (
`b_iblock_element_property`.`VALUE` = '20'
) AND (
`slice_by_offer_prop`.`IBLOCK_PROPERTY_ID` = '28'
)
<?php
$selectBuilder
->whereEqual("sort", 100)
->whereNotEqual("sort", 100)
->whereLower("sort", 600)
->whereLowerOrEqual("sort", 600)
->whereBetween("sort", 400, 600, $includeFrom = true, $includeTo = true)
->whereGreater("sort", 600)
->whereGreaterOrEqual("sort", 600)
->whereIn("sort", [ 100, 200, 300 ])
->whereNotIn("sort", [ 400, 500, 600 ])
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->addSelectField("ID")
->addSelectField("NAME")
->addSelectField("CODE")
->whereEqual("sort", 100)
->whereNotEqual("sort", 100)
->whereLower("sort", 600)
->whereLowerOrEqual("sort", 600)
->whereGreater("sort", 600)
->whereGreaterOrEqual("sort", 600)
->whereIn("sort", [ 100, 200, 300 ])
->whereNotIn("sort", [ 400, 500, 600 ])
->whereLike("code", "product", $inFront = false, $inEnd = true)
->whereLike("code", "new", $inFront = true, $inEnd = true)
->whereNotLike("code", "offer", $inFront = true, $inEnd = false)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock`.`ID`, `b_iblock`.`NAME`, `b_iblock`.`CODE`
FROM `b_iblock`
WHERE (
`b_iblock`.`sort` = '100'
) AND (
`b_iblock`.`sort` != '100'
) AND (
`b_iblock`.`sort` < '600'
) AND (
`b_iblock`.`sort` <= '600'
) AND (
`b_iblock`.`sort` > '600'
) AND (
`b_iblock`.`sort` >= '600'
) AND (
`b_iblock`.`sort` IN (
'100', '200', '300'
)
) AND NOT (
`b_iblock`.`sort` IN (
'400', '500', '600'
)
) AND (
`b_iblock`.`code` LIKE 'product%'
) AND (
`b_iblock`.`code` LIKE '%new%'
) AND NOT (
`b_iblock`.`code` LIKE '%offer'
)
<?php
$selectBuilder = $connection->selectFrom("b_iblock");
$result = $selectBuilder
->addSelectField("ID")
->addSelectField("NAME")
->addSelectField("CODE")
->whereEqual("NAME", "Товары")
->whereEqual("code", "products")
->whereOr(
$selectBuilder->newQuery()
->whereEqual("sort", 500)
)
->whereNot(
$selectBuilder->newQuery()
->whereEqual("code", "products_offers")
->whereEqual("code", "payment")
)
->whereOr(
$selectBuilder->newQuery()
->whereEqual("code", "products_offers")
->whereEqual("code", "payment")
)
->whereOrNot(
$selectBuilder->newQuery()
->whereEqual("code", "payment")
->whereEqual("code", "payment")
)
->whereNot(
$selectBuilder->newQuery()
->whereNot(
$selectBuilder->newQuery()
->whereEqual("code", "products_offers")
->whereEqual("code", "payment")
->whereNot(
$selectBuilder->newQuery()
->whereEqual("code", "products_offers")
->whereEqual("code", "payment")
)
)
)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock`.`ID`, `b_iblock`.`NAME`, `b_iblock`.`CODE`
FROM `b_iblock`
WHERE (
`b_iblock`.`NAME` = 'Товары'
) AND (
`b_iblock`.`code` = 'products'
) OR (
(
`b_iblock`.`sort` = '500'
)
) AND NOT (
(
`b_iblock`.`code` = 'products_offers'
) AND (
`b_iblock`.`code` = 'payment'
)
) OR (
(
`b_iblock`.`code` = 'products_offers'
) AND (
`b_iblock`.`code` = 'payment'
)
) OR NOT (
(
`b_iblock`.`code` = 'payment'
) AND (
`b_iblock`.`code` = 'payment'
)
) AND NOT (
NOT (
(
`b_iblock`.`code` = 'products_offers'
) AND (
`b_iblock`.`code` = 'payment'
) AND NOT (
(
`b_iblock`.`code` = 'products_offers'
) AND (
`b_iblock`.`code` = 'payment'
)
)
)
)
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element");
$selectBuilderSubquery = $connection->selectFrom("b_iblock_element");
$selectBuilderSubquery
->addSelectField("ID")
->whereEqual("SORT", 500)
;
$result = $selectBuilder
->whereInSubquery("ID", $selectBuilderSubquery)
Будет сформирован запрос
SELECT *
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`ID` IN (
SELECT `b_iblock_element`.`ID`
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`SORT` = '500'
)
)
)
Задача: отфильтровать товары по свойствам торговых предложений. Найти обувь 43 размера. Размер - свойство типа список, 43 размер - это ENUM_ID=20. ID свойства "размер" = 31, ID свойства "привязка к товару" = 28.
Решение: Сначала нужно отфильтровать торговые предложения по размеру, затем присоединить свнова таблицу со свойствами товаров (этих самых ТП), и взять оттуда только значение свойства=28, то есть получить IDшники товаров, к которым относятся ТП. Это и будут итогвовые товары по фильтру.
Этот запрос подставляем в качестве подзапроса.
<?php
$selectBuilderSubquery = $connection->selectFrom("b_iblock_element_property");
$selectBuilderSubquery
// filter by offer prop "size"
->whereEqual("IBLOCK_PROPERTY_ID", 31)
->whereEqual("VALUE", 20)
->join(
$selectBuilderSubquery->joinQuery("b_iblock_element_property", "iblock_element_id", "b_iblock_element_property", "IBLOCK_ELEMENT_ID", "INNER", "slice_by_offer_prop")
->addSelectField("IBLOCK_ELEMENT_ID", "PRODUCT_ID")
->setWhere(
$connection->selectFrom("slice_by_offer_prop")
->whereEqual("IBLOCK_PROPERTY_ID", 28)
)
)
;
$selectBuilderFIlteredProducts = $connection->selectFrom("b_iblock_element");
$result = $selectBuilderFIlteredProducts
->addSelectField("ID", "PRODUCT_ID")
->addSelectField("NAME", "PRODUCT_NAME")
->whereInSubquery("ID", $selectBuilderSubquery)
->setLimit(10)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock_element`.`ID` AS `PRODUCT_ID`, `b_iblock_element`.`NAME` AS `PRODUCT_NAME`
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`ID` IN (
SELECT `slice_by_offer_prop`.`IBLOCK_ELEMENT_ID` AS `PRODUCT_ID`
FROM `b_iblock_element_property`
INNER JOIN `b_iblock_element_property` AS `slice_by_offer_prop` ON `slice_by_offer_prop`.`iblock_element_id` = `b_iblock_element_property`.`IBLOCK_ELEMENT_ID`
WHERE (
`b_iblock_element_property`.`IBLOCK_PROPERTY_ID` = '31'
) AND (
`b_iblock_element_property`.`VALUE` = '20'
) AND (
`slice_by_offer_prop`.`IBLOCK_PROPERTY_ID` = '28'
)
)
)
LIMIT 10
<?php
$selectBuilder = $connection->selectFrom("test_table");
$result = $selectBuilder
->addGroupBy("column_1")
->count("ID")
->setHaving(
$selectBuilder->havingQuery()
->whereEqual("COUNT", 2)
->whereOr(
$selectBuilder->havingQuery()
->whereEqual("COUNT", 4)
)
)
->addOrderbyAlias("COUNT")
->fetchAll();
Будет сформирован запрос
SELECT COUNT( `debug`.`ID` ) AS `COUNT` , `debug`.`param_1`
FROM `debug`
GROUP BY `debug`.`param_1`
HAVING (
(
`COUNT` = '2'
) OR (
(
`COUNT` = '4'
)
)
)
ORDER BY `COUNT` ASC
<?php
$selectBuilder = $connection->selectFrom("b_iblock_element");
$catalogIbockId = 2;
$result = $selectBuilder
->addSelectField("SORT")
->whereEqual("iblock_id", $catalogIbockId)
->whereEqual("ACTIVE", "Y")
->setLimit(1)
->union(
$selectBuilder->unionQuery()
->whereEqual("iblock_id", $catalogIbockId)
->whereEqual("ACTIVE", "N")
->setLimit(2)
)
->union(
$selectBuilder->unionQuery()
->whereEqual("iblock_id", $catalogIbockId)
->whereEqual("ACTIVE", "N")
->whereEqual("SORT", 500)
->setLimit(3)
)
->fetchAll();
Будет сформирован запрос
SELECT `b_iblock_element`.`SORT`
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`iblock_id` = '2'
) AND (
`b_iblock_element`.`ACTIVE` = 'Y'
)
LIMIT 1
UNION (
SELECT `b_iblock_element`.`SORT`
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`iblock_id` = '2'
) AND (
`b_iblock_element`.`ACTIVE` = 'N'
)
LIMIT 2
)
UNION (
SELECT `b_iblock_element`.`SORT`
FROM `b_iblock_element`
WHERE (
`b_iblock_element`.`iblock_id` = '2'
) AND (
`b_iblock_element`.`ACTIVE` = 'N'
) AND (
`b_iblock_element`.`SORT` = '500'
)
LIMIT 3
)