WHERE
Where – класс определяет фрагмент WHERE SQL выражения. Данный класс позволяет получить sql любой сложности и массив биндов. Экземпляр класса получаем через фабрику (смотри раздел УСТАНОВКА).
В сгенерированное sql выражение не входит ключевое слово where!
Определимся с понятиями
Приведенные здесь определения не являются научными, это просто термины адаптированные для решения задачи и введенные для удобства.
Логическое выражение – набор логических операций. В нашем случае это where часть sql запроса.
Логическая операция – наименьшая автономная часть where запроса.
Пример: name like '%Вас%'
Под капотом в классе Where логическое выражение представлено в виде массива логических операций, которые в свою очередь также являются массивами.
Логическая операция: [колонка, знакСравнения, значение]
Пример: [‘name’, ‘like’, ‘%Вас%’]
Рассмотрим как же sql выражение curse = 1 and (old < 18 or old > 50) будет представлено под капотом:
[
['curse', '=', 1],
'and',
[
['old', '<', 18],
'or',
['old', '>', 50]
]
]
Данную структуру будем называть сырое выражение.
Сырое выражение можно получить с помощью метода getRaw() и задать методом setRaw($raw).
Самим составлять сырое выражение не нужно.
Методы linkAnd() и linkOr()
В простом случае, если в where логические операции связаны только оператором И или только ИЛИ, тогда можно использовать linkAnd() и linkOr() соответственно.
// sql: old > 18 and old < 50
$wh-> linkAnd([
['old', '<', 18],
['old', '>', 50]
]);
// sql: old < 18 or old > 50
$wh-> linkOr([
['old', '<', 18],
['old', '>', 50]
]);
Если же логическое выражение содержит всего одну логическую операцию, то можно использовать любой из двух методов и можно указать только массив логической операции, не обертывая его в еще один массив: $wh->linkOr(['old', '<', 18]).
Sql любой сложности можно описать тремя методами: linkAnd(), linkOr(), getRaw(). Рассмотрим на примере уже знакомого sql выражения:
// sql: curse = 1 and (old < 18 or old > 50)
$wh->linkAnd([
['curse', '=', 1],
$wh->linkOr([ ['old', '<', 18], ['old', '>', 50] ])->getRaw()
]);
В классе Where есть приватная переменная, в которой хранится сырое выражение. Методы linkAnd() и linkOr() перезаписывают эту переменную, поэтому при составлении логического выражения методы вкладываются друг в друга и переменная с сырым выражением содержит данные полученные из последнего выполненного метода.
Логическая операция
Мы рассматриваем логическую операцию, задаваемую с помощью методов linkAnd() и linkOr(). Для функции setRaw(), которая напрямую работает с сырым выражением, написанное здесь работать не будет.
В простом случае логическая операция представляет собой массив: [колонка, знакСравнения, значение].
Первый элемент массива - это просто строка, вставляющаяся в sql запрос без изменений, а следовательно в ней можно писать sql функции.
Пример: ['LOWER(name)', 'like', '%вас%']
Второй элемент - это то же строка, вставляющаяся в sql без изменений между двумя операндами. Может принимать следующие значения: =, >, <, >=, <=, <>, like, not like, between, not between, in, not in.
Третий элемент массива может быть как числового, так и строкового типа. Where класс автоматически подставит в sql запрос вместо значения псевдоним. Например name like :al_where_UsK63hsfVW. Значения можно получить с помощью функции getBind().
Элемент массива с ключом sql. Бывает нужно, чтобы значение вставлялось в sql код без изменений. Например, для применения функций. Этого можно добиться, указав в качестве ключа ‘sql’.
Пример: [‘LOWER(name)’, ‘like’, ‘sql’ => "LOWER('$name')"]
Элемент массива с ключом bind в логической операции используется для хранения массива биндов, где ключи – псевдонимы. Приведённый выше пример неправильный с точки зрения безопасности. Нельзя вставлять переменные в sql – черевато инъекциями. Поэтому в данном случае нужно будет самому указывать псевдонимы, например так:
['LOWER(name)', 'like', 'sql' => "LOWER(:name)", 'bind'=> ['name' => $name] ]
Функция flex($column, $value) сама по себе является логической операцией и полезна, если нужен поиск по тексту без учета регистра.
Пример:
// Sql: curse = 1 and LOWER(name) like LOWER(':aliasName')
// без функции flex
$name = 'м%А';
$wh->linkAnd([
['curse', '=', 1],
[ 'LOWER(name)',
'like',
'sql' => "LOWER(':aliasName')",
'bind'=> ['aliasName' => $name]
]
]);
// с функцией flex
$name = 'м*А';
$wh->linkAnd([
['curse', '=', 1],
$wh->flex('name', $name)
]);
Обратите внимание, что в обычном случае в качестве маски используется процент (%), а в функции flex() используется звездочка (*). Есть еще fullFlex($column, $value), это то же самое, что flex($column, "*$value*")
Оператор in в логической операции можно записать так: ['curse', 'not in', [1, 3, 5]]. Класс Where преобразует такую запись в следующий sql код: curse not in (:al_where_jCgWfr95kh, :al_where_mCqefr95kh, :al_where_jCfgfr9Gkh)
Оператор between в логической операции можно записать так: ['curse', ' between', 1, 5]. Класс Where преобразует такую запись в следующий sql код: curse between :al_where_Pi4CRr4xNn and :al_where_WiPPS4NKiG
Будьте внимательны в логической операции с ключевым словом between,
если третий и четвертый элемент массива это строка, то применяется особая логика. В данном случае считается, что
идет выборка из диапазона дат и, следовательно, применяется sql функция приведения строки к дате
(функция strToDate($aliasDate) из трейта BaseFunctions).
Пример:
-
['curse', ' between', 1, 5]- 3 и 4 элемент числа, преобразования нет
sql:curse between :al_where_Pi4CRr4xNn and :al_where_WiPPS4NKiG -
['build_date', 'between', '01.01.2016', '01.01.2019']
3 и 4 элемент массива - это строка, следовательно, указана дата, происходит преобразование
sql:build_date between TO_DATE(:al_where_fkD7nZg5lU, 'dd.mm.yyyy hh24:mi::ss') and TO_DATE(:al_where_LdyVRznPF8, 'dd.mm.yyyy hh24:mi::ss')
Описание функций и методов
| Метод | Описание |
|---|---|
| __construct() | Экземпляр класса создается в фабрике и при создании в конструктор передается массив настроек (смотри раздел УСТАНОВКА). |
| getRaw() | Получить сырое выражение. |
| setRaw($raw) |
$raw - Массив. Сырое выражение, описывающее логическое выражение.
Задать сырое выражение. |
| getSql() |
Получить строку sql запроса, данные формируются после вызова функции generate().
|
| getBind() |
Получить массив биндов, где ключи - это псевдонимы (алиасы). Данные формируются после вызова функции generate().
|
|
linkAnd($expression) linkOr($expression) |
$expression - массив логических операций.
Формируют из массива логических операций – логическое выражение. Подробнее в подразделах Методы linkAnd() и linkOr() и Логическая операция |
| flex($column, $value) |
$column - Колонка в базе данных, по которой осуществляется поиск. $value) - Поисковая фраза. Возвращает логическую операцию адаптированную для поиска в тексте без учета регистра. Подробнее в подразделе Логическая операция. |
| notFlex($column, $value) | То же самое, что и flex(), только в сгенерированный sql добавляется предлог not. |
| fullFlex($column, $value) |
То же самое, что flex($column, "*$value*"). Подробнее в подразделе Логическая операция.
|
| repStar($str) |
$str - Поисковая строка.
Заменяются * на %, причем можно экранировать \*, тогда * не заменяются (для оператора like). |
| generate() | Генерирует из сырого выражения строку sql запроса и массив биндов. |
Примеры
Пример 1
Используем максимальное количество элементов класса Where в запросе.
Выборка всех системных блоков с процессором i5 или i7, оперативкой между 4 и 32 гигами. Материнская плата должна быть одной из следующих марок: ASUS, GIGABYTE, Intel, MSI. Дата создания сборки должна быть между 01.01.2016 и 01.01.2019. Средняя оценка сборки не должна быть ниже 3, а автора сборки должны звать Вася.
sql:
(core = 'i5' or core = 'i7')
and size_ram between 4 and 32
and mark_motherboard in ('ASUS', 'GIGABYTE', 'Intel', 'MSI')
and build_date between TO_DATE('01.01.2016', 'dd.mm.yyyy hh24:mi::ss') and TO_DATE('01.01.2019', 'dd.mm.yyyy hh24:mi::ss')
and avg(build_rating) >= abs(3)
and lower(author_build) like lower('%Вася%')
php:
// Создаем экземпляры классов Where с помощью фабрики подробнее в документации в разделе УСТАНОВКА
// (здесь случай компонента для yii2)
$wh = Yii::$app->gp->where();
$sqlWhere = $wh->linkAnd([
$wh->linkOr([ ['core', '=', 'i5'], ['core', '=', 'i7'] ])->getRaw(),
['size_ram', 'between', 4, 32],
['mark_motherboard', 'in', ['ASUS', 'GIGABYTE', 'Intel', 'MSI'] ],
['build_date', 'between', '01.01.2016', '01.01.2019'],
['avg(build_rating)', '>=', 'sql' => 'abs(:x)', 'bind' => ['x' => 3]],
$wh->fullFlex('author_build', 'Вася')
])->generate()->getSql();
$bindWhere = $wh->getBind();
/* Содержимое переменной $sqlWhere:
( core = :al_where_FqzYHPa9TD or core = :al_where_wSqo2YqqS5 )
and size_ram between :al_where_wp5urPaF7F and :al_where_9DWystdstW
and mark_motherboard in ( :al_where_WQFd4y2lPL , :al_where_gb1cwjSwOQ , :al_where_1NjObC7fUr , :al_where_2H7xL2WEdB )
and build_date between TO_DATE(:al_where_fkD7nZg5lU, 'dd.mm.yyyy hh24:mi::ss') and TO_DATE(:al_where_LdyVRznPF8, 'dd.mm.yyyy hh24:mi::ss')
and avg(build_rating) >= abs(:x)
and LOWER(author_build) like LOWER(:al_where_Ha7bUPnrFE)
Содержимое переменной $bindWhere:
Array
(
[al_where_FqzYHPa9TD] => i5
[al_where_wSqo2YqqS5] => i7
[al_where_wp5urPaF7F] => 4
[al_where_9DWystdstW] => 32
[al_where_WQFd4y2lPL] => ASUS
[al_where_gb1cwjSwOQ] => GIGABYTE
[al_where_1NjObC7fUr] => Intel
[al_where_2H7xL2WEdB] => MSI
[al_where_fkD7nZg5lU] => 01.01.2016
[al_where_LdyVRznPF8] => 01.01.2019
[x] => 3
[al_where_Ha7bUPnrFE] => %Вася%
) */
Пример 2
Пример со множеством вложенных скобок.
sql:
core = 'i7' or
(
core = 'i5' and orm = 64 and
(
LOWER(mark_monitor) like LOWER('%apple%') or
( LOWER(mark_monitor) like LOWER('%samsung%') and diagonal = 27)
)
)
php:
$sqlWhere = $wh->linkOr([
['core', '=', 'i7'],
$wh->linkAnd([
['core', '=', 'i5'],
['orm', '=', '64'],
$wh->linkOr([
$wh->fullFlex('mark_monitor', 'apple'),
$wh->linkAnd([
$wh->fullFlex('mark_monitor', 'samsung'),
['diagonal', '=', 27]
])->getRaw()
])->getRaw()
])->getRaw()
])->generate()->getSql();
$bindWhere = $wh->getBind();
/* Содержимое переменной $sqlWhere:
core = :al_where_9yIsG56PAg or
( core = :al_where_LX8Pu4fIiQ and orm = :al_where_ydlEfqrfz2 and
( LOWER(mark_monitor) like LOWER(:al_where_w1dH6AonLZ) or
( LOWER(mark_monitor) like LOWER(:al_where_4EgcfW9iLu) and diagonal = :al_where_Azr8RX4ODu )
)
)
Содержимое переменной $bindWhere:
Array
(
[al_where_9yIsG56PAg] => i7
[al_where_LX8Pu4fIiQ] => i5
[al_where_ydlEfqrfz2] => 64
[al_where_w1dH6AonLZ] => %apple%
[al_where_4EgcfW9iLu] => %samsung%
[al_where_Azr8RX4ODu] => 27
) */