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' => 'Маша'] ]