QUERY

Query – основной класс, для работы с БД, через него происходит выборка, запись, обновление и удаление данных. Также можно проводить определенную обработку данных, полученных из БД. Экземпляр класса получаем через фабрику (смотри раздел УСТАНОВКА).

Описание функций

Все функции класса условно можно разбить на 4 группы. Для удобства присвоим каждой группе свой цвет:

подготовка запроса к выборке - Функции из данной группы подготавливают запрос к выборке, поэтому они должны предшествовать функциям из желтой группы, причем внутри зеленой группы порядок вызова функций не имеет значения.

получение данных из БД - Функции в данной группе выполняют запрос на получение данных, подготовленный функциями зеленой группы.

изменение данных в БД - Функции из данной группы изменяют данные в базе. На них никак не действуют функции из зеленой группы.

результат запроса и/или его обработка - Функции в синей группе работают с данными, полученными из базы с помощью функций из желтой и красной группы (подробнее в описании функций).

Функция Описание
__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]
                ]
            ],

        ]
    ]
]


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 записи на странице).

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