QUERY
Query – основной класс, для работы с БД, через него происходит выборка, запись, обновление и удаление данных. Также можно проводить определенную обработку данных, полученных из БД. Экземпляр класса получаем через фабрику (смотри раздел УСТАНОВКА).
Описание функций
Все функции класса условно можно разбить на 4 группы. Для удобства присвоим каждой группе свой цвет:
подготовка запроса к выборке - Функции из данной группы подготавливают запрос к выборке, поэтому они должны предшествовать функциям из желтой группы, причем внутри зеленой группы порядок вызова функций не имеет значения.
получение данных из БД - Функции в данной группе выполняют запрос на получение данных, подготовленный функциями зеленой группы.
изменение данных в БД - Функции из данной группы изменяют данные в базе. На них никак не действуют функции из зеленой группы.
результат запроса и/или его обработка - Функции в синей группе работают с данными, полученными из базы с помощью функций из желтой и красной группы (подробнее в описании функций).
Псевдонимы (алиасы) в базовом sql
запросе для частей sql запроса необходимо обрамлять знаком комментария,
например: /*_select_*/
. Т.к. в некоторых случаях, после генерации итогового sql запроса,
псевдонимы могут остаться нетронутыми. Например, если передать в функцию whereAnd
пустой массив.
Функция | Описание | ||
---|---|---|---|
__construct($db, $settings, $baseSQL = '') |
Экземпляр класса создается в фабрике (смотри раздел УСТАНОВКА), и при создании,
в конструктор передается экземпляр класса для работы с БД , массив настроек
и опционально базовый sql запрос.
|
||
sql($baseSQL) |
Задаем шаблон базового sql запроса . Пример:
sql('SELECT /*_select_*/ FROM dual'); |
||
sqlPart($alias, $sql, $binds) |
Задаем часть sql запроса, которая будет вставлена в базовый шаблон вместо $alias .
Если $alias новый, то добавляет, если $alias уже добавляли с помощью функции
sqlPart , то заменяет данные.
sqlPart('/*_select_*/', 'round(dbms_random.value(:a, :b)) as val', ['a'=>1, 'b'=>100]);ВАЖНО: за уникальность псевдонимов биндов, указанных вами, вы должны следить сами во всех частях sql. |
||
binds($binds) |
Перезаписывает весь массив биндов, проставленных функциями:
binds , addBinds и bind . ВАЖНО: бинды, проставленные с помощью функций sqlPart, where, whereAnd, whereOr, join - не
перезаписываются. Следите за уникальностью псевдонимов.
|
||
addBinds($binds) |
К массиву биндов добавляем новый массив биндов. ВАЖНО: следите за уникальностью псевдонимов. |
||
bind($alias, $value) |
Добавляем одно значение в массив биндов, причем, если псевдоним неуникальный, то произойдет перезапись значения. ВАЖНО: перезапишется псевдоним только в том случае, если он был задан одной из функций: binds, addBinds, bind .
|
||
where($alias, $where, $isWhere = false) |
В базовом шаблоне вместо $alias подставляется sql, который генерируется из $where
(экземпляр класса Where ). Параметр $isWhere принимает значение true/false, если true –
то sql генерируется с ключевым словом WHERE, если false – то без ключевого слова WHERE.ВАЖНО:Если $where пустой, то никакой замены не будет, поэтому в базовом SQL
запросе алиас для вставки sql части надо записывать в знаках комментария: /*xxx*/
|
||
whereAnd($alias, $where) |
Упрощенный способ задать sql where часть. $where – массив логических операций со связью AND
(смотри раздел WHERE). ВАЖНО: sql генерируется с ключевым словом WHERE, если $where пустой, то никакой замены
псевдонима (алиаса) на sql не будет.
|
||
whereOr($alias, $where) |
Упрощенный способ задать sql where часть. $where – массив логических операций со связью OR
(смотри раздел WHERE). ВАЖНО: sql генерируется с ключевым словом WHERE, если $where пустой, то никакой замены
псевдонима (алиаса) на sql не будет.
|
||
whereWithJoin($aliasJoin, $options, $aliasWhere, $where) | При запросе на выборку функция генерирует sql как для where части, так и для join части. Подробно принцип работы функции описан в пункте whereWithJoin(). | ||
join($alias, $join) |
В базовом шаблоне вместо $alias подставляется sql, который генерируется из $join
(экземпляр класса Join или CollectionJoin (смотри раздел JOIN)).
|
||
sort($nameColumn) |
Сортируем только по одному указанному столбцу. ВАЖНО: пишем только название столбца без указания таблицы или псевдонима таблицы через точку. |
||
sortDesc($nameColumn) |
Сортируем только по одному указанному столбцу (с ключевым словом desc ).ВАЖНО: указываем только название столбца. |
||
sorts($namesColumns) |
Сортируем по нескольким столбцам, указанным в массиве.
sorts(['size_ram desc', 'date']);ВАЖНО: указываем только название столбцов, без указания таблицы. |
||
pagination($currentPage, $numberRowOnPage) |
При выборке из БД получаем $numberRowOnPage записей, находящихся на
$currentPage странице.
|
||
all($options = null) |
Возвращает все строки запроса. Если нужна агрегация, то $options должна быть массивом, подробнее описано ниже
в пункте Функции all() и aggregator(). Если нужна пагинация, то необходимо сначала с помощью
функции pagination указать номер страницы и количество записей на странице.
Также необходимо в $options передать строку - название колонки первичного ключа,
например: all('id') . Если же в $options описаны правила агрегации,
то первичный ключ берется оттуда (подробнее в пункте Функции all() и aggregator()).
|
||
one($nameColumn = null) |
Возвращает одну строку запроса. В случае если в результате запроса получаем больше одной
строки - вызывается ошибка. Данную функцию необходимо использовать, когда мы ожидаем получить
только одну строку. Если указать название колонки – $nameColumn , то получим содержимое
конкретной ячейки.
|
||
first($nameColumn = null) |
Возвращает первую строку запроса. Если указать название колонки – $nameColumn , то получим
содержимое конкретной ячейки.
|
||
query() |
Особая функция, которая может выполнить любой sql запрос и возвращает результат выполнения.
Если запрос - это выборка из БД, то данная функция может работать со всеми функциями из зеленой
группы кроме: sort, sortDesc, sorts, pagination .
|
||
insert($table, $parameters, $primaryKey = null) |
Вставка в таблицу $table данных $parameters .
Структура массива $parameters :
[ 'название_колонки' => 'значение', 'название_колонки' => ["LOWER('AAAAA')"], ...... ]ВАЖНО: Значение автоматически оборачивается псевдонимом и добавляется в массив биндов. Если необходимо избежать этой логики и сделать так, чтобы значение вставилось в sql запрос напрямую, необходимо значение записать в квадратных скобках (в массиве). $primaryKey - название колонки первичного ключа (с автоинкрементом). Если
$primaryKey указана, то функция возвращает id новой строки, а в приватную переменную
rawData (класса Query) записывается целиком вся новая строчка, получить которую можно
с помощью одноименной функции (для mySql чтобы функция вернула вставленную запись параметр
$primaryKey обязателен).
|
||
update($table, $parameters, $where, $union = null) |
Обновление данных в таблице $table . Структура массива $parameters :
[ 'название_колонки' => значение, 'название_колонки' => ["LOWER('AAAAA')"], ...... ]ВАЖНО: Значение автоматически оборачивается псевдонимом и добавляется в массив биндов. Если необходимо избежать этой логики и сделать так, чтобы значение вставилось в sql запрос напрямую, необходимо значение записать в квадратных скобках (в массиве). $where может быть экземпляром класса Where , в таком случае
параметр $union не нужно указывать.Либо же можно использовать упрощенный вариант и сразу передать в $where массив
логических операций (смотри раздел WHERE). В таком случае, если передано больше
одного условия, необходимо указать в параметре $union какая будет логическая
связка ('OR' или 'AND').
// одно условие (без $union) update('student', ['name' => 'Глаша'], ['id', '=', 1]); // несколько условий (с $union) update('student', ['name' => 'noName'], [ ['name', 'like', '*аша'], ['semester_number', '=', 4] ], 'and');Функция возвращает все измененные строки. |
||
delete($table, $where, $union = null) |
Удаляет данные в таблице $table . $where может быть экземпляром класса
Where , в таком случае параметр $union не нужно указывать.Либо же можно использовать упрощенный вариант и сразу передать в $where массив
логических операций (смотри раздел WHERE). В таком случае, если передано больше
одного условия, необходимо указать в параметре $union какая будет логическая
связка ('OR' или 'AND').Функция возвращает все удаленные строки. |
||
rawData() |
Функции из желтой и красной группы получают данные из БД. Функция rawData() возвращает
исходные данные, полученные одной из этих функций.
|
||
aggregateData() | Функция возвращает агрегированные данные, полученные в результате выборки из БД. Как происходит агрегация данных описано в пункте Функции all() и aggregator(). | ||
getPagination() |
Получаем массив данных о пагинации. Пример возвращаемого массива:
[ 'currentPage' => 1, // текущая страница 'numberRowOnPage' => 10, // количество строк запроса на странице 'numberAllRow' => 100, // сколько всего строк запроса 'numberAllPage' => 10 // сколько всего страниц ] |
||
column($nameColumn) |
Если в исходных данных (смотри функцию rawData() ) было множество строк, то данная функция
формирует и возвращает обычный одномерный массив. Элементами сгенерированного массива являются данные из
колонки $nameColumn . Если в исходных данных только одна строка (например при вызове функций:
one, first, insert ), то функция возвращает данные конкретной ячейки.
|
||
map($pk, $columns = []) |
Функция из исходного массива выборки формирует и возвращает ассоциативный массив,
ключами которого будут данные из колонки $pk . Если параметр $columns не указывать, то
данные из всех колонок сохраняться, но можно задать массив из названий колонок и
тогда, в результате, будут данные только из перечисленных колонок. Пример:
// исходный массив выборки // [ // 0 => [ 'inn' => 111111222222, // 'name_org' => 'Красный буржуй', // 'address' => 'ул.Ленина д.3' ], // 1 => [ 'inn' => 333333444444, // 'name_org' => 'MakeGreatAgain', // 'address' => 'ул.Трампа д.666' ], // 2 => [ 'inn' => 555555666666, // 'name_org' => 'Собчак', // 'address' => 'ул.Лошадиная д.40' ] // ] map('inn'); // результат // [ // 111111222222 => [ 'inn' => 111111222222, // 'name_org' => 'Красный буржуй', // 'address' => 'ул.Ленина д.3' ], // 333333444444 => [ 'inn' => 333333444444, // 'name_org' => 'MakeGreatAgain', // 'address' => 'ул.Трампа д.666' ], // 555555666666 => [ 'inn' => 555555666666, // 'name_org' => 'Собчак', // 'address' => 'ул.Лошадиная д.40' ] // ] map('inn', ['name_org']); // результат // [ // 111111222222 => ['name_org' => 'Красный буржуй'], // 333333444444 => ['name_org' => 'MakeGreatAgain'], // 555555666666 => ['name_org' => 'Собчак'] // ] |
||
aggregator($option, $rawData) |
Преобразует массив $rawData в новый согласно правилам, описанным в
$option . Подробнее принцип работы функции описан в пункте Функции all() и aggregator().
|
||
debugInfo() |
Пока существует экземпляр класса (переменная), все запросы к БД логируются и данная
функция возвращает массив информации для отладки, пример:
[ 0 => [ 'sql' => 'SELECT * FROM table WHERE id = :id', // выполняемый sql 'binds' => ['id' => 1], // массив биндов 'timeQuery' => 0.012 // время выполнения запроса ], ......... ] |
Функции all() и aggregator()
Проще всего рассмотреть принцип работы функций на основе примера. В качестве примера возьмём вот такую схему базы данных:
Cодержимое таблиц:
mark (m) | |||
id | student id | lesson id | mark |
---|---|---|---|
1 | 1 | 1 | 3 |
2 | 1 | 1 | 4 |
3 | 1 | 2 | 2 |
4 | 1 | 2 | 2 |
5 | 1 | 2 | 3 |
6 | 1 | 4 | 5 |
7 | 2 | 1 | 2 |
8 | 2 | 1 | 3 |
9 | 2 | 2 | 5 |
10 | 2 | 4 | 4 |
11 | 2 | 3 | 3 |
12 | 2 | 3 | 2 |
13 | 2 | 2 | 5 |
14 | 2 | 3 | 3 |
15 | 2 | 1 | 2 |
16 | 3 | 4 | 2 |
17 | 3 | 1 | 5 |
18 | 3 | 2 | 2 |
19 | 3 | 4 | 3 |
20 | 3 | 3 | 4 |
coursework (c) | |||
id | student id | title | semester number |
---|---|---|---|
1 | 1 | Архитектура и производительность серверных ЦП | 1 |
2 | 2 | Закон единства и борьбы противоположностей | 1 |
3 | 2 | Серверные платформы RISC/UNIX | 2 |
4 | 2 | Вредоносное программное обеспечение | 3 |
5 | 2 | Графическое программирование на Ms Fortran | 4 |
6 | 3 | «Азы» программирования и обучающие программы | 1 |
7 | 3 | История возникновения Интернета | 2 |
student (s) | |||
id | name | semester number | |
---|---|---|---|
1 | Даша | 1 | |
2 | Маша | 4 | |
3 | Паша | 2 |
lesson (l) | |||
id | name | ||
---|---|---|---|
1 | КоМод | ||
2 | Матан | ||
3 | Физ-ра | ||
4 | Философия |
Зачастую при запросе к БД хочется получить не плоский ответ, а древовидный. К примеру, выполнив такой запрос:
SELECT s.id, s.name, c.id title_id, c.title FROM student s INNER JOIN coursework c ON c. student_id = s.id WHERE s.id = 3
Получим плоский результат:
[ 0 => [ 'id' => 3, 'name' => 'Паша', 'title_id' => 6, 'title' => '«Азы» программирования и обучающие программы', ], 1=> [ 'id' => 3, 'name' => 'Паша', 'title_id' => 7, 'title' => 'История возникновения Интернет' ] ]
Здесь данные из таблицы student дублируются, а мы бы хотели увидеть такой результат:
[ 3 => [ 'name' => 'Паша', 'courseworks' => [ 6 => ['title' => '«Азы» программирования и обучающие программы'], 7 => ['title' => 'История возникновения Интернет'] ] ] ]
Чтобы добиться такого результата, необходимо в функцию all
передать массив с опциями:
all([ 'id'=> 'pk', 'name' => 'name', 'courseworks' => [ 'title_id' => 'pk', 'title' => 'title' ] ])
Массив $option
в функциях aggregator($option
, $rawData) и
all($options
) строится по следующим правилам:
- Ключи массива – названия колонок. Элементы массива – новые названия для колонок, можно вписать старое название.
- Существует одно ключевое слово для значения массива – pk. Оно говорит, что данные будут сгруппированы по этой колонке (название колонки - это ключ у значения pk). На каждом уровне должен быть только один pk. В агрегированнам (результирующем) массиве в качестве ключей будут использоваться значения из колонки, объявленной pk.
- Если необходимо часть колонок поместить на уровень ниже, то в качестве ключа массива используется новое, выдуманное название, а в качестве значения будет массив, строящийся по правилам описанным выше.
Рассмотрим более сложный пример. Допустим нам надо получить всех студентов с названием их курсовых и со всеми оценками по всем предметам. Мы бы хотели получить это не в плоском виде, а в древовидном, без дублей. Ниже приведен нужный запрос к БД и результат.
SELECT s.id student_id, s.name student_name, s.semester_number, c.id coursework_id, c.semester_number coursework_semester, c.title coursework_title, l.id lesson_id, l.name lesson, m.id mark_id, m.mark FROM student s LEFT JOIN coursework c ON c.student_id = s.id LEFT JOIN mark m ON m.student_id = s.id LEFT JOIN lesson l ON l.id = m.lesson_id ORDER BY s.id, c.id, l.id, m.id
student id | student name | semester number | coursework id | coursework semester | coursework title | lesson id | lesson | mark id | mark |
---|---|---|---|---|---|---|---|---|---|
с таблицы: student | с таблицы: coursework | с таблицы: lesson | с таблицы: mark | ||||||
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 1 | КоМод | 1 | 3 |
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 1 | КоМод | 2 | 4 |
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 2 | Матан | 3 | 2 |
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 2 | Матан | 4 | 2 |
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 2 | Матан | 5 | 3 |
1 | Даша | 1 | 1 | 1 | Архитектура и производительность серверных ЦП | 4 | Философия | 6 | 5 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 1 | КоМод | 7 | 2 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 1 | КоМод | 8 | 3 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 1 | КоМод | 15 | 2 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 2 | Матан | 9 | 5 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 2 | Матан | 13 | 5 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 3 | Физ-ра | 11 | 3 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 3 | Физ-ра | 12 | 2 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 3 | Физ-ра | 14 | 3 |
2 | Маша | 4 | 2 | 1 | Закон единства и борьбы противоположностей | 4 | Философия | 10 | 4 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 1 | КоМод | 7 | 2 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 1 | КоМод | 8 | 3 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 1 | КоМод | 15 | 2 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 2 | Матан | 9 | 5 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 2 | Матан | 13 | 5 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 3 | Физ-ра | 11 | 3 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 3 | Физ-ра | 12 | 2 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 3 | Физ-ра | 14 | 3 |
2 | Маша | 4 | 3 | 2 | Серверные платформы RISC/UNIX | 4 | Философия | 10 | 4 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 1 | КоМод | 7 | 2 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 1 | КоМод | 8 | 3 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 1 | КоМод | 15 | 2 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 2 | Матан | 9 | 5 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 2 | Матан | 13 | 5 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 3 | Физ-ра | 11 | 3 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 3 | Физ-ра | 12 | 2 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 3 | Физ-ра | 14 | 3 |
2 | Маша | 4 | 4 | 3 | Вредоносное программное обеспечение | 4 | Философия | 10 | 4 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 1 | КоМод | 7 | 2 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 1 | КоМод | 8 | 3 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 1 | КоМод | 15 | 2 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 2 | Матан | 9 | 5 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 2 | Матан | 13 | 5 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 3 | Физ-ра | 11 | 3 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 3 | Физ-ра | 12 | 2 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 3 | Физ-ра | 14 | 3 |
2 | Маша | 4 | 5 | 4 | Графическое программирование на Ms Fortran | 4 | Философия | 10 | 4 |
3 | Паша | 2 | 6 | 1 | «Азы» программирования и обучающие программы | 1 | КоМод | 17 | 5 |
3 | Паша | 2 | 6 | 1 | «Азы» программирования и обучающие программы | 2 | Матан | 18 | 2 |
3 | Паша | 2 | 6 | 1 | «Азы» программирования и обучающие программы | 3 | Физ-ра | 20 | 4 |
3 | Паша | 2 | 6 | 1 | «Азы» программирования и обучающие программы | 4 | Философия | 16 | 2 |
3 | Паша | 2 | 6 | 1 | «Азы» программирования и обучающие программы | 4 | Философия | 19 | 3 |
3 | Паша | 2 | 7 | 2 | История возникновения Интернета | 1 | КоМод | 17 | 5 |
3 | Паша | 2 | 7 | 2 | История возникновения Интернета | 2 | Матан | 18 | 2 |
3 | Паша | 2 | 7 | 2 | История возникновения Интернета | 3 | Физ-ра | 20 | 4 |
3 | Паша | 2 | 7 | 2 | История возникновения Интернета | 4 | Философия | 16 | 2 |
3 | Паша | 2 | 7 | 2 | История возникновения Интернета | 4 | Философия | 19 | 3 |
Чтобы добиться поставленной задачи, нужно написать следующий массив $option:
$option = [ 'student_id' => 'pk', 'student_name' => 'name', 'courseworks' => [ 'coursework_semester' => 'pk', 'coursework_title' => 'title' ], 'lessons' => [ 'lesson_id' => 'pk', 'lesson' => 'lesson', 'marks' => [ 'mark_id' => 'pk', 'mark' => 'mark' ] ] ]; // Создаем экземпляры классов Query с помощью фабрики. Подробнее в документации в разделе УСТАНОВКА // (здесь случай компонента для yii2) $qr = Yii::$app->gp->query("SELECT s.id student_id, s.name student_name, s.semester_number, c.id coursework_id, c.semester_number coursework_semester, c.title coursework_title, l.id lesson_id, l.name lesson, m.id mark_id, m.mark FROM student s LEFT JOIN coursework c ON c.student_id = s.id LEFT JOIN mark m ON m.student_id = s.id LEFT JOIN lesson l ON l.id = m.lesson_id ORDER BY s.id, c.id, l.id, m.id");
Запрос к БД:
// результат варианта 1, варианта 2 и варианта 3 одинаковые // вариант 1 $result = $qr->all($option); // вариант 2 $result = $qr->aggregator($option, $qr->all()); // вариант 3 $qr->all(); $result = $qr->aggregator($option, $qr->rawData());
Функция aggregator
может обработать любой массив со структурой схожей с результатом запроса к
БД, по правилам описанным в $option
.
Переменная $result
содержит следующие данные:
[ 1 => [ 'name' => 'Даша', 'courseworks' => [ 1 => ['title' => 'Архитектура и производительность серверных ЦП'], ], 'lessons' => [ 1 => [ 'lesson' => 'КоМод', 'marks' => [ 1 => ['mark' => 3], 2 => ['mark' => 4] ] ], 2 => [ 'lesson' => 'Матан', 'marks' => [ 3 => ['mark' => 2], 4 => ['mark' => 2], 5 => ['mark' => 3] ] ], 4 => [ 'lesson' => 'Философия', 'marks' => [ 6 => ['mark' => 5] ] ] ] ], 2 => [ 'name' => 'Маша', 'courseworks' => [ 1 => ['title' => 'Закон единства и борьбы противоположностей'], 2 => ['title' => 'Серверные платформы RISC/UNIX'], 3 => ['title' => 'Вредоносное программное обеспечение'], 4 => ['title' => 'Графическое программирование на Ms Fortran'], ], 'lessons' => [ 1 => [ 'lesson' => 'КоМод', 'marks' => [ 7 => ['mark' => 2], 8 => ['mark' => 3], 15 => ['mark' => 2] ] ], 2 => [ 'lesson' => 'Матан', 'marks' => [ 9 => ['mark' => 5], 13 => ['mark' => 5] ] ], 3 => [ 'lesson' => 'Физ-ра', 'marks' => [ 11 => ['mark' => 3], 12 => ['mark' => 2], 14 => ['mark' => 3] ] ], 4 => [ 'lesson' => 'Философия', 'marks' => [ 10 => ['mark' => 4] ] ], ] ], 3 => [ 'name' => 'Паша', 'courseworks' => [ 1 => ['title' => '«Азы» программирования и обучающие программы'], 2 => ['title' => 'История возникновения Интернета'] ], 'lessons' => [ 1 => [ 'lesson' => 'КоМод', 'marks' => [ 17 => ['mark' => 5] ] ], 2 => [ 'lesson' => 'Матан', 'marks' => [ 18 => ['mark' => 2] ] ], 3 => [ 'lesson' => 'Физ-ра', 'marks' => [ 20 => ['mark' => 4] ] ], 4 => [ 'lesson' => 'Философия', 'marks' => [ 16 => ['mark' => 2], 19 => ['mark' => 3] ] ], ] ] ]
Пагинация с агрегированным запросом считает только верхние, самые основные данные. В приведенном выше примере для пагинации будет только 3 строчки.
whereWithJoin()
whereWithJoin ($aliasJoin, $options, $aliasWhere, $where) - при запросе на выборку функция генерирует sql как для where части, так и для join части.
$aliasJoin - псевдоним в базовом шаблоне вместо которого подставится sql часть с join’ами.
$options - массив с описаниями правил генерации join части.
$aliasWhere - псевдоним в базовом шаблоне вместо которого подставится where sql часть.
$where - экземпляр класса Where.
$options рассмотрим на примере:
$options = ['v' => ['val', 'product_id', 'p.id']]; whereWithJoin('/*join*/', $options, '/*where*/', $wh)
v – псевдоним таблицы. Если данный псевдоним встретится в $wh, то будет подключена join’ом нова таблица val (где product_id это внешний ключ таблицы val, а p.id это первичный ключ для таблицы с псевдонимом p) , для нее сгенерирован новый псевдоним и этот псевдоним заменит v.
Внутри одной скобки псевдонимы будут одинаковые (использована одна таблица). Ниже в примерах это видно.
Бывает необходимо написать запрос в рамках одной таблицы с поиском по нескольким столбцам, причем условие по нескольким столбцам описывает одну сущность (со связью И), а нам нужно поискать несколько сущностей. К примеру в таблице t есть две колонки cl1 и cl2. Составим запрос на псевдокоде:
(t.cl1 = x И t.cl1 = y)
И
(t.cl1 = x' И t.cl1 = y')
И т.д.
Данный запрос вернет нам пустую выборку, для корректной работы необходимо таблицу t сджойнить, это уже будет таблица t', тогда правильный запрос будет:
(t.cl1 = x И t.cl1 = y)
И
(t'.cl1 = x' И t'.cl1 = y')
И т.д.
На практике это может понадобиться при поиске по динамическим свойствам динамических объектов. Давайте рассмотрим этот вариант на примере.
Сделаем упрощенный, демонстрационный вариант. У нас есть таблица product, куда записываются новые, поступающие товары. У нее будет 2 колонки: тип товара и id. Так же будет табличка properties, где будут хранится все свойства товаров. В табличке val будут хранится id товара, id свойств этого товара и значения этих свойств. Вот содержимое таблиц:
К примеру пользователь на фронтенде сформировал следующее условие:
«Получить все товары у которых лошадиных сил меньше 100 или диагональ экрана меньше 6 дюймов. Также товары должны весить меньше тонны и быть в эксплуатации больше месяца.»
// Создаем экземпляры классов Query и Where с помощью фабрики, // подробнее в документации в разделе УСТАНОВКА // (здесь случай компонента для yii2). $qr = Yii::$app->gp->query(); $wh = Yii::$app->gp->where(); // Из пришедшего с фронтенда условия формируем наше условие по правилам класса Where. $wh->linkAnd([ $wh->linkOr([ $wh->linkAnd([['v.properties_id', '=', 3], ['v.value', '<', 100]])->getRaw(), $wh->linkAnd([['v.properties_id', '=', 4], ['v.value', '<', 6]])->getRaw(), ])->getRaw(), $wh->linkAnd([['v.properties_id', '=', 1], ['v.value', '<', 1000]])->getRaw(), $wh->linkAnd([['v.properties_id', '=', 2], ['v.value', '>=', 1]])->getRaw() ]); // Мы будем использовать 2 раза функцию all(), а whereWithJoin() изменяет $wh, // поэтому нам необходимо клонировать $wh для повторного использования. $wh2 = clone $wh; $rez = $qr->sql("select p.id, p.type, v.id val_id, pr.name, v.value from product p inner join val v on v.product_id = p.id inner join properties pr on pr.id = v.properties_id /*join*/ /*where*/ ") ->whereWithJoin('/*join*/', ['v' => ['val', 'product_id', 'p.id']], '/*where*/', $wh) ->all();
Выведем результат запроса (переменную $rez
):
[ [0] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => 'вес (кг)' ['value'] => 790 ], [1] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 24 ], [2] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => 'лошадиные силы' ['value'] => 75 ], [3] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => 'диагональ экрана' ['value'] => 5 ], [4] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => 'вес (кг)' ['value'] => 0.12 ], [5] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 1 ], [6] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => 'диагональ экрана' ['value'] => 5 ], [7] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => 'вес (кг)' ['value'] => 0.12 ], [8] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 1 ], [9] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => 'диагональ экрана' ['value'] => 5 ], [10] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => 'вес (кг)' ['value'] => 0.12 ], [11] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 1 ], [12] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => 'вес (кг)' ['value'] => 790 ], [13] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 24 ], [14] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => 'лошадиные силы' ['value'] => 75 ], [15] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => 'вес (кг)' ['value'] => 790 ], [16] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => 'количество месяцев в эксплуатации' ['value'] => 24 ], [17] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => 'лошадиные силы' ['value'] => 75 ] ]
Выведем информацию для отладки, посмотрим какой же сформировался итоговый sql
запрос ($qr->debugInfo()
):
[ [type] => info [sql] => select p.id, p.type, v.id val_id, pr.name, v.value from product p inner join val v on v.product_id = p.id inner join properties pr on pr.id = v.properties_id inner JOIN val val_B7FRUCQDYr ON val_B7FRUCQDYr.product_id = p.id inner JOIN val val_ROahQ70vaX ON val_ROahQ70vaX.product_id = p.id inner JOIN val val_XqSsf6DgDT ON val_XqSsf6DgDT.product_id = p.id inner JOIN val val_54QBLB4s4S ON val_54QBLB4s4S.product_id = p.id WHERE ( (val_B7FRUCQDYr.properties_id = :al_where_JMxKTdHKyH and val_B7FRUCQDYr.value < :al_where_xmYgE3c79G) or (val_ROahQ70vaX.properties_id = :al_where_R5AxxcZruU and val_ROahQ70vaX.value < :al_where_a4wxEgAbQZ) ) and (val_XqSsf6DgDT.properties_id = :al_where_JdbyjGrmEr and val_XqSsf6DgDT.value < :al_where_SlnjJKmy2H) and (val_54QBLB4s4S.properties_id = :al_where_i2BEp5KQ7r and val_54QBLB4s4S.value >= :al_where_FHuH5EdnQH) [binds] => [ [al_where_JMxKTdHKyH] => 3 [al_where_xmYgE3c79G] => 100 [al_where_R5AxxcZruU] => 4 [al_where_a4wxEgAbQZ] => 6 [al_where_JdbyjGrmEr] => 1 [al_where_SlnjJKmy2H] => 1000 [al_where_i2BEp5KQ7r] => 2 [al_where_FHuH5EdnQH] => 1 ] [timeQuery] => 0.0209629535675 ]
Как видим из результата запроса, мы получили 18 записей, хотя по сути тут всего 2 товара, давайте
приведем результат к древовидному виду и избавимся от дублей с помощью функции aggregator()
или выполнив запрос с массивом настроек агрегации:
$rez = $qr->whereWithJoin('/*join*/', ['v' => ['val', 'product_id', 'p.id']], '/*where*/', $wh2) ->all([ 'id'=> 'pk', 'type'=> 'prod_type', 'properties' => [ 'val_id' => 'pk', 'name' => 'name', 'value' => 'value' ] ]);
Выведем результат запроса (переменную $rez
):
[ [1] => [ ['prod_type'] => 'car' ['properties'] => [ [1] => [ ['name'] => 'вес (кг)' ['value'] => 790 ] [2] => [ ['name'] => 'количество месяцев в эксплуатации' ['value'] => 24 ] [3] => [ ['name'] => 'лошадиные силы' ['value'] => 75 ] ] ] [4] => [ ['prod_type'] => 'phone' ['properties'] => [ [10] => [ ['name'] => 'диагональ экрана' ['value'] => 5 ] [8] => [ ['name'] => 'вес (кг)' ['value'] => 0.12 ] [9] => [ ['name'] => 'количество месяцев в эксплуатации' ['value'] => 1 ] ] ] ]
Примеры
Пример 1
Необходимо получить список студентов и количество каждой из оценок (сколько 2, 3, 4 и 5) по всем предметам. Также нужно использовать постраничное разбиение, показать результаты первой страницы (по 2 записи на странице).
В sorts
название столбцов пишутся без префиксов.
// $qr - экземпляр класса Query $rez = $qr->sql("select /*select*/ from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id /*where*/ /*group*/") ->sqlPart('/*select*/', 's.id, s.name name_student, l.id id_lesson, l.name name_lesson, m.mark, count(m.mark) count_mark', []) ->sqlPart('/*group*/', 'group by s.id, s.name, l.id, l.name, m.mark', []) ->pagination(1, 2) ->sorts(['id']) ->all([ 'ID' => 'pk', 'NAME_STUDENT' => 'name', 'lessons' => [ 'ID_LESSON' => 'pk', 'NAME_LESSON' => 'name', 'marks' => [ 'MARK' => 'pk', 'COUNT_MARK' => 'count_mark' ] ] ]);
var_dump($qr->aggregateData()); // или var_dump($rez) - один и тотже результат // Результат: [ 1 => [ 'name' => Даша, 'lessons' => [ 1 => [ 'name' => КоМод, 'marks' => [ 3 => ['count_mark' => 1], 4 => ['count_mark' => 1] ] ], 2 => [ 'name' => Матан, 'marks' => [ 2 => ['count_mark' => 2], 3 => ['count_mark' => 1] ] ], 4 => [ 'name' => Философия, 'marks' => [ 5 => ['count_mark' => 1] ] ] ] ], 2 => [ 'name' => Маша, 'lessons' => [ 1 => [ 'name' => КоМод, 'marks' => [ 2 => ['count_mark' => 2], 3 => ['count_mark' => 1] ] ], 2 => [ 'name' => Матан, 'marks' => [ 5 => ['count_mark' => 2] ] ], 3 => [ 'name' => Физ-ра, 'marks' => [ 2 => ['count_mark' => 1], 3 => ['count_mark' => 2] ] ], 4 => [ 'name' => Философия, 'marks' => [ 4 => ['count_mark' => 1] ] ] ] ] ]
var_dump($qr->rawData()); // Результат: array (size=11) 0 => array (size=6) 'ID' => string '1' (length=1) 'NAME_STUDENT' => string 'Даша' (length=8) 'ID_LESSON' => string '1' (length=1) 'NAME_LESSON' => string 'КоМод' (length=10) 'MARK' => string '3' (length=1) 'COUNT_MARK' => string '1' (length=1) 1 => array (size=6) 'ID' => string '1' (length=1) 'NAME_STUDENT' => string 'Даша' (length=8) 'ID_LESSON' => string '1' (length=1) 'NAME_LESSON' => string 'КоМод' (length=10) 'MARK' => string '4' (length=1) 'COUNT_MARK' => string '1' (length=1) 2 => array (size=6) 'ID' => string '1' (length=1) 'NAME_STUDENT' => string 'Даша' (length=8) 'ID_LESSON' => string '2' (length=1) 'NAME_LESSON' => string 'Матан' (length=10) 'MARK' => string '2' (length=1) 'COUNT_MARK' => string '2' (length=1) 3 => array (size=6) 'ID' => string '1' (length=1) 'NAME_STUDENT' => string 'Даша' (length=8) 'ID_LESSON' => string '2' (length=1) 'NAME_LESSON' => string 'Матан' (length=10) 'MARK' => string '3' (length=1) 'COUNT_MARK' => string '1' (length=1) 4 => array (size=6) 'ID' => string '1' (length=1) 'NAME_STUDENT' => string 'Даша' (length=8) 'ID_LESSON' => string '4' (length=1) 'NAME_LESSON' => string 'Философия' (length=18) 'MARK' => string '5' (length=1) 'COUNT_MARK' => string '1' (length=1) 5 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '1' (length=1) 'NAME_LESSON' => string 'КоМод' (length=10) 'MARK' => string '2' (length=1) 'COUNT_MARK' => string '2' (length=1) 6 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '1' (length=1) 'NAME_LESSON' => string 'КоМод' (length=10) 'MARK' => string '3' (length=1) 'COUNT_MARK' => string '1' (length=1) 7 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '2' (length=1) 'NAME_LESSON' => string 'Матан' (length=10) 'MARK' => string '5' (length=1) 'COUNT_MARK' => string '2' (length=1) 8 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '3' (length=1) 'NAME_LESSON' => string 'Физ-ра' (length=11) 'MARK' => string '2' (length=1) 'COUNT_MARK' => string '1' (length=1) 9 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '3' (length=1) 'NAME_LESSON' => string 'Физ-ра' (length=11) 'MARK' => string '3' (length=1) 'COUNT_MARK' => string '2' (length=1) 10 => array (size=6) 'ID' => string '2' (length=1) 'NAME_STUDENT' => string 'Маша' (length=8) 'ID_LESSON' => string '4' (length=1) 'NAME_LESSON' => string 'Философия' (length=18) 'MARK' => string '4' (length=1) 'COUNT_MARK' => string '1' (length=1)
var_dump($qr->getPagination()); // Результат: [ 'currentPage' => 1, 'numberRowOnPage' => 2, 'numberAllRow' => 3, 'numberAllPage' => 2 ]
var_dump($qr->column('NAME_STUDENT')); // Результат: [ 0 => 'Даша', 1 => 'Даша', 2 => 'Даша', 3 => 'Даша', 4 => 'Даша', 5 => 'Маша', 6 => 'Маша', 7 => 'Маша', 8 => 'Маша', 9 => 'Маша', 10 => 'Маша' ]
var_dump($qr->map('ID', ['NAME_STUDENT'])); // Результат: [ 1 => ['NAME_STUDENT' => 'Даша'], 2 => ['NAME_STUDENT' => 'Маша'] ]
var_dump($qr->map('ID')); // Результат: [ 1 => [ 'ID' => '1', 'NAME_STUDENT' => 'Даша', 'ID_LESSON' => '4', 'NAME_LESSON' => 'Философия', 'MARK' => '5', 'COUNT_MARK' => '1' ], 2 => [ 'ID' => '2', 'NAME_STUDENT' => 'Маша', 'ID_LESSON' => '4', 'NAME_LESSON' => 'Философия', 'MARK' => '4', 'COUNT_MARK' => '1' ] ]
Для студентки Даши получить средний бал по всем предметам.
$rez = $qr->sqlPart('/*select*/', 's.name, avg(m.mark) average_mark', []) ->whereAnd('/*where*/', ['s.id', '=', 1]) ->sqlPart('/*group*/', 'group by s.name', []) ->one('AVERAGE_MARK'); print_r($rez); // Результат: 3,16666666666666666666666666666666666667
print_r($qr->debugInfo()); // Результат: [ 0 => [ 'sql' => "select * from ( select rownum rnum, ID from ( select ID from (select s.id, s.name name_student, l.id id_lesson, l.name name_lesson, m.mark, count(m.mark) count_mark from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id /*where*/ group by s.id, s.name, l.id, l.name, m.mark) group by ID ORDER BY id ) ) where rnum >= :indexStart and rnum < :indexEnd", 'binds' => [ 'indexStart' => 1, 'indexEnd' => 3 ], 'timeQuery' => 0.0026788711547852 ], 1 => [ 'sql' => "SELECT FROM (SELECT FROM (select s.id, s.name name_student, l.id id_lesson, l.name name_lesson, m.mark, count(m.mark) count_mark from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id /*where*/ group by s.id, s.name, l.id, l.name, m.mark) WHERE ID IN (1,2)) ORDER BY id", 'binds' => [], 'timeQuery' => 0.011198043823242 ], 2 => [ 'sql' => "SELECT COUNT(*) as total_count FROM ( select s.name, avg(m.mark) average_mark from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id WHERE s.id = :al_where_2NE2KdmIG7 group by s.name ) a", 'binds' => ['al_where_2NE2KdmIG7' => 1], 'timeQuery' => 0.012115955352783 ], 3 => [ 'sql' => "select s.name, avg(m.mark) average_mark from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id WHERE s.id = :al_where_2NE2KdmIG7 group by s.name", 'binds' => ['al_where_2NE2KdmIG7' => 1], 'timeQuery' => 0.019691944122314 ] ]
Пример 2
Пользователь должен иметь возможность получить всех студентов, которые подходят под указанные им критерии, а именно пользователь указывает оценки и массив id предметов, у которых должна встречаться такая оценка.
// Функция принимает массив, где ключи - это оценки, а значения – массив id предметов, у которых // должна встречаться такая оценка. На выходе функция возвращает экземпляр объекта CollectionJoin function joinForMarks ($marks) { // Создаем экземпляры классов CollectionJoin и Join с помощью фабрики, подробнее // в документации в разделе УСТАНОВКА (здесь случай компонента для yii2). $cJn = Yii::$app->gp->collectionJoin(); foreach ($marks as $mark => $lessonId) { $join = Yii::$app->gp->innerJoin('mark', 'student_id', 's.id'); $join->linkAnd([[$join->getAlias() . '.mark', '=', $mark], [$join->getAlias() . '.lesson_id', 'in', $lessonId]]); $cJn->add($join); } return $cJn; } $cJoin = joinForMarks(['2' => [1,2,3,4], '5' => [1, 2]]); $rez = $qr->sql("select s.name from student s inner join mark m on s.id = m.student_id /*join*/ group by s.name") ->join('/*join*/', $cJoin)->sortDesc('name')->all(); // Представленные ниже 2 записи выполняют одно и тоже: // join('/*join*/', $cJoin) // sqlPart('/*join*/', $cJoin->generate()->getSql(), $cJoin->getBind())
В результате будет сгенерирован примерно следующий sql запрос:
select s.name from student s inner join mark m on s.id = m.student_id inner join mark m5 on s.id = m.student_id and m5.mark = 2 and m5.lesson_id in (1,2,3,4) inner join mark m2 on s.id = m.student_id and m5.mark = 5 and m5.lesson_id in (1,2) group by s.name
var_dump($rez)); // Результат: [ 0 => ['NAME' => 'Паша'] 1 => ['NAME' => 'Маша'] ]