Пример 11: запросы на объединение как способ получения человекочитаемых данных
Oracle Решение 2.2.1.b
1SELECT "b_name",
2"s_id",
3"s_name",
4"sb_start",
5"sb_finish"
6 FROM "books"
7JOIN "subscriptions"
8ON "b_id" = "sb_book"
9JOIN "subscribers"
10ON "sb_subscriber" = "s_id"
Логика решения 2.2.1.b{69} полностью эквивалентна логике решения 2.2.1.a{67} (здесь приходится объединять даже меньше таблиц — всего три, а не пять). Обратите внимание на тот факт, что если объединение происходит не по одноимённым полям, а по разноимённым, в MySQL и Oracle тоже приходится использовать конструкцию ON вместо USING.
Задание 2.2.1.TSK.A: показать список книг, у которых более одного автора.
Задание 2.2.1.TSK.B: показать список книг, относящихся ровно к одному жанру.
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 70/545
Пример 12: запросы на объединение и преобразование столбцов в строки
2.2.2.Пример 12: запросы на объединение и преобразование столбцов в строки
Возможно, вы заметили, что в решении{67} задачи 2.2.1.a{66} есть неудобство: если у книги несколько авторов и/или жанров, информация начинает дублироваться (упорядочим для наглядности выборку по полю b_name):
b_name |
a_name |
g_name |
Евгений Онегин |
А.С. Пушкин |
Классика |
Евгений Онегин |
А.С. Пушкин |
Поэзия |
Искусство программирования |
Д. Кнут |
Классика |
Искусство программирования |
Д. Кнут |
Программирование |
Курс теоретической физики |
Л.Д. Ландау |
Классика |
Курс теоретической физики |
Е.М. Лифшиц |
Классика |
Основание и империя |
А. Азимов |
Фантастика |
Психология программирования |
Д. Карнеги |
Программирование |
Психология программирования |
Б. Страуструп |
Программирование |
Психология программирования |
Д. Карнеги |
Психология |
Психология программирования |
Б. Страуструп |
Психология |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Классика |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Поэзия |
Язык программирования С++ |
Б. Страуструп |
Программирование |
Пользователи же куда больше привыкли к следующему представлению дан-
ных:
Книга |
Автор(ы) |
Жанр(ы) |
Евгений Онегин |
А.С. Пушкин |
Классика, Поэзия |
Искусство программирования |
Д. Кнут |
Классика, |
|
|
Программирование |
Курс теоретической физики |
Е.М. Лифшиц, |
Классика |
|
Л.Д. Ландау |
|
Основание и империя |
А. Азимов |
Фантастика |
Психология программирования |
Б. Страуструп, |
Программирование, |
|
Д. Карнеги |
Психология |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Классика, Поэзия |
Язык программирования С++ |
Б. Страуструп |
Программирование |
Задача 2.2.2.a{72}: показать все книги с их авторами (дублирование названий книг не допускается).
Задача 2.2.2.b{76}: показать все книги с их авторами и жанрами (дублирование названий книг и имён авторов не допускается).
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 71/545
Пример 12: запросы на объединение и преобразование столбцов в строки
Ожидаемый результат 2.2.2.a.
book |
author(s) |
|
|
Евгений Онегин |
А.С. Пушкин |
|
|
Искусство программирования |
Д. Кнут |
|
|
Курс теоретической физики |
Е.М. Лифшиц, Л.Д. Ландау |
|
|
Основание и империя |
А. Азимов |
|
|
Психология программирования |
Б. Страуструп, Д. Карнеги |
|
|
Сказка о рыбаке и рыбке |
А.С. Пушкин |
|
|
Язык программирования С++ |
Б. Страуструп |
|
|
Ожидаемый результат 2.2.2.b. |
|
|
|
|
|
|
|
book |
author(s) |
|
genre(s) |
Евгений Онегин |
А.С. Пушкин |
Классика, Поэзия |
|
Искусство программирования |
Д. Кнут |
Классика, |
|
|
|
Программирование |
|
Курс теоретической физики |
Е.М. Лифшиц, |
Классика |
|
|
Л.Д. Ландау |
|
|
Основание и империя |
А. Азимов |
Фантастика |
|
Психология программирования |
Б. Страуструп, |
Программирование, |
|
|
Д. Карнеги |
Психология |
|
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Классика, Поэзия |
|
Язык программирования С++ |
Б. Страуструп |
Программирование |
|
Решение 2.2.2.a{71}.
MySQL Решение 2.2.2.a
1SELECT `b_name`
2AS `book`,
3GROUP_CONCAT(`a_name` ORDER BY `a_name` SEPARATOR ', ')
4AS `author(s)`
5 FROM `books`
6JOIN `m2m_books_authors` USING(`b_id`)
7JOIN `authors` USING(`a_id`)
8 |
|
GROUP |
BY |
`b_id` |
9 |
|
ORDER |
BY |
`b_name` |
Решение для MySQL получается очень простым и элегантным потому, что эта СУБД поддерживает функцию GROUP_CONCAT, которая и выполняет всю основную работу. У этой функции очень развитый синтаксис (даже в нашем случае мы используем сортировку и указание разделителя), с которым обязательно стоит ознакомиться в официальной документации.
Обратите особое внимание на строку 8 запроса: ни в коем случае не стоит выполнять группировку по названию книги! Такая ошибка приводит к тому, что СУБД считает одной и той же книгой несколько разных книг с одинаковым названием (что в реальной жизни может встречаться очень часто). Группировка же по значению первичного ключа таблицы гарантирует, что никакие разные записи не будут смешаны в одну группу.
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 72/545
Пример 12: запросы на объединение и преобразование столбцов в строки
И ещё одна особенность MySQL заслуживает внимания: в строке 1 мы извлекаем поле b_name, которое не упомянуто в выражении GROUP BY в строке 8 и не является агрегирующей функцией. MS SQL Server и Oracle не позволяют поступать подобным образом и строго требуют, чтобы любое поле из конструкции SELECT, не являющееся агрегирующей функций, было явно упомянуто в выражении GROUP BY.
Итак, что делает GROUP_CONCAT? Фактически, «разворачивает часть столбца в строку» (одновременно упорядочивая авторов по алфавиту и разделяя их набором символов «, »):
b_name |
a_name |
|
author(s) |
Евгений Онегин |
А.С. Пушкин |
|
А.С. Пушкин |
Искусство программирования |
Д. Кнут |
|
Д. Кнут |
Курс теоретической физики |
Л.Д. Ландау |
|
Е.М. Лифшиц, Л.Д. Ландау |
Е.М. Лифшиц |
|||
Основание и империя |
А. Азимов |
|
А. Азимов |
Психология программирования |
Д. Карнеги |
|
Б. Страуструп, Д. Карнеги |
Б. Страуструп |
|||
Сказка о рыбаке и рыбке |
А.С. Пушкин |
|
А.С. Пушкин |
Язык программирования С++ |
Б. Страуструп |
|
Б. Страуструп |
MS SQL Server не поддерживает функцию GROUP_CONCAT, а потому решение для него весьма нетривиально:
MS SQL Решение 2.2.2.a
1WITH [prepared_data]
2AS (SELECT [books].[b_id],
3 |
|
|
[b_name], |
|
4 |
|
|
[a_name] |
|
5 |
|
FROM |
[books] |
|
6 |
|
|
JOIN |
[m2m_books_authors] |
7 |
|
|
ON |
[books].[b_id] = [m2m_books_authors].[b_id] |
8 |
|
|
JOIN |
[authors] |
9 |
|
|
ON |
[m2m_books_authors].[a_id] = [authors].[a_id] |
10)
11SELECT [outer].[b_name]
12AS [book],
13STUFF ((SELECT ', ' + [inner].[a_name]
14 |
|
|
FROM |
[prepared_data] AS [inner] |
15 |
|
|
WHERE |
[outer].[b_id] = [inner].[b_id] |
16 |
|
|
ORDER |
BY [inner].[a_name] |
17 |
|
|
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), |
|
18 |
|
|
1, 2, '') |
|
19 |
|
|
AS [author(s)] |
|
20 |
|
FROM |
[prepared_data] AS [outer] |
|
21GROUP BY [outer].[b_id],
22[outer].[b_name]
Начнём рассмотрение со строк 1-10. В них представлено т.н. CTE (Common Table Expression, общее табличное выражение). Очень упрощённо общее табличное выражение можно считать отдельным поименованным запросом, к результату выполнения которого можно обращаться как к таблице. Это особенно удобно, когда таких обращений в дальнейшем используется несколько (без общего табличного выражения с использованием классического подзапроса, тело такого подзапроса пришлось бы писать везде, где необходимо к нему обратиться).
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 73/545
Пример 12: запросы на объединение и преобразование столбцов в строки
В нашем случае общее табличное выражение возвращает такие данные:
b_id |
b_name |
a_name |
1 |
Евгений Онегин |
А.С. Пушкин |
2 |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
3 |
Основание и империя |
А. Азимов |
4 |
Психология программирования |
Д. Карнеги |
4 |
Психология программирования |
Б. Страуструп |
5 |
Язык программирования С++ |
Б. Страуструп |
6 |
Курс теоретической физики |
Л.Д. Ландау |
6 |
Курс теоретической физики |
Е.М. Лифшиц |
7 |
Искусство программирования |
Д. Кнут |
Это — уже почти готовое решение, останется только «развернуть в строку» части столбца a_name с авторами одной и той же книги. Эту задачу выполнят строки 13-19 запроса.
Основной рабочей частью процесса является код коррелирующего подза-
проса:
SELECT ', ' + [inner].[a_name]
FROM [prepared_data] AS [inner]
WHERE [outer].[b_id] = [inner].[b_id]
ORDER BY [inner].[a_name]
Для каждой строки результата, полученного из общего табличного выражения, выполняется подзапрос, возвращающий данные из этого же результата, относящиеся к рассматриваемой на внешнем уровне строке. Более простой вариант коррелирующего подзапроса мы уже рассматривали{49}, а теперь покажем, как работает СУБД в данном конкретном случае:
Строка из внешней |
|
Какие строки бу- |
Какие данные будут собраны |
части запроса |
|
дут обработаны |
|
(b_id) |
|
внутренней ча- |
|
|
|
стью запроса |
|
|
|
(b_id) |
|
1 |
1 |
|
, А.С. Пушкин |
2 |
2 |
|
, А.С. Пушкин |
3 |
3 |
|
, А. Азимов |
4 |
4 и 4 |
, Б. Страуструп, Д. Карнеги |
|
4 |
4 и 4 |
, Б. Страуструп, Д. Карнеги |
|
5 |
5 |
|
, Б. Страуструп |
6 |
6 |
и 6 |
, Е.М. Лифшиц, Л.Д. Ландау |
6 |
6 |
и 6 |
, Е.М. Лифшиц, Л.Д. Ландау |
7 |
7 |
|
, Д. Кнут |
Символы «, » (запятая и пробел), стоящие в начале каждого значения в столбце «Какие данные будут собраны» — не опечатка. Мы явно говорим СУБД извлекать именно текст в виде «, » + имя_автора. Чтобы в конечном результате этих символов не было, мы используем функцию STUFF.
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 74/545