Пример 11: запросы на объединение как способ получения человекочитаемых данных
Oracle Решение 2.2.1
1SELECT.b "b name"
2"s id",
3"s name"
4"sb start",
5"sb finish"
6 |
FROM "books" |
7 |
JOIN "subscriptions" |
8 |
ON "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 в примерах © Богдан Марчук Стр: 75/545
Пример 12: запросы на объединение и преобразование столбцов в строки
2.2.2.Пример 12: запросы на объединение и преобразование столбцов в строки
Возможно, вы заметили, что в решении{67} задачи 2.2.1.a{66} есть неудобство: если у книги несколько авторов и/или жанров, информация начинает дублироваться (упорядочим для наглядности выборку по полю b_name):
b_name |
a_name |
g_name |
Евгений Онегин |
А.С. Пушкин |
Классика |
Евгений Онегин |
А.С. Пушкин |
Поэзия |
Искусство программирования |
Д. Кнут |
Классика |
Искусство программирования |
Д. Кнут |
Программирование |
Курс теоретической физики |
Л.Д. Ландау |
Классика |
Курс теоретической физики |
Е.М. Лифшиц |
Классика |
Основание и империя |
А. Азимов |
Фантастика |
Психология программирования |
Д. Карнеги |
Программирование |
Психология программирования |
Б. Страуструп |
Программирование |
Психология программирования |
Д. Карнеги |
Психология |
Психология программирования |
Б. Страуструп |
Психология |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Классика |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Поэзия |
Язык программирования C++ |
Б. Страуструп |
Программирование |
Пользователи же куда больше привыкли к следующему представлению дан-
ных:
Книга |
Автор(ы) |
Жанр(ы) |
Евгений Онегин |
А.С. Пушкин |
Классика, Поэзия |
Искусство программирования |
Д. Кнут |
Классика, |
|
|
Программирование |
Курс теоретической физики |
Е.М. Лифшиц, Л.Д. |
Классика |
|
Ландау |
|
Основание и империя |
А. Азимов |
Фантастика |
Психология программирования |
Б. Страуструп, Д. |
Программирование, |
|
Карнеги |
Психология |
Сказка о рыбаке и рыбке |
А.С. Пушкин |
Классика, Поэзия |
Язык программирования С++ |
Б. Страуструп |
Программирование |
Задача 2.2.2. a{72}: показать все книги с их авторами (дублирование названий книг не допускается).
Задача 2.2.2. b{76}: показать все книги с их авторами и жанрами (дублирование названий книг и имён авторов не допускается).
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 76/545
Пример 12: запросы на объединение и преобразование столбцов в строки
Ожидаемый результат 2..2..2..ab..
|
|
|
book |
|
|
|
author(s)author(s) |
|
genre(s) |
|
|
|
Евгений Онегин |
|
|
А..С.. Пушкин |
|
|
Классика, Поэзия |
|
|||
|
Искусство программирования |
|
|
Д.. Кнут |
|
|
Классика, |
|
|||
|
Курс теоретической физики |
|
Е.М. Лифшиц, Л.Д. ЛандауПрограммирование |
|
|||||||
|
ОснованиеКурстеор тическойимперияфизики |
|
|
АЕ..МАзимов. Лифшиц, Л.Д. |
|
Классика |
|
||||
|
Психология программирования |
|
|
Ландау |
|
|
|
|
|||
|
|
|
Б. Страуструп, Д. Карнеги |
|
|||||||
|
СказкаОснованиерыбакеи империяыбке |
|
|
А..САзимов. Пушкин |
|
|
Фантастика |
|
|||
|
Психология |
|
|
|
Б. |
Страуструп |
, Д. |
|
Программирование, |
|
|
|
Язык программированпрограммированияС++ |
|
|
. |
|
|
|
|
|||
|
|
|
|
|
|
Карнеги |
|
|
Психология |
|
|
|
|
о рыбаке и рыбке |
|
|
А.С. Пушкин |
|
|
Классика, Поэзия |
|||
|
программирования С++ |
|
|
Б. Страуструп |
|
|
Программирование |
||||
|
|
Решение 2.2.2.a{71}. |
|
|
|
|
|
|
|
|
|
|
MySQL |
Решение 2.2.2.a |
|
|
|
|
|
|
|
|
|
|
і |
SELECT 'b name' |
|
|
|
|
|
|
|
|
|
|
2 |
|
AS 'book', |
|
|
|
|
|
|
|
|
|
3 |
|
GROUP CONCAT 'a name' ORDER BY 'a name' SEPARATOR ', ') |
|
|||||||
|
4 |
|
AS 'author(s)' |
|
|
|
|
|
|
|
|
|
5 |
FROM |
'books' |
|
|
|
|
|
|
|
|
|
6 |
|
JOIN 'm2m books authors' USING('b id') |
|
|
|
|||||
|
7 |
|
JOIN '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 Стр: 77/545
Пример 12: запросы на объединение и преобразование столбцов в строки
И ещё одна особенность MySQL заслуживает внимания: в строке 1 мы извлекаем поле b_name, которое не упомянуто в выражении GROUP BY в строке 8 и не
является агрегирующей функцией. MS SQL Server и Oracle не позволяют поступать подобным образом и строго требуют, чтобы любое поле из конструкции SELECT, не
являющееся агрегирующей функций, было явно упомянуто в выражении GROUP BY. Итак, что делает GROUP_CONCAT? Фактически, «разворачивает часть столбца
в строку» (одновременно упорядочивая авторов по алфавиту и разделяя их набором символов «, »):
b_name |
a_name |
|
author(s) |
|
Евгений Онегин |
А.С. Пушкин |
^ |
А.С. Пушкин |
|
Искусство программирования |
Д. Кнут |
Д. Кнут |
||
^ |
||||
|
Л.Д. Ландау |
|
||
Курс теоретической физики |
^ |
Е.М. Лифшиц, Л.Д. Ландау |
||
Е.М. Лифшиц |
||||
|
|
|
||
Основание и империя |
А. Азимов |
^ |
А. Азимов |
|
|
Д. Карнеги |
|
||
Психология программирования |
^ |
Б. Страуструп, Д. Карнеги |
||
Б. Страуструп |
||||
|
|
|
||
Сказка о рыбаке и рыбке |
А.С. Пушкин |
^ |
А.С. Пушкин |
|
Язык программирования C++ |
Б. Страуструп |
Б. Страуструп |
||
^ |
||||
|
|
|
MS SQL Server не поддерживает функцию GROUP_CONCAT, а потому решение для него весьма нетривиально:
MS SQL І Решение 2.2.2.a |
| |
|
|
|
|
||
1 |
WITH [prepared_data] |
|
|
|
|
||
2 |
AS (SELECT [books] [b_id], |
|
|
||||
3 |
|
|
[b_name], |
|
|
||
4 |
|
|
[a_name] |
|
|
||
5 |
|
FROM |
[books] |
|
|
||
6 |
|
|
|
JOIN [m2m_books_authors] |
|
||
|
|
|
ON [books] [b_id] = |
[m2m_books_authors] [b_id] |
|
||
8 |
|
|
|
JOIN [authors] |
|
|
|
9 |
|
|
|
ON |
[m2m_books_authors] |
[a_id] |
|
= [authors] [a_id] |
|
|
|
|
|
||
10 |
|
) |
|
|
|
|
|
11 |
SELECT [outer] |
[b_name] |
|
|
|||
12 |
|
AS [book], |
|
|
|
|
|
13 |
|
STUFF ((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 |
|
|
12 |
'') |
|
|
|
19 |
|
AS [author(s)] |
|
|
|||
20 |
FROM |
[prepared_data] AS [outer] |
|
|
|||
21 |
GROUP |
BY [outer] [b_id], |
|
|
|||
|
|
|
|
|
|
|
|
Начнём рассмотрение со строк 1-10. В них представлено т.н. CTE (Common Table Expression, общее табличное выражение). Очень упрощённо общее табличное выражение можно считать отдельным поименованным запросом, к результату выполнения которого можно обращаться как к таблице. Это особенно удобно, когда таких обращений в дальнейшем используется несколько (без общего табличного выражения с использованием классического подзапроса, тело такого подзапроса пришлось бы писать везде, где необходимо к нему обратиться).
Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 78/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 Стр: 79/545