КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 714145 томов
Объем библиотеки - 1411 Гб.
Всего авторов - 274974
Пользователей - 125141

Новое на форуме

Новое в блогах

Впечатления

чтун про серию Вселенная Вечности

Все четыре книги за пару дней "ушли". Но, строго любителям ЛитАниме (кароч, любителям фанфиков В0) ). Не подкачал, Антон Романович, с "чувством, толком, расстановкой" сделал. Осталось только проду ждать, да...

Рейтинг: 0 ( 0 за, 0 против).
Влад и мир про Лапышев: Наследник (Альтернативная история)

Стиль написания хороший, но бардак у автора в голове на нечитаемо, когда он начинает сочинять за политику. Трояк ставлю, но читать дальше не буду. С чего Ленину, социалистам, эссерам любить монархию и терпеть черносотенцев,убивавших их и устраивающие погромы? Не надо путать с ворьём сейчас с декорациями государства и парламента, где мошенники на доверии изображают партии. Для ликбеза: Партии были придуманы ещё в древнем Риме для

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).
Влад и мир про Романов: Игра по своим правилам (Альтернативная история)

Оценку не ставлю. Обе книги я не смог читать более 20 минут каждую. Автор балдеет от официальной манерной речи царской дворни и видимо в этом смысл данных трудов. Да и там ГГ перерождается сам в себя для спасения своего поражения в Русско-Японскую. Согласитесь такой выбор ГГ для приключенческой фантастики уже скучноватый. Где я и где душонка царского дворового. Мне проще хлев у своей скотины вычистить, чем служить доверенным лицом царя

  подробнее ...

Рейтинг: +1 ( 1 за, 0 против).
kiyanyn про серию Вот это я попал!

Переписанная Википедия в области оружия, изредка перемежающаяся рассказами о том, как ГГ в одиночку, а потом вдвоем :) громил немецкие дивизии, попутно дирижируя случайно оказавшимися в кустах симфоническими оркестрами.

Нечитаемо...


Рейтинг: +2 ( 3 за, 1 против).
Влад и мир про Семенов: Нежданно-негаданно... (Альтернативная история)

Автор несёт полную чушь. От его рассуждений уши вянут, логики ноль. Ленин был отличным экономистом и умел признавать свои ошибки. Его экономическим творчеством стал НЭП. Китайцы привязали НЭП к новым условиям - уничтожения свободного рынка на основе золота и серебра и существование спекулятивного на основе фантиков МВФ. И поимели все технологии мира в придачу к ввозу промышленности. Сталин частично разрушил Ленинский НЭП, добил его

  подробнее ...

Рейтинг: +6 ( 6 за, 0 против).

Изучаем SQL. Генерация, выборка и обработка данных [Алан Болье] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Изучаем

ье е
ет ни
Т р з да
И

O'REILLY *

SQL

Алан Болье

ТРЕТЬЕ ИЗДАНИЕ

Изучаем SQL

Генерация , выборка и обработка данных

THIRD EDITION

Learning SQL
Generate, Manipulate, and Retrieve Data

Alan Beaulieu

-

Beijing • Boston • Farnham • Sebastopol Tokyo

O'REILLY

ТРЕТЬЕ ИЗДАНИЕ

Изучаем SQL
Генерация, выборка и обработка данных

Алан Болье

КиТв
Комп’ютерне видавництво
"Д 1 АЛЕКТИКА"
2021

УДК 004.655.3 (075.8)
Б79
Перевод с английского и редакция канд. техн. наук И .В. Красикова

Болье, А.

Б79

Изучаем SQL. Генерация, выборка и обработка данных, 3-е изд./
Киев. : “ Диалектика”,
Алан Болье; пер. с англ. И.В. Красикова.
2021. 402 с. : ил. Парал. тит . англ.
ISBN 978-617- 7987-01-6 (укр.)
ISBN 978-1- 492-05761-1 ( англ.)







Данная книга отличается широким охватом как тем ( от азов SQL до таких
сложных вопросов, как аналитические функции и работа с большими базами
данных), так и конкретных баз данных (MySQL, Oracle Database, SQL Server ) и
особенностей реализации тех или иных функциональных возможностей SQL на
этих серверах. Книга идеально подходит в качестве учебника для начинающего
разработчика в области баз данных. В ней описаны все возможные применения
языка SQL и наиболее распространенные серверы баз данных.
УДК 004.655.3 (075.8 )

Все права защищены.
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в
какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или
механические, включая фотокопирование и запись на магнитный носитель, если на это нет
письменного разрешения издательства O' Reilly & Associates.
Authorized Russian translation of the English edition of Learning SQL: Master SQL Fundamentals ,
3rd Edition ( ISBN 978-1-492 -05761-1) © 2020 Alan Beaulieu. All rights reserved.
This translation is published and sold by permission of O’Reilly Media, Inc., which owns or
controls all rights to publish and sell the same.
All rights reserved. No part of this work may be reproduced or transmitted in any form or by any
means, electronic or mechanical, including photocopying, recording, or by any information storage
or retrieval system, without the prior written permission of the copyright owner and the Publisher.

ISBN 978-617-7987-01-6 ( укр.)
ISBN 978- 1- 492-05761- 1 (англ.)

© “Диалектика ”, перевод, 2021
© 2020 Alan Beaulieu

Оглавление

Предисловие

13

Глава 1 Небольшая предыстория

19

.
Глава 2. Создание и наполнение базы данных
Глава 3. Запросы

67

Глава 4 Фильтрация

91

.

37

.
Глава б. Работа с множествами
Глава 7.Генерация, обработка и преобразование данных
Глава 8. Группировка и агрегация

129

Глава 9 Подзапросы

195

Глава 10 Соединения

223

Глава 11 Условная логика

239

Глава 5 Запросы к нескольким таблицам

.

.

.
Глава 12. Транзакции
Глава 13. Индексы и ограничения
Глава 14. Представления

113
145

179

251

263
281

.
Глава 16. Аналитические функции

295

Глава 17 Работа с большими базами данных

331

Глава 18 SQL и большие данные

349

Приложение А Схема базы данных Sakila

367

Глава 15 Метаданные

.

.

.
Приложение Б. Ответы к упражнениям
Предметный указатель

311

369
397

Содержание

Предисловие
Зачем изучать SQL
Почему стоит использовать данную книгу
Структура книги
Соглашения, принятые в этой книге
Использование примеров из этой книги
Благодарности
Ждем ваших отзывов!

13
14
14
16
17
18
18

Глава 1 Небольшая предыстория

19

Введение в базы данных
Нереляционные СУБД
Реляционная модель
Немного терминологии
Что такое SQL
Классы инструкций SQL
SQL: непроцедурный язык
Примеры SQL
Что такое MySQL
Отказ от SQL
Что дальше

19
20
22
26
26
27
29
30
33
34
35

Глава 2 Создание и наполнение базы данных
Создание базы данных MySQL
Использование инструмента командной строки mysql
Типы данных MySQL
Символьные данные
Числовые данные
Временные данные

37

.

.

13

37
39

40
41
45
46

Создание таблицы
Шаг 1. Проектирование
Шаг 2. Уточнение
Шаг 3. Построение инструкции схемы SQL
Заполнение и изменение таблиц
Добавление данных
Изменение данных
Удаление данных
Когда хорошие инструкции становятся плохими
Не уникальный первичный ключ
Несуществующий внешний ключ
Нарушения значений столбцов
Некорректное преобразование данных
База данных Sakila

49
49
50
52
56
56
61
61
62
62
62
63
63
64

Глава 3 Запросы

67

Механика запросов
Части запроса
Предложение select
Псевдонимы столбцов
Удаление дубликатов
Предложение from
Таблицы
Связи таблиц
Определение псевдонимов таблиц
Предложение where

67
69
70
72
74
75
76
79
80
81
84
85
87
88
89

.

Предложения group by и having
Предложение order by
Сортировка по возрастанию и убыванию
Сортировка с помощью номера столбца
Проверьте свои знания

.

Глава 4 Фильтрация
Вычисление условий
Использование скобок
Использование оператора not
Построение условия
Типы условий
Условия равенства

91
91
92
93
94
95
95
Содержание

7

Условия диапазона
Условия членства
Условия соответствия
Этот таинственный null
Проверьте свои знания

97
102
104
107
110

Глава 5 Запросы к нескольким таблицам
Что такое соединение
Декартово произведение
Внутренние соединения
Синтаксис соединения ANSI
Соединение трех и более таблиц
Использование подзапросов в качестве таблиц
Использование одной таблицы дважды

113

.

Самосоединение
Проверьте свои знания

.

Глава 6 Работа с множествами
Основы теории множеств
Теория множеств на практике
Операторы для работы с множествами

113
114
115
117
119
122
124
125
126
129

Правила применения операторов для работы с множествами
Сортировка результатов составного запроса
Приоритеты операций над множествами
Проверьте свои знания

129
132
134
134
137
138
139
140
141
143

Глава 7 Генерация, обработка и преобразование данных

145

Работа со строковыми данными
Генерация строк
Манипуляции строками
Работа с числовыми данными
Выполнение математических функций
Управление точностью чисел
Работа со знаковыми данными
Работа с временными данными

145
146
152
160
161
163
165
166
166

Оператор union
Оператор intersect
Оператор except

.

Часовые пояса
8

Содержание

Генерация временных данных
Манипуляции временными данными

168
172
177
178

Функции преобразования
Проверьте свои знания

.

Глава 8 Группировка и агрегация
Концепции группировки
Агрегатные функции
Неявная и явная группировка

179
179
183
184
186
186
188
188
189
190

Использование выражений
Обработка значений null

Генерация групп
Группировка по одному столбцу
Многостолбцовая группировка
Группировка с помощью выражений
Генерация итоговых данных
Условия группового фильтра
Проверьте свои знания

190

192
194

.

Глава 9 Подзапросы

195

Что такое подзапрос
Типы подзапросов
Некоррелированные подзапросы
Подзапросы с несколькими строками и одним столбцом
Многостолбцовые подзапросы
Коррелированные подзапросы

195
197
197
199
204
205
207
209

Оператор exists
Работа с данными с помощью коррелированных подзапросов

Применение подзапросов
Подзапросы как источники данных
Подзапросы как генераторы выражений
В заключение
Проверьте свои знания

211
211
218
221
221

Глава 10 Соединения
Внешние соединения
Левое и правое внешние соединения
Трехсторонние внешние соединения
Перекрестные соединения

223

.

223
226
227
228
Содержание

9

Естественные соединения
Проверьте свои знания

235
237

Глава 11 Условная логика
Что такое условная логика
Выражение case
Поисковые выражения case
Простые выражения case
Примеры выражений case
Преобразования результирующего набора
Проверка существования
Ошибки деления на нуль
Условные обновления
Обработка значений null
Проверьте свои знания

239

.

.

Глава 12 Транзакции
Многопользовательские базы данных
Блокировка
Гранулярность блокировок
Что такое транзакция

239
240
240
242
243
243
244
247
248
249
250
251

Запуск транзакции
Завершение транзакции
Точки сохранения транзакции
Проверьте свои знания

251
252
253
254
256
257
259
262

Глава 13 Индексы и ограничения

263

Индексы
Создание индекса
Типы индексов
Как используются индексы
Обратная сторона индексов
Ограничения
Создание ограничения
Проверьте свои знания

263
264
269
272
274
275
276
279

Глава 14 Представления
Что такое представление
Зачем использовать представления

281

.

.

10

Содержание

281
284

Безопасность данных
Агрегация данных
Сокрытие сложности
Соединение разделенных данных

Обновление сложных представлений
Проверьте свои знания

284
285
286
287
288
288
290
293

Глава 15 Метаданные

295

Данные о данных

Работа с метаданными
Сценарии генерации схемы
Проверка базы данных
Динамическая генерация SQL
Проверьте свои знания

295
296
302
302
305
306
310

Глава 16 Аналитические функции

311

Концепции аналитических функций

Проверьте свои знания

311
312
313
315
315
318
321
324
327
328
329

Глава 17 Работа с большими базами данных

331

Секционирование
Концепции секционирования
Секционирование таблицы
Секционирование индекса
Методы секционирования
Преимущества секционирования
Кластеризация

331
332
333
333
334
343
343

Обновляемые представления
Обновление простых представлений

.

information_schema

.

Окна данных
Локализованная сортировка

Ранжирование
Функции ранжирования
Генерация нескольких рейтингов
Функции отчетности
Рамки окон
Запаздывание и опережение
Конкатенация значений в столбце

.

Содержание

11

Шардинг
Большие данные
Hadoop
NoSQL и базы данных документов
Облачные вычисления
Заключение

.

344
345
346
347
347
348

Глава 18 SQL и большие данные
Введение в Apache Drill
Запрос файлов с помощью Apache Drill
Запрос MySQL с использованием Apache Drill
Запрос MongoDB с использованием Apache Drill
Apache Drill и несколько источников данных
Будущее SQL

349

Приложение А Схема базы данных Sakila
Приложение Б Ответы к упражнениям

367

.

.

Глава 3
Глава 4
Глава 5
Глава 6
Глава 7
Глава 8
Глава 9
Глава 10
Глава 11
Глава 12
Глава 13
Глава 14
Глава 15
Глава 16

Предметный указатель

12

Содержание

349
350
353
356
363
365

369

369
371
373
375
378
378
380
384
386
387
388
389
391
393

397

Предисловие

Языки программирования постоянно появляются и исчезают , и сегодня
используется очень мало языков, которые имеют корни, уходящие в прошлое
более чем на десятилетие. Вот некоторые из них: COBOL, который все еще
довольно активно используется в мейнфреймах; Java, который родился в середине 1990-х и стал одним из самых распространенных языков програм мирования; С, который по - прежнему довольно популярен при разработке
операционных систем и серверов, а также для встроенных систем. В области
баз данных у нас имеется SQL, который появился в 1970-х годах.
SQL был разработан как язык для создания, выборки и обработки данных
из реляционных баз данных, которые существуют уже более 40 лет . Однако
за последнее десятилетие или около того приобрели большую популярность
другие платформы данных, такие как Hadoop, Spark и NoSQL, занявшие свои
ниши на рынке реляционных баз данных. Однако, как описано в нескольких
последних главах этой книги, язык SQL постоянно развивается, чтобы упростить выборку данных на различных платформах независимо от того, где
хранятся данные в таблицах, документах или простых “плоских” файлах.



Зачем изучать SQL
Независимо от того, будете ли вы использовать реляционную базу данных,
если вы работаете в области науки о данных или бизнес-аналитиком либо
сталкиваетесь с некоторым иным аспектом анализа данных, вам, вероятно,
потребуется знание SQL, а также других языков / платформ, таких как Python
и R. Данные окружают нас в огромных количествах ( и их объемы продол жают возрастать быстрыми темпами ) , и люди, способные извлекать из них
значимую информацию, пользуются большим спросом.

Почему стоит использовать данную книгу
Есть много книг, в которых к вам относятся как к чайнику, идиоту или какому то иному простаку, но эти книги, как правило, поверхностны. На другом
справочные руководства, в которых подробно описывает конце спектра
ся каждая перестановка каждой инструкции языка, что может быть полезно,
только если у вас уже есть хорошее представление о том, что вы хотите сделать, и нужно только вспомнить синтаксис. В этой книге мы попытаемся най ти золотую середину, начав с некоторых азов языка SQL, пройдя через его ос-



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



Структура книги
Книга состоит из 18 глав и двух приложений.

Глава I, “Небольшая предыстория”

История компьютеризованных баз данных, в том числе реляционной
модели и языка SQL.

Глава 2, “Создание и наполнение базы данных”
Создание базы данных MySQL, а также таблиц, используемых в качестве
примеров в этой книге, и заполнение таблиц данными.
Глава 3, “Запросы”

Инструкция select и наиболее распространенные конструкции
( select , from, where ).
Глава 4, “Фильтрация”
Типы условий, которые можно использовать в конструкции where ин струкции select, update или delete.

Глава 5, “Запросы к нескольким таблицам”
Использование запросов нескольких таблиц посредством их соединения.

Глава 6, “Работа с множествами”

Наборы данных и их взаимодействие в рамках запросов.
14

Предисловие

Глава 7, “Генерация, обработка и преобразование данных”
Встроенные функции, используемые для управления данными или их

преобразования.

Глава 8, “ Группировка и агрегация”

Способы агрегирования данных.
Глава 9, “Подзапросы”

Подзапросы и их использование.
Глава 10, “Соединения”
Дальнейшее исследование типов соединения таблиц.

Глава 11 , “Условная логика”

Применение условной логики в инструкциях select , insert , update
и delete.

Глава 12, “Транзакции”

Транзакции и их использование.
Глава 13, “Индексы и

ограничения”

Индексы и ограничения.
Глава 14, “Представления”

Создание интерфейса для защиты пользователей от сложных данных.
Глава 15, “Метаданные”

Словарь данных, его преимущества и работа с ним.
Глава 16 , “Аналитические функции”

Функциональность, используемая для создания рейтингов, промежуточных итогов и других значений, активно используемых в отчетности
и анализе.
Глава 17, “Работа с большими базами данных”

Технологии, упрощающие управление и обход очень больших баз данных.
Глава 18, “SQL и большие данные”

Вариации языка SQL, позволяющие извлекать данные из нереляцион ных платформ данных.
Предисловие

15

Приложение А, “Схема базы данных Sakila
Схема базы данных, используемая для всех примеров в книге.
Приложение Б , “Ответы к упражнениям”
Ответы к упражнениям.

Соглашения, принятые в этой книге
В этой книге используются следующие типографские соглашения.

Курсив
Используется для обозначения новых терминов.
Моноширинный шрифт

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

Используется для оформления команд или другого текста, который
пользователь должен набирать буквально.
Обозначает совет , предложение или общее примечание. Напри мер, здесь такие примечания используются, чтобы указать новые
полезные функции в Огас1е9/.
Обозначает предупреждение или предостережение, например
предупреждение о том, что некоторая инструкция SQL может
иметь непредвиденные последствия, если не использовать ее с
осторожностью.

16

Предисловие

Использование примеров из этой книги
Чтобы экспериментировать с данными, использованными в примерах в
этой книге, у вас есть два варианта.



Загрузите и установите сервер MySQL версии 8.0 ( или более поздней )
и загрузите пример базы данных Sakila по адресу https : / / dev . mysql .
com / doc / index-other . html.



Перейдите по адресу https : / / www . katacoda . com / mysql - db sandbox / scenarios / mysql - sandbox , чтобы получить доступ к “ пе сочнице” MySQL, в которой база данных Sakila загружена в экзем пляр MySQL. Вам потребуется создать ( бесплатную ) учетную запись
Katacoda, а затем щелкнуть на кнопке Start Scenario.

Если вы выберете второй вариант , то, как только вы запустите сцена рий, будет установлен и запущен сервер MySQL, а затем загружены схема и
данные Sakila. Когда все будет готово, появится стандартное приглашение
mysql >, и вы сможете начать посылать запросы в базу данных. Это, безусловно, самый простой вариант , и я думаю, что большинство читателей выберет
именно его; если он вам нравится, смело переходите к следующему разделу.
Если вы предпочитаете иметь собственную копию данных и хотите, чтобы
внесенные вами изменения были постоянными, или если вы просто заинтересованы в установке сервера MySQL на собственной машине, то, возможно, вы предпочтете первый вариант . Вы можете также использовать сервер
MySQL, размещенный в такой среде, как Amazon Web Services или Google
Cloud. В любом случае вам нужно будет выполнить установку и настройку
самостоятельно, так как этот материал выходит за рамки данной книги. Как
только ваша база данных станет доступной, вам нужно будет выполнить еще
несколько шагов, чтобы загрузить базу данных Sakila.
Сначала запустите клиент командной строки mysql и укажите пароль,
а затем выполните следующие действия .
1. Перейдите по адресу https : / / dev . mysql . com / doc / index-other . html
и загрузите файлы “sakila database” в разделе Example Databases.

2. Поместите файлы в локальный каталог, например в С : \ temp \ sakila db ( используется в следующих двух шагах; воспользуйтесь путем к ва шему реальному каталогу).

3. Введите source с : \ temp \ sakila-db \ sakila-schema sql ; и нажмите

.

< Enter >.
Предисловие

17

.

4 Введите source c : \ temp \ sakila -db \ sakila - data . sql ; и нажмите

< Enter >.
Теперь у вас должна быть рабочая база данных, заполненная всеми данны ми, необходимыми для примеров в этой книге.

Благодарности
Я хотел бы поблагодарить моего редактора Джеффа Блейла ( Jeff Bleiel )
за то, что он помог сделать это издание реальностью, а также Томаса Нил да (Thomas Nield), Энн Уайт -Уоткинс ( Ann White-Watkins) и Чарльза Живра
( Charles Givre), которые были столь любезны, что просмотрели мою книгу и
высказали свои замечания. Спасибо также Деб Бейкер ( Deb Baker ), Джесс Хаберман ( Jess Haberman ) и всему персоналу O’Reilly Media, которые участвовали в создании этой книги. Наконец, я благодарю свою жену Нэнси ( Nancy) и
моих дочерей Мишель ( Michelle) и Николь ( Nicole) за поддержку и терпение.

Ждем ваших отзывов!
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересны
любые ваши замечания в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать
нам электронное письмо либо просто посетить наш веб-сайт и оставить свои
замечания там. Одним словом, любым удобным для вас способом дайте нам
знать, нравится ли вам эта книга, а также выскажите свое мнение о том, как
сделать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги
и ее авторов, а также свой обратный адрес. Мы внимательно ознакомимся
с вашим мнением и обязательно учтем его при отборе и подготовке к изда нию новых книг.
Наши электронные адреса:
E-mail: info . dialektika @ gmail . com
WWW: http : / / www . dialektika . com

18

Предисловие

ГЛАВА 1

Небольшая предыстория

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

я

Читателям, желающим поскорее начать писать запросы, можно
пропустить все до главы 3, “Запросы”, но я рекомендую позже вернуться к первым двум главам, чтобы лучше понять историю и преимущества языка SQL.

Введение в базы данных



это не что иное, как набор связанной информации. На База данных
,
это база данных имен, номеров телефонов и
пример телефонная книга
адресов всех людей, живущих в конкретном районе. Хотя телефонная книга,
безусловно, встречается повсеместно и является часто используемой базой
данных, она страдает следующими недостатками.





Поиск телефонного номера человека может занять много времени, особенно если книга содержит большое количество записей.



Телефонная книга индексируется только по фамилии / имени, поэтому
поиск имен людей, живущих по определенному адресу, хотя и возможен
теоретически, на практике для этой базы данных не реален.



С момента печати телефонной книги информация в ней становится все
менее и менее точной, так как люди переезжают в этот район или уезжают из него, меняют свой номер телефона или переезжают в другое
место в этом же районе.

Те же недостатки, что и у телефонной книги, присущи любым ручным си стемам хранения данных, например историям болезней, хранящимся в картотеке. Из-за громоздкости бумажных баз данных одними из первых ком пьютерных приложений были системы управления базами данных ( СУБД ),
которые представляли собой компьютеризированные механизмы хранения
и поиска данных. Поскольку СУБД хранит данные в электронном виде, а не
на бумаге, такая компьютеризированная система может гораздо быстрее извлекать данные, индексировать их разными способами и предоставлять пользователям самую последнюю информацию.
Ранние СУБД управляли данными, хранящимися на магнитных лентах.
Поскольку, как правило, кассет было гораздо больше, чем считывателей, техническим специалистам приходилось постоянно загружать и выгружать ленты при запросе конкретных данных. Поскольку компьютеры той эпохи имели
очень мало памяти, несколько запросов к одним и тем же данным обычно
требовали многократного чтения ленты. Хотя такие СУБД были значительным улучшением по сравнению с бумажными базами данных, они очень
далеки от современных технологий. ( Современные СУБД могут управлять
петабайтами данных, обращаться к кластерам серверов, каждый из которых
кеширует десятки гигабайт данных в высокоскоростной памяти ... но я несколько забегаю вперед.)

Нереляционные СУБД

Б

В этом разделе содержится базовая информация о СУБД, предва ряющих реляционные. Читатели, которые хотят поскорее погрузиться в SQL, могут сразу перейти к следующему разделу.

В течение нескольких первых десятилетий компьютеризированных СУБД
хранение и предоставление информации пользователям производилось различными способами. В иерархической СУБД, например, данные представлены в виде одной или нескольких древовидных структур. На рис. 1.1 показано,
как могут быть представлены в виде деревьев данные, относящиеся к банковским счетам Джорджа Блейка и Сью Смит .
У Джорджа и Сью есть собственные деревья, содержащие их счета и тран закции, проведенные с этими счетами. Иерархическая СУБД предоставляет
инструменты для поиска дерева конкретного клиента, а затем выполняет обход дерева в поисках нужных счетов и / или транзакций. У каждого узла в де реве может быть либо нуль, либо один родительский узел и нуль, один или
20

.

Глава 1 Небольшая предыстория

много дочерних узлов. Такая конфигурация известна как иерархия с одним
родителем.
Джордж Блейк

Сью Смит

Клиенты

Сберегательный

Чековый

Валютные
операции

Чековый

Кредитная
линия

Счета

Дебит $100.00
от 2004-01 - 22

Дебит $250.00
от 2004-03-09

Кредит $25.00
от 2004-02 -05

Дебит $1000.00
от 2004-03- 25

Кредит $77.86
on 2004-04-04

Дебит $500.00
от 2004-03- 27

Кредит $138.50
on 2004-04-02

Транзакции

Рис. 1.1. Иерархическое представление данных

Другой распространенный подход, называемый сетевой СУБД , предоставляет наборы записей и наборы ссылок, которые определяют отношения
между различными записями. На рис. 1.2 показано, как те же счета Джорджа
и Сью могут выглядеть в такой системе.
Чтобы найти транзакции валютных операций на счете Сью, необходимо
выполнить следующие действия.
1. Найти запись о клиенте Сью Смит .

.
3.
4.

2 Перейти по ссылке из записи клиента Сью Смит к списку ее счетов.

Пройти по цепочке счетов, пока не будет найден валютный счет .
Перейти по ссылке из записи валютного счета к списку транзакций.

Одна интересная особенность сетевых СУБД демонстрируется набором
записей Услуги в правом ряду на рис. 1.2. Обратите внимание, что каждая
запись об услуге ( чековые счета, сберегательные счета и т .д.) указывает на
список учетных записей, относящихся к этому типу услуг. Таким образом,
к учетным записям можно получить доступ из нескольких мест ( как из за писей о клиентах, так и из записей об услугах ) , что позволяет сетевой базе
данных действовать в качестве иерархии со многими родителями.

.

Глава 1 Небольшая предыстория

21

Клиенты

Транзакции

Счета

I
>

*

Чековый

Услуги

Дебит $100.00
от 2004-01- 22
''

Джордж Блейк

Чековые счета

Кредит $25.00
от 2004- 02 - 05

J k

Сберегательный

>

_

Дебит $ 250.00
от 2004 - 03 -09

^

Дебит $1000.00
от 2004 - 03 - 25

i

>

к

Сберегательные
счета

Валютные
операции

Чековый

Кредит $138.50
от 2004-04- 02

Сью Смит
J

к

Валютный

\

Кредитная
линия

п

<
<
Кредит $77.86
от 2004- 04 - 04
Кредитная линия

Дебит $500.00
от 2004- 03 -27

Рис. 1.2. Сетевое представление данных

И иерархические, и сетевые СУБД и сегодня живы и здоровы, хотя в основном используются в мире мейнфреймов. Кроме того, иерархические СУБД
пережили второе рождение в сфере служб каталогов, таких как Microsoft
Active Directory и Apache Directory Server с открытым исходным кодом. Од нако, начиная с 1970-х годов, начал укореняться новый способ представления
данных, более строгий, но простой для понимания и реализации.

Реляционная модель
В 1970 году д- р Э.Ф. Кодд ( E.F. Codd ) из исследовательской лаборатории
IBM опубликовал статью под названием Реляционная модель данных для больших общих банков данных, в которой было предложено представление дан ных в виде наборов таблиц. Вместо указателей для перехода между связан ными сущностями для связывания записей в разных таблицах используются
22

Глава 1. Небольшая предыстория

избыточные данные. На рис. 1.3 показано, как в этом контексте будет отобра жаться информация о клиентах Джордже и Сью.
Клиент
cust id

fname

Iname

1

Джордж

Блейк

103

CHK

1

$75.00

2

Сью

Смит

104

SAV

1

$250.00

105

СНК

2

$783.64

106

ММ

2

$500.00

107

ЮС

2

0

Услуга
product cd

name

CHK

Чековые счета

SAV

Сберегательные

Счет
account id product cd custjd balance

'

Транзакция
txn id txn type cd accountjd amount

date

978

DBT

103

$100.00 2004-01 -22

счета

979

CDT

103

$25.00 2004-02-05

ММ

Валютные
операции

980

DBT

104

$250.00 2004-03-09

ЮС

Кредитная линия

981

DBT

105

$1000.00 2004-03- 2:

982

CDT

105

$138.50 2004-04-02

983

CDT

105

$77.86 2004-04-04

984

DBT

106

$500.00 2004-03- 27,

Puc. 1.3. Реляционное представление данных

Четыре таблицы на рис. 1.3 представляют четыре объекта, которые обсуждались до сих пор: клиент , услуга , счет и транзакция. Просматривая
верхнюю часть таблицы клиентов на рис. 1.3, вы видите три столбца: cust
id ( номер идентификатора клиента ) , fname ( имя клиента ) и Iname ( фами лия клиента ). Взглянув на таблицу клиентов, вы можете увидеть две строки,
одна из которых содержит данные Джорджа Блейка, а другая данные Сью
Смит . Количество столбцов, которые может содержать таблица, отличается
от сервера к серверу, но обычно оно достаточно велико, чтобы не создавать
проблем ( например, Microsoft SQL Server позволяет содержать в таблице
до 1024 столбцов ) . Количество строк, которые может содержать таблица,
определяется в большей степени физическими ограничениями ( доступность

_



Глава 1. Небольшая предыстория

23

дискового пространства ) и возможностью поддержки ( насколько большой
может стать таблица, прежде чем с ней станет трудно работать) , а не ограничениями сервера базы данных.
Каждая таблица в реляционной базе данных включает информацию, которая однозначно идентифицирует строку ( известную как первичный ключ
( primary key)), вместе с дополнительной информацией, необходимой для полного описания объекта. Посмотрите еще раз на таблицу клиентов: столбец
cust id содержит разные значения для каждого клиента; Джордж Блейк, на пример, однозначно идентифицируется идентификатором клиента 1. Ни одному другому клиенту никогда не будет назначен этот идентификатор, и чтобы найти данные Джорджа Блейка в таблица клиентов, не требуется никакой
другой информации.

_

я
я

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

Хотя в качестве первичного ключа я мог бы использовать комбинацию
столбцов fname и lname ( первичный ключ, состоящий из двух и более столбцов, называется составным ключом), в банке запросто могут найтись два
( или более) клиента с одинаковыми именами и фамилиями. Поэтому я решил
включить столбец cust id в таблицу клиентов специально для использова ния в качестве столбца первичного ключа.

_

В этом примере выбор fname / lname в качестве первичного ключа приведет к тому, что именуется естественным ключом, в то
время как выбор cust _ id в качестве такового будет называться
суррогатным ключом. Решение о том, следует ли использовать
естественные или суррогатные ключи, остается на усмотрение
разработчика базы данных, но в данном случае выбор очевиден,
поскольку фамилия человека может измениться ( например, при
принятии фамилии супруга ) , а столбцы первичного ключа после
присваивания значения никогда не должны изменяться.

Некоторые таблицы также содержат информацию, используемую для перехода к другой таблице ( это и есть упомянутые ранее “избыточные данные” ).
Например, таблица счетов включает столбец cust id, который содержит

_

24

Глава 1. Небольшая предыстория

уникальный идентификатор клиента, открывшего счет , вместе со столбцом
product cd, который содержит уникальный идентификатор услуги, которой
соответствует счет . Эти столбцы известны как внешние ключи ( foreign key)
и служат той же цели, что и линии, соединяющие объекты в иерархической
и сетевой версиях. Если вы просматриваете конкретный счет и хотите получить больше информации об открывшем его клиенте, вы должны взять зна чение столбца cust_id и использовать его, чтобы найти соответствующую
строку в таблице клиентов ( этот процесс на жаргоне реляционных баз дан ных известен как соединение ( join ); соединения вводятся в главе 3, “Запросы”,
и подробно рассматриваются в главах 5, “Запросы к нескольким таблицам”,
и 10, “ Соединения ” ).
Может показаться расточительным хранить несколько копий одних и тех
же данных, но в реляционной модели совершенно ясно, какие избыточные
данные могут храниться. Например, в таблицу счетов можно включить стол бец для уникального идентификатора клиента, который открыл счет , но будет
неверным указывать в таблице счетов имя и фамилию клиента. Например,
если клиент изменит свое имя / фамилию, вы хотите быть уверены, что в базе
данных есть только одно место, где хранятся эти данные о клиенте; в противном случае данные могут быть изменены в одном месте, но не в другом, что
приведет к недостоверности информации в базе данных. Правильное место
для этих данных таблица клиентов, и в другие таблицы могут быть включены только значения cust id. Недопустимо также, чтобы в одном столбце
содержалось несколько частей информации, например столбец имени, содержащий как имя, так и фамилию человека, или столбец адреса, который содержит информацию об улице, городе, штате и почтовом индексе. Процесс
доработки дизайна базы данных, гарантирующий, что каждая независимая
часть информации находится только в одном месте ( кроме внешних ключей ),
называется нормализацией.
Возвращаясь к четырем таблицам на рис. 1.3, вы можете задаться вопросом, как использовать эти таблицы, чтобы найти транзакции Джорджа Блейка по его чековому счету. Сначала вы находите уникальный идентификатор
Джорджа Блейка в таблице клиентов. Затем вы находите строку в таблице
счетов, столбец cust id которой содержит значение уникального иденти фикатора Джорджа, а столбец product cd которой соответствует строке в
таблице услуг, столбец name которой равен “ Чековые счета”. Наконец, нуж но найти строки в таблице транзакций, столбец account id которых соответствует уникальному идентификатору из таблицы счетов. Это может

_



_

_

_

Глава 1. Небольшая предыстория

25

показаться сложным, но, как вы вскоре увидите, все это можно сделать с помощью одной команды языка SQL.

Немного терминологии
В предыдущих разделах я ввел новую терминологию, так что, возможно,
пришло время дать некоторые формальные определения. В табл. 1.1 показа ны термины, которые мы используем в оставшейся части книги, а также их
определения.
Таблица 1.1. Термины и определения
Термин

Определение

Объект, сущность
(entity)
Столбец

Нечто интересное для сообщества пользователей баз данных, например клиенты, запчасти, географические местоположения и т.д.
Отдельная часть данных, хранящаяся в таблице

(column)

Строка
(row)
Таблица
(table)
Результирующий набор
(result set)
Первичный ключ
(primary key)
Внешний ключ
(foreign key)

Набор столбцов, которые вместе взятые полностью описывают
объект или какое-либо действие с ним. Именуется также записью
Набор строк, хранящихся либо в оперативной памяти (непостоянная), либо в энергонезависимом хранилище (постоянная)
Другое название непостоянной таблицы, обычно являющейся
результатом SQL-запроса
Один или несколько столбцов, которые можно использовать как
уникальный идентификатор для каждой строки таблицы
Один или несколько столбцов, которые можно использовать
совместно для идентификации одной строки в другой таблице

Что такое SQL
Кроме определения реляционной модели, Кодд предложил язык, назван ный “DSL/ Alpha”, для управления данными в реляционных таблицах. Вско ре после выхода статьи Кодда IBM поручила группе разработчиков создать
прототип базы данных, основанный на идеях Кодда. Эта группа создала
упрощенную версию DSL/ Alpha, которую они назвали “SQUARE”. Усовершен ствования в SQUARE привели к созданию языка под названием “SEQUEL”, которое в конечном итоге было сокращено до “SQL”. SQL начинался как язык,
используемый для управления данными в реляционных базах данных, но постепенно превратился (как вы увидите в конце этой книги ) в язык для управ ления данными в различных технологиях баз данных.
SQL уже более 40 лет , и за это время он претерпел большие изменения.
В середине 1980-х годов Американский национальный институт стандартов
26

Глава 1. Небольшая предыстория

( ANSI ) начал работу над первым стандартом языка SQL, опубликованным
в 1986 году. Последующие усовершенствования привели к появлению новых
выпусков стандарта SQL в 1989, 1992, 1999, 2003, 2006, 2008, 2011 и 2016 годах.
Наряду с усовершенствованиями основного языка, в язык SQL были добавлены новые возможности, среди прочего
для возможности объектно-ориен тированной функциональности. Более поздние стандарты ориентированы на
интеграцию родственных технологий, таких как расширяемый язык разметки
(XML) и объектная нотация JavaScript ( JSON ).
SQL тесно связан с реляционной моделью, потому что результат SQL-за проса представляет собой таблицу ( в этом контексте также именуемую ре зультирующим набором ). Таким образом, в реляционной базе данных новая
постоянная таблица может быть создана просто путем сохранения результа тов запроса. Аналогично запрос может использовать в качестве входных дан ных как постоянные таблицы, так и результирующие наборы других запросов
( мы подробно рассмотрим этот вопрос в главе 9, “ Подзапросы”).
И последнее замечание: SQL не является аббревиатурой для чего- либо
( хотя многие люди будут настаивать на том, что это означает Structured Query
Language ( язык структурированных запросов ) ) . Что касается названия языка,
то одинаково приемлемо как произносить буквы по отдельности (S.Q.L.), так
и использовать слово sequel.



Классы инструкций SQL
Язык SQL делится на несколько отдельных частей: части, которые мы рассматриваем в этой книге, включают инструкции схемы SQL, которые используются для определения структур данных, хранящихся в базе данных; ин струкции данных SQL, которые используются для управления структурами
данных, ранее определенными с использованием инструкций схемы SQL;
и инструкции транзакций SQL, которые используются для начала, завершения и отката транзакций ( концепции, рассматриваемые в главе 12, “Транзакции ”). Например, чтобы создать новую таблицу в своей базе данных, необходимо использовать инструкцию схемы SQL create table, в то время как
процесс наполнения новой таблицы данными требует инструкции данных
SQL insert.
Вот как выглядит инструкция схемы SQL, которая создает таблицу с именем corporation:
CREATE TABLE corporation
(corp id SMALLINT,

_

Глава 1. Небольшая предыстория

27

name VARCHAR(30),
CONSTRAINT pIncorporation PRIMARY KEY (corp id)

_

_

Этот оператор создает таблицу с двумя столбцами, corp id и паше, при
этом столбец corp id указан как первичный ключ таблицы. Мы исследуем
детали этой инструкции, например различные типы данных, доступные в
MySQL, в главе 2, “ Создание и наполнение базы данных” А вот как выглядит
инструкция данных SQL, которая вставляет строку в таблицу corporation
для Acme Paper Corporation:

_

INSERT INTO corporation (corp_id, name)
VALUES (27, 'Acme Paper Corporation');

Эта инструкция добавляет в таблицу corporation строку со значением 27
для столбца corp_id и значением Acme Paper Corporation для столбца name.
Наконец, вот какой вид имеет простой оператор select для извлечения
только что созданных данных:
mysql< SELECT name
-> FROM corporation
-> WHERE corp_id = 2 7;
+
+
I name
+

+

I

Acme Paper Corporation |

+

+

Все элементы базы данных, созданные с помощью инструкций схемы SQL,
хранятся в специальном наборе таблиц, называемом словарем данных. Эти
“ данные о базе данных” известны под общим названием метаданные и рассматриваются в главе 15, “Метаданные”. Как и таблицы, созданные вами, та блицы словаря данных можно запрашивать с помощью оператора select,
что позволяет вам обнаруживать текущие структуры данных, развернутые
в базе данных во время выполнения. Например, если вас попросят написать
отчет , показывающий новые учетные записи, созданные в прошлом месяце,
вы можете либо жестко закодировать имена столбцов в таблице account, которые известны при написании отчета, либо запросить словарь данных, чтобы определить текущий набор столбцов и динамически генерировать отчет
при каждом его выполнении.
Большая часть этой книги посвящена части языка SQL, относящейся к дан ным, которая состоит из команд select, update, insert и delete. Инструкции схемы SQL показаны в главе 2, “ Создание и наполнение базы данных”,
которая проведет вас через проектирование и создание некоторых простых
28

Глава 1. Небольшая предыстория

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

SQL: непроцедурный язык
Если вы работали с языками программирования, то привыкли определять
переменные и структуры данных, использовать условную логику (if-then-else) ,
циклы ( do while ... end) и разбиение кода на мелкие многократно используемые части ( объекты, функции, процедуры ). Ваш код обрабатывается компи лятором, а получившийся выполнимый файл выполняет в точности ( хорошо,
не всегда в точности ) то, что вы запрограммировали. Работаете ли вы с Java,
Python, Scala или каким-либо иным процедурным языком, вы полностью кон тролируете то, что делает программа.

я

Процедурный язык определяет как желаемые результаты, так и
механизм, или процесс, с помощью которого эти результаты достигаются. Непроцедурный язык также определяет желаемые результаты, но процесс, с помощью которого достигаются результа ты, остается на усмотрение внешнего агента.

При использовании SQL вам придется отказаться от части контроля, к которому вы привыкли, потому что операторы SQL определяют необходимые
входные и выходные данные, но способ выполнения инструкции оставлен на
усмотрение компонента вашего механизма базы данных, известного как оптимизатор. Задача оптимизатора просматривать ваши инструкции SQL
и, принимая во внимание, как именно настроены таблицы и какие индексы
доступны, определять самый эффективный способ выполнения ( хорошо, не
всегда самый эффективный ). Большинство механизмов баз данных позволя ют вам влиять на решения оптимизатора, указывая для него подсказки, например предлагаяиспользовать определенный индекс. Однако большинство
пользователей SQL никогда не достигнут этого уровня сложности и оставят
такую настройку администратору базы данных или эксперту по производительности.
Таким образом, с помощью SQL вы не сможете писать полноценные
приложения. Если только вы не пишете простой сценарий для управления



Глава 1. Небольшая предыстория

29

определенными данными, вам необходимо интегрировать SQL с вашим языком программирования. Некоторые поставщики баз данных сделали это вместо вас, например, язык PL/SQL Oracle, язык хранимых процедур MySQL и
язык Transact -SQL от Microsoft. В этих языках инструкции данных SQL являются частью грамматики языка, что позволяет легко интегрировать запросы к
базе данных с процедурными командами. Однако, если вы используете язык,
не зависящий от базы данных, такой как Java или Python, вам нужно будет
использовать набор инструментов / API для выполнения инструкций SQL из
вашего кода. Одни из этих наборов инструментов предоставляются поставщиком вашей базы данных, тогда как другие созданы сторонними поставщи ками или поставщиками с открытым исходным кодом. В табл. 1.2 показаны
некоторые из доступных вариантов интеграции SQL в конкретный язык.



Таблица 1.2. Инструменты интеграции SQL
Язык

Инструментарий

Java
C#

JDBC (Java Database Connectivity)
ADO.NET (Microsoft)

Ruby
Python

Ruby DBI
Python DB
Пакет database/sql

Go

Если вам нужно только интерактивно выполнять команды SQL, то каждый поставщик баз данных предоставляет как минимум простой инструмент командной строки, обеспечивающий отправку команд SQL базе данных
и просмотр результатов. Большинство поставщиков также предоставляют
графический инструментарий, который включает в себя окно, показываюс результатами выполнения этих
щее ваши команды SQL, и другое окно
команд SQL. Дополнительно имеются сторонние инструменты, например
SQuirrel, который может подключаться через JDBC ко многим различным
серверам баз данных. Поскольку примеры в этой книге выполняются для
базы данных MySQL, для выполнения примеров и форматирования результатов я использую инструмент командной строки mysql , который включен
в установку MySQL.



Примеры SQL
Ранее в этой главе я обещал показать вам инструкцию SQL, которая вернет все транзакции чекового счета Джорджа Блейка. Вот она:

30

Глава 1. Небольшая предыстория

SELECT t.txn_id, t.txn_type_cd, t.txn_date, t.amount
FROM individual i
INNER JOIN account a ON i.cust id = a.cust id
INNER JOIN product p ON p.product cd = a.product cd
INNER JOIN transaction t ON t.account id = a.account id
WHERE i.fname = i George' AND i.lname = i Blake i
AND p.name = i checking account';

_

+

_

+

+

_

I
+

+

11
+

+

txn id | txn_type cd

I DBT

_

_

+

txn_date
2008- 01- 05 00 : 00 : 00

+
amount |
+

+

100.00
+

+

+

_

+

1 row in set (0.00 sec)

He вдаваясь пока что в подробности, замечу, что этот запрос идентифици рует строку в таблице individual для Джорджа Блейка и строку в таблице
product для ‘ checking account”, находит строку в таблице account для данной комбинации человека / услути и возвращает четыре столбца из таблицы
transaction для всех транзакций этого счета. Если вы знаете, что идентификатор клиента Джорджа Блейка равен 8, а текущие счета обозначены кодом ' СНК ' , то, чтобы найти соответствующие транзакции, вы можете найти
чековый счет Джорджа Блейка в таблице account на основании идентификатора клиента и использовать идентификатор счета:
SELECT t.txn id, t.txn type_cd, t.txn date, t.amount

_

_

_

FROM account a
INNER JOIN transaction t ON t.account_id = a.account id
WHERE a.cust_id = 8 AND a.product_cd = 'CHK';

_

Я рассмотрю все концепции в этих запросах ( и многое другое) в следующих главах, но я просто хотел показать, как будут выглядеть эти запросы.
Предыдущие запросы содержат три разных предложения (clause): select,
from и where. Почти каждый запрос, с которым вы будете сталкиваться, будет включать как минимум эти три предложения, хотя есть и еще несколько, которые можно использовать для более специализированных целей. Роль
каждого из этих предложений демонстрируется в следующем фрагменте:
SELECT /* Одна или несколько вещей
/* Одно или несколько мест
FROM
WHERE /* Одно или несколько условий */ ...

в

Большинство реализаций SQL обрабатывают любой текст между
/ * и * / как комментарии.

Глава 1. Небольшая предыстория

31

При построении запроса ваша первая задача обычно состоит в том, чтобы
определить, какая таблица (или таблицы) потребуется, а затем добавить ее в
предложение from. Далее вам понадобится добавить условия в предложение
where , чтобы отфильтровать данные из этих таблиц, которые вас не интересуют . Наконец, вы решаете, какие столбцы из разных таблиц необходимо
получить, и добавляете их в предложение select . Вот простой пример, который демонстрирует , как найти всех клиентов с фамилией “Smith”:
SELECT cust id, fname

_

FROM individual
WHERE lname = 'Smith';

Этот запрос ищет в таблице individual все строки, столбец lname которых соответствует строке ' Smith ' , и возвращает столбцы cust _ id и fname
этих строк.
Наряду с запросами к базе данных вы, вероятно, будете заполнять базу
данных новыми данными и изменять имеющиеся. Вот простой пример встав ки новой строки в таблицу product:
INSERT INTO product (product_cd, name)
VALUES ('CD', 'Certificate of Depysit')

Ой, похоже, мы неправильно написали слово Deposit . Никаких проблем ситуацию можно исправить с помощью инструкции update:



UPDATE product
SET name = 'Certificate of Deposit'
WHERE product cd = 'CD';

_

Обратите внимание, что инструкция update так же, как и select , содержит предложение where . Это связано с тем, что инструкция update долж на идентифицировать строки, которые необходимо изменить; в данном случае вы указываете, что следует изменить только те строки, значение столб ца product _ cd которых соответствует строке ' C D ' . Поскольку столбец
product cd является первичным ключом таблицы product , следует ожи дать, что ваша инструкция обновления изменит ровно одну строку ( или ни
одной, если такое значение в таблице отсутствует ). Всякий раз, выполняя ин струкцию данных SQL, вы получаете ответ от механизма базы данных о том,
на какое количество строк она подействовала. Если вы используете интерактивный инструмент наподобие упомянутого ранее инструмента командной
строки mysql , то в ответе вы получите информацию о том, сколько строк

_

32

Глава 1. Небольшая предыстория






возвращено инструкцией select;

создано инструкцией insert;
изменено инструкцией update;

удалено инструкцией delete.

Если вы используете с одним из упоминавшихся ранее наборов инструментов процедурный язык, то этот инструментарий будет включать вызов
для запроса такой информации о количестве строк после выполнения вашей инструкции данных SQL. В общем случае рекомендуется проверить эту
информацию, чтобы убедиться, что ваша инструкция не сделала ничего неожиданного ( например, если вы забудете добавить предложение where в ин струкцию delete, то удалите все строки в таблице!).

Что такое MySQL
Реляционные базы данных доступны коммерчески уже более трех десяти летий. Вот некоторые из наиболее зрелых и популярных коммерческих продуктов:



Oracle Database от Oracle Corporation;




SQL Server от Microsoft;

DB2 Universal Database от IBM.

Все эти серверы баз данных делают примерно одно и то же, хотя некоторые из них лучше подходят для работы с очень большими базами данных или
с базами данных с очень высокой пропускной способностью. У других лучше
выполняется обработка объектов, очень больших файлов или XML-докумен тов и т .д. Кроме того, все эти серверы довольно точно соответствуют последнему стандарту ANSI SQL. Следование стандарту всегда хорошо, и я хочу
показать вам, как писать инструкции SQL, которые будут работать на любой
из этих платформ с минимальными изменениями или вовсе без них.
Последние два десятилетия с целью создания жизнеспособной альтернати вы коммерческим серверам баз данных большую активность проявляло сообщество разработчиков программного обеспечения с открытым кодом. Двумя
наиболее часто используемыми серверами баз данных с открытым исходным
кодом являются PostgreSQL и MySQL. Сервер MySQL бесплатен и к тому же
чрезвычайно прост в загрузке и установке. По этим причинам я решил, что
все примеры для данной книги должны выполняться с базой данных MySQL



Глава 1. Небольшая предыстория

33

( версия 8.0) и что для форматирования результатов запроса может использоваться инструмент командной строки mysql . Даже если вы уже пользуетесь
другим сервером и никогда не планируете работать с MySQL, я настоятельно
рекомендую вам установить последнюю версию сервера MySQL, загрузить
образец схемы и данных и поэкспериментировать с данными и примерами
из этой книги.

Однако помните о следующем предостережении:
Это не книга о реализации SQL в MySQLl
Данная книга предназначена для того, чтобы научить вас создавать ин струкции SQL, которые без изменений будут выполняться на MySQL, а также
будут работать с последними выпусками Oracle Database, DB2 и SQL Server с
небольшими изменениями или без них.

Отказ от SQL
За десятилетие между вторым и третьим изданиями этой книги в мире
баз данных многое изменилось. Хотя реляционные базы данных по- прежнему широко используются и будут использоваться еще некоторое время, для
удовлетворения потребностей таких компаний, как Amazon и Google, появи лись новые технологии баз данных. Эти технологии включают Hadoop, Spark,
NoSQL и NewSQL, которые представляют собой распределенные масштабируемые системы, обычно развертываемые на кластерах стандартных серверов.
Подробное изучение этих технологий выходит за рамки данной книги, но все
они имеют нечто общее с реляционными базами данных: SQL.
Поскольку организации часто хранят данные с использованием нескольких технологий, необходимы службы, которые в состоянии охватить несколько баз данных. Например, в отчете может потребоваться объединить данные,
хранящиеся в файлах Oracle, Hadoop, JSON, CSV и файлах журналов Unix.
Для решения этой задачи было создано новое поколение инструментов, и одним из наиболее многообещающих из них является Apache Drill механизм
запросов с открытым исходным кодом, который позволяет пользователям
писать запросы, могущие получить доступ к данным, хранящимся практически в любой базе данных или файловой системе. Мы рассмотрим Apache Drill
в главе 18, “SQL и большие данные”.



34

Глава 1. Небольшая предыстория

Что дальше



Общая цель следующих четырех глав познакомить вас с инструкциями
данных SQL, с особым акцентом на трех основных предложениях инструкции
select. Кроме того, вы увидите множество примеров, в которых используется схема Sakila ( представленная в следующей главе), которая будет применяться для всех примеров в книге. Я надеюсь, что знакомство с единственной
базой данных позволит вам добраться до сути примера без того, чтобы постоянно останавливаться и проверять используемые таблицы. Если для вас
окажется немного утомительной работа с одним и тем же набором таблиц, не
стесняйтесь дополнить образец базы данных новыми таблицами или создать
собственную базу данных, с которой можно будет экспериментировать.
После того как вы хорошо усвоите основы, в оставшихся главах книги мы
углубимся в дополнительные концепции, большинство из которых не зависят
одна от другой. Таким образом, если вы обнаружите, что запутались, то вы
всегда сможете двигаться дальше, а позже вернуться, чтобы еще раз прочесть
непонятную главу. Изучив эту книгу и проработав все примеры, вы будете на
пути к тому, чтобы стать опытным практиком SQL.
Для читателей, желающих получить больше информации о реляционных
базах данных, истории компьютеризированных СУБД и языке SQL, чем при ведено в этом кратком введении, представляю несколько ресурсов, на которые стоит обратить внимание.

• C.J. Date. Database in Depth: Relational Theory for Practitioners (O’Reilly)
• К.Дж. Дейт . Введение в системы баз данных, 8-е изд. ( пер. с англ., ООО
“Диалектика”, 2020)



C.J. Date The Database Relational Model: A Retrospective Review and Analysis
( Addison -Wesley)



Статья в Wikipedia Database Management System ( https : / / oreil . ly /
sj 2 xR )

.

Глава 1 Небольшая предыстория

35

ГЛАВА 2

Создание и наполнение
базы данных

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

Создание базы данных MySQL
Если вы хотите иметь возможность экспериментировать с данными, использованными в примерах в этой книге, у вас есть два варианта.



Загрузите и установите сервер MySQL версии 8.0 ( или новее) и загрузите образец базы данных Sakila по адресу https : / / dev . mysql . com /
doc / index-other . html .



Перейдите по адресу https : / / www . katacoda com / mysql - db- sand
box / scenarios / mysql -sandbox , чтобы получить доступ к “песочнице”
MySQL, в которой образец базы данных Sakila загружен в экземпляр
MySQL. Вам необходимо создать ( бесплатную ) учетную запись Katacoda
и щелкнуть на кнопке Start Scenario ( Начать сценарий ).

.

Если вы выберете второй вариант , то, как только вы запустите сценарий,
сервер MySQL будет установлен и запущен, а затем будут загружены схема
и данные Sakila. Когда все будет готово, появится стандартное приглашение
mysql > , и вы сможете начать выполнять запросы к базе данных. Это, безусловно , самый простой вариант , и я ожидаю, что большинство читателей



можете сразу переходить
выберет именно его. Если вы так и поступили
к следующему разделу.
Если же вы предпочитаете иметь собственную копию данных и хотите,
чтобы любые вносимые вами изменения были постоянными, или если вы
просто заинтересованы в установке сервера MySQL на своем компьютере, то
можете предпочесть первый вариант . Вы также можете выбрать использова ние сервера MySQL, размещенного в такой среде, как Amazon Web Services
или Google Cloud. В любом случае вам нужно будет выполнить установку и
настройку самостоятельно, поскольку эта тема выходит за рамки данной книги. Как только ваша база данных станет доступной, вам нужно будет выполнить несколько шагов, чтобы загрузить образец базы данных Sakila.
Сначала нужно будет запустить клиент командной строки mysql и указать
пароль, а затем выполнить следующие шаги.
1. Перейти по адресу https://dev.mysql.com/doc/index-other.html

и загрузить файлы базы данных Sakila из раздела Example Databases.
2. Поместить загруженные файлы в свой каталог наподобие C:\temp\
sakila db (он использован и в следующих двух шагах; просто замените его своим реальным каталогом ).

-

Введите source c:\temp\sakila-db\sakila-schema.sql; и нажмите
< Enter >.
4. Введите source c:\temp\sakila-db\sakila data.sql; и нажмите
< Enter >.
3.

-

Теперь у вас должна быть рабочая база данных, заполненная всеми
данными, необходимыми для примеров в этой книге.
Образец базы данных Sakila предоставляется MySQL и имеет ли цензию New BSD. Sakila содержит данные для фиктивной компании по прокату фильмов и включает в себя различные таблицы
с информацией об инвентаре, фильмах, клиентах и др. Реальные
пункты проката фильмов в основном ушли в прошлое, так что при
небольшой фантазии вы можете переименовать свою компанию
в компанию по потоковому воспроизведению фильмов и переделать соответственно базу данных, но примеры в этой книге соот ветствуют оригинальной базе данных.

38

Глава 2. Создание и наполнение базы данных

Использование инструмента командной строки mysql
Если только вы не используете временный сеанс работы с базой данных
(второй вариант работы с базой данных в предыдущем разделе), вам нужно
запустить инструмент командной строки mysql , чтобы взаимодействовать
с базой данных. Для этого вам нужно будет открыть оболочку Windows или
Unix и запустить утилиту mysql. Например, если вы входите в систему, используя учетную запись root, выполните следующее:

-р;

mysql -u root

Затем вас попросят ввести пароль, после чего вы увидите приглашение

mysql >. Чтобы увидеть все доступные базы данных, можно использовать следующую команду:
mysql> show databases;
+

+

Database
+

+

information_schema
mysql
performance schema
sakila
sys

_

+
+
5 rows in set (0.01 sec)

Поскольку вы будете использовать базу данных Sakila, вам нужно указать
ее с помощью команды use:
mysql> use sakila;
Database changed

Всякий раз, вызывая инструмент командной строки mysql , вы можете
указать как имя пользователя, так и используемую базу данных, как показано ниже:
mysql

-u

root

-р sakila;

Это избавит вас от необходимости набирать use sakila ; при каждом запуске инструмента mysql. Теперь, когда вы установили сеанс и указали базу
данных, можете выполнять инструкции SQL и просматривать результаты.
Например, если вы хотите узнать текущие дату и время, можете ввести следующий запрос:
mysql> SELECT now();
+

+

I now()
Глава 2. Создание и наполнение базы данных

39

+

+
2 0 1 9- 0 4 - 0 4 2 0 : 4 4 : 2 6 |

+

+

1 row in set (0.01 sec)



это встроенная функция MySQL, которая возвращает
Функция now ( )
текущие дату и время. Как видите, инструмент командной строки mysql форматирует результаты ваших запросов внутри прямоугольника, ограниченного символами +, - и | . После того как результаты исчерпаны ( в данном случае имеется только одна строка результатов ) , инструмент командной строки
mysql показывает , сколько строк было возвращено и сколько времени потребовалось для выполнения инструкции SQL.

Об отсутствии предложения from
К некоторым серверам баз данных вы не сможете отправить запрос без пред ложения from, в котором указана хотя бы одна таблица. Одним из таких серверов является Oracle Database. Для случаев, когда вам нужно только вызвать
функцию, Oracle предоставляет таблицу с именем dual , которая состоит из
единственного столбца с именем dummy, который содержит единственную
строку данных. Для совместимости с Oracle Database MySQL также предоставляет таблицу dual. Таким образом, предыдущий запрос для определения текущей даты и времени можно было бы записать как
mysql> SELECT now()
FROM dual;
+

+

now()
+

+

2 0 1 9- 0 4 - 0 4 2 0 : 4 4 : 2 6 |
+

+

1 row in set (0.01 sec)

Если вы не используете Oracle и вам не нужна совместимость с Oracle, можете
просто игнорировать таблицу dual и использовать только select ( без from).

Завершив работу с инструментом командной строки mysql , просто введите quit ; или e x i t ; , чтобы вернуться в командную оболочку Unix или
Windows.

Типы данных MySQL
В целом все популярные серверы баз данных могут хранить одни и те
же типы данных, такие как строки, даты и числа. Обычно они различаются
40

Глава 2. Создание и наполнение базы данных

поддержкой специализированных типов данных, таких как документы XML
и JSON, или пространственных данных. Поскольку это вводная книга по SQL
и поскольку 98% столбцов, с которыми вы столкнетесь, будут простыми ти пами данных, в этой главе рассматриваются только символьные данные, даты
( также известные как временные данные) и числовые данные. Использование
SQL для запроса документов JSON будет рассмотрено в главе 18, “SQL и большие данные”.

Символьные данные
Символьные данные могут храниться как строки фиксированной или переменной длины; разница в том, что строки фиксированной длины дополня ются пробелами справа и всегда используют одинаковое количество байтов,
а строки переменной длины не дополняются справа пробелами и не всегда
занимают одинаковое количество байтов. При определении символьного
столбца вы должны указать максимальный размер строки, которая может
храниться в столбце. Например, чтобы хранить строки длиной до 20 символов, можно использовать любое из следующих определений:
/* Постоянная длина */
char(20)
varchar(20) /* Переменная длина */

Максимальная длина столбцов char в настоящее время равна 255 бай тов, тогда как столбцы varchar могут иметь размер до 65 535 байтов. Если
вам нужно хранить более длинные строки ( например, электронные письма, XML-документы и т .д.), можно использовать один из текстовых типов
(mediumtext и longtext), о которых я расскажу позже в этом разделе. В общем случае вы должны использовать тип char, когда все строки, которые
должны храниться в столбце, имеют одинаковую длину ( например, аббреви атуры состояний ), и тип varchar, когда строки, которые должны храниться
в столбце, имеют разную длину. И char, и varchar одинаково используются
на всех основных серверах баз данных.

в

Исключением является использование varchar в Oracle Database.
Пользователи Oracle при определении столбцов символов переменной длины должны использовать тип varchar2.

Наборы символов
В языках, использующих латинский алфавит , например в английском
языке, имеется достаточно малое количество символов, при котором для
Глава 2. Создание и наполнение базы данных

41

хранения каждого символа достаточно одного байта. Другие языки, например
японский и корейский, содержат большое количество символов, так что для
каждого символа требуется несколько байтов памяти. Поэтому такие наборы
символов называются многобайтовыми наборами символов .
MySQL может хранить данные с использованием различных наборов сим волов, как однобайтовых, так и многобайтовых. Чтобы просмотреть поддерживаемые наборы символов на своем сервере, можно использовать команду
show, как показано в следующем примере:
mysql> SHOW CHARACTER SET;
+

+

+

+

I Charset |Description

+

|Default collation |Maxlen|
+
+
+
+
+
1
armscii8 general ci
armscii8|ARMSCII-8 Armenian
1
ascii_general_ci
US ASCII
ascii
2
big5 chinese_ci
big5
Big5 Traditional Chinese
1
binary
binary
Binary pseudo charset
cpl250 general ci
1
cpl250 Windows Central European
cpl251_general ci
1
cpl251 Windows Cyrillic
1
cpl256_general ci
cpl256 Windows Arabic
1
cpl257 general ci
cpl257 Windows Baltic
1
cp850_general_ci
cp850
DOS West European
1
cp852_general ci
cp852
DOS Central European
1
cp866_general ci
cp866
DOS Russian
cp932_japanese ci
2
cp932
SJIS for Windows Japanese
1
dec8_swedish ci
DEC West European
dec8
3
eucjpms_japanese ci
eucjpms UJIS for Windows Japanese
2
euckr korean ci
EUC-KR Korean
euckr
gbl8030 China National Standard
4
gbl8030_chinese_ci
GB18030
2
gb2312_chinese ci
gb2312 GB2312 Simplified Chinese
2
gbk
gbk_chinese_ci
GBK Simplified Chinese
geostd8 general ci
1
geostd8 GE0STD8 Georgian
greek_general ci
1
ISO 8859-7 Greek
greek
1
hebrew general ci
hebrew
ISO 8859 8 Hebrew
hp8_english ci
1
hp8
HP West European
1
keybcs2 DOS Kamenicky Czech Slovak keybcs2_general_ci
koi8r general ci
1
koi8r
K0I8-R Relcom Russian
1
koi8u general ci
koi8u
K0I8-U Ukrainian
1
latinl Swedish ci
latinl |cpl252 West European
1
latin2 general ci
|
latin2 |ISO 8859 2 Central European
latin5 |ISO 8859-9 Turkish
latin5 turkish ci
1
latin7 general ci
1
latin7 |ISO 8859-13 Baltic
macce
macce_general_ci
1
Mac Central European
1
macroman_general_ci
macroman|Mac West European
sjis japanese ci
sjis
2
Shift JIS Japanese
1
7bit Swedish
swe7
swe7_swedish_ci
1
tis620 thai ci
tis620 ITIS620 Thai

_

_

_

_

_

_
_

-

-

-

-

42

.

Глава 2 Создание и наполнение базы данных

_
_
_
_
_
_
_
_

_

_

_
_
_
_

_
_
_
_

_
_
_
_

_

_

I ucs2
I ujis

IUCS-2 Unicode
IEUC-JP Japanese

Iutf16 |UTF-16 Unicode
|utfl61e IUTF-16LE Unicode
Iutf32 |UTF-32 Unicode
IUTF-8 Unicode
I utf8
Iutf8mb4 IUTF-8 Unicode
+

+

_

_

Iucs2 general ci
Iujis_japanese_ci
|utf16_general_ci
Iutf161e general ci
Iutf32 general ci
Iutf8 general ci
|utf8mb4 0900 ai ci

_

_

_

_
_

+

2

3
4
4
4
3
4

_

+

+

41 rows in set (0.00 sec)

Если значение в четвертом столбце, maxlen , больше 1, то набор символов
является многобайтовым.
В предыдущих версиях сервера MySQL в качестве набора символов по
умолчанию автоматически выбирался набор символов latinl , но версия 8
по умолчанию использует utf 8 mb 4 . Однако вы можете выбрать использование разных наборов символов для каждого символьного столбца в своей
базе данных и даже хранить разные наборы символов в одной таблице. Чтобы при определении столбца выбрать набор символов, отличный от значения
по умолчанию, просто укажите один из поддерживаемых наборов символов
после определения типа, например:
varchar(20) character set latinl

В случае MySQL вы можете установить набор символов по умолчанию для
всей базы данных:
create database european_sales character set latinl;

Здесь я не могу разместить больше информации о наборах символов, чем
допустимо для введения в SQL, но на самом деле интернационализация
это очень большая тема. Если вы планируете иметь дело с несколькими ( или
с незнакомыми) наборами символов, можете обратиться к такой книге, как
Jukka Korpela. Unicode Explained: Internationalize Documents, Programs, and Web
Sites ( O’Reilly) .



Текстовые данные
Для того чтобы хранить данные, которые могут превышать ограничение
в 64 Кбайта для столбцов varchar, следует использовать один из текстовых
типов. В табл. 2.1 показаны доступные текстовые типы и их максимальные

размеры.

Глава 2. Создание и наполнение базы данных

43

Таблица 2.1. Текстовые типы MySQL
Текстовый тип Максимальное количество байтов

tinytext
text
mediumtext
longtext

255
65 535
16777215
4294967295

Выбирая для использования один из текстовых типов, необходимо учиты вать следующее.



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




Конечные пробелы при загрузке данных в столбец не удаляются.





При сортировке или группировке текстовых столбцов используются
только первые 1024 байта данных, хотя при необходимости этот предел
может быть увеличен.
Различные текстовые типы уникальны для MySQL. SQL Server имеет единственный текстовый тип для больших символьных данных, в
то время как DB2 и Oracle используют тип данных, именуемый с lob
(Character Large Object большой символьный объект ).



Теперь, когда MySQL позволяет использовать до 65 535 байт для столб цов varchar ( в версии 4 было ограничение в 255 байтов ) , нет никакой
необходимости использовать типы tinytext и text.

Если вы создаете столбец для ввода данных произвольного вида, напри мер столбец примечаний для хранения данных о взаимодействии клиентов
с отделом обслуживания вашей компании, то varchar , вероятно, будет адекватным выбором. Однако, если вы храните документы, следует выбрать тип
mediumtext или longtext.

в
44

Oracle Database позволяет использовать до 2000 байтов для столбдля столбцов типа varchar 2 .
цов типа char и 4000 байтов
Для больших документов вы можете использовать тип с lob. SQL
Server может обрабатывать до 8000 байтов как для char , так и для
varchar , но вы можете хранить до 2 Гбайтов данных в столбце,
определенном как varchar ( max ) .

Глава 2. Создание и наполнение базы данных



Числовые данные
Хотя может показаться разумным иметь единственный числовой тип данных, на самом деле существует несколько различных числовых типов данных,
которые отражают различные способы использования чисел, как показано
далее.
Столбец, показывающий, отправлен ли заказ клиента

.

Этот тип столбца, именуемый логическим ( Boolean ) , будет содержать
значение 0 , чтобы указать ложь, и 1 для истины.



.

Сгенерированный системой первичный ключ для таблицы транзакций
Эти данные обычно начинаются с 1 и увеличиваются на единицу для
каждого очередного значения до потенциально очень большого числа.

.

Номер позиции электронной корзины покупателя
Значения для этого типа столбца
положительные целые числа от 1
(
,
и до, возможно 200 для шопоголиков ).



.

Позиционные данные для сверлильного станка для печатных плат

Высокоточные научные или производственные данные часто требуют
точности до восьми десятичных знаков.
Для обработки этих ( и других) типов данных MySQL имеет несколько различных числовых типов данных. Чаще всего используются числовые типы,
используемые для хранения целочисленных значений, или целых чисел. При
указании одного из этих типов вы также можете указать, что данные будут
без знака, что сообщает серверу, что все данные, хранящиеся в столбце, будут
не меньше нуля. В табл. 2.2 показаны пять различных типов данных, используемых для хранения целых чисел.
Таблица 2.2. Целочисленные типы MySQL
Тип

Знаковый диапазон

tinyint
smallint
mediumint
int
bigint

От
От
От
От
От

Беззнаковый диапазон

-128 до 127
-32768 до 32 767
-8388608 до 8388607
-2147 483 648 до 2147 483 647
-263 до 263-1

От
От
От
От
От

0 до 255
0 до 65 535
0 до 16777215
0 до 4 294 967 295
0 до 264-1

Глава 2. Создание и наполнение базы данных

45

Когда вы создаете столбец с использованием одного из целочисленных ти пов, MySQL выделяет соответствующий объем пространства для хранения
данных, который варьируется от одного байта для tinyint до восьми байтов
для bigint . Следовательно, вы должны попытаться выбрать тип, который будет достаточно большим, чтобы вместить самое большое число, которое может быть в вашей задаче, при этом без излишних затрат места для хранения.
Для чисел с плавающей точкой ( например, 3.1415927 ) вы можете выби рать числовые типы, показанные в табл. 2.3.
Таблица 2.3. Типы с плавающей точкой MySQL
Тип

,

float ( р s )
double ( р, s )

Числовой диапазон

От - 3.402823466Е + 38 ДО -1.175494351Е- 38
и ОТ 1.175494351Е 38 ДО 3.402823466 Е + 38
От -1.7 976931348623157 Е + 308 до -2.2250738585072014 Е- 308
и от 2.2250738585072014 Е- 308 до 1.7976931348623157 Е + 308



При использовании типа с плавающей точкой можно указать его точ ность (precision общее количество допустимых цифр слева и справа от деколичество допустимых цифр справа
сятичной точки) и масштаб (scale
от десятичной точки), но это необязательно. Эти значения представлены в
табл. 2.3 как p u s. Если вы укажете точность и масштаб столбца с плавающей точкой, помните, что данные, хранящиеся в столбце, будут округлены,
если количество цифр превышает указанный масштаб и / или точность столб ца. Например, столбец, определенный как float ( 4 , 2 ) , будет хранить в общей сложности четыре цифры, две слева от десятичной точки и две справа.
Следовательно, такой столбец отлично справится с числами 27,44 и 8,19, но
число 17,8675 будет округлено до 17,87, а попытка сохранить число 178,375
в столбец float ( 4 , 2 ) вызовет ошибку.
Как и целочисленные типы , столбцы с плавающей запятой могут быть
определены как беззнаковые, но это указание всего лишь предотвращает сохранение в столбце отрицательных чисел и не влияет на диапазон данных,
которые могут в нем храниться.





Временные данные
Вы почти наверняка будете работать не только со строками и числами, но
и с информацией о датах и/ или времени. Этот тип данных называется вре менными, а некоторые примеры временных данных в базе данных включают

следующее.
46

Глава 2. Создание и наполнение базы данных



Дата в будущем, когда ожидается конкретное событие, например от правка заказ клиенту.




Дата отправки заказа клиента.





Дата и время, когда пользователь изменил определенную строку в та-

блице.
Дата рождения сотрудника.

_

Год, соответствующий строке в таблице yearly sales данных склада.

Время, необходимое для выполнения определенных работ на автомобильном конвейере.

MySQL включает типы данных для обработки всех этих ситуаций.
В табл . 2.4 показаны временные типы данных, поддерживаемые MySQL.
Таблица 2.4. Временные типы MySQL
Тип

Формат по умолчанию

date
datetime

YYYY MM DD
YYYY-MM-DD HH:MI:SS

- -

timestamp YYYY-MM-DD HH:MI:SS
year
time

YYYY
HHH:MI:SSS

Допустимые значения

От 1000 - 01- 01 до 9999-12 - 31
От 1000 - 01- 01 00 : 00 : 00.000000
до 9999-12 -3 1 2 3 : 59 : 59.999999
От 1970 - 01- 01 00 : 00 : 00.000000
до 2038- 01-18 22 : 14 : 07.999999
От 1901 до 2155
От - 838 : 59 : 59.000000 до 838 : 59 : 59.000000

Хотя серверы баз данных хранят временные данные различными способами, назначение строки формата ( второй столбец табл. 2.4) показать, как
данные будут представлены при выборке, а также как должна быть построена строка даты при вставке или обновлении временного столбца. Таким образом, если вы хотите внести дату “23 марта 2020 года ” в столбец date , используя формат по умолчанию YYY- MM - DD, вы должны использовать строку 2020 - 03- 23. В главе 7, “ Генерация, обработка и преобразование данных”,
подробно исследуется, как создаются и отображаются временные данные.
Типы date time , timestamp и time также допускают до 6 десятичных разрядов дробной части секунд (микросекунды ). При определении столбцов с
использованием одного из этих типов данных вы можете указать значение от
О до 6; например, date time ( 2 ) позволит включать в ваши значения времени
сотые доли секунды.



Глава 2. Создание и наполнение базы данных

47

Серверы баз данных допускают различные диапазоны дат для
временных столбцов. Oracle Database принимает даты от 4712 года
до н.э. до 9999 года н.э., в то время как SQL Server обрабатывает
только даты от 1753 года н.э. до 9999 года н.э. ( если только вы не
используете тип данных date time 2 SQL Server 2008, который допускает даты в диапазоне от 1 года н.э. до 9999 года н.э.). MySQL
находится между Oracle и SQL Server и может хранить даты с 1000 года н.э. до 9999 года н.э. Хотя для большинства систем, отслежива ющих текущие и будущие события, это может не иметь никакого
значения, следует помнить об этом при хранении исторических дат .
В табл. 2.5 описаны различные компоненты форматов даты, представлен ных в табл. 2.4.
Таблица 2.5. Компоненты формата даты
Компонент

Определение

Диапазон

YYYY
ММ
DD

Год, включая век
Месяц
День
Час
Часы (прошедшие)
Минуты
Секунды

От 1000 до 9999

нн
ннн
MI
SS

От 01 (январь) до 12 (декабрь)
От 01 до 31
От 00 до 23
От - 838 до 838
От 00 до 59
От 00 до 59

Вот как можно использовать различные временные типы для реализации
приведенных выше примеров.

48



Столбцы для хранения ожидаемой будущей даты отгрузки заказа клиента или дня рождения сотрудника могут использовать тип date , так
как нереально запланировать будущую отгрузку с точностью до секун ды и необязательно знать, в какое именно время человек родился.



Столбец для хранения информации о том, когда заказ клиента был от правлен на самом деле, может использовать тип datetime, так как важно отслеживать не только дату отправки, но и ее время.



Столбец , который отслеживает , когда пользователь в последний
раз изменял определенную строку в таблице, может использовать
тип timestamp. Тип timestamp содержит ту же информацию, что
и тип datetime ( год, месяц, день, час, минута, секунда ), но столбец
timestamp будет автоматически заполняться текущим значением даты /
Глава 2. Создание и наполнение базы данных

времени сервером MySQL при добавлении строки в таблицу или при ее
последующем изменении.



Столбец, содержащий только данные о годе, может использовать тип



Столбцы, содержащие данные о продолжительности времени, необходимого для выполнения задания, могут использовать тип time . Для
этого типа данных компонент даты не нужен и только запутывает , поскольку нас интересует только количество часов /минут / секунд, необходимых для выполнения задания. Эта информация может быть получена
с использованием двух столбцов datetime ( один — для даты / времени
начала выполнения задания, другой для даты / времени его завершения ) путем вычитания одного из другого, но проще использовать единственный столбец time.

year.



В главе 7, “ Генерация, обработка и преобразование данных”, рассказывает ся, как работать с каждым из этих временных типов данных.

Создание таблицы
Теперь, когда у вас есть четкое представление о том, какие типы данных
могут храниться в базе данных MySQL, пора рассмотреть, как использовать
эти типы в определениях таблиц. Начнем с определения таблицы для хранения информации о человеке.

Шаг 1. Проектирование



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








Имя
Цвет глаз
Дата рождения

Адрес

Любимые блюда
Это, конечно, не исчерпывающий список, но на данный момент его доста точно. Следующий шаг состоит в присвоении имен столбцам и выборе типов
данных. В табл. 2.6 показана моя первая попытка.
Глава 2. Создание и наполнение базы данных

49

.

Таблица 2.6 Таблица person (первая попытка )
Столбец
name
eye color
birth date
address
favorite foods

_

_

Допустимые значения
Тип
varchar(40)
char(2)
BLf BR, GR
date
varchar(100)
varchar(200)

Столбцы name , address и favorite _ foods относятся к типу varchar и
допускают ввод данных в свободной форме. Столбец eye _color позволяет
использовать два символа, которые должны представлять собой только BR,
BL или GR. Столбец birth_date имеет тип date , поскольку компонент времени не нужен.

Шаг 2. Уточнение
В главе 1, “Небольшая предыстория ”, вы познакомились с понятием нормализации, которая представляет собой процесс обеспечения отсутствия дубликатов ( кроме внешних ключей ) или составных столбцов в дизайне базы
данных. При втором взгляде на столбцы в таблице person возникают следующие вопросы.



Столбец name фактически представляет собой составной объект , состоящий из имени и фамилии.



Поскольку несколько человек могут иметь одинаковое имя, цвет глаз,
дату рождения и так далее, в таблице person нет столбцов, гарантирующих уникальность.



Столбец address также является составным объектом, состоящим из
улицы, города, штата /области, страны и почтового индекса.



это список, содержащий нуль, одно или
Столбец favorite foods
несколько независимых блюд. Лучше всего для этих данных создать
отдельную таблицу, в которую будет входить внешний ключ к таблице
person , чтобы вы знали, к какому человеку можно отнести то или иное
конкретное блюдо.

_



С учетом этих замечаний в табл. 2.7 приведена нормализованная версия
таблицы person.

50

.

Глава 2 Создание и наполнение базы данных

.

Таблица 2.7 Таблица person ( вторая попытка )
Допустимые значения
Тип
smallint (unsigned)
varchar(20)
varchar(20)
char(2)
BL, BR, GR
date
varchar(30)
varchar(20)
varchar(20)
country
varchar(20)
postal code varchar(20)

Столбец
person id
first name
last name
eye color
birth date
street
city
state

_
_

_
_
_

_

_

Теперь, когда у таблицы person есть первичный ключ (person id), чтобы гарантировать уникальность, следующим шагом будет создание таблицы
favorite_ food, которая включает внешний ключ к таблице person. Результат показан в табл. 2.8.
Таблица 2.8. Таблица favorite food
Столбец
person id
food

_

Тип
smallint (unsigned)
varchar(20)

_

Столбцы person id и food составляют первичный ключ таблицы favo
rite food, а столбец person id, кроме того, является внешним ключом для
таблицы person.

_

_

Чего достаточно?
Перемещение столбца favorite food из таблицы person было, определен но, хорошей идеей , но все ли мы сделали ? Что произойдет , например, если
один человек укажет в качестве любимой еды “макароны”, в то время как
другой — “спагетти ” ? Это одно и то же ? Чтобы предотвратить эту проблему,
вы можете решить, что хотите, чтобы люди выбирали свои любимые продукты из списка вариантов, и в этом случае вы должны создать таблицу food со
столбцами food id и food name , а затем изменить таблицу favorite food
так, чтобы она содержала внешний ключ к таблице food. Хотя такой дизайн
будет полностью нормализован, вы можете решить, что будете просто хра нить введенные пользователем значения, и в этом случае вы можете оставить
таблицу такой, как она есть.

_

_

_

_

Глава 2. Создание и наполнение базы данных

51

Шаг 3. Построение инструкции схемы SQL
Теперь, когда дизайн двух таблиц, содержащих информацию о людях и их
любимых блюдах, готов, следующий шаг состоит в генерации инструкций
SQL для создания таблиц в базе данных. Вот инструкция для создания табли цы people:
CREATE TABLE person
(person_id SMALLINT UNSIGNED,
fname VARCHAR(20),
lname VARCHAR(20),
eye_color CHAR(2),
birth date DATE,
street VARCHAR(30),
city VARCHAR(20),
state VARCHAR(20),
country VARCHAR(20),
postal code VARCHAR(20),
CONSTRAINT pk _person PRIMARY KEY (person_id)
);

_

_

В этой инструкции все должно быть достаточно очевидным, за исключением последнего пункта; когда вы определяете свою таблицу, вам нужно
сообщить серверу базы данных, какой столбец ( или столбцы) будет служить
первичным ключом для таблицы. Вы делаете это, создавая ограничение таблицы. В определение таблицы можно добавить несколько типов ограничений.
Данное ограничение является ограничением первичного ключа. Он создается
в столбце person id и получает имя pk person.
Говоря об ограничениях, следует упомянуть еще один тип ограничения,
который был бы полезен для таблицы person. В табл. 2.6 я добавил третий
столбец, чтобы показать допустимые значения для определенных столбцов
( например, BR и BL для столбца еуе со1ог ). Еще один тип ограничения, именуемый проверочным ограничением, приводит к проверке допустимых зна чений для конкретного столбца. MySQL позволяет присоединить к столбцу

_

_

_

проверочное ограничение:

_

eye color CHAR(2) CHECK (eye_color IN ('BR 1 ,'BL', 1 GR 1 )),

Хотя проверочные ограничения должным образом работают на большин стве серверов баз данных, сервер MySQL позволяет определять проверочные
ограничения, но не применяет их. Однако MySQL предоставляет еще один
тип символьных данных, именуемый епшп, который вводит проверочное
ограничение в определение типа данных. Вот как это выглядит в определении
столбца eye color:

52

.

Глава 2 Создание и наполнение базы данных

_

eye color ENUM('BR','BL 1 , 1 GR'),

А вот как выглядит определение таблицы person с типом данных enum
для столбца eye color:

_

CREATE TABLE person
(person id SMALLINT UNSIGNED,
fname VARCHAR(20),
lname VARCHAR(20),
eye_color ENUM('BR','BL','GR'),
birth date DATE,
street VARCHAR(30),
city VARCHAR(20),
state VARCHAR(20),
country VARCHAR(20),
postal code VARCHAR(20),
CONSTRAINT pk_person PRIMARY KEY (person_id)

_

_

_

Позже в этой главе вы увидите, что произойдет , если вы попытаетесь добавить в столбец данные, которые нарушают проверочное ограничение ( или,
в случае MySQL, его значения перечисления ).
Теперь вы готовы выполнить инструкцию создания таблицы с помощью
командной строки mysql. Вот как это выглядит :
mysql> CREATE TABLE person
-> (person id SMALLINT UNSIGNED,
-> fname VARCHAR(20),
lname VARCHAR(20),
>
-> eye_color ENUM('BR','BL','GR 1 ),
-> birth_date DATE,
-> street VARCHAR(30),
-> city VARCHAR(20),
>
state VARCHAR(20),
-> country VARCHAR(20),
-> postal code VARCHAR(20),
-> CONSTRAINT pk_person PRIMARY KEY (person id)
-> );
Query OK, 0 rows affected (0.37 sec)

_

-

_

_

После выполнения инструкции create table сервер MySQL возвращает
сообщение Query OK , 0 rows affected (Запрос успешен, затронуты 0 строк ) ,
что говорит о том, что в инструкции нет синтаксических ошибок.
Если вы хотите убедиться, что таблица person действительно существует ,
можете использовать команду describe ( или для краткости
desc), чтобы
посмотреть определение таблицы:



.

Глава 2 Создание и наполнение базы данных

53

mysql> desc person;
+

Field
+

+

+

+

I Type

I Null

+

+

| Key | Default | Extra |
+
+
+
+
PRI | NULL
| NULL
| NULL
| NULL
| NULL
| NULL
| NULL
| NULL
| NULL
| NULL
+
+
+
+

person_id
fname
lname
eye color
birthdate
street
city
state
country
postal code

_

smallint unsigned
varchar(20)
varchar(20)
enum( 1 BR','BL','GR')
date
varchar(30)
varchar(20)
varchar(20)
varchar(20)
varchar(20)

_

_

+

+

NO
YES
YES
YES
YES
YES
YES
YES
YES
YES
+

+

+

+

10 rows in set (0.00 sec)

Столбцы 1 и 2 выходных данных не требуют пояснений. Столбец 3 показывает , можно ли пропустить конкретный столбец при добавлении данных
в таблицу. В данный момент я намеренно опустил эту тему из обсуждения,
но мы подробно рассмотрим ее в главе 4, “Фильтрация ”. В четвертом столбце
указано, участвует ли данный столбец в каких-либо ключах (первичных или
внешних); в нашем случае столбец person id указан как первичный ключ.
В пятом столбце показано значение по умолчанию, которым будет заполнен
столбец, если его значение при добавлении данных в таблицу будет опущено.
Шестой столбец показывает иную информацию, которая может иметь отношение к столбцу.

_

Что такое NULL
В некоторых случаях невозможно указать значение для определенного столбца таблицы. Например, при добавлении данных о новом заказе клиента столбец ship_date ( дата отгрузки ) не может быть определен. В этом случае говорят , что столбец нулевой ( null ) (обратите внимание, что я не говорю, что он
равен нулю!), что указывает на отсутствие в нем значения. NULL используется
в различных случаях, когда значение не может быть предоставлено из- за того,
что оно неизвестно, представляет собой пустое множество или неприменимо
в некотором конкретном случае.
При разработке таблицы вы можете указать, какие столбцы могут быть нулевыми ( по умолчанию ), а какие не могут ( что указывается добавлением ключевых слов not null после определения типа ).

Теперь, когда мы создали таблицу person , перейдем к созданию таблицы
favorite food:

54

Глава 2. Создание и наполнение базы данных

mysql> CREATE TABLE favorite_food
-> (person id SMALLINT UNSIGNED,
-> food VARCHAR(20),
-> CONSTRAINT pk favorite food PRIMARY KEY (person_id, food),
-> CONSTRAINT fk_fav_food_person_id FOREIGN KEY (person_id)
-> REFERENCES person (person id)
-> );
Query OK, 0 rows affected (0.10 sec)

_

_

_

Все это выглядит очень похоже на инструкцию create table для таблицы
person, но со следующими отличиями.



Поскольку у человека может быть более одного любимого блюда (в первую очередь из-за этого и была создана это таблица), таблице требуется
больше, чем единственный столбец person id, чтобы гарантировать
уникальность записи в ней. Таким образом, эта таблица имеет составной ключ из двух столбцов: person id и food.

_

_



_

Таблица favorite food содержит еще один тип ограничения, который называется ограничением внешнего ключа. Он ограничивает зна чения столбца person id в favorite food таким образом, что этот
столбец может включать только те значения, которые имеются в таблице person. При таком ограничении я не смогу добавить в таблицу favorite food строку, которая указывает , что некто со значением
person id, равным 27 , любит пиццу, если в таблице person еще нет
строки с записью, значение person id которой равно 27.

_

я

_

_

_

_

Если вы забыли указать ограничение внешнего ключа при первоначальном создании таблицы, добавьте его позже с помощью инструкции alter table.

Вот что показывает инструкция desc после выполнения инструкции
create table:
mysql> desc favorite_food;
+
+
Field
I Type
+

_

+

+

+

+

+

person id I smallint unsigned
| food
varchar(20)
+

+
+
+
+
Null | Key | Default Extra

N0
NO
+

+

+

+

+

+

| PRI | NULL
| PRI | NULL
+

+

2 rows in set (0.00 sec)

Теперь, когда таблицы созданы, следующий логический шаг состоит в добавлении в них некоторых данных.
Глава 2. Создание и наполнение базы данных

55

Заполнение и изменение таблиц

_

Создав таблицы person и favorite food, мы можем приступить к изучению четырех инструкций данных SQL: insert, update, delete и select.

Добавление данных

_

Поскольку данных в таблицах person и favorite food еще нет , первая
из четырех инструкций данных SQL, которую мы изучим, будет инструкцией
insert. У нее есть три основных компонента:





имя таблицы, в которую нужно добавить данные;
имена столбцов в таблице, которые необходимо заполнить;
значения, которыми заполняются столбцы.

Вы не обязаны предоставлять данные для каждого столбца таблицы (если
только все столбцы в таблице не определены как not null). В некоторых случаях тем столбцам, которые не включены в начальную инструкцию insert,
значение будет присвоено позже с помощью инструкции update. В других
случаях в некоторой строке данных столбец может так никогда и не получить
значение ( например, заказ клиента, который отменен до отправки; таким образом, никакое значение столбца ship date не применимо ) .

Генерация данных числовых ключей

Прежде чем добавлять данные в таблицу person, было бы полезно обсудить, как генерируются значения для числовых первичных ключей. Помимо
высасывания чисел из пальца, у нас есть несколько более разумных вариантов:



найти самое большое значение в таблице и увеличить его;



позволить серверу базы данных предоставить нам требуемое значение.

Хотя первый вариант может показаться вполне допустимым, в многопользовательской среде он оказывается проблематичным, поскольку два пользователя могут одновременно просматривать таблицу и сгенерировать одно и
то же значение для первичного ключа. Вместо этого все серверы баз данных,
имеющиеся на рынке в настоящее время, предоставляют безопасный и на дежный метод создания цифровых ключей. На некоторых серверах, таких как
Oracle Database, используется отдельный объект схемы ( именуемый последовательностью ) ; в случае же MySQL нужно просто включить функцию автоинкремента ( автоматического увеличения ) для столбца первичного ключа.
56

.

Глава 2 Создание и наполнение базы данных

Обычно это делается при создании таблицы, но сейчас мы получаем возможность изучить другую инструкцию схемы SQL, alter table, которая используется для изменения определения существующей таблицы:

_

ALTER TABLE person MODIFY person id SMALLINT UNSIGNED AUTO_INCREMENT;

_

Эта инструкция, по существу, переопределяет столбец person id в табли це person . Если теперь выполнить инструкцию desc для таблицы, можно
увидеть автоинкремент в столбце “Extra” для person id:

_

mysql> DESC person;
+

+

+

+

I Type

Field

+

+

+

Null | Key | Default | Extra

+
+
+
|person id|smallint(5) unsignedl NO

_

+

+

| PRI ! NULL

+
+
|auto increment|

_

Добавляя данные в таблицу person, вы просто предоставляете person id
значение null, и MySQL самостоятельно заполнит столбец следующим доступным числом ( по умолчанию MySQL для столбцов с автоматическим увеличением начинает отсчет с 1).

Инструкция insert
Теперь, когда все готово, пора заняться добавлением данных. Следующая
инструкция создает строку в таблице person для Уильяма Тернера ( William
Turner ):
mysql> INSERT INTO person
> (person_id, fname, lname, eye color, birth_date)
-> VALUES (null, 'William','Turner', 'BR', '1972 05 27');
Query OK, 1 row affected (0.22 sec)

_

-

- -

Сообщение “Query OK , 1 row affected” означает , что синтаксис инструкции верен и в таблицу была добавлена одна строка (поскольку это была инструкция insert ). Вы можете просмотреть только что добавленные в таблицу данные, выполнив инструкцию select:

_

mysql> SELECT person_id, fname, lname, birth date
-> FROM person;
+

+

+

_

person id I fname
+

+

+

birth date
+

+

William | Turner | 1972-05-27

1
+

+

+

I lname

+

+

+



+

1 row in set (0.06 sec)
Глава 2. Создание и наполнение базы данных

57

Как видите, сервер MySQL сгенерировал для первичного ключа значение 1.
Так как в таблице person есть только одна строка, я не указал, какая именно
строка меня интересует , и просто получил все строки таблицы. Если бы в
таблице было больше одной строки, я мог бы добавить предложение where,
чтобы указать, что я хочу получить данные только для строки, имеющей зна чение 1 в столбце person id:

_
_
mysql> SELECT person id, fname, lname, birth_date
-> FROM person
-> WHERE person_id = 1;
+

+

person_id

+

+

+
+

birth date |
+

+

+

+

1972-05-27 |

1 j William | Turner
+

+

+

I fname | lname
+

+

1 row in set (0.00 sec)

Хотя этот запрос указывает конкретное значение первичного ключа, вы
можете использовать для поиска строк любой столбец таблицы, как показано в следующем запросе, который находит все строки со значением Turner
в столбце lname:

_

mysql> SELECT person_id, fname, lname, birth date
> FROM person
> WHERE lname = 'Turner';

-

+

+

| person_id

+

+

I fname | lname

+

_

| birth date
+
+
+
+
+
1 | William | Turner | 1972-05 27
+
+
+
+
+
1 row in set (0.00 sec)

-

Прежде чем двигаться дальше, стоит сказать еще пару слов о рассмотрен ной выше инструкции insert.

58



Значения не были предоставлены ни для одного из столбцов адреса. Это
нормально, поскольку для этих столбцов разрешены значения null.



Значение, предоставленное для столбца birth date, было строкой.
Пока эта строка соответствует требуемому формату, показанному в
табл. 2.4, MySQL сам преобразует строку в дату.



Имена столбцов и указанные значения должны совпадать по количеству
и типу. Если вы укажете семь столбцов и предоставите только шесть
значений или если вы предоставите значения, которые не могут быть
преобразованы в соответствующий тип данных для соответствующего
столбца, вы получите сообщение об ошибке.

_

Глава 2. Создание и наполнение базы данных

Уильям Тернер предоставил также информацию о трех своих любимых
блюдах, так что вот три инструкции insert для сохранения его предпочтений в еде:

_

mysql> INSERT INTO favorite food (personnel, food)
-> VALUES (1, 'pizza');
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO favorite food (person id, food)
-> VALUES (1, 'cookies');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO favorite_food (person id, food)
-> VALUES (1, 'nachos');
Query OK, 1 row affected (0.01 sec)

_

_
_

А вот запрос, который извлекает любимые блюда Уильяма в алфавитном
порядке с помощью предложения order by:
mysql> SELECT food
> FROM favorite food
-> WHERE person id = 1
-> ORDER BY food;

_
_

-

+

+

I food
+

+

cookies
nachos
pizza
+
+
3 rows in set (0.02 sec)

Предложение order by сообщает серверу, как следует сортировать данные,
возвращаемые запросом. Без order by нет гарантии, что данные из таблицы
будут извлекаться в каком- то определенном порядке.
Чтобы Уильям не чувствовал себя одиноким, выполним еще одну инструкцию insert , чтобы добавить в таблицу person Сьюзен Смит :
mysql> INSERT INTO person
-> (person id, fname, lname, eye color, birth date,
-> street, city, state, country, postal code)
-> VALUES (null, 'Susan','Smith', 'BL', '1975-11-02',
-> '23 Maple St.', 'Arlington', 'VA', 'USA', '20220');
Query OK, 1 row affected (0.01 sec)

_

_

_

_

Поскольку Сьюзен любезно предоставила свой адрес, мы включили в запрос на пять столбцов больше, чем при добавлении данных Уильяма. Снова
запросив таблицу, вы увидите, что строке Сьюзен в качестве значения первичного ключа присвоено значение 2:
Глава 2. Создание и наполнение базы данных

59

_

_

mysql> SELECT person id, fname, lname, birth date
FROM person;

->
+

_

+

+

+

+

| person id | fname
+

| lname
birth date
+
+
+
+
1 William | Turner|1972-05-27
2 Susan | Smith | 1975 11 02

- -

+

+

+

+

+

2 rows in set (0.00 sec)

Как получить информацию в виде XML
Всем, кто планирует работать с данными XML, будет приятно узнать, что

большинство серверов баз данных предоставляют простой способ генерации
выходных данных запроса в виде XML. Например, с MySQL вы можете использовать параметр xml при вызове инструмента mysql , и весь ваш вывод
будет автоматически отформатирован в виде XML. Вот как выглядят в указанном виде данные о любимой еде:



C:\database> mysql -u lrngsql -p
Enter password: xxxxxx
Welcome to the MySQL Monitor...

--xml

bank

_

Mysql> SELECT * FROM favorite food;




nachos


SELECT (7 * 5) / ((3 + 14) * null);
+

+

(7 * 5) / ((3 + 14) * null)
+

+

NULL |
+
1 row in set (0.08 sec)

+

При выполнении вычислений выражения case полезны для перевода зна чения null в число ( обычно 0 или 1), что позволяет вычислениям выдавать
результат , не равный null.

Глава 11. Условная логика

249

Проверьте свои знания
Проверьте свое умение решать задачи с использованием условной логики
на приведенных здесь примерах и сравните свои решения с решениями из
приложения Б.
УПРАЖНЕНИЕ 11.1
Перепишите следующий запрос, в котором используется простое выражение case, так, чтобы получить те же результаты с использованием поискового выражения case . Старайтесь, насколько это возможно, использовать как
можно меньше предложений when.
SELECT паше,
CASE name
WHEN 'English i
WHEN 'Italian
WHEN 'French
WHEN 'German
WHEN 'Japanese'
WHEN 'Mandarin'
ELSE 'Unknown'
END character set
FROM language;

THEN
THEN
THEN
THEN
THEN
THEN

'latinl
'latinl
'latinl
'latinl'
i

'utf8
'utf8 i

_

УПРАЖНЕНИЕ 11.2
Перепишите следующий запрос так, чтобы результирующий набор содержал одну строку с пятью столбцами ( по одному для каждого рейтинга ). На зовите эти пять столбцов G, PG, PG_13 , R и NC_17 .
mysql> SELECT rating, count(*)
-> FROM film
-> GROUP BY rating;
+

+

+

rating | count(*) |
+

+

+

PG
G
NC-17
PG-13
R

194
178
210

223
195

+
+
+
5 rows in set (0.00 sec)

250

|

Глава 11. Условная логика

ГЛАВА 12

Транзакции

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

Многопользовательские базы данных
Системы управления базами данных позволяют единственному пользователю запрашивать и изменять данные, но в современном мире вносить
изменения в базу данных могут одновременно тысячи людей. Если каждый
пользователь только выполняет запросы, как обычно и происходит в случае
с хранилищем данных в обычное рабочее время, то это для сервера базы дан ных практически не составляет проблем. Но если некоторые пользователи
добавляют и / или изменяют данные, то серверу приходится решать намного
более сложные задачи.
Предположим, например, что вы составляете отчет , в котором суммируются данные о прокате фильмов за текущую неделю. Однако одновременно с
выполнением вашего отчета происходят следующие события.






Клиент берет фильм напрокат .

Другой клиент возвращает фильм после установленного срока возврата
и платит штраф за просрочку.
Пункт проката получает пять новых фильмов.

Таким образом, пока генерируется отчет , несколько пользователей изменя ют информацию в базе данных. Так какие числа должны появиться в отчете ?
Ответ зависит от того, как ваш сервер обрабатывает блокировку, описываемую в следующем разделе.

Блокировка



Блокировка это механизм, который сервер базы данных использует для
управления одновременным использованием ресурсов данных. Когда какая - то часть базы данных заблокирована, другие пользователи, желающие
изменить ( или, возможно, прочесть) эти данные, должны ждать, пока блоки ровка не будет снята. Большинство серверов баз данных используют одну из
следующих двух стратегий блокировки.



Записывающие информацию в базу данных (“писатели” ) должны запрашивать и получать от сервера блокировку записи для изменения данных,
а извлекающие информацию ( “ читатели” ) из базы данных для выпол нения запроса должны запрашивать и получать от сервера блокировку
чтения. В то время как несколько пользователей могут читать данные
одновременно, за один раз для каждой таблицы (или ее части) выдается
только одна блокировка записи, а запросы на чтение блокируются до
тех пор, пока блокировка записи не будет снята.



Пишущие в базу данных пользователи должны запрашивать и получать
от сервера блокировку записи для изменения данных, но читателям не
нужны никакие блокировки для запроса данных. Вместо этого сервер
гарантирует , что каждый читатель с момента начала его запроса видит
согласованное представление данных ( данные кажутся теми же самы ми, даже если другие пользователи могли внести в них изменения ) до
тех пор, пока запрос не завершится. Этот подход известен как управле ние версиями (versioning ).

У обоих подходов есть свои плюсы и минусы. Первый подход может при вести к долгому времени ожидания при наличии большого количества одновременных запросов на чтение и запись. Второй подход может быть проблематичным, если во время изменения данных поступают длительные запросы.
Из трех обсуждаемых в этой книге серверов Microsoft SQL Server использует
первый подход, Oracle Database использует второй подход, a MySQL использует оба подхода (в зависимости от вашего выбора механизма хранения, который мы обсудим далее в этой главе) .

252

Глава 12. Транзакции

Гранулярность блокировок
Существует ряд различных стратегий, которые вы можете использовать
при принятии решения о том, как именно блокировать ресурс. Сервер может применять блокировку на одном из трех разных уровней, или гранулярностей.

Блокировка таблиц
Не позволяет нескольким пользователям одновременно изменять данные в одной таблице.

Блокировка страниц
Не позволяет нескольким пользователям изменять данные в одной и той
же странице ( страница
это сегмент памяти, обычно в диапазоне от 2
до 16 Кбайт ) таблицы одновременно.



Блокировка строк
Не позволяет нескольким пользователям одновременно изменять одну
и ту же строку в таблице.
У этих подходов есть свои плюсы и минусы. Чтобы блокировать целые таблицы, требуется совсем немного времени, но этот подход по мере увеличения числа пользователей быстро приводит к неприемлемому времени ожида ния. Блокировка строк требует большего объема дополнительных действий,
но зато позволяет нескольким пользователям изменять одну и ту же таблицу,
если они работают с разными строками. Из трех серверов, рассматриваемых
в этой книге, Microsoft SQL Server использует блокировку страниц, строк и
таблиц, Oracle Database использует только блокировку строк, a MySQL использует блокировку таблиц, страниц или строк (в зависимости от вашего
выбора механизма хранения). SQL Server при определенных обстоятельствах
наращивает блокировки от строки к странице и от страницы к таблице, тогда
как Oracle Database никогда не увеличивает гранулярность блокировок.
Возвращаясь к нашему отчету данные, которые появляются на страни цах отчета, будут отражать либо состояние базы данных при запуске вашего
отчета ( если сервер использует подход с управлением версиями ) , либо состояние базы данных, когда сервер выполняет для приложения, генерирующего
отчет , блокировку чтения ( если ваш сервер использует блокировки чтения
и записи).



Глава 12. Транзакции

253

Что такое транзакция
Если бы серверы баз данных безотказно работали все 100% времени, если
бы пользователи всегда позволяли программам завершать выполнение и если
бы приложения всегда завершались без фатальных ошибок, останавливающих выполнение, не было бы ничего, что следовало было бы обсуждать об
одновременном доступе к базе данных. Однако мы не можем полагаться ни
на что из перечисленного, поэтому необходимо поднять еще один вопрос,
позволяющий нескольким пользователям получать доступ к одним и тем же
данным.
Этой дополнительной частью головоломки параллелизма является тран закция, которая представляет собой механизм группировки нескольких ин струкций SQL, так что либо все инструкции выполняются успешно, либо не
выполняется ни одна из них (свойство “ все или ничего”, известное как ато марность ). Если вы попытаетесь перевести 500 долларов со своего сберегательного счета на текущий счет , вероятно, вы будете несколько расстроены,
если деньги будут успешно сняты со сберегательного счета, но так и не поступят на текущий счет . Какой бы ни была причина сбоя (сервер был отключен
на техническое обслуживание, истекло время запроса блокировки страницы
в таблице учетных записей и т .д.), вы захотите вернуть свои 500 долларов.
Для защиты от такого рода ошибок программа, обрабатывающая ваш за прос на перевод денег, сначала начинает транзакцию, затем выдает SQL- за просы, необходимые для перевода денег с одного счета на другой , и, если
все проходит успешно, завершает транзакцию, выполнив команду commit
фиксации изменений. Однако, если произойдет что-то непредвиденное, программа выдаст команду отката rollback, которая потребует от сервера отменить все изменения, сделанные с момента начала транзакции. Весь процесс
может выглядеть примерно так:



START TRANSACTION;
/* Снятие денег с первого счета с проверкой
наличия достаточной суммы на счету */
UPDATE account SET avail balance = avail balance

_

WHERE account_id = 9988
AND avail balance > 500;

_

-

500

IF Предыдущей инструкцией обновлена ровно одна строка> THEN
/* Помещение денег на второй счет */
UPDATE account SET avail_balance = avail_balance + 500
WHERE account id = 9989;

254

Глава 12. Транзакции

IF Предыдущей инструкцией обновлена ровно одна строка> THEN
/* Все отработано успешно; делаем изменения постоянными */
COMMIT;
ELSE
/* Что-то пошло не так; откат изменений */
ROLLBACK;
END IF;
ELSE
/* Недостаточно денег или ошибка при внесении изменений */
ROLLBACK;
END IF;

я

Хотя предыдущий блок кода и может показаться похожим на один
из процедурных языков, предоставляемых крупными компаниями
баз данных, такой как Oracle PL/SQL или Microsoft Transact -SQL,
он написан на псевдокоде и не пытается имитировать какой -либо
конкретный язык.

Показанный блок кода начинается с запуска транзакции, а затем пытается
снять 500 долларов с одного счета и добавить их на другой. Если все пройдет
хорошо, транзакция будет выполнена; если же что- то пойдет не так, прои зойдет откат транзакции, т .е. будут отменены все изменения данных с начала

транзакции.
Используя транзакцию, программа гарантирует , что ваши 500 долларов
останутся на первом счету или будут перенесены на второй счет без возможности их потери. Независимо от того, была ли транзакция зафиксирована
или был выполнен откат , все полученные во время выполнения транзакции
ресурсы ( например, блокировки записи) высвобождаются после завершения
транзакции.
Конечно, если программе удастся выполнить обе инструкции update, но
сервер остановится до того, как будет выполнена фиксация или откат , то,
когда работоспособность сервера будет восстановлена, будет выполнен от кат . ( Одна из задач, которые сервер базы данных должен выполнить перед
найти все незавершенные транзакции, которые
подключением к сети,
обрабатывались при отключении сервера, и откатить их.) Кроме того, если
ваша программа завершает транзакцию и выполняет commit, но сервер вы ключается до того, как изменения будут внесены в постоянное хранилище
( т .е. измененные данные все еще находятся в памяти и не сброшены на диск ),
то сервер базы данных должен повторно применить к данным изменения из
вашей транзакции при перезапуске сервера ( свойство, известное как устой чивость ( durability) ).



Глава 12. Транзакции

255

Запуск транзакции
Серверы баз данных обрабатывают создание транзакций одним из двух
способов.



Активная транзакция всегда связана с сеансом базы данных, поэтому
нет необходимости в явном начале транзакции и нет никакого метода
для этого. Когда текущая транзакция заканчивается, сервер автоматически начинает новую транзакцию для вашего сеанса.



Если вы не начинаете транзакцию явно, отдельные инструкции SQL
автоматически считаются совершенно независимыми одна от другой.
Чтобы начать транзакцию, вы должны сначала выполнить соответствующую команду.

Из трех серверов Oracle Database использует первый подход, a Microsoft
SQL Server и MySQL
второй подход. Одно из преимуществ подхода Oracle
к транзакциям заключается в том, что даже если вы вводите только одну команду SQL, у вас есть возможность отменить изменения, если вам не понра вился результат или если вы передумали. Таким образом, если вы забыли добавить предложение where в инструкцию delete, у вас будет возможность
исправить содеянное ( при условии, что вы вовремя сообразите, что удалить
все 125000 строк из таблицы не входило в ваши намерения ). Однако в MySQL
и SQL Server изменения, вызванные вашей инструкцией SQL после нажатия
клавиши < Enter > , будут постоянными ( если только ваш администратор баз
данных может не сможет получить исходные данные из резервной копии или
каким-либо иным способом ).
Стандарт SQL:2003 включает команду start transaction , которая используется, когда вы хотите явно начать транзакцию. В то время как MySQL
соответствует стандарту, пользователи SQL Server должны ввести команду
begin transaction . В обоих серверах, пока вы явно не начнете транзакцию,
вы находитесь в так называемом режиме автоматической фиксации ( авто фиксации ) , что означает , что результаты выполнения отдельных инструкций
автоматически фиксируются сервером. Таким образом, вы можете принять
решение о выполнении транзакции и выполнить команду ее запуска или про-



сто позволить серверу фиксировать результаты выполнения отдельных инструкций.
И MySQL, и SQL Server позволяют отключать режим автоматической фик сации для отдельных сеансов работы, и в этом случае серверы будут действовать в отношении транзакций так же, как и сервер Oracle Database. Чтобы
256

.

Глава 12 Транзакции

отключить режим автоматической фиксации, в SQL Server вы вводите следу-

ющую команду:
SET IMPLICIT_TRANSACTIONS ON

MySQL позволяет отключить режим автоматической фиксации следующим образом:
SET AUTOCOMMIT=0

После выхода из режима автоматической фиксации все команды SQL вы полняются в пределах области видимости транзакции и должны быть явно
зафиксированы или отменены.

я

Небольшой совет : отключайте режим автоматической фиксации
каждый раз при входе в систему и приобретите привычку вы полнять все операторы SQL в транзакциях. Это по крайней мере
может избавить вас от смущения при необходимости просить администратора базы данных восстановить данные, которые вы по
неосторожности удалили.

Завершение транзакции
После того как транзакция началась ( явно ли с помощью команды start
transaction или неявно сервером базы данных) , вы должны явно ее завершить, чтобы внесенные вами изменения стали постоянными. Это делается
с помощью команды commit , которая дает указание серверу пометить изменения как постоянные и освободить все ресурсы ( т .е. блокировки страниц
или строк), использовавшиеся во время транзакции.
Если вы решите отменить все изменения, сделанные с момента начала
транзакции, вы должны ввести команду rollback, которая требует от сервера вернуть данные в состояние до начала транзакции. После завершения
отката любые ресурсы, используемые сеансом, освобождаются.
Наряду с выполнением команды commit или rollback имеется несколько
других сценариев того, как может завершиться транзакция как косвенный
результат ваших действий или в результате чего-то, находящегося вне вашего
контроля.





Сервер выключается, и в этом случае ваша транзакция будет автомати чески отменена при перезапуске сервера.



Вы вводите инструкцию схемы SQL, такую как alter table, которая
приводит к тому, что текущая транзакция должна быть зафиксирована,
а новая транзакция должна быть запущена.
Глава 12. Транзакции

257

Вы выполняете еще одну команду start transaction, которая вызовет
завершение предыдущей транзакции, которая должна быть фиксирова на.



Сервер преждевременно завершает вашу транзакцию, поскольку обна руживает взаимоблокировку и приходит к выводу, что в ней виновата
ваша транзакция. В этом случае будет выполнен откат транзакции, и вы
получите сообщение об ошибке.

Из этих четырех сценариев первый и третий достаточно просты, но другие два заслуживают дополнительного рассмотрения. Что касается второго
сценария, то изменения базы данных, будь то добавление новой таблицы или
индекса или удаление столбца из таблицы, не может быть отменено, поэтому команды, изменяющие вашу схему, должны происходить вне транзакции.
Следовательно, если в настоящее время выполняется транзакция, сервер за фиксирует текущую транзакцию, выполнит команды инструкции схемы SQL,
а затем автоматически начнет новую транзакцию для вашего сеанса. Сервер
не сообщит вам о том , что произошло, поэтому вы должны быть осторожны,
чтобы инструкции, составляющие единую логическую единицу, не были случайно разбиты сервером на несколько транзакций.
Четвертый сценарий связан с обнаружением взаимоблокировок. Взаи моблокировка возникает , когда две разные транзакции ждут ресурсов, которые в настоящее время захвачены другой транзакцией. Например, транзакция А могла только что обновить таблицу account и ожидает блокировки
записи в таблице transaction, в то время как транзакция В вставила запись
в таблицу transaction и ожидает блокировки записи в таблице account.
Если обе транзакции изменяют одну и ту же страницу или строку ( в зависимости от гранулярности блокировок, используемой сервером базы данных),
то каждая из них будет вечно ждать завершения другой транзакции и освобождения необходимого ресурса. Серверы баз данных должны всегда отслеживать такие ситуации, чтобы не страдала пропускная способность; при об наружении взаимоблокировки одна из транзакций ( выбранная произвольно
или по некоторым критериям) откатывается, чтобы можно было продолжить
другую транзакцию. В большинстве случаев прекращенная транзакция может
быть перезапущена вновь и будет успешной без возникновения новой взаи -

моблокировки.

В отличие от рассматривавшегося ранее второго сценария, сервер базы дан ных сгенерирует сообщение об ошибке с информацией об отмене транзакции
258

Глава 12. Транзакции

из- за обнаружения взаимоблокировки. Например, в случае MySQL вы получите ошибку 1213, которая содержит следующее сообщение:
Message: Deadlock found when trying to get lock; try restarting transaction1

Как следует из сообщения об ошибке, разумно повторить транзакцию, которая был откачена из - за обнаружения взаимоблокировки. Однако, если взаимоблокировки будут распространенной ситуацией, то может потребоваться
изменить приложения, которые обращаются к базе данных, чтобы уменьшить
вероятность таких взаимоблокировок (одна из распространенных стратегий
гарантировать доступ к ресурсам всегда в одном и том же порядке, например
всегда изменять данные учетной записи перед вставкой данных транзакции).



Точки сохранения транзакции
В некоторых случаях вы можете столкнуться в транзакции с проблемой,
требующей отката, но при этом не захотеть отменять всю проделанную работу. Для таких ситуаций можно установить одну или несколько точек сохранения в транзакции и использовать их, чтобы выполнить откат к определенному месту вашей транзакции, а не к ее началу.
Выбор механизма хранения
При использовании Oracle Database или Microsoft SQL Server за низкоуровневые операции с базой данных (такие, как получение определенной строки из
таблицы на основе значения первичного ключа) отвечает единый набор кода.
Однако сервер MySQL спроектирован таким образом, чтобы для обеспечения низкоуровневой функциональности базы данных, включая блокировку
ресурсов и управление транзакциями, могло использоваться несколько меха низмов хранения. Начиная с версии 8.0, MySQL включает следующие механизмы хранения.
MylSAM

Нетранзакционный механизм , использующий табличную блокировку
MEMORY
Нетранзакционный механизм, используемый для таблиц в памяти
CSV
Транзакционный механизм, который хранит данные в файлах с данными
с разделением запятыми
Сообщение: при попытке блокировки обнаружена взаимоблокировка. Попытайтесь
повторить транзакцию.
1

Глава 12. Транзакции

259

InnoDB

Транзакционный механизм, использующий блокировку на уровне строки
Merge
Специальный механизм, используемый для создания нескольких идентич ных таблиц MylSAM в виде единой таблицы ( так называемое разбиение
таблицы)
Archive
Специальный механизм, используемый для хранения больших количеств
неиндексированных данных, в основном для архивных целей
Хотя вы можете подумать, что требуется выбрать единственный механизм
хранения для вашей базы данных, MySQL достаточно гибок, чтобы позволить
вам выбирать механизмы хранения на основе таблиц. Однако для любых та блиц, которые могут участвовать в транзакциях, следует выбирать механизм
InnoDB, который использует блокировку уровня строки и управление верси ями для обеспечения наивысшего уровня параллелизма среди всех различных
механизмов хранения.
Вы можете явно указать механизм хранения при создании таблицы, а также
изменить существующую таблицу, указав для ее хранения другой механизм.
Если вы не знаете, какой механизм хранения назначен той или иной таблице,
можете использовать команду show table, как показано далее:
mysql> show table status like ’ customer' \G;
k k k k k k k k k k k k k k k k k k k k k k k k k k k 1. row k k k k k k k k k k
Name: customer
Engine : InnoDB
Version: 10
Row_format: Dynamic
Rows: 599
Avg row length: 136
Data length: 81920
Max_data length: 0
Index_length: 49152
Data free: 0
Auto_increment: 599
Create time: 2019-03-12 14:24:46
Update_time: NULL
Check time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create options:
Comment:
1 row in set (0.16 sec)

_ _
_
_

_
_
_

_

260

Глава 12. Транзакции

k k k k k k k k k k k k k k k k k

Взглянув на вторую строку, вы видите, что таблица customer уже использует
механизм InnoDB. Если бы это было не так, вы могли бы назначить механизм
InnoDB с помощью следующей команды:
ALTER TABLE customer ENGINE

INNODB;

Всем точкам сохранения должны быть даны имена, которые позволяют
иметь несколько различных точек сохранения в пределах одной транзакции.
Чтобы создать точку сохранения с именем my savepoint , выполните следу-

_

ющую инструкцию:

_

SAVEPOINT my savepoint;

Для отката к определенной точке сохранения просто вводится команда
rollback, за которой следуют ключевые слова to savepoint и имя точки
сохранения, например:

_

ROLLBACK ТО SAVEPOINT my savepoint;

Вот пример того, как можно использовать точки сохранения:
START TRANSACTION;

UPDATE product
SET date retired = CURRENT_TIMESTAMP()
WHERE product cd = 'XYZ';

_

_

SAVEPOINT before close accounts;

UPDATE account
SET status = 'CLOSED', close date = CURRENT TIMESTAMP(),
last_activity_date = CURRENT TIMESTAMP{)
WHERE product_cd = 'XYZ';

_

_

_

_

ROLLBACK TO SAVEPOINT before_close accounts;
COMMIT;

В результате этой транзакции мифический товар XYZ снят с производства,
но ни один счет не закрыт .
При использовании точек сохранения помните следующее.



Несмотря на название, при создании точки сохранения ничего не сохраняется. В конечном итоге вы должны выполнить commit , если хотите, чтобы ваша транзакция стала постоянной.



Если вы выполняете откат без указания точки сохранения, все точки
сохранения в пределах транзакции игнорируются и выполняется откат
всей транзакции в целом.
Глава 12. Транзакции

261

Если вы используете SQL Server, для создания точки сохранения нужно
использовать его нестандартную команду save transaction , а для отката
к точке сохранения rollback transaction . Каждая команда сопровождается именем точки сохранения.



Проверьте свои знания
Предложенное здесь упражнение призвано закрепить понимание вами
транзакций. Ответ к упражнению представлен в приложении Б.
УПРАЖНЕНИЕ 12.1
Создайте логическую единицу работы для перевода 50 долларов со счета
123 на счет 789. Для этого вставьте две строки в таблицу transaction и обновите две строки в таблице account. Используйте следующие определения /
данные таблиц:
Account :
account_id avail_balance last_activity date

_

123
789

2019- 07 -10 20 : 53 : 27
2019- 06 - 22 15 : 18 : 35

500
75

Transaction :

_

_

txn id

txn date

account_id

txn type cd amount

1001
1002

2019- 05-15
2019- 06- 01

123
789

С
С

_

500
75

Используйте txn _ type cd = ’ С ' , чтобы указать операцию кредита ( до бавление на счет ), и txn type_ cd = * D * для обозначения дебета ( снятия со
счета).

_

262

Глава 12. Транзакции

ГЛАВА 13

Индексы и ограничения

Поскольку основное внимание в этой книге уделяется методам програм мирования, предыдущие главы сосредоточены на элементах языка SQL, которые можно использовать для создания мощных инструкций select , insert ,
update и delete. Однако другие возможности баз данных также косвенно
влияют на код, который вы пишете. В этой главе рассматриваются две из этих
возможностей: индексы и ограничения.

Индексы
Когда вы вставляете в таблицу строку, сервер базы данных не пытается поместить данные в определенное конкретное место в таблице. Например, если
вы добавляете строку в таблицу customer , сервер не размещает строки в
числовом порядке значений столбца customer _id или в алфавитном порядке значений столбца last name . Вместо этого он просто помещает данные
в следующее доступное место в файле ( сервер поддерживает список свободных мест для каждой таблицы ). Когда вы запрашиваете таблицу customer ,
серверу приходится проверять каждую строку таблицы, чтобы ответить на
ваш запрос. Например, предположим, что вы вводите следующий запрос:

_

_

_

mysql> SELECT first name, last name
-> FROM customer
-> WHERE last name LIKE 'Y%';
+

+

+

first name | last name I
+

+

+

+

LUIS
MARVIN
CYNTHIA

YANEZ
YEE
YOUNG
+

+

3 rows in set (0.09 sec)

Чтобы найти всех клиентов, чьи фамилии начинаются с Y, сервер дол жен посетить каждую строку в таблице customer и проверить содержимое

столбца last _name; если фамилия начинается с Y, то строка добавляется
к результирующему набору. Этот тип доступа известен как сканирование

таблицы.
Хотя этот метод неплохо работает для таблицы только с тремя строками,
представьте, сколько времени может потребоваться для ответа на запрос,
если таблица содержит три миллиона строк. При некотором количестве
строк, большем, чем три, и меньшем трех миллионов, находится тот размер
таблицы, для которого сервер не в состоянии ответить на запрос за разумное
время без дополнительной помощи. Эта помощь приходит в виде одного или
нескольких индексов для таблицы customer.
Даже если вы никогда не слышали об индексах баз данных, вы, безусловно, знаете, что индекс это средство быстрого доступа ( например, в данной
книге имеется индекс предметный указатель). Индекс это просто меха низм поиска конкретного объекта внутри ресурса. Таковым индексом являет ся предметный указатель, который позволяет быстро находить определенное
слово в книге, располагая термины в алфавитном порядке. Это позволяет чи тателю сначала быстро найти конкретную букву в пределах предметного указателя, а затем найти нужную запись и страницу или страницы, на которых
можно найти искомое слово.
Таким же образом, как человек использует предметный указатель, чтобы
найти нужные слова в книге, сервер базы данных использует индексы для
определения местонахождения строк в таблице. Индексы это специальные
таблицы, которые, в отличие от обычных таблиц данных, хранятся в опре деленном порядке. Но вместо того, чтобы содержать все данные о некоторой
записи, индекс содержит только столбец (или столбцы), используемый, чтобы
найти строки в таблице данных, вместе с информацией, описывающей, где
физически расположена эта строка. Таким образом, роль индексов состоит
в том, чтобы облегчить поиск подмножества строк и столбцов таблицы без








необходимости сканировать каждую строку в таблице.

Создание индекса
Возвращаясь к таблице customer , вы можете принять решение добавить
индекс к столбцу email, чтобы ускорить любые запросы, которые работают
со значением этого столбца, а также любые операции update или delete, которые указывают адрес электронной почты клиента. Вот как можно добавить
такой индекс к базе данных MySQL:
mysql> ALTER TABLE customer
-> ADD INDEX idx email (email);

264

Глава 13. Индексы и ограничения

Query OK, 0 rows affected (1.87 sec)
Records: 0 Duplicates: 0 Warnings: 0

Эта инструкция создает индекс ( точнее, индекс В-дерева, но подробнее об
этом речь пойдет ниже) для столбца customer . email; этот индекс получает
имя idx email. При наличии индекса оптимизатор запросов ( который мы
упоминали в главе 3, “ Запросы” ) может выбрать использование индекса, если
сочтет это полезным. Если в таблице имеется более одного индекса, оптимизатор должен решить, применение какого именно индекса наиболее выгодно
для конкретной инструкции SQL.

_

s

MySQL рассматривает индексы как необязательные компонен ты таблицы. Вот почему в более ранних версиях для добавления
или удаления индекса вы использовали бы команду alter table.
Другие серверы баз данных, включая SQL Server и Oracle Database,
рассматривают индексы как независимые объекты схемы. Таким
образом, как для SQL Server, так и для Oracle индекс генерируется
с помощью команды create index:

_

CREATE INDEX idx email
ON customer (email);

Начиная c MySQL версии 5 доступна команда create index, хотя
она и отображается в соответствующую команду alter table.
Однако для создания индексов первичного ключа все равно требуется использовать команду alter table.
Все серверы баз данных позволяют просматривать доступные индексы.
Пользователи MySQL могут использовать команду show, чтобы увидеть все
индексы в определенной таблице, как в следующем примере:
mysql> SHOW INDEX FROM customer \G;
A ******
** **** -*: * * * * * ***** *: ** 1. row
Table: customer
Non unique: 0
Key name: PRIMARY
Seq_in index: 1
Column_name: customer_id
Collation: A
Cardinality: 599
Sub part: NULL
Packed: NULL
Null:
Index type: BTREE
• '

'

'

'

'

_

_
_

_

******* * * * ** *****
••

** * 2. row ***************************

.

Глава 13 Индексы и ограничения

265

Table:
Non unique:
Key name:
Seq_in_index:
Column name:
Collation:
Cardinality:
Sub part:
Packed:
Null:
Index_type:

_

_
_

_

Table:
Non unique:
Key name:
Seq_in_index:
Column_name:
Collation:
Cardinality:
Sub part:
Packed:
Null:
Index type:

_

_

_

_

customer

1
idx fk store id
1
store id
A
2
NULL
NULL

_ _
_

_

BTREE

^

rOW ***************************
customer
1
idx fk address_id
1
address id
A
599
NULL
NULL

_ _

_

BTREE

* ** * * ****** *** **** 4. row ***************************
Table: customer
Non_unique: 1
Key name: idx_last name
Seq_in_index: 1
Column_name: last name
Collation: A
Cardinality: 599
Sub part: NULL
Packed: NULL


_

_

_

_

Null:
Index type: BTREE

_

row ***************************
** ** ** ** *** ** * * ****
Table: customer
Non unique: 1
Key name: idx email
Seq in index: 1
Column_name: email
Collation: A
Cardinality: 599
Sub part: NULL
Packed: NULL
Null: YES
Index_type: BTREE


_
_
_ _

_

_

5 rows in set (0.06 sec)

266 |

.

Глава 13 Индексы и ограничения



Вывод демонстрирует , что в таблице customer есть пять индексов: один
для столбца customer id с именем PRIMARY и четыре других для столбцов store id, address id, last name и email. Если вам интересно, откуда
взялись эти индексы, то этоя создал индекс для столбца email, а остальные
были установлены как часть образца базы данных Sakila. Вот инструкция, используемая для создания таблицы:

_
_

_



_

CREATE TABLE customer (
customer id SMALLINT UNSIGNED NOT NULL AUTO INCREMENT,
store id TINYINT UNSIGNED NOT NULL,
first name VARCHAR(45) NOT NULL,
last_name VARCHAR(45) NOT NULL,
email VARCHAR(50) DEFAULT NULL,
address_id SMALLINT UNSIGNED NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
create date DATETIME NOT NULL,
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

_

_
_

_

_

PRIMARY
KEY idx
KEY idx
KEY idx

_

KEY ( customer id) ,

_fk_store_id ( store_id) ,
_fk_address_id ( address_id) ,
_las t_name ( last_name ) ,

Когда таблица была создана, сервер MySQL автоматически сгенерировал
индекс для столбца первичного ключа, которым в данном случае является
customer_id, и присвоил индексу имя PRIMARY. Это особый тип индекса, используемый с ограничением первичного ключа, но об ограничениях я расскажу позже в этой главе.
Если после создания индекса вы решите, что индекс бесполезен, можете
удалить его следующим образом:
mysql> ALTER TABLE customer
-> DROP INDEX idx email;
Query OK, 0 rows affected (0.50 sec)
Records: 0 Duplicates: 0 Warnings: 0

я

_

Пользователи SQL Server и Oracle Database должны использовать
для удаления индекса команду drop index:
DROP INDEX idx_email;
DROP INDEX idx email ON customer;

_

(Oracle)
(SQL Server)

MySQL теперь поддерживает и команду drop index, хотя она так
же, как и команда create index, отображается в команду alter
table.
Глава 13. Индексы и ограничения

267

Уникальные индексы

При разработке базы данных важно учитывать, каким столбцам разрешенет . Например , в таблице
но содержать повторяющиеся данные, а каким
customer допустимо наличие двух клиентов с именем John Smith , поскольку
в каждой строке будут свои идентификатор(customer id), электронная почта и адрес, чтобы их можно было различить. Вы, однако, не хотите позволять
двум разным клиентам иметь один и тот же адрес электронной почты. Этого
можно добиться, создав уникальный индекс для столбца customer.email.
Уникальный индекс играет несколько ролей; наряду с предоставлением
всех преимуществ обычного индекса он также служит механизмом для за прета повторяющихся значений в индексируемом столбце. При вставке строки или изменении индексированного столбца сервер базы данных проверя ет уникальный индекс, чтобы узнать, не существует ли уже такое значение
в другой строке таблицы. Вот как можно создать уникальный индекс для
столбца customer.email:



_

mysql> ALTER TABLE customer
-> ADD UNIQUE idx_email (email);
Query OK, 0 rows affected (0.64 sec)
Records: 0 Duplicates: 0 Warnings: 0

я

Пользователям SQL Server и Oracle Database нужно только доба вить ключевое слово unique при создании индекса, например:

_

CREATE UNIQUE INDEX idx email
ON customer (email);

При наличии индекса вы получите сообщение об ошибке, если попытаетесь добавить нового клиента с уже существующим адресом электронной
почты:
mysql> INSERT INTO customer
-> (store id, first name, last name, email, address id, active)
-> VALUES
> (1,'ALAN','KAHN 1 , 1 ALAN.KAHN@sakilacustomer.org', 394, 1);
ERROR 1062 (23000): Duplicate entry 'ALAN.KAHN@sakilacustomer.org'
for key 'idx email 1 1

_

_

_

_

-

_

Вы не должны создавать уникальные индексы для столбца ( столбцов) первичного ключа, поскольку сервер и без того проверяет уникальность значений первичного ключа . Однако вы можете создать более чем один уникальный индекс в одной и той же таблице, если считаете, что это оправдано.

Дубликат значения 'ALAN.KAHN@sakilacustomer.org' для ключа ,idx_email'.
268

Глава 13. Индексы и ограничения

Многостолбцовые индексы
Наряду с рассматривавшимися до сих пор одностолбцовыми индексами
можно строить индексы, охватывающие несколько столбцов. Если, например,
вы ищете клиентов по имени и фамилии, то можете построить индекс по
обоим столбцам вместе:
mysql> ALTER TABLE customer
> ADD INDEX idx full name (last name, first name);
Query OK, 0 rows affected (0.35 sec)
Records: 0 Duplicates: 0 Warnings: 0

_

-

_

_

_

Этот индекс будет полезен для запросов, в которых указываются имя
и фамилия, или просто фамилия, но будет бесполезен для запросов, в которых указывается только имя клиента. Чтобы понять, почему это так, подумайте, как бы вы могли найти телефонный номер человека. Если вы знаете
имя и фамилию человека, вы можете использовать телефонную книгу, чтобы быстро найти номер, так как телефонная книга упорядочена сначала по
по имени. Если же вы знаете только имя человека, вам
фамилии, а затем
нужно будет сканировать каждую запись в телефонной книге, чтобы найти
все записи с указанным именем.
Поэтому при построении многостолбцовых индексов следует тщательно
вторым и так да подумать о том, какой столбец указать первым, какой
лее, чтобы сделать индекс максимально полезным. Однако имейте в виду, что,
если это необходимо для обеспечения адекватного времени отклика, вас ни что не останавливает от построения нескольких индексов с использованием
одного и того же набора столбцов, но в другом порядке.





Типы индексов



Индексирование мощный инструмент , но, поскольку существует много
разных типов данных, имеется не одна стратегия индексирования. В следующих разделах показаны различные типы индексирования, доступные у различных серверов.
Индексы B-tree
Все показанные до сих пор индексы представляют собой индексы на основе сбалансированного дерева, которые чаще известны как индексы B- tree.
MySQL, Oracle Database и SQL Server по умолчанию используют B -tree ин дексирование, поэтому, если вы явно не запросите другой тип, то получи те индекс B-tree. Как и следовало ожидать, индексы B- tree организованы в
виде деревьев с одним или несколькими уровнями узлов ветвления, ведущих
Глава 13. Индексы и ограничения

269

к одному уровню листовых узлов . Узлы ветвления используются для нави гации по дереву, а листья содержат фактические значения и информацию
о местоположении. Например, индекс B - tree, построенный для столбца
customer . last name, может выглядеть так, как показано на рис. 13.1.

_

А-М

N-Z
у

А-С
D-F
G -I
J-M
V

Barker
Blake

V

Fleming
Fowler



N-Р
Q-S
T-V
W-Z




Gooding

Jameson

Grossman

Markham

Hawthorne

Mason

Parker
Portman

\!

Roberts
Smith

V



Tucker
Tulman
Tyler

Ziegler

Puc. 13.1. Пример сбалансированного дерева В -tree

Если выдан запрос для извлечения всех клиентов, фамилия которых начинается с буквы G, сервер просматривает верхний узел ветвления ( именуемый
корневым узлом ) и следует по ссылке к узлу ветвления, который обрабатывает фамилии, начинающиеся с букв от А до М. Этот узел, в свою очередь,
направляет сервер к листовому узлу, содержащему фамилии, начинающиеся
с букв от G до I. После этого сервер начинает чтение значений в листе, пока
не столкнется со значением, которое уже не начинается с буквы G (в данном
случае это фамилия Hawthorne ).
При вставке, обновлении и удалении строк из таблицы customer сервер
попытается сохранить дерево сбалансированным, чтобы с одной стороны
корневого узла не оказалось узлов и листьев существенно больше, чем с другой. Сервер может добавлять или удалять узлы ветвления для более равно мерного распределения значений и даже добавлять или удалять некоторые
уровни узлов ветвления полностью. Поддерживая дерево сбалансированным,
сервер может быстро добираться до листов при поиске требуемых значений
без необходимости перемещения по многим уровням узлов ветвления.
Битовые индексы
Хотя индексы B- tree великолепны при обработке столбцов, которые со держат много разных значений, таких как имя /фамилия клиента, они могут
стать слишком громоздкими при построении для столбца, который допускает
270

Глава 13. Индексы и ограничения

только небольшое количество значений. Например, вы можете решить сгенерировать индекс для столбца customer.active, чтобы быстро получать все
активные или неактивные учетные записи. Но поскольку в этом столбце есть
только два разных значения ( которые хранятся как 1 для активных и 0 для
неактивных клиентов), а также поскольку активных клиентов гораздо больше, чем неактивных, может оказаться трудно поддерживать сбалансирован ность деревьев при увеличении численности клиентов.
Для столбцов, которые содержат только небольшое количество значений
при большом количестве строк ( известные как данные с низкой мощностью
( кардинальностью)) , необходима другая стратегия индексации. Для более
эффективной обработки такой ситуации Oracle Database включает битовые
индексы, или bitmap - индексы ( bitmap indexes), которые генерируют битовое
представление для каждого значения, сохраненного в столбце. Если построить такой индекс для столбца customer.active, он будет поддерживать две
битовые карты: одну для значения 0, а другую для значения 1. Когда вы
пишете запрос для извлечения всех неактивных клиентов, сервер базы дан ных может обойти битовую карту для значения 0 и быстро извлечь нужные





строки.
Битовые индексы
хорошее, компактное решение для индексирования
для данных с низкой мощностью, но эта стратегия не будет эффективно ра ботать, если количество значений, хранящихся в столбце, станет слишком
большим по отношению к количеству строк (данные с высокой мощностью),
поскольку сервер должен будет поддерживать слишком много битовых карт .
Например, никогда не следует создавать bitmap- индекс для столбца первич ного ключа, поскольку он обеспечивает максимально возможную мощность
( различные значения для каждой строки ).
Пользователи Oracle могут генерировать битовые индексы, просто добавив ключевое слово bitmap в инструкцию create index:



_

CREATE BITMAP INDEX idx active ON customer (active);

Битовые индексы обычно используются в средах хранилищ данных, где
большие объемы данных обычно индексируются по столбцам, содержащим
относительно небольшое количество значений ( например, продажи по кварталам, географические регионы, товары, продавцы ).
Текстовые индексы
Если ваша база данных хранит документы, вам может потребоваться
разрешить пользователям искать слова или фразы в документах. Конечно,

.

Глава 13 Индексы и ограничения

271

нежелательно, чтобы сервер просматривал каждый документ и сканировал
текст каждый раз, когда запрашивается поиск, но и традиционные стратегии
индексирования в этой ситуации не работают . Чтобы справиться с такой ситуацией, MySQL, SQL Server и Oracle Database включают специальные меха низмы индексации и поиска для документов; и SQL Server, и MySQL включают так называемые полнотекстовые индексы, a Oracle Database включает
мощный набор инструментов, известный как Oracle Text. Поиск документов
достаточно специализирован, поэтому показывать конкретный пример непрактично, но полезно хотя бы знать, что такое индексирование доступно.

Как используются индексы
Индексы обычно используются сервером для быстрого поиска строк в
определенной таблице, после чего сервер посещает связанную таблицу для
извлечения дополнительной информации, запрошенной пользователем. Рассмотрим следующий запрос:
mysql> SELECT customer id, first_name, last_name

_

->
->
+
+

FROM customer
WHERE first name LIKE » S%' AND last name LIKE 'P% 1 ;
+

+

+

last name
customer id | first name
+
+
+
84 | SARA
| PERRY
197 | SUE
| PETERS
| PIERCE
167 | SALLY


+

+

+

+

3 rows in set (0.00 sec)

Для этого запроса сервер может использовать любую из следующих стра тегий.



Сканировать все строки в таблице клиентов.



Использовать индекс в столбце last name, чтобы найти всех клиентов,
чьи фамилии начинается с Р; затем посетить каждую строку таблицы
клиентов, чтобы найти только те строки, имя в которых начинается с S.



Использовать индекс для столбцов last _ name и first name , чтобы
с S.
найти всех клиентов, чьи фамилии начинаются с Р, а имя

_

_


Третий вариант кажется наилучшим, поскольку индекс вернет все строки,
необходимые для результирующего набора, без необходимости повторного
посещения таблицы. Но откуда вы знаете, какой из трех вариантов будет использован ? Чтобы узнать, как именно оптимизатор запросов MySQL решает
272

Глава 13. Индексы и ограничения

выполнить запрос, я использую инструкцию explain , которая просит сервер
показать план выполнения для запроса, а не выполнить его:
mysql>
->
->
->

EXPLAIN
SELECT customer_id, first name, last name
FROM customer
WHERE first name LIKE 1 S% 1 AND last name LIKE 'P% ' \G;
A:
row
id: 1
select type: SIMPLE
table: customer
partitions: NULL
type: range
possible keys: idx_last_name,idx full name
key: idx full name
key len: 274
ref: NULL
rows: 28
filtered: 11.11
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)

_

_

^

_
_

_

_

я

_

_

_

_

Каждый сервер базы данных включает инструменты, позволяющие увидеть, как оптимизатор запросов обрабатывает ваш оператор SQL. SQL Server позволяет увидеть план выполнения с помощью команды set show plan text on перед выполнением вашей
инструкции SQL. Oracle Database включает инструкцию explain
plan, которая записывает план выполнения в специальную табли цу с именем plan table.

_

_

_

Взглянув на результаты запроса, мы увидим, что столбец possible keys
говорит о том, что сервер может решить использовать индекс idx last
name или idx _full name, а столбец key сообщает вам, что был выбран ин декс idx _full name. Кроме того, столбец type сообщает , что будет использоваться сканирование диапазона, т .е. что сервер базы данных будет искать
диапазон значений в индексе, а не получать единственную строку.

я

_

_

_

_

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



Глава 13. Индексы и ограничения

273

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

Обратная сторона индексов
Если индексы настолько хороши, почему бы не проиндексировать все, что
только можно? Ключ к пониманию того, почему лучшее
враг хорошего,
в том, что каждый индекс представляет собой таблицу ( пусть и особый тип
таблицы, но все же это таблица). Следовательно, каждый раз, когда строка
добавляется в таблицу или удаляется из нее, должны быть изменены все ин дексы в этой таблице. При обновлении строки любые индексы для столбца
( или столбцов ), которые были затронуты, также должны быть изменены.
Следовательно, чем больше у вас индексов, тем больше должен работать сервер, чтобы поддерживать все объекты схемы в актуальном состоянии что
приводит к замедлению работы.
Кроме того, индексы требуют дополнительного дискового пространства,
а также некоторой осторожности со стороны администраторов, поэтому луч шая стратегия
добавлять индекс только тогда, когда в нем возникнет явная необходимость. Если вам нужен индекс только для специальных целей,
например для ежемесячного обслуживания, можете добавить индекс, выполнить необходимые процедуры, а затем удалить индекс до тех пор, пока он
вновь вам не понадобится. В случае хранилищ данных, где индексы имеют
решающее значение в бизнес-время, когда пользователи составляют отчеты
и выполняют запросы, но приводят к проблемам, когда ночью данные загружаются в хранилище, обычно перед загрузкой данных индексы удаляются,
а затем воссоздаются перед тем, как хранилище вновь откроется для активной работы.
В общем случае вы должны стремиться не иметь ни слишком много ин дексов, ни слишком мало. Если вы не знаете, сколько индексов вам нужно,
попробуйте по умолчанию использовать следующую стратегию.









274

Убедитесь, что все столбцы первичного ключа проиндексированы
( большинство серверов автоматически создают уникальные индексы
при создании ограничений первичного ключа ) . Для многостолбцового
первичного ключа рассмотрите возможность создания дополнительных
индексов для подмножества столбцов первичного ключа или для всех
столбцов первичного ключа, но в ином порядке, чем в определении
ограничения первичного ключа.
Глава 13. Индексы и ограничения





Создайте индексы для всех столбцов, на которые имеются ссылки в
ограничениях внешнего ключа. Помните, что при удалении родительского элемента сервер проверяет , нет ли соответствующих дочерних
строк, поэтому он должен выполнить запрос для поиска определенного
значения в столбце. Если для такого столбца нет индекса, необходимо
просканировать всю таблицу.
Проиндексируйте все столбцы, которые будут часто использоваться для
извлечения данных. Хорошими кандидатами являются большинство
столбцов дат вместе с короткими ( от 2 до 50 символов ) строковыми
столбцами.

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

Ограничения



Ограничение это просто определенные условия, накладываемые на один
или несколько столбцов таблицы. Имеется несколько различных типов ограничений, в том числе следующие.
Ограничения первичного ключа
Определяют столбец или столбцы, для которых гарантируется их уни кальность в таблице

Ограничения внешнего ключа
Ограничивают содержимое одного или нескольких столбцов таким образом, что они могут содержать только значения, найденные в столбце первичного ключа другой таблицы ( могут также ограничиваться
допустимые значения в других таблицах при установке правил update
cascade и / или delete cascade )
Ограничения уникальности

Ограничивают один или несколько столбцов таким образом, чтобы они
содержали уникальные значения в таблице ( ограничение первичного
ключа частный случай ограничения уникальности )



Проверочные ограничения
Ограничивают допустимые значения для столбца

.

Глава 13 Индексы и ограничения

275

Без ограничений согласованность базы данных сомнительна. Например,
если сервер позволяет изменять идентификатор клиента в таблице customer,
не меняя тот же идентификатор клиента в таблице rental, то вы получите
данные о прокате, которые больше не указывают на корректные записи кли ентов ( известные как потерянные строки ( orphaned rows)). При наличии же
ограничений первичного ключа и ограничений внешнего ключа сервер либо
сообщит об ошибке, если будет предпринята попытка изменить или удалить
данные, на которые ссылаются другие таблицы, либо распространит изменения и на другие таблицы ( подробнее об этом чуть ниже).



S

Если вы хотите использовать ограничения внешнего ключа с сервером MySQL, для своих таблиц вы должны использовать меха низм хранения InnoDB.

Создание ограничения
Ограничения обычно создаются одновременно со связанной таблицей
с помощью инструкции create table. Чтобы проиллюстрировать это, ниже
приведен пример из сценария генерации схемы образца базы данных Sakila:
CREATE TABLE customer (
customer id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
store id TINYINT UNSIGNED NOT NULL,
first_name VARCHAR(45) NOT NULL,
last name VARCHAR(45) NOT NULL,
email VARCHAR(50) DEFAULT NULL,
address id SMALLINT UNSIGNED NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
create date DATETIME NOT NULL,
last_update TIMESTAMP DEFAULT CURRENT TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY ( customer id ) ,
KEY idx fk store id (store id),
KEY idx_fk_address_id (address id),
KEY idx_last_name (last name),
CONSTRAINT fk customer address FOREIGN KEY ( address id )
REFERENCES address ( address id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT f k cus tomer store FOREIGN KEY ( store id)

_
_

_

_
_

_

_ _

_ _

_

_

_

_

_

_

_

_

_

_

_

REFERENCES store ( store id )

ON DELETE RESTRICT ON UPDATE CASCADE
)ENGINE=InnoDB DEFAULT CHARSET=utf8;



чтобы указать,
Таблица customer включает три ограничения: одно
что столбец customer id служит первичным ключом таблицы, и еще

_

276

Глава 13. Индексы и ограничения



чтобы указать, что столбцы address_id и store id служат внешнидва
ми ключами для таблиц address и store. В качестве альтернативы можно
создать таблицу customer без ограничений внешнего ключа и добавить необходимые ограничения внешнего ключа с помощью инструкций alter table:
ALTER TABLE customer
ADD CONSTRAINT fk_customer address FOREIGN KEY (address_id)
REFERENCES address (address id) ON DELETE RESTRICT ON UPDATE CASCADE;

_

ALTER TABLE customer
ADD CONSTRAINT fk_customer store FOREIGN KEY (store id)
REFERENCES store (store id) ON DELETE RESTRICT ON UPDATE CASCADE;

_

_

_

Обе эти инструкции включают несколько предложений on.



on delete restrict, которое приводит к тому, что сервер выдаст
ошибку, если в родительской таблице(address или store)будет удалена строка, на которую есть ссылка в дочерней таблице(customer)
.



on update cascade, которое заставит сервер распространить изменение на значения первичного ключа родительской таблицы(address
или store)на дочернюю таблицу(customer).

Предложение on delete restrict защищает от потерянных записей при
удалении строк из родительской таблицы. Для иллюстрации возьмем строку
в таблице address и покажем данные из таблиц address и customer, которые используют это значение:
mysql> SELECT с.first_name, c.last name, с.address_id, a.address

_

->

FROM customer c
-> INNER JOIN address a
-> ON c.address id = a.address id
-> WHERE a.address id = 123;
+
+
+
+
+
first name |last name |address id |address
+
+
+
+
+
| SHERRY
|MARSHALL
123 11987 Coacalco de Berriozbal Loopl
+
+
+
+
+
1 row in set (0.00 sec)

_

_

Результаты показывают , что существует одна строка customer ( для Sherry
Marshall ) , столбец address id которой содержит значение 123.
Вот что произойдет , если вы попытаетесь удалить эту строку из родительской таблицы(address):

_

_

mysql> DELETE FROM address WHERE address id = 123;
ERROR 1451 (23000): Cannot delete or update a parent row:
Глава 13. Индексы и ограничения

277

a foreign key constraint fails2 ('sakila'.'customer',
CONSTRAINT 'fk customer address FOREIGN KEY ('address id')
REFERENCES 'address' ('address id')
ON DELETE RESTRICT ON UPDATE CASCADE)

_

_

4

_

_

Поскольку по крайней мере одна строка в дочерней таблице содержит значение 123 в столбце address id, предложение on delete restrict ограни чения внешнего ключа приводит к сообщению об ошибке.
Предложение on update cascade также защищает от потерянных запи сей, когда значение первичного ключа обновляется в родительской таблице,
используя другую стратегию. Вот что произойдет , если вы измените значение
в столбце address . address id:

_

_

mysql> UPDATE address
-> SET address id = 9999
-> WHERE address id = 123;
Query OK, 1 row affected (0.37 sec)
Rows matched: 1 Changed: 1 Warnings: 0

_

_

Инструкция выполняется без ошибок, изменена одна строка. Но что произойдет со строкой Sherry Marshall в таблице customer ? По- прежнему ли она
указывает на идентификатор адреса 123, которого больше не существует ?
Чтобы выяснить это, давайте снова выполним последний запрос, но заменим
предыдущее значение ( 123) новым значением ( 9999 ):
mysql> SELECT с.first name, c.last_name, c.address_id, a.address

_

->
->
->
->

FROM customer c
INNER JOIN address a
ON c.address id = a.address id
WHERE a.address id = 9999;

+

_

+

_

+

+

+

|first name |last name |address id |address
+

+

+

I SHERRY

|MARSHALL i
+
+
+
1 row in set (0.00 sec)

+

+

9999 |1987 Coacalco de Berriozbal Loop I
+

+

Как видите, возвращаются те же результаты, что и раньше ( кроме нового значения идентификатора адреса ), а это означает , что значение 9999 было
автоматически обновлено в таблице customer . Это называется каскадным
обновлением, и это второй механизм, используемый для защиты против потерянных строк.

Невозможно удалить или обновить родительскую строку: нарушение ограничения
внешнего ключа.
2

278

Глава 13. Индексы и ограничения

Наряду с restrict и cascade можно выбрать set null, что приведет
к установке значения внешнего ключа в дочерней таблице равным null ,
когда в родительская таблице удаляется или обновляется строка. Всего существует шесть различных вариантов выбора при определении ограничения
внешнего ключа:





on delete restrict



on update cascade




on delete set null

on update restrict

on update cascade
on update set null

Все они необязательны, поэтому вы можете выбрать нуль, один или два
варианта ( один
для delete и еще один
для update ) при определении
ограничений внешнего ключа.
Наконец, если вы хотите удалить ограничение первичного или внешнего
ключа, то можете использовать оператор alter table еще раз, только вместо
add укажите drop. В то время как сброс ограничения первичного ключа
действие необычное, ограничения внешнего ключа иногда сбрасываются при
некоторых операциях по техническому обслуживанию, а затем вновь восстанавливаются.







Проверьте свои знания
Предлагаемые здесь упражнения призваны закрепить понимание вами индексов и ограничений. Ответы к упражнениям представлены в приложении Б.
УПРАЖНЕНИЕ 13.1
Сгенерируйте инструкцию alter table для таблицы rental так, чтобы
генерировалось сообщение об ошибке, когда строка со значением, имеющим ся в столбце rent . customer id, удаляется из таблицы customer .

_

УПРАЖНЕНИЕ 13.2

Создайте многостолбцовый индекс в таблице payment , который может
использоваться обоими приведенными далее запросами:

_

SELECT customer id , payment_date, amount
FROM payment
WHERE payment date > cast( 12019-12-31 23:59:59

_

_

as datetime);

_

SELECT customer id, payment date, amount
FROM payment
WHERE payment_date > cast('2019-12-31 23:59:59 as datetime)
AND amount < 5;

.

Глава 13 Индексы и ограничения

279

ГЛАВА 14

Представления

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

Что такое представление



Представление это просто механизм запроса данных. В отличие от та блиц, представления не включают хранилище данных; вам не нужно беспокоиться о том, что представления займут место на диске. Вы создаете пред ставление путем присвоения имени инструкции select с последующим
сохранением запроса для использования другими пользователями. Другие
пользователи могут затем использовать ваше представление для доступа
к данным, как если бы они запрашивали таблицы непосредственно (на самом
деле они могут даже не знать, что используют представления ).
В качестве простого примера предположим , что вы хотите частично
скрыть адрес электронной почты в таблице клиентов. Например, доступ
к адресам электронной почты может потребоваться отделу маркетинга для
рекламных акций, но в остальном политика конфиденциальности вашей ком пании требует , чтобы эти данные хранились в безопасности. Поэтому вместо
того, чтобы разрешить прямой доступ к таблице клиентов, вы определяете
представление с именем customer vw и требуете, чтобы все, кроме персона ла маркетинга, использовали его для доступа к данным о клиентах. Вот определение этого представления:

_

_

CREATE VIEW customer vw
(customer id,
first_name,
last name,

_

_

email
AS
SELECT
customer id,
first name,
last_name,
concat(substr(email,1,2), i ***** i , substr(email, -4)) email
FROM customer;

_

_

В первой части инструкции перечислены имена столбцов представления,
которые могут отличаться от таковых в таблице, лежащей в основе пред ставления. Вторая часть инструкции
инструкция выбора, которая должна содержать по одному выражению для каждого столбца в представлении.
Столбец email создается путем соединения первых двух символов адреса
гл
, а затем
с последними четырьмя
электронной почты со строкой
символами электронного адреса.
Когда выполняется инструкция create view, сервер базы данных просто
сохраняет определение представления для будущего использования. Запрос
не выполняется, данные не извлекаются и не хранятся. После создания пред ставления пользователи могут запрашивать его так же, как и таблицу:





_

_

mysql> SELECT first name, last name, email
-> FROM customer vw;
+

+

+

first name
+

last name
+

+

email
+

+

MARY
PATRICIA
LINDA
BARBARA
ELIZABETH

SMITH
JOHNSON
WILLIAMS
JONES
BROWN

MA*****.org
PA*****.org
LI*****.org
BA*****.org
EL*****.org

ENRIQUE
FREDDIE
WADE
AUSTIN

FORSYTHE
DUGGAN
DELVALLE
CINTRON

EN*****.org
FR*****.org
WA*****.org
AU*****.0rg

+

+

+

+

599 rows in set (0.00 sec)

_

Несмотря на то что определение представления customer vw включает
четыре столбца таблицы customer , приведенный запрос извлекает только
три из них. Как вы увидите ниже в главе, это важное отличие, если некоторые
из столбцов в вашем представлении связаны с функциями или подзапросами.
282

Глава 14. Представления

С точки зрения пользователя, представление выглядит точно так же, как
и таблица . Если вы хотите узнать, какие столбцы доступны в представлении,
можете использовать команду MySQL ( или Oracle Database) describe для его
изучения:
mysql> describe customer vw;
+

+

+

+

+

| Null

Type

Field
+

+

+
+
+
Key | Default | Extra

+

+

customer_id smallint(5) unsigned | NO
first_name | varchar {45)
I NO
last name
I varchar(45)
I NO
email
varchar(11)
I YES

+

+

+

NULL
NULL
NULL
+

+

+

+

+

0

+

4 rows in set (0.00 sec)

Вы можете использовать любые предложения инструкции select при за просе данных через представление, в том числе group by, having и order by:
mysql> SELECT first name, count(*), min(last_name), max(last_name)

_

_
_

->
->
->

FROM customer vw
WHERE first_name LIKE J% '
GROUP BY first name
> HAVING count(*) > 1
> ORDER BY 1;

-

+

+

+

+

+

| JAMIE
| JESSIE
+

+

+

+

count(*) | min(last name) | max(last name)

first name

WAUGH
MILAM

+

+

+

+

2 | RICE
2 | BANKS
+

+

2 rows in set (0.00 sec)

Кроме того, в запросе вы можете соединять представления с другими та блицами ( или даже с другими представлениями):
mysql> SELECT cv.first name, cv.last_name, p.amount

->
->
->
->

_

_

FROM customer vw cv
INNER JOIN payment p
ON cv.customer_id = p.customer id
WHERE p.amount >= 11;

_

+

+

+

| first name I last name
+

+

+

KAREN
VICTORIA
VANESSA
ALMA
ROSEMARY
TANYA

+

amount |

JACKSON
GIBSON
SIMS
AUSTIN
SCHMIDT
GILBERT

+

11.99
11.99
11.99
11.99
11.99
11.99
Глава 14. Представления

283

| RICHARD
| NICHOLAS
| KENT
| TERRANCE

11.99 |

| MCCRARY
| BARFIELD
| ARSENAULT

11.99 |
11.99 |
11.99 |

I ROUSH

+
+
10 rows in set (0.01 sec)
+

+

_

Этот запрос соединяет представление customer vw с таблицей payment
для поиска клиентов, заплативших за прокат фильма не менее 11 долларов.

Зачем использовать представления
В предыдущем разделе я продемонстрировал простое представление, един ственной целью которого была маскировка содержимого столбца customer ,
email. Хотя представления часто используются для этой цели, есть много
иных причин для использования представлений, как подробно описано ниже.

Безопасность данных
Если вы создадите таблицу и разрешите пользователям запрашивать ее,
они смогут получить доступ к каждому столбцу и каждой строке таблицы.
Однако, как уже указывалось, ваша таблица может включать некоторые
столбцы, содержащие конфиденциальные данные, такие как идентификационные номера или номера кредитных карт . Предоставлять такие данные всем
пользователям
это не просто плохая идея; это может нарушить политику
конфиденциальности вашей компании или даже законы страны.
Наилучший подход в таких ситуациях оставить таблицу закрытой ( т .е.
не предоставлять разрешение выполнять select всем пользователям ) , а за тем создать одно или несколько представлений, которые либо опускают , либо
скрывают ( например, с помощью подхода • ***** » , использованного в столбце customer vw . email ) конфиденциальные столбцы. Вы также можете ука зать, к каким строкам могут иметь доступ пользователи, добавив в определение вашего представления предложение where. Например, следующее определение представления исключает неактивных клиентов:





_

_

_

CREATE VIEW active customer vw
(customer id,
first_name,
last name,
email

_

_

AS
SELECT

284

Глава 14. Представления

customer_id,
first_name,
last name,
concat(substr(email,1,2),
FROM customer
WHERE active = 1;

_

I

***** ! , substr(email,

-4))

email

Если вы обеспечите такое представление для отдела маркетинга, они смогут избежать отправки информации неактивным клиентам, потому что условие в предложении where представления будет всегда включено в их запросы.

S

У пользователей Oracle Database есть еще один вариант защиты
как строк, так и столбцов таблиц: виртуальная частная база дан ных ( Virtual Private Database, VPD ) . VPD позволяет назначать политики вашим таблицам, после чего сервер будет при необходимости изменять запрос пользователя так, чтобы обеспечить соблюдение назначенных политик. Например, если вы вводите политику,
согласно которой сотрудники отделов продаж и маркетинга могут
видеть только активных клиентов, ко всем их запросам к таблице
customer будет добавлено условие active = 1.

Агрегация данных
Приложениям для создания отчетов обычно требуются агрегированные
данные, а представления отличное средство создания впечатления, что в
базе данных сохраняются предварительно агрегированные данные. В качестве примера предположим, что приложение ежемесячно создает отчет , показывающий общий объем продаж по каждой категории фильмов, чтобы менеджеры могли решить, какие новые фильмы закупать. Вместо того чтобы
позволять разработчикам приложений писать запросы, обращающиеся к базовым таблицам, им можно предложить следующее представление1:



_ _

_

CREATE VIEW sales by film category
AS
SELECT
c.name AS category,
SUM(p.amount) AS total sales
FROM payment AS p
INNER JOIN rental AS r ON p.rental id = r.rental id
INNER JOIN inventory AS i ON r.inventory id = i.inventory id
INNER JOIN film AS f ON i.film id = f.film id

_

_

_

_

_

Это определение представления включено в образец базы данных Sakila вместе
с шестью другими ( некоторые из них будут использованы в следующих примерах).
l

Глава 14. Представления

285

__

INNER JOIN film_category AS fc ON f.film_id = fc.film id
INNER JOIN category AS c ON fc.category_id = c.category id
GROUP BY c.name
ORDER BY total_sales DESC;

Этот подход дает вам как разработчику базы данных большую гибкость.
Если в какой -то момент в будущем будет решено, что производительность
запросов значительно улучшится, если данные будут предварительно сгруп пированы в таблице, а не подытожены с использованием представления, вы
сможете создать таблицу film category sales, загрузить в нее агрегированные данные и изменить определение представления sales by film
category так, что оно будет извлекать данные из этой таблицы. После этого
все запросы, использующие представление sales by f ilm category, будут
извлекать данные из новой таблицы film category sales, так что пользователи увидят улучшение производительности, никак не меняя свои запросы.

_

_

_

_ _

_ _
_

_

_

Сокрытие сложности
Одна из наиболее распространенных причин развертывания представлений
защита конечных пользователей от сложности запросов. Например,
предположим, что каждый месяц создается отчет , в котором отображается
информация обо всех фильмах наряду с категорией фильмов, количеством
актеров, фигурирующих в фильме, общим наличным количеством копий и
количеством прокатов каждого фильма. Вместо того чтобы ожидать, что составитель отчетов будет выбирать информацию из шести разных таблиц для
сбора необходимых данных, вы можете предложить ему представление, которое выглядит следующим образом:



CREATE VIEW film stats
AS
SELECT f.filmed, f.title, f.description, f.rating,
(SELECT c.name
FROM category c
INNER JOIN film category fc
ON c.category_id = fc.category id
WHERE fc.film id = f.film id) category name,
(SELECT count(*)
FROM film_actor fa
WHERE fa.film id = f.film id
) num actors,
(SELECT count(*)
FROM inventory i
WHERE i.film_id = f.film id
) inventory cnt,
(SELECT count(*)
FROM inventory i

_

_

_

_

_

_

_

286

Глава 14. Представления

_

_

_

INNER JOIN rental r
ON i.inventory id = r.inventory id
WHERE i.film_id = f.film id
) num_rentals
FROM film f;

_

_

_

Это определение представления очень интересно, потому что, хотя через
представление можно получить данные из шести разных таблиц, предложение from запроса включает только одну таблицу ( film). Данные из остальных
пяти таблиц генерируются с помощью скалярных подзапросов. Если кто- то
использует это представление, но не обращается к столбцам category name ,
num_actors, inventory cnt и num rentals, то ни один из подзапросов вы полняться не будет . Этот подход позволяет использовать представление для
предоставления описательной информации из таблицы film без необходи мости соединения с пятью другими таблицами.

_

_

_

Соединение разделенных данных
Некоторые проекты базы данных разбивают большие таблицы на несколько таблиц, чтобы повысить производительность. Например, если таблица
payment становится слишком большой, дизайнеры могут решить разбить ее
на две таблицы: payment current, которая содержит сведения за последние
шесть месяцев, и payment _historic, которая содержит все более поздние
платежи. Если требуется увидеть все платежи для конкретного клиента, нужно запрашивать обе таблицы. Создавая представление, которое запрашивает
обе таблицы и объединяет полученные результаты, можно просмотреть результаты, как если бы все данные о платежах хранились в одной таблице. Вот
определение такого представления:

_

_

CREATE VIEW payment all
(payment id,
customer id,
staff id,
rental_id,
amount,
payment date,
last_update

_

_
_
_

AS
SELECT payment_id, customer_id, staff_id, rental_id,
amount, payment_date, last update
FROM payment historic
UNION ALL
SELECT payment id , customer id, staff id, rental id,

_

_

Глава 14. Представления

287

amount, payment_date, last_update
FROM payment current;

_



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

Обновляемые представления
Мы показали, как использовать представления для поиска данных пользователями. Но что следует сделать, если пользователям нужно также изменять
данные ? Решение позволить пользователям получать информацию с помо щью представления, а затем вносить изменения в лежащие в их основе табли цы с помощью update или insert выглядит несколько непоследовательным,
чтобы не сказать большего. Для этого Mysql, Oracle Database и SQL Server позволяют изменять данные через представления, если при этом соблюдаются
определенные ограничения. В случае MySQL представление является обновляемым, если выполнены следующие условия.



Не используются агрегатные функции(шах(), min ( ), avg ( ) и т .д.).




Представление не использует положения group by и having.





В предложениях select и from нет никаких подзапросов, а любой под запрос в предложении where не ссылается на таблицы в предложении
from.
Представление не использует union, union all или distinct.
Предложение from содержит как минимум одну таблицу или обновля емое представление.

Если имеется более одной таблицы или представления, предложение
from использует только внутренние соединения.

Чтобы продемонстрировать полезность обновленных представлений, луч ше всего начать с определения простого представления, а затем перейти к более сложным представлениям.

Обновление простых представлений
Представление в начале главы очень простое, так что давайте начнем с
него:

288

|

Глава 14. Представления

_

CREATE VIEW customer vw
(customer id,
first name,
last_name,
email

_

AS
SELECT

_

_

customer id,
first_name,
last_name,
concat(substr(email,1,2),
FROM customer;

i

* * *** i , substr(email,

-4))

email

Представление customer_vw запрашивает единственную таблицу, и только один из четырех столбцов получается с помощью выражения. Это определение представления не нарушает приведенные выше ограничения, поэтому
его можно использовать для изменения данных в таблице customer. Давай те воспользуемся данным представлением, чтобы обновить фамилию Магу
Smith на Smith-Alien:

_

mysql> UPDATE customer vw
-> SET last_name = 1 SMITH-ALLEN'
> WHERE customer id = 1;
Query OK, 1 row affected (0.11 sec)
Rows matched: 1 Changed: 1 Warnings: 0

_

-

Как видите, мы получили сообщение об успешном изменении одной строки. Тем не менее давайте проверим лежащую в основе представления таблицу
customer, чтобы убедиться в этом:

_

_

mysql> SELECT first name, last name, email
-> FROM customer
-> WHERE customer id = 1;
+
+

I last name

email
+

+

+



MARY.SMITH@sakilacustomer.org

SMITH-ALLEN

MARY
+

+

+

+

first name

+

+

+

1 row in set (0.00 sec)

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

получен из выражения:

_

mysql> UPDATE customer vw
-> SET email = 'MARY.SMITH ALLEN@sakilacustomer.org

-

Глава 14. Представления

289

-> WHERE customer_id = 1;
ERROR 1348 (HY000): Column ' email ' is not updatable 2
В данном конкретном случае это может быть и неплохо, поскольку основной причиной создания представления было сокрытие адресов электронной
почты.
Если вы захотите вставить данные с помощью представления customer
vw, то потерпите неудачу. Представление, содержащее выводимые столбцы,
не может быть использовано для вставки данных, даже если эти столбцы не
включены в инструкцию. Например, следующая инструкция, используя пред ставление customer vw, пытается заполнить только столбцы customer id,
first name и last name:

_

_
_

_

_

mysql> INSERT INTO customer_vw
-> (customer_id,
> first_name,
> last name)
-> VALUES (99999,'ROBERT','SIMPSON');
ERROR 1471 (HY000): The target table customer vw of
the INSERT is not insertable-into3

-

_

_

Теперь, когда вы видели ограничения простых представлений, в следующем разделе будет показано использование представления, которое присоединяется к нескольким таблицам.

Обновление сложных представлений
В то время как представления с одной таблицей, безусловно, являются
наиболее распространенными, многие представления, с которыми вы будете встречаться, будут включать несколько таблиц в предложении from их запроса. Следующее представление, например, соединяет таблицы customer ,
address, city и country так, чтобы можно было легко запросить все данные
клиентов:
CREATE VIEW customer details
AS
SELECT c.customer id,
c.store id,
c.first name,
c.last name,
c.address_id,
c.active,

_
_
_

2
3

_

Столбец ' email1 ' является необновляемым.
Целевая таблица customer _vw инструкции INSERT не допускает вставку.

290

Глава 14. Представления

_

c.create date,
a.address,
ct.city,
cn.country,
a.postal_code
FROM customer c
INNER JOIN address a
ON c.address id = a.address id
INNER JOIN city ct
ON a.city_id = ct.city id
INNER JOIN country cn
ON ct.country_id = cn.country id;

_

_

_

Это представление можно использовать для обновления данных в таблице
customer или address, как показано в следующих инструкциях:
mysql> UPDATE customer_details
-> SET last name = 'SMITH-ALLEN', active = 0
-> WHERE customer id = 1;
Query OK, 1 row affected (0.10 sec)
Rows matched: 1 Changed: 1 Warnings: 0

_

_

_

mysql> UPDATE customer details
> SET address = '999 Mockingbird Lane'
> WHERE customer_id = 1;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0

-

Первая инструкцияизменяет столбцы customer . last name и customer ,
active, в то время как вторая модифицирует столбец address . address. Вас
может заинтересовать, что произойдет , если попытаться обновить столбцы
обеих таблиц в одной инструкции. Давайте попробуем:

_

_

mysql> UPDATE customer details
-> SET last name = 'SMITH-ALLEN',
active = 0,
>
-> address = '999 Mockingbird Lane'
-> WHERE customer id = 1;
ERROR 1393 (HY000): Can not modify more than one base table
4
|
through a join view 'sakila.customer details

-

_

_

_

Как видите, вам разрешено изменять базовые таблицы по отдельности, но
не в одной инструкции. Давайте теперь попробуем вставить данные в обе
таблицы для некоторых новых клиентов (со значениями customer id, равными 9998 и 9999 ):

_

Невозможно изменить более одной базовой таблицы с помощью соединяющего
представления 'sakila.customer_details'.

4

.

Глава 14 Представления

291

mysql> INSERT INTO customer_details
-> (customer id, store id, first name, last name,
-> address id, active, create date)
-> VALUES (9998, 1, 'BRIAN', 'SALAZAR', 5, 1, now());
Query OK, 1 row affected (0.23 sec)

_

_

_

_

_

_

Эта инструкция, которая заполняет столбцы только таблицы customer,
отлично работает . Давайте теперь посмотрим, что произойдет , если мы расширим список столбцов и включим в него столбец из таблицы address:
mysql> INSERT INTO customer_details
-> (customer id, store id, first name, last name,
-> address_id, active, create date, address)
-> VALUES (9999, 2, 'THOMAS', 'BISHOP', 7, 1, now(),
-> ' 9 9 9 Mockingbird L a n e ' ) ;
ERROR 1393 (HY000): Can not modify more than one base table
through a join view 'sakila.customer_details'5

_

_

_

_

_

Эта версия, которая включает в себя столбцы, охватывающие две разные
таблицы, приводит к ошибке. Для того чтобы вставить данные с помощью
сложного представления, нужно знать, откуда получен каждый из столбцов.
Поскольку многие представления созданы для сокрытия сложности от конечных пользователей, явное знание пользователями структуры представления
противоречит поставленной цели.

в

Oracle Database и SQL Server также разрешают вставку и обновление данных через представления, но, как и MySQL, предъявля ют много ограничений. Если вы готовы писать код на PL/SQL или
Transact -SQL, можете использовать функциональную возможность
под названием “ триггеры вместо” ( instead -of triggers), которая,
по сути, позволяет перехватывать инструкции insert, update и
delete для представления и писать пользовательский код для внесения соответствующих изменений. Без применения такого рода
функций, как правило, имеется слишком много ограничений, чтобы суметь выполнить обновление данных с помощью представлений в нетривиальных приложениях.

Невозможно изменить более одной базовой таблицы с помощью соединяющего
представления 'sakila.customer_details'.

5

292

Глава 14. Представления

Проверьте свои знания
Предлагаемые здесь упражнения призваны закрепить понимание вами
представлений. Ответы к упражнениям представлены в приложении Б.
УПРАЖНЕНИЕ 14.1
Создайте определение представления, которое можно использовать с помощью следующего запроса для генерации приведенного результирующего
набора:
SELECT title, category name, first name, last name

_

_

_

_

_

FROM film ctgry actоr
WHERE last name = 'FAWCETT';
+

I title

I category_name | first_name | last_name I

+

+

ACE GOLDFINGER
ADAPTATION HOLES
CHINATOWN GLADIATOR
CIRCUS YOUTH
CONTROL ANTHEM
DARES PLUTO
DARN FORRESTER
DAZED PUNK
DYNAMITE TARZAN
HATE HANDICAP
HOMICIDE PEACH
JACKET FRISCO
JUMANJI BLADE
LAWLESS VISION
LEATHERNECKS DWARFS
OSCAR GOLD
PELICAN COMFORTS
PERSONAL LADYBUGS
RAGING AIRPLANE
RUN PACIFIC
RUNNER MADIGAN
SADDLE ANTITRUST
SCORPION APOLLO
SHAWSHANK BUBBLE
TAXI KICK
BERETS AGENT
BOILED DARES
CHISUM BEHAVIOR
CLOSER BANG
DAY UNFAITHFUL
HOPE TOOTSIE
LUKE MUMMY
MULAN MOON

+

+

+

+

+
Horror
Documentary
New
Children
Comedy
Animation
Action
Games
Classics
Comedy
Family
Drama
New
Animation
Travel
Animation
Documentary
Music
Sci-Fi
New
Music
Comedy
Drama
Travel
Music
Action
Travel
Family
Comedy
New
Classics
Animation
Comedy

+

BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
BOB
JULIA

JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA

+

FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
Глава 14. Представления

293

OPUS ICE
POLLOCK DELIVERANCE
RIDGEMONT SUBMARINE
SHANGHAI TYCOON
SHAWSHANK BUBBLE
THEORY MERMAID
WAIT CIDER

JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA

Foreign
Foreign

New
Travel
Travel
Animation
Animation

+
+
40 rows in set (0.00 sec)

+

FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
+

+

УПРАЖНЕНИЕ 14.2
Менеджер компании по прокату фильмов хотел бы иметь отчет , который
включает в себя название каждой страны, а также общие платежи всех кли ентов, которые живут в данной стране. Создайте определение представления,
которое запрашивает таблицу country и использует для вычисления значения столбца tot _payments скалярный подзапрос.

294

Глава 14. Представления

ГЛАВА 15

Метаданные

Наряду с хранением всех данных, которые различные пользователи вносят
в базу данных, сервер базы данных должен хранить информацию обо всех
объектах базы данных ( таблицах, представлениях, индексах и т .д. ), которые
были созданы для хранения этих данных. Сервер базы данных хранит эту
информацию, что не удивительно, в самой базе данных. В этой главе обсуждается, как и где хранится эта информация, известная как метаданные, как
получить к ней доступ и как ее использовать для построения гибких систем.

Данные о данных
Метаданные, по существу, являются данными о данных. Каждый раз, когда
вы создаете объект базы данных, сервер базы данных должен записывать различную информацию о нем. Например, если вам необходимо создать таблицу
с несколькими столбцами, ограничением первичного ключа, тремя индексами
и ограничением внешнего ключа, сервер базы данных должен хранить следующую информацию.



Имя таблицы



Информация о хранении таблицы (табличное пространство, начальный
размер и т .д.)



Механизм хранения












Имена столбцов
Типы данных столбцов
Значения столбцов по умолчанию

Ограничения not null для столбцов
Столбцы первичного ключа
Имя первичного ключа
Имя индекса первичного ключа












Имена индексов

Типы индексов ( В- tree, bitmap)
Индексированные столбцы
Порядок сортировки индексов столбцов ( по возрастанию или по убы ванию)

Информация о хранении индекса
Имя внешнего ключа
Столбцы внешнего ключа
Связанные таблицы/столбцы для внешних ключей

Эти данные коллективно известны как словарь данных или системный ка талог. Сервер базы данных должен хранить эти данные в постоянном хра нилище и быть в состоянии быстро их получить для проверки и выполнения
инструкций SQL. Кроме того, сервер базы данных должен защищать эти данные, чтобы их можно было модифицировать только через соответствующий
механизм, такой как инструкция alter table.
При наличии стандартов для обмена метаданными между разными серверами каждый сервер базы данных использует свой механизм для публикации
метаданных.





Набор представлений, таких как представления user _ tables и а11_
constraints в Oracle Database
Набор системных процедур, таких как процедура sp tables в SQL
Server или пакет dbms metadata в Oracle Database
Специальная база данных, такая как база данных information schema
в MySQL

_

_

Наряду с системно хранимыми процедурами SQL Server, которые являются
пережитком Sybase, SQL Server включает в себя специальную схему с именем
information schema, которая автоматически предоставляется в каждой базе
данных. И MySQL, и SQL Server предоставляют этот интерфейс для соответ ствия стандарту ANSI SQL:2003. В оставшейся части этой главы рассматрива ются объекты inf ormation schema, которые доступны в MySQL и SQL Server.

_

_

information schema

_

Все объекты , доступные в базе данных information schema , являются
представлениями. В отличие от утилиты describe, которую я использовал
296

Глава 15. Метаданные

в нескольких главах этой книги как способ показать структуру различных
таблиц и представлений, представления в information schema могут быть
запрошены и, таким образом, использоваться программно ( подробнее об
этом ниже в данной главе). Вот пример, который демонстрирует , как получить имена всех таблиц в базе данных Sakila:

_



_
_

_

SELECT table name, table type
FROM information_schema.tables
WHERE table schema = 'sakila'
ORDER BY 1;

mysql>
->
->
->
+

+

TABLE NAME

+

TABLE TYPE
+

+

actor
actor_info
address
category
city
country
customer
customer list
film
film actor
film category
film list
film text
inventory
language
nicer but slower film list

_

_
_
_
_

_

_

_

_

payment
rental
sales by film category
sales by store
staff
staff list

_ _
_ _
_

+

BASE
VIEW
BASE
BASE
BASE
BASE
BASE
VIEW
BASE
BASE
BASE
VIEW
BASE
BASE
BASE
VIEW
BASE
BASE
VIEW
VIEW
BASE
VIEW
BASE

_

store

TABLE
TABLE
TABLE
TABLE
TABLE
TABLE
TABLE
TABLE
TABLE

TABLE
TABLE
TABLE
TABLE
TABLE
TABLE
TABLE

+

+

+

23 rows in set (0.00 sec)

_

Как видите, представление information schema . tables включает в себя
и таблицы, и представления. Если вы хотите исключить представления, просто добавьте второе условие в предложение where:
mysql>
->
->
->
>

-

+

_

_

SELECT table name, table type
FROM information schema.tables
WHERE table_schema = i sakila
AND table type = ' BASE TABLE i
ORDER BY 1;

_

+

+

.

Глава 15 Метаданные

297

I TABLE NAME

| TABLE TYPE |

+

+

address
category
city
country
customer

film
film actor
film_category
film text
inventory
language
payment
rental
staff
store

_
_

+

+

BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE
BASE

actor

.
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |
TABLE |

+

+

16 rows in set (0.00 sec)

Если вас интересует только информация о представлениях, можете запросить inf ormation schema . views. Наряду с именами представлений вы можете получить дополнительную информацию, такую как флаг, который пока зывает , является ли представление обновляемым:

_

mysql>
->
->
->

_

SELECT table name, is_updatable
FROM information_schema.views
WHERE table_schema = 'sakila i
ORDER BY 1;
+

+

TABLE NAME
+

_
_
_
_ _
_ _

+

IS UPDATABLE |
+

actor info
customer list
film list
nicer_but_s1ower_film list
sales by film category
sales by store
staff list

_

_

+

+

NO
YES
NO
NO
NO
NO
YES
+

+

7 rows in set (0.00 sec)

Информация о столбцах как таблиц, так и представлений доступна с помощью представления columns. Следующий запрос показывает информацию
о столбцах таблицы film:
mysql> SELECT column_name, data_type,
-> character maximum length char max len,
> numeric_precision num prcsn, numeric scale num_scale

_

-

298

Глава 15. Метаданные

_

_

_ _

_

->

->
->

_
_

FROM information schema.columns
WHERE table schema = 'sakila' AND table_name
ORDER BY ordinal position;

_

+
|COLUMN NAME

+
+
|DATA TYPE |char max len
+
+
+
film id
NULL
smallint
title
varchar
255
description
text
65535
NULL
release year
year
tinyint
language_id
NULL
original language id|tinyint
NULL
tinyint
rental duration
NULL
NULL
rental rate
decimal
length
NULL
smallint
NULL
replacement cost
decimal
enum
5
rating
special features
set
54
timestamp
NULL
last update
+
+
+
13 rows in set (0.00 sec)

_

_ _

_

+
+
+
|num prcsn |num scale |
+
+
+

_

_

0
NULL
NULL
NULL
0
0
0
2
0
2
NULL
NULL
NULL

3
3

_

4
5
5
NULL
NULL
NULL

_

_

_

_

5
NULL
NULL
NULL
3

_

_

= 'film'

+

+

+

Столбец ordinal _position включен просто как средство для получения
столбцов в порядке, в котором они были добавлены в таблицу.
Вы можете получить информацию об индексах таблицы с помощью представления information schema . statistics, как показано в следующем за просе, который извлекает информацию об индексах, созданных для таблицы

_

rental:
mysql>
>
->
->

-

_
_
_

_ _

SELECT index name, non_unique, seq in index, column_name
FROM information schema.statistics
WHERE table schema = 'sakila' AND table name = 'rental i
ORDER BY 1, 3;

+
+
+
+
| NON UNIQUE | SEQ IN INDEX | COLUMN NAME
+
+
+
+
+
1
1 | customer_id
idx fk customer id
idx fk inventory id
1 | inventory id
1
1 | staff id
1
idx_fk_staff_id
0
1 | rental_id
PRIMARY
1 | rental date
rental_date
0
rental date
0
2 | inventory id
3 | customer id
rental date
0
+
+
+
+
+
7 rows in set (0.02 sec)
+

INDEX NAME

_ _
_ _

_

_

_

_

_

_

_

Таблица rental имеет в общей сложности пять индексов, один из которых трехстолбцовый ( rental date ), а другой является PRIMARY - индексом,
используемым для ограничения первичного ключа.



_

Глава 15. Метаданные

299

Вы можете получить различные типы ограничений ( внешнего ключа,
первичного ключа, уникальный) с помощью представления information
schema . table constraints. Вот запрос, который извлекает все ограничения из схемы Sakila:
mysql> SELECT constraint_name, table name, constraint type

_

_

_

FROM information schema.table_constraints
->> WHERE
table_schema
sakila

->

=

i

'

ORDER BY 3,1;

+

+

I constraint name

I table

+

+

_
_
_
_
_
_

_

+

_

_
_ _
_
_
_
_
_
_ _
_
_
_
_
_
_
_
_
_
_
_
_ _
_ _
_ _
_

_
_
_

_

.

+

+

+

_

Глава 15 Метаданные

_

constraint type

name

fk address city
address
city
fk city country
customer
fk_customer_address
customer
fk customer store
fk film actor actor
film actor
film actor
fk film actor_film
fk film category category | film category
film category
fk_film_category film
fk_film language
film
fk film language original film
inventory
fk inventory film
inventory
fk_inventory_store
payment
fk payment customer
payment
fk_payment_rental
fk payment staff
payment
fk rental customer
rental
fk rental inventory
rental
rental
fk_rental_staff
staff
fk staff address
fk staff store
staff
store
fk store address
fk store_staff
store
PRIMARY
film
film actor
PRIMARY
PRIMARY
staff
PRIMARY
film_category
PRIMARY
store
PRIMARY
actor
film text
PRIMARY
PRIMARY
address
inventory
PRIMARY
PRIMARY
customer
PRIMARY
category
language
PRIMARY
city
PRIMARY
PRIMARY
payment
country
PRIMARY
PRIMARY
rental

300 |

_

FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
FOREIGN
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY
PRIMARY

KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY
KEY

I idx _email

UNIQUE
UNIQUE
UNIQUE

customer
store

_

| idx _unique manager

I rental date
+

rental

+

+

+

41 rows in set ( 0.02 sec )

_

В табл. 15.1 показано множество представлений information schema, доступных в MySQL версии 8.0.
Таблица 15.1. Представления information schema
Представление

Предоставляемая информация

schemata

Базы данных
Таблицы и представления
Столбцы таблиц и представлений

tables
columns

statistics
user _privileges
schema _privileges
table_privileges
column_privileges

character _ sets
collations
collation_character _
set _applicability
table constraints
key _ column_usage
routines
views
triggers
plugins
engines
partitions
events

processlist

referential_
constraints
parameters

profiling

Индексы
Кто имеет привилегии для различных объектов схемы
Кто имеет привилегии для различных баз данных
Кто имеет привилегии для различных таблиц
Кто имеет привилегии для различных столбцов таблиц
Доступные наборы символов
Какие сравнения доступны для различных наборов символов
Какие наборы символов доступны для различных сравнений

Ограничения — первичного ключа, внешнего ключа, уникальности
Ограничения, связанные с каждым ключевым столбцом
Сохраненные подпрограммы (процедуры и функции)
Представления
Триггеры таблиц
Подключаемые модули сервера
Доступные механизмы хранения
Разбиения таблиц
Запланированные события
Выполняющиеся процессы
Внешние ключи

Параметры хранимых процедур и функций
Информация о профилях пользователей

В то время как некоторые из этих представлений, такие как engines ,
events и plugins , специфичны для MySQL, многие из них доступны и в
SQL Server. Если вы используете Oracle Database, обратитесь к онлайн -справочнику Oracle Database Reference Guide ( https : / / oreil . ly / qV 7 sE ) за

.

Глава 15 Метаданные

301

_

_

_

информацией о представлениях user , all и dba , а также о пакете dbms
metadata.

Работа с метаданными
Как уже упоминалось, возможность получить информацию об объектах
схемы через SQL-запросы открывает некоторые интересные возможности.
В этом разделе показано несколько способов использования метаданных в

ваших приложениях.

Сценарии генерации схемы
В то время как в одних проектных командах работают отдельные проектировщики баз данных на полной ставке, которые контролируют проектирование и реализацию базы данных, в других используется подход “проекти рование голосованием”, позволяющее создавать объекты базы данных множеству сотрудников. Через несколько недель или месяцев разработки вам,
возможно, понадобится генерировать сценарий, который будет создавать
различные таблицы, индексы, представления и так далее, которые разработа ны командой. Хотя имеется масса разнообразных инструментов и утилит для
генерации такого типа сценариев, вы можете также запросить представление
information_schema и создать такой сценарий самостоятельно.
В качестве примера давайте построим сценарий, который создает таблицу
sakila.category. Вот команда создания таблицы, которую я извлек из сценария, используемого для создания этой базы данных:
CREATE TABLE category (
category id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(25) NOT NULL,
last update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (category id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

_

_

_

Хотя, безусловно, было бы легче создать сценарий с использованием процедурного языка ( наподобие Transact-SQL или Java), поскольку все же это
книга о SQL, я предпочитаю написать запрос, который будет генерировать
инструкцию create table. Первый шаг состоит в том, чтобы запросить та блицу information schema.columns и получить информацию о столбцах
таблицы:
mysql> SELECT 'CREATE TABLE category ( create table statement

_

->
302

UNION ALL

.

Глава 15 Метаданные

->

->
->
->
->

->

->
->
->
->
->

->

->
->
->
->
->
->
->
->

SELECT cols.txt
FROM
(SELECT concat( i i ,column name, i i , column type,
CASE
WHEN is nullable = 'N0' THEN i not null I
ELSE I I
END,
CASE
WHEN extra IS NOT NULL AND extra LIKE 'DEFAULT GENERATED% i
THEN concat(' DEFAULT ',column_default,substr(extra,18))
WHEN extra IS NOT NULL THEN concat(' ', extra)
ELSE i i
END,
i
,') txt
FROM information schema.columns
WHERE table_schema = 'sakila' AND table name = 'category i
ORDER BY ordinal position
) cols
UNION ALL
SELECT ')';

_

_

_
_

_

+

+

create table statement
+

+

CREATE TABLE category (
category id tinyint(3) unsigned not null auto_increment,
name varchar(25) not null ,
last update timestamp not null DEFAULT CURRENT TIMESTAMP
on update CURRENT TIMESTAMP,

_

_

_

_

+
5 rows in set (0.00 sec)

+

Мы получили довольно близкий к нужному результат . Нам просто нужно добавить запросы к представлениям table constraints и key column
usage , чтобы получить информацию об ограничении первичного ключа:

_

_

_

mysql> SELECT 'CREATE TABLE category ( T create table statement
-> UNION ALL
-> SELECT cols.txt
-> FROM
-> (SELECT concat( ,column name, i , column type,
-> CASE
WHEN is nullable = 'N0' THEN i not null I
->
ELSE I I
->
-> END,
-> CASE
WHEN extra IS NOT NULL AND extra LIKE 'DEFAULT GENERATED%'
->
THEN concat(' DEFAULT ',соlumn default,substr(extra,18))
->
WHEN extra IS NOT NULL THEN concat(' ', extra)
->
ELSE i i
->

_

_

_

Глава 15. Метаданные

303

->
->
->
->
->
->

->>
->

-

>
->

->
->
->

->

->
->
->

->
>
>

->
->

->>
-

END,
i , ) txt
'
FROM information schema.columns
WHERE table_schema = 'sakila' AND table_name = 'category
ORDER BY ordinal position
) cols
UNION ALL
SELECT concat(' constraint primary key (')
FROM information_schema.table constraints
WHERE table schema = 'sakila' AND table name = 'category!
AND constraint type = 'PRIMARY KEY'
UNION ALL
SELECT cols.txt
FROM
(SELECT concat(CASE WHEN ordinal position > 1 THEN i r
ELSE i i END, column_name) txt
FROM information schema.key column_usage
WHERE table schema = 'sakila' AND table name = 'category i
AND constraint name = 'PRIMARY'
ORDER BY ordinal position
) cols
UNION ALL
SELECT
UNION ALL
SELECT ')';

_
_

_

_

_

_

_

_

_

_

_

_

+

+

I create table statement
+

+

CREATE TABLE category (
category_id tinyint(3) unsigned not null auto increment,
name varchar(25) not null ,
last update timestamp not null DEFAULT CURRENT TIMESTAMP
on update CURRENT TIMESTAMP,
constraint primary key (
category id

_

_

_

_

_

)
+

+

8 rows in set (0.02 sec)

Чтобы увидеть, правильно ли сформирована инструкция, я вставлю вывод
запроса в приглашение mysql (я изменил имя таблицы на category 2 , чтобы
не было коллизий с уже имеющейся таблицей ):
mysql> CREATE TABLE category2 (
> category id tinyint(3) unsigned not null auto_increment,
> name varchar(25) not null ,
> last_update timestamp not null DEFAULT CURRENT TIMESTAMP
on update CURRENT TIMESTAMP,
->
-> constraint primary key (

-

304

_

_

Глава 15. Метаданные

_

->
->
->

_

category id

);
Query OK, 0 rows affected (0.61 sec)

Инструкция выполнена без ошибок, и теперь у вас есть таблица category 2
в базе данных Sakila. Для запроса для создания правильно сформированной
инструкции create table для любой таблицы требуется больше работы ( например, обработка индексов обработки и ограничений внешних ключей), но
это задание я оставлю вам в качестве упражнения.

s

Если вы используете графический инструмент разработки, такой
как Toad, Oracle SQL Developer или MySQL Workbench, вы сможете
легко создавать такие сценарии без написания собственных запросов. Но я рассказываю вам о том, как писать свой запрос, на тот
пожарный случай, если вы застряли на необитаемом острове и у
вас под рукой только клиент командной строки MySQL...

Проверка базы данных
Многие организации обеспечивают окна для обслуживания базы данных,
во время которых выполняются работы по изменению объектов существующей базы данных ( например, добавление/удаление частей), а также могут

быть развернуты новые объекты схемы базы данных и код. После выпол нения сценариев обслуживания неплохо бы запустить сценарий проверки,
чтобы гарантировать, что новые объекты схемы находятся на месте со всеми соответствующими столбцами, индексами, первичными ключами и т .д.
Вот запрос, который возвращает количество столбцов, количество индексов
и количество ограничений первичного ключа (0 или 1) для каждой таблицы
в схеме Sakila:
mysql > SELECT tbl.table name,

_

->
->
->
->
->
->

-->

>
->
->
->

->

_
_
_
_
_
_
_

(SELECT count(*) FROM information schema.columns elm
WHERE elm.table schema = tbl.table schema
AND elm.table name = tbl.table name) num_columns,
(SELECT count(*) FROM information_schema.statistics sta
WHERE sta.table schema = tbl.table schema
AND sta.table name = tbl.table name) num indexes,
(SELECT count(*) FROM information schema.table constraints tc
WHERE tc.table schema = tbl.table schema
AND tc.table_name = tbl.table_name
AND tc.constraint type = 'PRIMARY KEY') num primary keys
FROM information_schema.tables tbl
WHERE tbl.table schema = sakila

_
_
_
_
_

_

_

_
_

_

Глава 15. Метаданные

305

_

->

AND tbl.table type = 'BASE TABLE i
BY 1;
>
ORDER
num columns | num indexes

TABLE NAME
+

+

+

city
country
customer
film
film actor
film category
film_text
inventory
language
payment

_
_

rental

3

1
1
1
1
1
1
1
1
1
1
1
1
1

4
3
3
3
4
1

4
7
3
3

4
+

+

+

1
1
1

1
2
1
7

11

staff
store

+

num_primary_keys |

+

2

4
9
3
4
3
9
13
3
3
3
4
3
7
7

actor
address
category

+

+

+

+

+

+

+

16 rows in set (0.01 sec)

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

Динамическая генерация SQL
Некоторые языки, такие как PL/SQL в Oracle и Transact-SQL от Microsoft,
являются надмножествами языка SQL, что означает , что они включают в
себя инструкции SQL с его грамматикой наряду с обычными процедурными
конструкциями, такими как if-then-else, или циклами типа while. Другие
языки, такие как Java, включают возможность взаимодействия с реляционной
базой данных, но не включают в свою грамматику инструкции SQL, что означает , что все инструкции SQL должны содержаться в виде строк.
Таким образом, большинство серверов реляционных баз данных, включая SQL Server, Oracle Database и MySQL, позволяют отправлять серверу
инструкции SQL в виде строк. Отправка строк вместо непосредственного
использования интерфейса SQL в общем случае известна как динамическое
выполнение SQL. Язык Oracle PL/SQL, например, включает команду execute
immediate, которую можно использовать для отправки строки для выполнения; SQL Server для динамического выполнения инструкций SQL включает
системную процедуру sp_executesql.
306

Глава 15. Метаданные

MySQL для динамического выполнения SQL предоставляет инструкции
prepare , execute и deallocate. Вот простой пример их применения:
mysql> SET @qry =
-> i SELECT customer id, first name, last_name FROM customer';
Query OK, 0 rows affected (0.00 sec)

_

_

mysql> PREPARE dynsqll FROM @qry;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE dynsqll;
+

+

+

customer id | first name
+

505
504
36
96

+
|
|
|
|

+

last name
+

RAFAEL
NATHANIEL
KATHLEEN
DIANA

31 | BRENDA
318 | BRIAN
402 | LUIS
413 | MARVIN
28 | CYNTHIA
+
+
599 rows in set (0.02 sec)

+

ABNEY
ADAM
ADAMS
ALEXANDER
WRIGHT
WYMAN
YANEZ
YEE
YOUNG
+

+

mysql> DEALLOCATE PREPARE dynsqll;
Query OK, 0 rows affected (0.00 sec)

Инструкция set просто присваивает строку переменной qry, которая затем передается механизму базы данных ( для анализа, проверки безопасности и оптимизации ) с помощью инструкции prepare. После выполнения
инструкции с помощью вызова execute инструкция должна быть закрыта
с использованием команды deallocate prepare, которая освобождает все
ресурсы базы данных (например, курсоры ) , которые были использованы во
время выполнения.
В следующем примере показано, как можно выполнить запрос, который
включает заполнители, значения для которых могут быть указаны во время
выполнения:

_

_

mysql> SET @qry = 'SELECT customer_id, first name, last name
FROM customer WHERE customer_id = ?';
Query OK, 0 rows affected (0.00 sec)
mysql> PREPARE dynsql2 FROM @qry;
Query OK, 0 rows affected (0.00 sec)
Глава 15. Метаданные

307

Statement prepared

mysql> SET @custid = 9;
Query OK, 0 rows affected (0.00 sec)
!
mysql> EXECUTE dynsq
2 USING @custid;
+

+

I

+

+

customer id | first name | last name

+

+

9 | MARGARET
+

+

+

+

| MOORE
+

+

1 row in set (0.00 sec)
mysql> SET Ocustid = 145;
Query OK, 0 rows affected (0.00 sec)
mysql> EXECUTE dynsql2 USING Ocustid;
+

+

+

+

| customer id I first name I last name I
+
+
+
+
145 | LUCILLE
I HOLMES
+

+

+

+

1 row in set (0.00 sec)

mysql> DEALLOCATE PREPARE dynsql2;
Query OK, 0 rows affected (0.00 sec)

В этой последовательности запрос содержит заполнитель (символ ? в кон це инструкции), так что значение идентификатора клиента может быть указано во время выполнения. Инструкция подготавливается один раз, а затем
выполняется дважды, один раз для идентификатора клиента 9 и второй
для клиента с идентификатором 145, после чего инструкция закрывается.
Вы спрашиваете, какое отношение это все имеет к метаданным ? Если вы
собираетесь использовать динамический SQL для запроса к таблице, то почему бы не создать строку запроса с использованием метаданных, вместо того
чтобы жестко прошивать в коде определение таблицы ? В следующем примере
генерируется та же динамическая SQL строка, что и в предыдущем примере,
но имена столбцов она извлекает из представления information schema .





_

columns:
mysql> SELECT concat('SELECT ',
-> concat ws(',', cols.coll, cols.col2, cols.col3, cols.col4,
-> cols.col5, cols.col6, cols.col7, cols.col8, cols.col9),
-> i FROM customer WHERE customer id = ? 1 )

_

308

->

INTO @ qry

->
->
->

FROM
(SELECT
max(CASE WHEN ordinal position

|

_

Глава 15. Метаданные

=

1 THEN column_name

->
->
->
->
->
->
->
->

ELSE NULL END) coll,
max(CASE WHEN ordinal position =
ELSE NULL END) col2,
max(CASE WHEN ordinal position =
ELSE NULL END) col3,
max(CASE WHEN ordinal_position =
ELSE NULL END) col4,
max(CASE WHEN ordinal position =
ELSE NULL END) col5,
>
-> max(CASE WHEN ordinal position =
ELSE NULL END) col6,
->
-> max(CASE WHEN ordinal position =
ELSE NULL END) col7,
->
-> max(CASE WHEN ordinal_position =
ELSE NULL END) col8,
>
-> max(CASE WHEN ordinal position =
ELSE NULL END) col9
->
> FROM information schema.columns
-> WHERE table_schema = 'sakila' AND
> GROUP BY table name
-> ) cols;
Query OK, 1 row affected (0.00 sec)

_
_

_
_
_

-

-

_

_
3 THEN column_name
4 THEN column_name
5 THEN column_name
6 THEN column_name
7 THEN column_name

2 THEN column name

8 THEN column_name

_

9 THEN column name

_

-

_

table_name

= 'customer'

mysql> SELECT @qry;
+

+

I @qry
+

_

_

+

_

I SELECT customer id,store_id,first name,last name,email,
address id,active,create date,last_update
I

_

_

| FROM customer WHERE customer id
+
1 row in set (0.00 sec)

=?

mysql> PREPARE dynsql3 FROM @qry;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> SET Ocustid = 45;
Query OK, 0 rows affected (0.00 sec)
mysql> EXECUTE dynsql3 USING Ocustid;
+

+

+

+

+

store id | first name | last name
+
+
+
+
| PHILLIPS
45
1 | JANET

customer id
+

+

+

+

+

+

+

+

I email
+

+

+

+

49 |
+

JANET.PHILLIPS@sakilacustomer.org
+

+

address id I active
1



Глава 15. Метаданные

309

+

I

+

create date
+

+

I

last_update

2006-02-14 22:04:36

+

2006-02-15 04:57:20
+

+

+

+

1 row in set (0.00 sec)

mysql> DEALLOCATE PREPARE dynsql3;
Query OK, 0 rows affected (0.00 sec)

Запрос получает первые девять столбцов таблицы customer, создает строку с использованием функций concat и concat _ws и присваивает ее переменной qry. Выполняется строка запроса так же, как и ранее.

я

Как правило, лучше генерировать такой запрос с использованием процедурного языка, который включает циклические кон струкции, такого как Java, PL/SQL, Transact-SQL или MySQL Stored
Procedure Language. Я просто хотел продемонстрировать пример
на чистом SQL, поэтому мне пришлось ограничить количество извлекаемых столбцов некоторым разумным числом , в этом примере
равным девяти.

Проверьте свои знания
Предлагаемые здесь упражнения призваны закрепить понимание вами метаданных. Ответы к упражнениям представлены в приложении Б.
УПРАЖНЕНИЕ 15.1
Напишите запрос, который перечисляет все индексы в схеме Sakila. Не за будьте включить в результаты имена таблиц.

УПРАЖНЕНИЕ 15.2
Напишите запрос, генерирующий вывод, который можно было бы использовать для создания всех индексов таблицы sakila . customer. Вывод должен
иметь вид
"ALTER TABLE ADD INDEX ( ) "

ГЛАВА 16

Аналитические функции

Объемы данных растут в ошеломляющем темпе, так что организации стал киваются с проблемами их хранения, не говоря уже об их адекватной обработке. В то время как анализ данных традиционно выполняется за пределами
серверов баз данных, с использованием таких специализированных инструментов или языков, как Excel, R и Python, язык SQL включает в себя набор
функций, полезных для аналитической обработки данных. Если вам нужно
сгенерировать рейтинги для определения лучших десяти продавцов вашей
компании или если вы генерируете финансовый отчет для клиента и должны
рассчитать средние отклонения за три месяца, можете использовать встроенные аналитические функции SQL для выполнения расчетов такого вида.

Концепции аналитических функций
После того как сервер базы данных выполнил все шаги, необходимые для
выполнения запроса, включая соединения, фильтрацию, группирование и сортировку, результирующий набор создан и готов к возвращению вызывающей программе/ коду. Представьте, что вы можете приостановить выполнение
запроса в этот момент и прогуляться по результирующему набору, пока он
все еще находится в памяти. Какие виды анализа данных вы можете захотеть
выполнить ? Если ваш результирующий набор содержит данные продаж, возможно, вы захотите сформировать рейтинги продавцов или регионов либо
рассчитать процент различия в продажах в разные промежутки времени. Генерируя результаты для финансового отчета, вы можете захотеть рассчитать
промежуточные итоги для каждого раздела отчета и общий итог для всего
отчета. С помощью аналитических функций вы можете сделать все это и
многое другое. Прежде чем погрузиться в детали, в следующих подразделах
будут рассмотрены механизмы, используемые некоторыми из наиболее часто
используемых аналитических функций.

Окна данных
Допустим, вы написали запрос, который генерирует ежемесячные итоги
продаж в течение определенного периода времени. Например, следующий за прос подсчитывает итоговую сумму общих ежемесячных платежей за прокат
фильмов за период с мая по август 2005 года:
mysql> SELECT quarter(payment_date) quarter,
-> monthname(payment date) month nm,
sum(amount) monthly sales
>
-> FROM payment
-> WHERE year(payment date) = 2005
-> GROUP BY quarter(payment date), monthname(payment date);
+
+
+
+
I quarter | month_nm | monthly_sales |
+
+
+
+
4824.43 |
2 | May
9631.88 |
2 | June
3 | July
28373.89 |
24072.13 |
3 | August

_

-

_

+

_

_

_

+

+

_

+

4 rows in set (0.13 sec)

Глядя на результаты, можно увидеть, что самый высокий ежемесячный
в июне. Но для
итог за четыре месяца был в июле, а за второй квартал
того, чтобы определить эти самые высокие значения программно, нужно добавить к каждой строке дополнительные столбцы, показывающие максимальные значения за квартал и за весь период. Вот предыдущий запрос, но с добавленными новыми столбцами для вычисления указанных значений:



mysql> SELECT quarter(payment_date) quarter,
-> monthname(payment date) month_nm,
> sum(amount) monthly sales,
-> max ( Siam ( amount) )
over ( ) max overall sales ,
->
-> max ( sum ( amount ) )

_

-

_

__

->

+

_

_

+

_

+

+

month_nm | monthly_sales | max_overall sales

quarter

312

_

WHERE year(payment date) = 2005
GROUP BY quarter(payment date), monthname(payment_date);

+
+

_

over (partition by quarter (payment date ) ) max qrtr sales

-> FROM payment
->
->

_

+

+

Глава 16. Аналитические функции

+

+

max_qrtr_sales
+

+

2 | May
2 | June
3 | July
3 | August
+

+

4824.43
9631.88
28373.89
24072.13
+

9631.881
9631.881
28373.891
28373.891

28373.89
28373.89
28373.89
28373.89

|
|
|
|
+

+

+

4 rows in set (0.09 sec)

Аналитические функции, используемые для генерации этих дополнительных столбцов, группируют строки в два разных набора: один набор, содержа щий все строки в том же квартале, а второй
содержащий все строки. Для
этого типа анализа аналитические функции включают возможность группи рования строк в окна, которые эффективно разделяют данные для использования аналитической функцией без изменения общего результирующего
набора. Окна определяются с использованием предложения over в сочетании с необязательной конструкцией partition by. В предыдущем запросе
обе аналитические функции включают предложение over , но первое из них
пустое, указывающее, что окно должно включать в себя весь набор результа тов, тогда как второе указывает , что окно должно включать только строки из
одного и того же квартала. Окно данных может содержать от одной строки
до всех строк в результирующем наборе. Различные аналитические функции
могут определять различные окна данных.



Локализованная сортировка
Вы можете не только разделить свой результирующий набор на окна данных для аналитических функций, но и указать порядок сортировки. Например, чтобы определить рейтинг для каждого месяца, в котором значение 1
отдается месяцу, имеющему самые высокие продажи, нужно указать, какой
столбец ( или столбцы) используется для вычисления рейтинга:

_
_

mysql> SELECT quarter(payment date) quarter,
-> monthname(payment date) month nm,
-> sum(amount) monthly sales,
-> rank ( ) over ( order by sum ( amount ) desc ) sales rank
-> FROM payment
> WHERE year(payment date) = 2005
-> GROUP BY quarter(payment_date), monthname(payment date)
-> ORDER BY 1, month(payment_date);
+
+
+
+
+
quarter | month_nm | monthly_sales
sales rank
+
+
+
+
+
4 I
4824.43 |
2 | May

_

-

_

_

_

_

Глава 16. Аналитические функции

313

2 | June
3 | July
3 | August
+

9631.88 |
28373.89 |
24072.13 |
+

+

+

3
1
2 I
+

4 rows in set (0.00 sec)

Этот запрос включает в себя вызов функции rank ( о которой будет рассказано в следующем разделе) и указывает , что для генерации рейтинга используется сумма столбца amount со значениями, отсортированными в по рядке убывания. Таким образом, месяц, имеющий самые высокие продажи (в
данном случае это июль), получит рейтинг 1.

в

Множественные предложения order by
В предыдущем примере имеется два предложения order by,
в конце запроса для определения, как должен быть от одно
в рамках функции
сортирован результирующий набор, и одно
rank, чтобы определить, как должны быть распределены значения
рейтингов. Неудачно то, что одно и то же предложение используется для разных целей, но помните, что даже если вы используете
аналитические функции с одним или несколькими предложения ми order by, но хотите, чтобы результат был отсортирован определенным образом, вам все равно необходимо предложение order
by в конце вашего запроса.





В некоторых случаях вы захотите использовать в одном и том же вызове
аналитической функции как предложение partition by, так и предложение
order by. Например, предыдущий пример можно изменить так, чтобы пре доставлять поквартальные рейтинги, а не единый рейтинг для всего резуль-

тирующего набора:
mysql> SELECT quarter(payment_date) quarter,
-> monthname(payment date) month nm,
-> sum(amount) monthly_sales,

_

_

( ) over (partition by quarter ( payment_date )
->> rank
by sum ( amount ) desc ) qtr_sales_rank
order
-> FROM payment
-> WHERE year(payment_date) 2005

->
->

_=
_

+

+

quarter
+

314

_

GROUP BY quarter(payment date), monthname(payment date)
ORDER BY If month(payment date);
+

+

month_nm | monthly_sales | qtr_sales_rank

+
2 | May
2 | June

+

+



4824.43 |
9631.88 |

Глава 16 . Аналитические функции

+
+

2
1 I

3 | July
3 | August

28373.89 |
24072.13 |

1
2
+

4 rows in set (0.00 sec)

Эти примеры были разработаны, чтобы проиллюстрировать использование предложения over; в следующих разделах будут подробно описаны различные аналитические функции.

Ранжирование
Люди любят ранжировать. Если вы посетите свои любимые сайты, посвя щенные новостям, спорту, путешествиям и так далее, то увидите заголовки,
подобные следующим.




Лучшие 10 мест для отпуска



Наихудшие столицы Европы

Лучшие фильмы XX века



50 лучших вузов страны

Компании также любят генерировать рейтинги, но для более практических
целей. Знание того, что продается лучше/ хуже остального, или какие регионы дают наименьший / наибольший доход, помогает организациям принимать
стратегические решения.

Функции ранжирования
В стандарте SQL есть несколько функций ранжирования с разными подходами к обработке одинаковых данных.

_

row number

Возвращает для каждой строки уникальное число с произвольно назначаемым рейтингом для одинаковых данных
rank

Возвращает при одинаковых данных один и тот же рейтинг с соответ ствующими пропусками в общем рейтинге

_

dense rank

Возвращает при одинаковых данных один и тот же рейтинг без пропусков в общем рейтинге
Давайте рассмотрим пример, чтобы понять различия. Пусть отдел продаж хочет найти 10 лучших клиентов, чтобы предложить им взять фильм
Глава 16. Аналитические функции

315

напрокат бесплатно. Следующий запрос определяет количество прокатов
фильмов для каждого клиента и сортирует результаты в порядке убывания:
mysql>
->
->
->

_

SELECT customer id, count(*) num_rentals
FROM rental
GROUP BY customer id
ORDER BY 2 desc;

_

+

+

+

customer id I num rentals
+

+

46
45

236
144

42
42

75
469
197
137
468
178
459
410
5
295
257
366
176
198
267
439
354
348
380
29
371
403

41
40
40
39
39
39
38
38
38
38
37
37
37
37
36
36
36
36
36
36
35
35
35

21

15
15
14
14
14

136
248
110

281
61
318
+

+

148
526

12
+

+

599 rows in set (0.16 sec)

В результирующем наборе видно, что как третий, так и четвертый клиен ты брали напрокат по 42 фильма. Должны ли они оба получить один и тот
же рейтинг 3? И если да, то какой рейтинг должен иметь клиент , бравший
316

.

Глава 16 Аналитические функции

напрокат 41 фильм: рейтинг 4, или мы должны пропустить одно значение
и дать ему рейтинг 5? Чтобы увидеть, как каждая функция обрабатывает совпадения при назначении рейтинга, в следующий запрос добавлены еще три
столбца, каждый из которых использует свою функцию ранжирования:

_

_

mysql> SELECT customer id, count(*) num rentals,
-> row_number() over (order by count(*) desc) row number rnk,
-> rank ( ) over (order by count(*) desc) rank_rnk,
-> dense_rank ( ) over (order by count(*) desc) dense rank rnk
-> FROM rental
-> GROUP BY customer_id
-> ORDER BY 2 desc;
+
+
+
+
+
+
|customer idlnum rentals|row number rnkjrank rnk|dense rank rnk|

_

_

+

+

148
526

144
236
75
197
469
468
137
178
5
295
410
459
198
257
366
176
348
354
380
439
29
267
50
506
368
91
371
196
373
204
381
273
21

+

46

45
42
42
41
40
40
39
39
39
38
38
38
38
37
37
37
37
36
36
36
36
36
36
35
35
35
35
35
35
35
35
35
35
35

+

1
2
3
4
5
6
7

10
8

9
11

12
13
14
16
17
18
15
21
22
23
24
19
20
26
37
32
27
33
28
34
29
35
30
25

_
_

+

+

1
2

1
2

3
3
5
6
6
8
8
8

3

11
11
11

11
15
15
15
15
19
19
19
19
19
19
25
25
25
25
25
25
25
25
25
25
25

3
4
5
5
6
6
6
7
7
7
7
8
8
8
8

9
9
9
9
9
9
10
10
10
10
10
10
10
10
10
10
10

Глава 16. Аналитические функции

317

403

35

36

25

274
66

35
34

31
42

25
38

136
248
110
281
61
318

15
15
14
14
14
12

594
595
597
598
596
599

594
594
596
596
596
599

+
+
+
599 rows in set ( 0.01 sec )

+

10
10
11

30
30
31
31
31
32
+

+

_

Третий столбец использует функцию row number для назначения уни кального рейтинга каждой строке без учета совпадений. Каждой из 599 строк
присваивается значение рейтинга от 1 до 599 с рейтингом, произвольно назначаемым клиентам, которые имеют одинаковое количество взятых напрокат фильмов. Следующие же два столбца назначают в случае совпадения один
и тот же рейтинг, но различаются наличием пропуска в использованных рангах. Взглянув на ряд 5 результирующего набора, можно увидеть, что функция
rank пропускает значение 4 и присваивает следующему клиенту ранг 5, тогда
как функция dense _ rank присваивает значение 4.
Вернемся к первоначальному запросу. Как бы вы определили 10 лучших
клиентов? Есть три возможных решения.

_

Использовать функцию row number, чтобы определить клиентов с рей тингом от 1 до 10, что приводит ровно к 10 клиентам в данном примере,
но в других случаях может исключить ряд клиентов, имеющих такое же
количество взятых напрокат фильмов, как и клиент с рейтингом 10.

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

_

Использовать функцию dense rank определения клиентов с рейтингом
не более 10, что в нашем случае дает список из 37 клиентов.
Если в вашем результирующем наборе нет совпадений, то подходит любая
из этих функций, но для множества ситуаций лучшим вариантом может быть

функция rank.

Генерация нескольких рейтингов
Пример в предыдущем разделе генерирует один рейтинг для всего множества клиентов. Но что если вы хотите создать несколько наборов рейтингов
318

Глава 16. Аналитические функции

для одного и того же результирующего набора ? Чтобы расширить преды дущий пример, пусть отдел продаж решает предложить бесплатный прокат
фильмов для лучших пяти клиентов каждый месяц. Чтобы сгенерировать такие данные, можно добавить к запросу столбец rental month:

_

_
_
count(*)num_rentals

mysql> SELECT customer id,

->
->
->
->
->

_

monthname ( rental date ) rental month ,

FROM rental
GROUP BY customer_id, monthname ( rental date )
ORDER BY 2, 3 desc;

_

+

+

+

+

customer id | rental month | num rentals |
+

+

+

+

119
15
569
148
141
21
266
418
410
342
274

August
August
August
August
August
August
August
August
August
August
August

281
318
75
155
175
516
361
269
208
53

August
August
February
February
February
February
February
February
February
February

2

22
472
148
102
236
75
91
30
64
137

February
February
July
July
July
July
July
July
July
July

1
1
22
21
20

339
485

May
May

18
18
18
18
17
17
17
17
17
17
16

1
3
2
2
2
2
2
2
2

20
19
19
19
19
1
1
Глава 16. Аналитические функции

319

116 | May
497 | May
180 | May
+

1
1
1

+

I
+

+

2466 rows in set (0.02 sec)

Для того чтобы каждый месяц создавать новый набор рейтингов, нужно
добавить в функцию rank нечто, описывающее, как разделить результирующий набор на различные окна данных (в нашем случае месяцы ). Это дела ется с использованием предложения partition by, добавляемого в предложение over:



_
_
_

SELECT customer id,
monthname(rental date) rental month,
count(*) num rentals,
rank() over ( partition by monthname ( rental date )
order by count(*) desc) rank rnk
FROM rental
GROUP BY customer id, monthname(rental_date)
ORDER BY 2, 3 desc;
+
+
+
+
+
customer id rental month | num rentals I rank rnk |
+
+
+
+
+
18
1
569 | August
18
1
119 | August
18
1
148 | August
18
1
15 | August
5
141 | August
17
5
17
410 | August
5
17
418 | August
21 | August
17
5
17
5
266 | August
5
342 | August
17
11
144 | August
16
16
11
274 | August

mysql>
->
->
>
->
>
>
->

-

_

_

_

164 | August
318 | August
75 | February
457 | February
53 | February
354 | February
352 | February
373 | February
148 | July
102 | July
236 | July
75 I July
91 | July
354 | July

320 |

_

Глава 16 . Аналитические функции

2
1
3
2
2
2
1
1
22
21
20
20
19
19

596
599
1
2
2
2
24
24
1
2
3
3
5
5

30 | July
64 | July
137 | July
526 | July
366 | July
595 | July
469 | July

19
19
19
19
19
19
18

5
5
5
5
5
5
13

457 | May
356 | May
481 May
10 | May

1
1
1
1

347
347
347
347

+
+
2466 rows in set (0.03 sec)

+

+

+

Взглянув на результаты, можно увидеть, что рейтинги для каждого месяца
сбрасываются и начинаются с 1. Чтобы генерировать необходимые для отдела продаж результаты (5 лучших клиентов каждого месяца), можно просто
обернуть предыдущий запрос в подзапрос и добавить условие фильтрации
для исключения любых строк с рейтингом выше пяти:

_

_

SELECT customer id , rental month, num_rentals,
rank rnk ranking
FROM
(SELECT customer id,
monthname(rental date) rental month,
count(*) num_rentals,
rank() over (partition by monthname(rental_date)
order by count(*) desc) rank_rnk
FROM rental
GROUP BY customer id, monthname(rental date)
) cust_rankings
WHERE rank rnk SELECT monthname(payment date) payment month,
-> amount,
->

sum ( amount )
over (partition by monthname ( payment date ) ) monthly total ,
sum ( amount ) over ( ) grand total
-> FROM payment

->
->

->
->

_

_

_

WHERE amount >= 1 0
ORDER BY 1;
+

+

+

+

+

I payment_month

| amount | monthly total | grand total |
+
+
+
+

+

_

_

August
August
August
August

10.99
11.99
10.99
10.99

521.53
521.53
521.53
521.53

1262.86
1262.86
1262.86
1262.86

August
August
August
July
July
July
July

10.99
10.99
10.99
10.99
10.99
10.99
10.99

521.53
521.53
521.53
519.53
519.53
519.53
519.53

1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86

July
July
July
June
June
June
June
June
June
June
June
June
June
June
June
June
June
June
May
May
May
May

10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
11.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99
10.99

519.53
519.53
519.53
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
165.85
55.95
55.95

1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86
1262.86

322

Глава 16. Аналитические функции

55.95
55.95

55.95

11.99

May

+

+

+

1262.86 |
+

+

114 rows in set (0.01 sec)

_

Столбец grand total содержит то же значение ( 1262,86 доллара ) для
каждой строки, потому что предложение over пустое ( что указывает , что
суммирование проводится по всему результирующему набору). Столбец
monthly total , однако, содержит для каждого месяца другое значение, поскольку имеется предложение partition by, указывающее, что результирующий набор будет разделен на несколько окон данных (по одному для каждого
месяца ).
Хотя включение столбца, такого как grand_ total , с одним и тем же зна чением для каждой строки выглядит нелепо, столбцы такого типа могут использоваться и для расчетов, как показано в следующем запросе:

_

_

_

mysql> SELECT monthname(payment date) payment month,
-> sum(amount) month_totaI,
-> round ( sum ( amount ) / sum ( sum ( amount ) ) over ( )
->
* 100 , 2 ) pct of total
-> FROM payment
> GROUP BY monthname(payment_date);
+
+
+
+
payment_month | month_total | pct_of total
+
+
+
+
7.16
May
4824.43
June
14.29
9631.88
July
42.09
28373.89
35.71
August
24072.13
0.76
February
514.18
+
+
+
+
5 rows in set (0.04 sec)

_ _

-

_

Этот запрос рассчитывает общие платежи для каждого месяца, суммируя
столбец amount , а затем рассчитывает для каждого месяца процент от общих
платежей, используя сумму ежемесячных значений в качестве знаменателя
при расчете.
Функции отчетности также могут быть использованы для сравнений, таких как следующий запрос, который использует выражение case, чтобы
определить, является ли ежемесячный итог максимальным, минимальным
или находящимся где -то посредине:
mysql> SELECT monthname(payment_date) payment_month,

->

->

->
->

_

sum(amount) month total,
CASE sum ( amount )
WHEN max ( sum ( amount ) ) over ( ) THEN ' Highest
WHEN min ( sum ( amount ) ) over ( ) THEN ' Lowest
Глава 16 . Аналитические функции

323

->

->
->
->

ELSE ' Middle '
END descriptor

FROM payment
GROUP BY monthname(payment_date);

+
+
+
+
| payment month | month total | descriptor |

_

+

_

+

May
June
July
August
February
+

+

+

4824.43
9631.88
28373.89
24072.13
514.18

Middle
Middle
Highest
Middle
Lowest
+

+

+

5 rows in set (0.04 sec)

Столбец descriptor действует как функция квазирейтинга, в том смысле,
что помогает идентифицировать наибольшие/ наименьшие значения в наборе
строк.

Рамки окон
Как было описано ранее в главе, окна данных для аналитических функций
определяются с использованием предложения partition by, которое позволяет группировать строки с общими значениями. Но что если требуется
еще более тонкий контроль над строками для включения их в окно данных?
Например, возможно, вы хотите создать “ скользящее” итоговое значение,
вычисляемое с начала года и до текущей строки. Для такого рода расчетов
можно включить подпредложение “рамки” для точного определения, какие
именно строки включаются в окно данных. Вот запрос, который суммирует
платежи за каждую неделю и включает функцию отчетности для вычисления

суммы:

_

_

mysql> SELECT yearweek(payment date) payment week,
-> sum(amount) week total,
-> sum(sum(amount))
over ( order by yearweek ( payment date )
->
rows unbounded preceding ) rolling sum
->
-> FROM payment
> GROUP BY yearweek(payment date)
-> ORDER BY 1;

_

_

_

-

+

+

+

+

200521
200522
200524
200525

324

+

+

payment_week | week_total

I rolling sum |
+

+

2847.18
1977.25
5605.42
4026.46

|
|
|
|

Глава 16. Аналитические функции

2847.18
4824.43
10429.85
14456.31

|
|
|
|

200527
200528
200530
200531
200533
200534
200607

8490.83
5983.63
11031.22
8412.07
10619.11
7909.16
514.18

+
+
11 rows in set (0.04 sec)

22947.14
28930.77
39961.99
48374.06
58993.17
66902.33
67416.51
+

+

Выражение для столбца rolling_ sum включает предложение rows unbo
unded preceding для определения окна данных от начала результирующего набора и до текущей строки, включая ее. Окно данных состоит из одной
строки для первой строки в результирующем наборе, две строки для второй и т .д. Значение для последней строки представляет собой сумму всего
результирующего набора.
Вместе с такими “ скользящими ” суммами можно рассчитать и соответ ствующие средние значения. Вот запрос, который рассчитывает трехнедельное “скользящее” среднее для общих платежей:



mysql> SELECT yearweek(payment_date) payment_week,
-> sum(amount) week_total,
avg(sum(amount) )
>
over (order by yearweek(payment_date)
>
rows between 1 preceding and 1 following) rolling_3wk_avg
->
-> FROM payment
> GROUP BY yearweek(payment_date)
-> ORDER BY 1;
+
+
+
+
| payment week | week_total I rolling 3wk avg |
+
+
+
+
2412.215000
200521
2847.18
200522
1977.25
3476.616667
200524
5605.42
3869.710000
200525
4026.46
6040.903333
6166.973333
200527
8490.83
200528
5983.63
8501.893333
200530
11031.22
8475.640000
200531
8412.07
10020.800000
200533
10619.11
8980.113333
200534
7909.16
6347.483333
200607
4211.670000
514.18

-

-

_ _

_

+

+

+

+

11 rows in set (0.03 sec)

_ _

Столбец rolling 3 wk avg определяет окно данных, состоящее из текущей строки, предыдущей и следующей строк. Таким образом, окно дан ных состоит из трех строк, за исключением первых и последних строк, для
Глава 16. Аналитические функции

325

которых окно данных состоит только из двух строк ( так как для первой строки нет предшествующей, а для последней строки последующей).
Указание количества строк для окна данных во многих случаях работает
нормально, но если в данных есть пробелы, то можно попробовать другой
подход. В предыдущем результирующем наборе, например, есть данные для
недель 200521, 200522 и 200524, но нет данных для недели 200523. Если вы
хотите указать интервал даты, а не количество строк, то можете указать диа пазон для вашего окна данных, как показано в следующем запросе:



_

mysql > SELECT date ( payment date ) , sum ( amount ) ,
> avg ( sum ( amount ) ) over ( order by date ( payment date )
range between interval 3 day preceding
->
and interval 3 day following ) 7 day avg
>
-> FROM payment
- > WHERE payment _date BETWEEN ’ 2005 - 07 01 ! AND ' 2005- 09 - 01
- > GROUP BY date ( payment date )
> ORDER BY 1;
+
+
+
+
date ( payment _date ) I sum ( amount ) | 7 day avg
+
+
+
+

-

_

_ _

-

-

_

-

_ _

2005- 07 - 05
2005- 07 - 06
2005- 07 - 07
2005- 07 - 08
2005-07 -09
2005- 07 -10
2005- 07 -11
2005- 07 -12
2005- 07 - 26
2005- 07 - 27
2005- 07 - 28
2005- 07 -29
2005- 07 - 30
2005- 07 - 31
2005 -08- 01
2005- 08 - 02
2005- 08 -16
2005- 08 -17
2005- 08 -18
2005- 08 -19
2005- 08-20
2005- 08 - 21
2005- 08 - 22
2005 -08 - 23

128.73
2131.96
1943.39
2210.88
2075.87
1939.20
1938.39
2106.04
160.67
2726.51
2577.80
2721.59
2844.65
2868.21
2817.29
2726.57
111.77
2457.07
2710.79
2615.72
2723.76
2809.41
2576.74
2523.01

+
+
24 rows in set ( 0.03 sec )

326

Глава 16. Аналитические функции

1603.740000
1698.166000
1738.338333
1766.917143
2049.390000
2035.628333
2054.076000
2014.875000
2046.642500
2206.244000
2316.571667
2388.102857
2754.660000
2759.351667
2795.662000
2814.180000
1973.837500
2123.822000
2238.086667
2286.465714
2630.928571
2659.905000
2649.728000
2658.230000
+

+

_

Столбец 7 day_avg определяет диапазон ±3 дня и будет включать только
строки, значения рауте nt date которых попадают в пределы этого диапазона. Например, для расчета 2005-08-16 включаются только значения для 08-16,
08-17, 08-18 и 08-19, так как для трех предшествующих дат ( от 08-13 до 08-15)
строк нет .

_

Запаздывание и опережение
Наряду с вычислением сумм и средних по окну данных еще одна распространенная задача отчетности включает в себя сравнение значений от одной
строки к другой. Например, если вы генерируете ежемесячные суммы продаж,
то можете попросить создать столбец, показывающий процентное отличие от
предыдущего месяца, что потребует способа получения ежемесячных продаж
из предыдущей строки. Это может быть достигнуто с помощью функции lag,
которая извлекает значение столбца из предыдущей строки в результирующем наборе, или функции lead, которая получает значение столбца из сле дующей строки. Вот пример с использованием обеих упомянутых функций:

_

mysql> SELECT yearweek(payment date) payment_week,
>
sum(amount) week_total,
-> lag ( sum ( amount ) , 1 )
over ( order by yearweek (payment date ) ) prev wk tot ,
->
-> lead ( sum ( amount) , 1 )
over ( order by yearweek ( payment date ) ) next wk tot
->
> FROM payment
> GROUP BY yearweek(payment_date)
-> ORDER BY 1;

-

_
_

-

+

+

+

+

_ _
__ _
+

payment_week | week_total | prev_wk_tot
next wk tot |
+
+
+
+
+
NULL
200521
1977.25
2847.18

200522
200524
200525
200527
200528
200530
200531
200533
200534
200607
+

1977.25
5605.42
4026.46
8490.83
5983.63
11031.22
8412.07
10619.11
7909.16
514.18
+

5605.42
4026.46
8490.83
5983.63
11031.22
8412.07
10619.11
7909.16
514.18
NULL

2847.18
1977.25
5605.42
4026.46
8490.83
5983.63
11031.22
8412.07
10619.11
7909.16
+

+

+

11 rows in set (0.03 sec)

Взглянув на результаты, мы видим, что недельная сумма 8490,83 для недели 200527 появляется в столбце next wk tot для недели 200525, а также в
Глава 16. Аналитические функции

327



столбце prev_wk_ tot для недели 200528. Так как в результирующем наборе нет строки, предшествующей неделе 200521, функция lag генерирует зна чение null для первой строки. Аналогично значение, создаваемое функцией lead для последней строки в результирующем наборе, также равно null .
Обе функции допускают наличие необязательного второго параметра ( который по умолчанию равен 1) для указания количества строк до / после текущей
строки для получения значения столбца.
Вот как вы можете использовать функцию lag, чтобы создать разницу
в процентах по отношению к предыдущей неделе:
mysql>
->
->
>
->
->
>
->
->
>

-

-

+

I

-

_

SELECT yearweek(payment_date) payment week,
sum(amount) week total,
round ( ( siim ( amount ) - lag ( sum ( amount ) , 1 )
over ( order by yearweek (payment date ) ) )
/ lag ( sum ( amount ) , 1 )
over ( order by yearweek (payment date ) )

_

_

_

* 100 , 1 ) pct_diff

FROM payment
GROUP BY yearweek(payment date)
ORDER BY 1;

_

_

+

payment week | week_total
+

+

200521
200522
200524
200525
200527
200528
200530
200531
200533
200534
200607
+

+

_

+

+

NULL
30.6
183.5
-28.2
110.9
-29.5
84.4
-23.7
26.2
-25.5
-93.5

2847.18
1977.25
5605.42
4026.46
8490.83
5983.63
11031.22
8412.07
10619.11
7909.16
514.18
+

+

pct diff |

-

+

+

11 rows in set (0.07 sec)

Сравнение значений из разных строк в одном и том же результирующем
наборе обычная практика в системах отчетности, поэтому вы, скорее всего, найдете множество применений для функций lag и lead.



Конкатенация значений в столбце
Есть еще одна важная функция, о которой надо рассказать и которая, хотя
технически и не является аналитической, работает с группами строк в окне данных. Функция group concat используется для превращения набора

_

328

Глава 16. Аналитические функции

значений столбца в единую строку с разделителями, что является удобным
способом денормализации вашего результирующего набора для генерации
документов XML или JSON. Вот пример того, как эта функция может быть
использована для генерации списка актеров ( разделенных запятыми ) для
каждого фильма:
mysql> SELECT f.title,
-> group concat ( a . last name order by a . last name
separator ', 1 ) actors
->
-> FROM actor a
-> INNER JOIN film actor fa
ON a.actor id = fa.actor id
->
-> INNER JOIN film f
ON fa.film id = f.film id
->
-> GROUP BY f.title
-> HAVING count(*) = 3;

_

_

_

_

_

_

_

_

+

+

+

I title
+

actors
+

+

ANNIE IDENTITY
ANYTHING SAVANNAH
ARK RIDGEMONT
ARSENIC INDEPENDENCE

GRANT, KEITEL, MCQUEEN
MONROE, SWANK, WEST
BAILEY, DEGENERES, GOLDBERG
ALLEN, KILMER, REYNOLDS

WHISPERER GIANT
WIND PHANTOM

BAILEY, PECK, WALKEN
BALL, DENCH, GUINESS
DEGENERES, MONROE, TANDY

ZORRO ARK
+
+
119 rows in set (0.04 sec)

+

Этот запрос группирует строки по названию фильма и включает только те
фильмы, в которых указаны ровно три актера. Функция group_ concat действует как особый тип агрегирующей функции, которая собирает фамилии
всех актеров, появляющихся в фильме, в одну строку. Если вы используете
SQL Server, то можете использовать для этого функцию string agg, а пользователи Oracle могут использовать функцию listagg.

_

Проверьте свои знания
Предлагаемые здесь упражнения призваны закрепить понимание вами
аналитических функций. Ответы к упражнениям представлены в приложении Б.
Для всех упражнений этого раздела используйте набор данных из таблицы
Sales Fact:
Глава 16 . Аналитические функции

329

Sales Fact
+
+
+
+
year _ no | month _ no
tot sales
+
+
+
+

1

2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2020
2020
2020

2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5

2020
2020
2020
2020
2020

6
7
8
9

2020
2020
2020
2020

10
11
12

19228
18554
17325
13221
9964
12658
14233
17342
16853
17121
19095
21436
20347
17434
16225
13853
14589
13248
8728
9378
11467
13842
15742
18636

+
+
+
24 rows in set ( 0.00 sec )

+

УПРАЖНЕНИЕ 16.1
Напишите запрос, который извлекает каждую строку из Sales Fact и добавляет столбец для генерации рейтинга на основе значений столбца tot _
sales. Самое высокое значение должно получить рейтинг 1, а самое низ кое рейтинг 24.

_



УПРАЖНЕНИЕ 16.2
Измените запрос из предыдущего упражнения так, чтобы генерирова лись два набора рейтингов от 1 до 12: один
для 2019 года и один
для
2020 года.





УПРАЖНЕНИЕ 16.3
Напишите запрос, который извлекает все данные за 2020 год, и включите
столбец, который будет содержать значение tot sales для предыдущего
месяца.

_

330

.

Глава 16 Аналитические функции

ГЛАВА 17

Работа с большими базами данных

Во времена, когда реляционные базы данных только появились на свет , ем кость жесткого диска измеряли в мегабайтах и базами данных в целом было
легко управлять просто потому, что они не могли быть очень большими. Сегодня емкость отдельного жесткого диска выросла до 15 Тбайт , современный
дисковый массив может хранить более 4 Пбайт данных, а хранение в облаке,
по сути, не имеет границ. Хотя реляционные базы постоянно сталкиваются
с новыми проблемами, связанными с ростом данных, имеются такие стра тегии, как секционирование, кластеризация и шардинг ( разделение данных
между разными серверами), которые позволяют продолжать компаниям использовать реляционные базы данных, разделяя данные по различным хра нилищам и серверам. Чтобы обрабатывать огромные объемы данных, другие
компании решили перейти на платформы больших данных, как, например,
Hadoop. В этой главе рассматриваются некоторые из упомянутых стратегий
с акцентом на методиках масштабирования реляционных баз данных.

Секционирование
Когда именно таблица базы данных становится “ слишком большой ” ? Если
вы зададите этот вопрос десяти различным архитекторам / администраторам /
разработчикам баз данных, то вы, вероятно, получите десять разных ответов.
Однако большинство согласится, что перечисленные ниже задачи становятся
все более трудными и / или затратными в смысле машинного времени по достижении таблицей размера в несколько миллионов строк.






Выполнение запросов, требующих полного сканирования таблицы
Создание/ восстановление индексов

Архивирование/ удаление данных
Создание статистик таблиц/ индексов




Перенос таблицы ( например, в другое табличное пространство )
Резервное копирование базы данных

Эти задачи могут начинаться в качестве рутинных, когда база данных
невелика, но становятся трудоемкими по мере накопления большего ко личества данных, а затем превращаются в проблематичные или попросту
невозможные из - за ограниченности административных временных окон.
Лучший способ предотвратить возникновение административных проблем
в будущем заключается в том, чтобы секционировать, или разделять, большие таблицы на части, или разделы, при первоначальном создании таблиц
(хотя таблицы могут быть разделены и позже, легче сделать это изначально) .
Административные задачи могут выполняться для отдельных разделов, часто
параллельно, а некоторые задачи могут полностью пропустить один или несколько разделов.

Концепции секционирования
Секционирование таблиц было введено в конце 1990-х годов в Oracle, но
с тех пор каждый из основных серверов баз данных получил возможность
секционирования таблиц и индексов. Когда таблица секционируется, создаются две или более таблиц - разделов, каждая из которых имеет такое же
определение, но с неперекрывающимися подмножествами данных. Напри мер, таблица, содержащая данные продаж, может быть разделена по месяцам,
используя столбец, содержащий дату продажи, или разделена географически
с использованием кода страны/ области.
После секционирования сама таблица становится виртуальной концеп цией. Данные хранятся в разных разделах, и любые индексы построены для
данных в разделах таблицы. Однако пользователи базы данных все еще могут
взаимодействовать с таблицей, не зная, что на самом деле она была секционирована. Это похоже на концепцию представлений в том, что пользователи
взаимодействуют с объектами схемы, которые являются интерфейсами, а не
фактическими таблицами. Хотя каждый раздел должен иметь одинаковое
определение схемы ( столбцы, типы столбцов и т .д.), имеется несколько адми нистративных функциональных возможностей, которые могут различаться
для каждого раздела.



Разделы могут храниться в разных табличных пространствах, которые
могут находиться в различных физических хранилищах.



Разделы могут быть сжаты с использованием различных схем сжатия.

332

Глава 17. Работа с большими базами данных



Локальные индексы ( о них чуть позже) для некоторых разделов могут

отсутствовать.



Статистика таблицы для некоторых разделов может быть заморожена,
при этом периодически обновляясь для других.



Отдельные разделы могут быть закреплены в памяти или храниться во
флеш -хранилище.

Таким образом , секционирование таблицы обеспечивает высокую гибкость хранения и администрирования данных, при этом предоставляя сооб ществу пользователей простоту работы с одной таблицей.

Секционирование таблицы
Схема секционирования, доступная в большинстве реляционных баз дан ных, представляет собой горизонтальное секционирование, которое назначает
каждую отдельную строку ровно одному разделу. Таблицы также могут быть
секционированы вертикально, когда выполняется присвоение наборов столбцов разным разделам, но это необходимо делать вручную. При секционировании таблицы горизонтально необходимо выбрать ключ секционирования,
который является столбцом, значения которого используются для назначения
строки определенному разделу. В большинстве случаев ключ секционирования таблицы состоит из одного столбца, а функция секционирования применяется к этому столбцу, чтобы определить, в какой раздел следует помещать
ту или иную строку.

Секционирование индекса
Если ваша секционированная таблица имеет индексы, вы можете выбрать,
должен ли конкретный индекс оставаться неразделенным, известным как глобальный индекс, или быть разбитым на части, так что каждый раздел будет
иметь собственный индекс, который называется локальным индексом. Глобальные индексы объединяют все разделы таблицы и полезны для запросов,
которые не указывают значение ключа секционирования . Пусть, например,
ваша таблица секционирована по столбцу sale date и пользователь выполняет следующий запрос:

_

_

_

SELECT sum(amount) FROM sales WHERE geo region cd =

’ US'

Поскольку этот запрос не включает в себя условие фильтра для столбца
sale date , сервер должен будет выполнять поиск в каждом разделе, чтобы

_

Глава 17. Работа с большими базами данных

333

найти общие продажи в США. Если же на столбце geo region cd построен
глобальный индекс, то сервер сможет использовать его, чтобы быстро найти
все строки, содержащие искомые продажи.

Методы секционирования
Хотя каждый сервер базы данных имеет свои уникальные возможности
секционирования, в следующих трех разделах описаны распространенные
методы секционирования, доступные у большинства серверов.

Секционирование по диапазону

Секционирование по диапазону стало первым методом секционирования, который был реализован, и он по- прежнему является одним из наиболее популярных. Хотя секционирование по диапазону можно использовать
для нескольких различных типов столбцов, наиболее распространенным его
использованием является секционирование таблиц по диапазонам даты. На пример, таблица sales может быть секционирована с использованием столбца salejdate так, что данные для каждой недели будут храниться в отдельном разделе:
mysql> CREATE TABLE sales
-> (sale id INT NOT NULL,
-> cust id INT NOT NULL,
> store id INT NOT NULL,
-> sale date DATE NOT NULL,
-> amount DECIMAL(9,2)
-> )
> PARTITION BY RANGE (yearweek(sale date))
> (PARTITION si VALUES LESS THAN (202002),
>
PARTITION s2 VALUES LESS THAN (202003),
-> PARTITION s3 VALUES LESS THAN (202004),
-> PARTITION s4 VALUES LESS THAN (202005),
-> PARTITION s5 VALUES LESS THAN (202006),
-> PARTITION s999 VALUES LESS THAN (MAXVALUE)

_
_
_
_

-

_

-

->

);

Query OK, 0 rows affected (1.78 sec)

Эта инструкция создает шесть разных разделов: по одному для каждой из
первых пяти недель 2020 года и шестой раздел с именем S999, чтобы хранить
все строки за пределами указанных недель. Для этой таблицы выражение
yearweek ( sale date ) используется в качестве функции секционирования,
а столбец sale date служит ключом секционирования. Чтобы увидеть ме таданные, связанные с секционированными таблицами, можно использовать
таблицу partitions в базе данных information schema:

_
_

_

334

Глава 17. Работа с большими базами данных

mysql>
->
->
->

_

_

SELECT partition name, partition_method, partition expression
FROM information schema . partitions
WHERE table name = 'sales i
ORDER BY partition_ordinal_position;

_

_

+
+
PARTITION METHOD | PARTITION EXPRESSION
+
+
+
+
yearweek('sale_date',0)
si
RANGE
s2
yearweek('sale date',0)
RANGE
s3
yearweek('sale date',0)
RANGE
yearweek('sale date',0)
RANGE
s4
yearweek( sale date',0)
s5
RANGE
yearweek('sale date',0)
s999
RANGE
+
+
+
+
6 rows in set (0.00 sec)

+

+

PARTITION NAME



4

_
_
_
_
_

Одной из административных задач , которые необходимо будет выполнить для таблицы sales , является генерация новых разделов для хранения
будущих данных ( чтобы хранить данные, добавленные в раздел s 999 ). Разные базы данных обрабатывают это задание по- разному, но в MySQL, чтобы разделить раздел S 999 на три части, можно использовать предложение
reorganize partition в команде alter table:
ALTER TABLE
(PARTITION
PARTITION
PARTITION

sales REORGANIZE PARTITION s999 INTO
s6 VALUES LESS THAN (202007),
s7 VALUES LESS THAN (202008),
s999 VALUES LESS THAN (MAXVALUE)

Если снова выполнить предыдущий запрос метаданных, то теперь можно
увидеть восемь разделов:

_

_

_

SELECT partition name, partition method, partition expression
FROM information_schema.partitions
WHERE table_name = 'sales'
ORDER BY partition ordinal_position;
+
+
+
+
| PARTITION NAME | PARTITION METHOD | PARTITION EXPRESSION
+
+
+
+
si
yearweek( sale_date ,0)
RANGE
s2
yearweek('sale date',0)
RANGE
s3
yearweek('sale_date ,0)
RANGE
yearweek('sale date',0)
s4
RANGE
yearweek('sale date',0)
s5
RANGE
mysql>
>
->
>

-

_

4

4

_
_
_

l s6
I s7
+

_
_

yearweek ( ' sale date ' , 0 )
yearweek ( ' sale date ' , 0 )

RANGE
RANGE

s999

yearweek( sale_date',0)
4

RANGE
+

4

+

+

8 rows in set (0.00 sec)
Глава 17 . Работа с большими базами данных

335

Теперь добавим в таблицу несколько строк:
mysql> INSERT INTO sales
-> VALUES
-> (1, 1, 1, 2020 01 18', 2765.15),
-> (2, 3, 4, 2020-02-07', 5322.08);
Query OK, 2 rows affected (0.18 sec)
Records: 2 Duplicates: 0 Warnings: 0

- -

В таблице теперь есть две строки, но в какие разделы они вставлены ? Чтобы это выяснить, давайте используем подпредложение partition предложения from, чтобы подсчитать количество строк в каждом разделе:
mysql> SELECT concat('# of rows in SI = ', count(*))
partition_rowcount
>
-> FROM sales PARTITION (si) UNION ALL
> SELECT concat('# of rows in S2 = ', count(*))
partition_rowcount
>
> FROM sales PARTITION (s2) UNION ALL
-> SELECT concat('# of rows in S3 = ', count(*))
partition_rowcount
>
-> FROM sales PARTITION (s3) UNION ALL
> SELECT concat('# of rows in S4 = ', count(*))
partition_rowcount
->
-> FROM sales PARTITION (s4) UNION ALL
-> SELECT concat('# of rows in S5 = ', count(*))
partition rowcount
>
> FROM sales PARTITION (s5) UNION ALL
-> SELECT concat('# of rows in S6 = ', count(*))
partition_rowcount
->
> FROM sales PARTITION (s6) UNION ALL
-> SELECT concat('# of rows in S7 = ', count(*))
partition rowcount
->
> FROM sales PARTITION (s7) UNION ALL
> SELECT concat('# of rows in S999 = ', count(*))
partition rowcount
->
-> FROM sales PARTITION (s999);

-

_

-

-

_

-

+

_

+

_

partition rowcount
+

+

#
#
#
#
#
#
#
#

of
of
of
of
of
of
of
of

+

rows in
rows in
rows in
rows in
rows in
rows in
rows in
rows in

SI = 0
S2 = 1
S3 =
S4 =
S5 =
S6 =
S7 =
S999

0
0
1
0
0

=0
+

8 rows in set (0.00 sec)

336 |

Глава 17. Работа с большими базами данных

Результаты показывают , что одна строка была вставлена в раздел S2, а другая
в раздел S 5. Возможность запрашивать определенный раздел требует
знания схемы секционирования, поэтому маловероятно, что пользователи
будут выполнять запросы такого вида; они обычно используются для административных видов деятельности.



Секционирование по списку
Если столбец, выбранный в качестве ключа секционирования, содержит ,
например, коды штатов ( СА, ТХ, VA и т .д.), валюты ( USD, EUR, JPY и т .д.)
или иной набор перечислимых значений, можно использовать секционирование по списку, которое позволяет указать, какие значения будут назначены каждому разделу. Например, пусть таблица sales включает столбец део_
region cd, который содержит следующие значения:

_

+
+

+

+

geo_region_cd I description
+

_
_
_
_

US NE
US SE
US MW
US NW
US SW
CAN
MEX
EUR E
EUR W
CHN

+

United States North East
United States South East
United States Mid West
United States North West
United States South West
Canada
Mexico
Eastern Europe
Western Europe
China
Japan
India
Korea

_

JPN
IND
KOR
+

+

+

13 rows in set (0.00 sec)

Вы можете сгруппировать эти значения в географические регионы и со здать раздел для каждого из них:
mysql>
->
->
->
->
->
->
->
->
>
->

-

CREATE TABLE sales
(sale_id INT NOT NULL,
cust id INT NOT NULL,
store_id INT NOT NULL,
sale date DATE NOT NULL,
geo_region_cd VARCHAR(6) NOT NULL,
amount DECIMAL(9,2)
)
PARTITION BY LIST COLUMNS (geo region cd)
(PARTITION NORTHAMERICA VALUES IN ('US NE','US SE','US MW',
'US NW','US SW','CAN','MEX'),

_
_

_

_

_

_

_

Глава 17. Работа с большими базами данных

337

_

PARTITION EUROPE VALUES IN ( 1 EUR E 1 ,'EUR_W 1 ),
- PARTITION ASIA VALUES IN ('CHN','JPN','IND 1 )
-> );
Query OK, 0 rows affected (1.13 sec)

->>

В таблице есть три раздела и каждый из них включает в себя набор из двух
или более значений geo region cd. Теперь давайте добавим в таблицу несколько строк:

_

_

mysql> INSERT INTO sales
-> VALUES
> (1, 1, 1, '2020-01-18', US_NE', 2765.15),
-> (2, 3, 4, '2020-02-07', CAN', 5322.08),
> (3, 6, 27, '2020-03-11', 'KOR', 4267.12);

ERROR 1526

( HY000 ) : Table has no partition for value from column list 1
^



Похоже, возникла проблема в сообщении об ошибке указано, что один
из географических кодов региона не был назначен ни одному разделу. Глядя
на инструкцию create table, я вижу, что забыл добавить Корею к разделу
asia. Это можно исправить с помощью инструкции alter table:
mysql> ALTER TABLE sales REORGANIZE PARTITION ASIA INTO
-> (PARTITION ASIA VALUES IN ('CHN','JPN','IND' , ' K O R ' )
Query OK, 0 rows affected (1.28 sec)
Records: 0 Duplicates: 0 Warnings: 0

);

Давайте проверим метаданные, чтобы быть уверенными, что все получи лось правильно:

_
_

_

mysql> SELECT partition name, partition expression,
-> partition_description
-> FROM information schema.partitions
-> WHERE table name = 'sales'
> ORDER BY partition ordinal position;
+
+
+
|PARTITION NAME|PARTITION EXPRESSION|PARTITION DESCRIPTION

_

-

+

+

_

_

_
geo_region_cd
geo_region_cd

|NORTHAMERICA | geo_region cd'
4

|EUROPE
|ASIA
+

'

'

'

'

+

+

_

_

_

_

_

+
+

|'US NE','US SE','US MW',
|'US_NW','US_SW','CAN','MEX'
|'EUR E','EUR W'
|'CHN','JPN','IND', ' KOR '
+

+

3 rows in set (0.00 sec)

Корея действительно была добавлена в раздел asia, и теперь вставка данных не вызывает никаких проблем:

1

_

В таблице нет раздела для значения из column list .

338

Глава 17 . Работа с большими базами данных

mysql> INSERT INTO sales
-> VALUES
-> ( 1 , 1 , 1 , 2020-01-18', 'US NE', 2765.15),
-> (2, 3, 4, 2020-02-07', 'CAN', 5322.08),
-> (3, 6, 27, '2020-03-11', ' KOR ' , 4267.12);
Query OK, 3 rows affected (0.26 sec)
Records: 3 Duplicates: 0 Warnings: 0

_

В то время как секционирование по диапазону позволяет разделу s 999 перехватывать и хранить любые строки, которые не подходят для других разделов, секционирование по списку такой возможности не обеспечивает . Таким
образом, если в некоторый момент вам может потребоваться добавить еще
одно значение столбца ( например, компания начнет продажи в Австралии),
вам нужно будет изменить определение секционирования перед добавлением
в таблицу строк с новым значением.
Секционирование по хешу
Если столбец ключа секционирования не обеспечивает секционирование
по диапазону или по списку, есть третий вариант , который увеличивает равномерность распределения строк по множеству разделов. Сервер делает это,
применяя функцию хеширования к значению столбца, и этот тип секционирования называется ( что не удивительно ) секционированием по хешу. В отличие
от секционирования по списку, при котором столбец, выбранный в качестве
ключа секционирования, должен содержать только небольшое количество
значений, секционирование по хешу лучше всего работает тогда, когда стол бец ключа секционирования содержит большое количество различных значений. Вот еще одна версия таблицы sales, но с четырьмя хеш - разделами,
генерируемыми путем хеширования значений в столбце cust id:

_

mysql> CREATE TABLE sales
-> (sale_id INT NOT NULL,
->
cust id INT NOT NULL,
-> store_id INT NOT NULL,
-> sale date DATE NOT NULL,
-> amount DECIMAL(9,2)
-> )
-> PARTITION BY HASH (cust id)
-> PARTITIONS 4
-> (PARTITION HI,
PARTITION H2,
->
PARTITION H3,
->
PARTITION H4
->
>
Query OK, 0 rows affected (1.50 sec)

_
_

_

-

.

Глава 17 Работа с большими базами данных

339

Строки при добавлении в таблицу sales будут равномерно распределены
по всем четырем разделам, которые я назвал Н1, Н 2 , Н З и Н 4. Для того чтобы
увидеть, насколько хорошо это работает , давайте добавим 16 строк, каждая
с другим значением столбца cust id:

_

mysql> INSERT INTO sales
-> VALUES
> (1,1,1,'2020 01 18',1.1), (2,3,4,'2020-02-07',1.2),
-> (3,17,5,'2020-01-19',1.3), (4,23,2,'2020 02-08',1.4),
-> (5,56,1, '2020-01-20',1.6), (6,77,5,'2020 02 09',1.7),
-> (7,122,4,'2020 01 21',1.8), (8,153,1,'2020-02-10',1.9),
-> (9,179,5,'2020-01-22',2.0), (10,244,2,'2020-02-11',2.1),
> (11,263,1,'2020 01 23',2.2),(12,312,4,'2020-02-12',2.3),
-> (13,346,2,'2020-01-24',2.4),(14,389,3,'2020-02-13',2.5),
-> (15,472,1, '2020-01 25',2.6),(16,502,1,'2020 02-14',2.7);
Query OK, 16 rows affected (0.19 sec)
Records: 16 Duplicates: 0 Warnings: 0

-

- -

- -

- - -

-

-

Если функция хеширования равномерно распределяет строки, в идеале мы
должны увидеть в каждом разделе по четыре строки:
mysql> SELECT concat('# of rows in HI = ', count(*))
partition_rowcount
->> FROM sales PARTITION
(hi) UNION ALL
('# of rows in H2 = ', count(*))
->> SELECT concatpartition
-> FROM sales PARTITION (_hrowcount
2) UNION ALL
('# of rows in H3 = ', count(*))
->> SELECT concatpartition
_rowcount
>
(
FROM
PARTITION
sales
h3) UNION ALL
-> SELECT concat( # of rows
, count(*))
in H4

->

'

partition_rowcount
> FROM sales PARTITION (h4);

= '

-

+

I

+

_

partition rowcount |
+

+

#
#
#
#
+

of
of
of
of

rows
rows
rows
rows

in
in
in
in

HI =
H2 =
H3 =
H4 =

4|
5|
3|
4|
+

4 rows in set (0.00 sec)

Учитывая, что вставлено только 16 строк, это весьма хорошее распределение; по мере увеличения количества строк каждый раздел должен содержать
количество строк, близкое к 25% от общего количества
при наличии достаточно большого количества различных значений в столбце cust id.



340

Глава 17. Работа с большими базами данных

_

Композитное секционирование

Если вам нужен тонкий контроль над тем, как данные распределяются по
разделам таблицы, можете использовать композитное секционирование, позволяющее использовать два разных типа секционирования для одной и той
же таблицы. При композитном секционировании первый метод определяет
подразделы. Вот пример, который вновь использует таразделы, а второй
блицу sales, теперь с применением как секционирования по диапазону, так
и хеша:



mysql>
->
->
->
->
->
->
->
->
->
->

->>
->

->>
->
->
->
->
->
->

->>
->>

->
->
->
->
->
->
->
->
->
->
->
->
->

CREATE TABLE sales
(sale_id INT NOT NULL,
cust id INT NOT NULL,
store_id INT NOT NULL,
sale date DATE NOT NULL,
amount DECIMAL(9,2)
)
PARTITION BY RANGE (yearweek(sale date))
SUBPARTITION BY HASH (cust id)
(PARTITION si VALUES LESS THAN (202002)
(SUBPARTITION sl hl,
SUBPARTITION sl_h2,
SUBPARTITION sl h3,
SUBPARTITION sl h4),
PARTITION s2 VALUES LESS THAN (202003)
(SUBPARTITION s2_hl,
SUBPARTITION s2 h2,
SUBPARTITION s2 h3,
SUBPARTITION s2 h4),
PARTITION s3 VALUES LESS THAN (202004)
(SUBPARTITION s3 hl,
SUBPARTITION s3 h2,
SUBPARTITION s3_h3,
SUBPARTITION s3_h4),
PARTITION s4 VALUES LESS THAN (202005)
(SUBPARTITION s4 hl,
SUBPARTITION s4 h2,
SUBPARTITION s4 h3,
SUBPARTITION s4_h4),
PARTITION s5 VALUES LESS THAN (202006)
(SUBPARTITION s5 hl,
SUBPARTITION s5 h2,
SUBPARTITION s5 h3,
SUBPARTITION s5_h4),
PARTITION s999 VALUES LESS THAN (MAXVALUE)
(SUBPARTITION s999_hl,
SUBPARTITION s999_h2,
SUBPARTITION s999_h3,
SUBPARTITION s999 h4)

_
_

_

_

_
_
_

_
_
_
_
_

_
_
_

__
_

Глава 17. Работа с большими базами данных

341

-> );
Query OK, 0 rows affected (9.72 sec)
Имеется 6 разделов, каждый из которых имеет по 4 подраздела, в общей
24 подраздела. Теперь давайте заново вставим 16 строк рядов
сложности
из более раннего примера для секционирования по хешу:



mysql> INSERT INTO sales
-> VALUES
-> (1,1,1,'2020-01-18',1.1), (2,3,4,'2020 02 07',1.2),
-> (3,17,5,'2020-01-19',1.3), (4,23,2,'2020-02-08',1.4),
-> (5,56,1,'2020-01 20',1.6), (6,77,5,'2020-02-09',1.7),
-> (7,122,4,'2020 01-21',1.8), (8,153,1,'2020-02-10',1.9),
-> (9,179,5,'2020-01-22',2.0), (10,244,2,'2020 02-11',2.1),
> (11,263, 1,'2020-01-23',2.2),(12,312,4,'2020-02-12',2.3),
-> (13,346,2,'2020 01 24',2.4),(14,389,3,'2020-02-13',2.5),
-> (15,472,1,'2020-01-25',2.6),(16,502,1,'2020-02 14',2.7);
Query OK, 16 rows affected (0.22 sec)
Records: 16 Duplicates: 0 Warnings: 0

- -

-

-

-

-

- -

-

Запрашивая таблицу sales, вы можете извлечь данные из одного из разделов; в таком случае вы извлекаете данные из четырех подразделов, связанных с разделом:
mysql> SELECT *
> FROM sales

-

+

PARTITION ( s 3 ) ;

+
+
+
+
amount |
sale date
cust id | store id
+
+
+
+
+
1
1.60
56
2020-01-20
5
2.60
2020-01-25
1
15
472
1.30
5 2020-01-19
3
17
1.80
4
2020-01-21
7
122
2.40
2 2020-01 24
13
346
5 2020-01-22
2.00
179
9
2.20
1
2020-01 23
263
11

+

sale id
+

-

+

+

+

+

+

+

7 rows in set (0.00 sec)

Поскольку таблица разделена на подразделы, можно также получить данные из одного отдельного подраздела:
mysql> SELECT *
-> FROM sales PARTITION (s3 h3);
+

+

+

I sale id | cust id
+

+

+

7 I
13 |

+

-

+

+

+

I amount |
+

4 | 2020-01 21 |
2 | 2020-01-24 |

122
346

+

+

+

+

store id | sale date

+

+

1.80 |
2.40 |
+

2 rows in set (0.00 sec)

_

Этот запрос извлекает данные только из подраздела s 3 h 3 раздела s 3.

342

.

Глава 17 Работа с большими базами данных

Преимущества секционирования
Одним из основных преимуществ секционирования является то, что вам,
возможно, придется взаимодействовать только с несколькими разделами, а не
со всей таблицей в целом. Например, если ваша таблица секционирована по
диапазону с использованием столбца sales date и вы выполняете запрос,
который включает в себя условие фильтра, такое как

_

- -

WHERE sales_date BETWEEN '2019 12 01' AND

'2020-01-15'

то сервер проверит метаданные таблицы, чтобы определить, какие разделы
на самом деле следует включить. Эта концепция называется отсечением раз делов, и это одно из наибольших преимуществ секционирования таблиц.
Точно так же, если вы выполняете запрос, который включает в себя соеди нение с секционированной таблицей, и запрос включает условие для столб ца секционирования, то сервер может исключить любой раздел, который
не содержат данных, относящихся к запросу ( что известно как поразделъное
соединение ). Этот подход подобен отсечению разделов в том, что в обработ ку включаются только те разделы, которые содержат необходимые запросу

данные.
С административной точки зрения одним из основных преимуществ секционирования является возможность быстрого удаления данных, которые
больше не нужны. Например, пусть финансовые данные нужно хранить в
течение семи лет . Если таблица была секционирована на основании дат тран закций, то любые разделы, содержащие данные старше семи лет , могут быть
удалены. Еще одно административное преимущество секционированных та блиц способность выполнять обновления в нескольких разделах одновременно, что может значительно сократить время, необходимое для работы со
всеми строками таблицы.



Кластеризация
При наличии хранилища достаточного объема в сочетании с разумной
стратегией секционирования в одной реляционной базе данных можно хранить очень много информации. Но что если вам нужно обеспечить одновременную работу тысяч пользователей или сгенерировать десятки тысяч
отчетов во время ночного цикла ? Даже если у вас есть хранилище данных
достаточного размера, вам может не хватать вычислительной мощности
процессоров, памяти или пропускной способности сети при использовании
Глава 17. Работа с большими базами данных

343



одного сервера. Один из потенциальных ответов на эту проблему кластеризация, которая позволяет нескольким серверам действовать как единая
база данных.
Хотя имеется несколько различных архитектур кластеризации, в этом об суждении для упрощения я имею в виду конфигурацию с общим диском /
общим кешем, где каждый сервер кластера имеет доступ ко всем дискам, а
кешированные на одном сервере данные могут быть доступны любым другим
серверам в кластере. При наличии такого типа архитектуры сервер приложений может соединяться с любым из серверов баз данных в кластере с автоматическим переподключением к другому серверу кластера в случае сбоя.
С восьмисерверным кластером вы должны иметь возможность одновременно
работать с очень большим количеством пользователей и связанных с ними
запросами/ отчетами/ рабочими местами.
Из коммерческих поставщиков баз данных лидером в этом сегменте яв ляется Oracle. Многие крупнейшие компании мира используют платформу
Oracle Exadata для размещения очень больших баз данных с одновременным
доступом тысяч пользователей. Тем не менее даже эта платформа не соот ветствует потребностям таких крупнейших компаний, как Google, Facebook,
Amazon и др.

Шардинг
Допустим, вас наняли в качестве архитектора данных в новую компанию
социальной сети. Вам говорят , что ожидается примерно один миллиард
пользователей, каждый из которых будет генерировать в день в среднем
3,7 сообщения, и эти данные должны быть доступны в течение неопределен ного срока. После небольших прикидок вы понимаете, что исчерпаете самую
большую доступную платформу реляционной базы данных менее чем за год.
Чтобы обеспечить работоспособность, имеется возможность разделять не
только отдельные таблицы, но и всю базу данных. Этот подход, известный
как шардинг (sharding), разделяет данные по нескольким базам данных ( именуемым осколками (shard)), так что он похож на секционирование таблиц, но
в большем масштабе и с гораздо большей сложностью. Применяя эту стратегию для социальной сети, вы могли бы решить реализовать 100 отдельных
баз данных, каждая из которых принимает данные примерно для 10 миллионов пользователей.

344

|

Глава 17. Работа с большими базами данных



Шардинг сложная тема, а так как эта книга лишь вводная, я воздержусь
от описания этого подхода в деталях. Упомяну лишь несколько вопросов, которые должны быть решены.



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



В то время как большие таблицы будут разделены на части, с отдельны ми строками, назначенными тому или иному осколку, меньшие табли цы могут реплицироваться во все осколки. Также должна быть определена стратегия того,как могут быть изменены ссылочные данные, что бы изменения распространялись на все осколки.



Если отдельные осколки становятся слишком большими (например, социальная сеть вырастает до двух миллиардов пользователей ), вам понадобится план добавления дополнительных осколков и перераспределения данных между ними.



При необходимости внесения изменений в схему требуется стратегия
для развертывания изменений по всем осколкам, чтобы все схемы оста вались синхронизированными.



Если логика приложений должна получать доступ к данным, храня щимся в двух или более осколках, нужна стратегия того, как запросить
несколько баз данных, а также как реализовать транзакции, распределенные по нескольким базам данных.

Если это кажется слишком сложным, то это потому, что так и есть . ..
И к концу 2000-х годов многие компании начали искать новые подходы к
этой проблеме. В следующем разделе рассмотрены другие стратегии обработ ки очень больших массивов данных, лежащие полностью за пределами царства реляционных баз данных.

Большие данные
Взвесив все плюсы и минусы шардинга, вы ( архитектор данных большой
социальной сети) решаете рассмотреть другие подходы к проблеме. Вместо
того чтобы придумывать свой путь, вы пытаетесь извлечь выгоду из обзора
работы, проделанной другими компаниями, занимающимися большими объемами данных, такими как Amazon , Google, Facebook и Twitter. Набор технологий, развитых этими ( и другими ) компаниями, стал известен как большие
Глава 17. Работа с большими базами данных

345



данные
модный термин, который имеет несколько возможных определений. Один из способов определения границ больших данных называют “3V ”.
Объем (Volume )
В этом контексте объем обычно означает миллиарды или триллионы точек данных.

Скорость (Velocity )
Это мера того, насколько быстро поступают данные.

Разнообразие ( Variety )
Это означает , что данные не всегда структурированы ( как в строках и
столбцах реляционных баз данных) и могут быть не структурированы,
например электронные письма, видео, фотографии, аудиофайлы и т .д.



Таким образом, один из способов охарактеризовать большие данные
считать таковыми любую систему, предназначенную для обработки огромного количества данных различных форматов, поступающих в быстром темпе.
В следующих разделах кратко описаны некоторые технологи больших данных, которые развивались в течение последних 15 лет или около того.

Hadoop
Hadoop лучше всего описать как экосистему или набор технологий и инструментов, работающих совместно. Некоторые из основных компонентов
Hadoop включают следующее.

Распределенная файловая система Hadoop ( HDFS )
Как подразумевает название, HDFS позволяет управлять файлами на
большом количестве серверов.

MapReduce
Эта технология обрабатывает большое количество структурированных
и неструктурированных данных, разбивая задачи на многие маленькие
части, которые можно выполнять параллельно на многих серверах.

YARN
Это диспетчер ресурсов и планировщик заданий для HDFS.
Вместе эти технологии позволяют хранить и обрабатывать файлы на сот нях или даже тысячах серверов, действующих как единая логическая си стема. Запросы данных с использованием MapReduce, в отличие от широко
346

|

Глава 17. Работа с большими базами данных

применяемого Hadoop, обычно требуют участия программиста, что привело
к разработке нескольких интерфейсов SQL, включая Hive, Impala и Drill.

NoSQL и базы данных документов
В реляционной базе данных данные обычно должны соответствовать зара нее определенной схеме, состоящей из таблиц, которые состоят из столбцов,
содержащих числа, строки, даты и т .д. Но что делать, если структура данных
неизвестна заранее или если она известна, но часто меняется ? Для многих
компаний ответ заключается в том, чтобы объединить как определение дан ных, так и схему в документы с использованием формата наподобие XML или
JSON, а затем хранить эти документы в базе данных. При этом различные
типы данных могут храниться в одной и той же базе данных без необходимости модификаций схемы. Это облегчает хранение, но переносит нагрузку на
запросы и аналитические инструменты, чтобы данные, хранящиеся в документах, имели смысл.
Базы данных документов являются подмножеством того, что называется
базами данных NoSQL, которые обычно хранят данные с использованием
простого механизма “ ключ - значение”. Например, используя такую базу дан ных документов, как MongoDB, можно использовать идентификатор клиен та в качестве ключа для хранения документа JSON, содержащего все данные
клиента. Чтобы придать смысл хранимым данным, другие пользователи могут читать схему, сохраненную в документе.

Облачные вычисления
До появления больших данных большинство компаний должны были создать собственные центры обработки данных для размещения баз данных,
серверов веб и приложений, используемых предприятием. С появлением облачных вычислений вы, по сути, можете перенести ваш центр обработки дан ных на внешние платформы, такие как Amazon Web Services ( AWS), Microsoft
Azure или Google Cloud. Одним из самых больших преимуществ размещения
ваших служб в облаке является немедленная масштабируемость, которая позволяет увеличивать или уменьшать вычислительную мощность, необходи мую для работы ваших служб. Эти платформы особенно любят стартапы, потому что они могут начать писать код, не тратя предварительно деньги на за купку серверов, хранения, сетей или лицензий на программное обеспечение.
Поскольку мы рассматриваем базы данных, перечислим некоторые предложения баз данных и аналитики AWS.
Глава 17. Работа с большими базами данных

347



Реляционные базы данных ( MySQL, Aurora, PostgreSQL, MariaDB, Oracle
и SQL Server )




База данных в памяти ( ElastiCache)




База данных NoSQL ( DynamoDB)






База данных графов ( Neptune)

База данных для складирования данных ( Redshift )
База данных документов ( DocumentDB)
База данных временных рядов (TimeStream )

Hadoop ( EMR )
Озера данных ( Lake Formation )

Довольно легко увидеть, что в то время как в ландшафте баз данных до
средины 2000-х годов доминировали реляционные базы данных, в настоящее
время компании комбинируют в работе различные платформы, так что что
реляционные базы данных со временем могут стать менее популярными.

Заключение
Базы данных становятся все больше, но в то же время технологии хранения, кластеризации и секционирования становятся все более надежными.
Работа с огромным количеством данных может быть довольно сложной независимо от используемого технологического стека. Независимо от того, что
вы используете ( реляционные базы данных, большие платформы данных или
множество серверов баз данных), язык SQL развивается таким образом, чтобы облегчить поиск данных в базах данных, построенных на различных технологиях. Этот вопрос и будет темой последней главы этой книги, в которой
будет показано использование механизма SQL для запроса данных, храня щихся в нескольких форматах.

348

Глава 17. Работа с большими базами данных

ГЛАВА 18

SQL и большие данные

Хотя большая часть материала этой книги охватывает различные функциональные возможности языка SQL при использовании реляционной базы
данных, такой как MySQL, сам ландшафт данных за последнее десятилетие
существенно изменился, и SQL тоже вынужден меняться, чтобы удовлетворять потребности нынешних быстро развивающихся сред. Многие организации, которые всего несколько лет назад использовали исключительно реля ционные базы данных, теперь хранят данные в кластерах и нереляционных
базах данных. Идет постоянный поиск способов обработки и понимания постоянно растущих объемов данных, и тот факт , что эти данные теперь разбросаны среди нескольких хранилищ данных, возможно в облаке, делает этот
поиск непростой задачей.
SQL используется миллионами пользователей и интегрирован в тысячи
приложений, а потому имеет смысл найти пути использовать SQL для работы
с этими данными. За несколько последних лет появились новые инструмен ты, которые обеспечивают доступ SQL к структурированным, полуструктурированным и неструктурированным данным, такие как Presto, Apache Drill
и Toad Data Point. В этой главе рассматривается один из этих инструментов,
Apache Drill, и демонстрируется, как для отчетности и анализа могут быть
собраны вместе данные разных форматов, хранящиеся на разных серверах.

Введение в Apache Drill
Для SQL-доступа к данным, хранящимся в Hadoop, Spark и облачных распределенных файловых системах, были разработаны многочисленные ин струменты и интерфейсы. Примеры включают Hive, который был одной из
первых попыток позволить пользователям запрашивать данные, хранящиеся
в Hadoop, а также Spark SQL, который представляет собой библиотеку для
запроса данных, хранящихся в различных форматах в Spark. Относительным



новичком является Apache Drill
инструмент , появившийся в 2015 году
и обладающий следующими возможностями.



Облегчает запросы к данным в разных форматах, включая данные
с разделителями, JSON, Parquet и журнальные файлы.



Подключается к реляционным базам данных, Hadoop, HBase и Kafka,
а также работает со специализированными форматами данных, такими
как РСАР, BlockChain и др.



Позволяет создавать пользовательские плагины для подключения
к большинству любых других хранилищ данных.





Не требует знания определений схем заранее.

Поддерживает стандарт SQL:2003.

Работает с популярными инструментами бизнес- аналитики, такими как
Tableau и Apache Superset.

Используя Apache Drill, вы можете подключиться к любому количеству
источников данных и выполнять запросы без первоначальной настройки репозитория метаданных. Вопросы установки и настройки Apache Drill выходят за рамки данной книги, так что, если вы заинтересованы в этих темах, я
настоятельно рекомендую книгу Learning Apache Drill Чарльза Живра (Charles
Givre) и Пола Роджерса ( Paul Rogers).

Запрос файлов с помощью Apache Drill
Давайте начнем с использования Apache Drill для запроса данных, хра нящихся в файле. Apache Drill умеет читать несколько различных форматов
файлов, включая файлы захвата пакетов РСАР, которые используют бинарный формат и содержат информацию о пакетах, проходящих по сети. Все, что
нужно сделать для запроса файла РСАР, это настроить плагин распределенной файловой системы(dfs), указав путь к каталогу, содержащему файлы.
это узнать, какие столбцы доступны
Первое, что я хотел бы сделать,
в файле, который я буду запрашивать. Apache Drill включает в себя частич ную поддержку information_schema ( см. главу 15, “ Метаданные” ), так что
вы можете получить информацию высокого уровня о файлах данных в вашем





рабочем пространстве:

_

apache drill> SELECT file_name, is_directory, is file, permission
> FROM information schema . ' files '
> WHERE schema name = 'dfs.data 1 ;

_

350

Глава 18. SQL и большие данные

+

+

+

+

_

I

file name

+

is directory | is file
permission
+
+
+
+
+
| rwxrwx
attack- trace .pcap | false
I true
+
+
+
+
+
1 row selected (0.238 seconds)

Результаты показывают , что в моем рабочем пространстве данных есть
один файл с именем attack- trace . pcap ( что является полезной информа цией ) , но я не могу запросить information schema . columns, чтобы узнать,
какие столбцы имеются в файле. Однако выполнение запроса, который не
возвращает никаких строк из файла, показывает набор доступных столбцов1:
apache drill> SELECT * FROM dfs.data.'attack-trace.pcap'

_

=

> WHERE 1 2 ;
+
+
+
+
+
+
type | network I timestamp I timestamp_micro I src_ip I dst_ip I
+
+
+
+
+
+
+

+

+

+

+

+

+

+

src_port I dst_port | src_mac_address | dst_mac_address
+

+

+
+
+
tcp_session | tcp ack | tcp_flags I tcp_flags_ns |

+

_

+

+

+

_

tcp_flags cwr

+

_

+

_

+



_

tcp flags_ece congestion experienced

+

_

_

_

+

+

_

+

_

+

tcp flags rst | tcp_flags syn |

tcp_flags_ack | tcp flags psh
+
+

_

+

tcp_flags urg

+

+

+

_ _ _

tcp flags_ece | tcp_flags ece ecn capable

+

_

+

+

+

+

+

+
+
+
tcp_parsed_flags | packet length | is corrupt |
tcp_flags_fin
+
+
+
+

_

_

+

data
+

No rows selected (0.285 seconds)

Теперь, когда я знаю имена столбцов в файле РСАР, я готов писать запросы. Вот запрос, который считает количество пакетов, отправленных из каждого IP-адреса каждому целевому порту:
Эти результаты показывают столбцы в файле на основании понимания Apache Drill
структуры файлов РСАР. Если запросить файл, формат которого не известен Apache
Drill, результирующий набор будет содержать массив строк с единственным столб цом с именем columns.
1

.

Глава 18 SQL и большие данные

351

apache drill>
>
>
>
+

_ip

src

_

_
_

SELECT src ip, dst port,
count(*) AS packet count
FROM dfs.data.'attack-trace.pcap'
GROUP BY src_ip, dstjport;
+
+
+
dst port | packet count |

_

_

+

+

98.114.205.102
192.150.11.111
192.150.11.111
98.114.205.102
192.150.11.111
192.150.11.111
98.114.205.102
98.114.205.102
192.150.11.111
+

+

+

445
1821
1828
1957
1924
8884
36296
1080
2152

18
3
17
6
6
15
12
159
112
+

+

+

9 rows selected (0.254 seconds)

Вот еще один запрос, который посекундно агрегирует информацию о па кетах:
apache drill> SELECT trunc(extract(second from 'timestamp'))
as packet time,
. >
> count(*) AS num_packets,
> sum(packet length) AS tot volume
> FROM dfs.data.'attack trace.pcap'
> GROUP BY trunc(extract(second from 'timestamp'));
+
+
+
+
I packet time num packets | tot volume
+
+
+
+
15
28.0
1260
29.0
1809
12
30.0
4292
13
31.0
286
3
32.0
2
118
33.0
15
1054
34.0
35
14446
35.0
29
16926
36.0
25
16710
37.0
25
16710
38.0
26
17788
39.0
23
15578
40.0
25
16710
41.0
23
15578
42.0
30
20052
43.0
25
16710
44.0
22
7484

_

_

_

-

_

+

+

_

+

17 rows selected (0.422 seconds)

352

Глава 18. SQL и большие данные

+

В этом запросе необходимо заключить timestamp в обратные кавычки ( ' ),
потому что это зарезервированное слово.
Вы можете запросить файлы, хранящиеся локально, в вашей сети, в распределенной файловой системе или в облаке. Apache Drill имеет встроенную
поддержку многих типов файлов, но можно также создавать собственные
плагины , чтобы разрешить Apache Drill запрашивать другие типы файлов.
В следующих двух разделах будут рассматриваться запросы данных, храня щихся в базе данных.

Запрос MySQL с использованием Apache Drill
Apache Drill может подключаться к любой реляционной базе данных с испока пользованием драйвера JDBC, так что следующий логический шаг
зать, как Apache Drill может запросить пример базы данных Sakila, используемой в этой книге. Все, что вам нужно сделать, это загрузить драйвер JDBC
для MySQL и настроить Apache Drill для подключения к базе данных MySQL.



в



Вас может удивить, зачем использовать Apache Drill для запроса к
MySQL. Одна из причин состоит в том, что (как вы увидите в конце главы ) Apache Drill позволяет писать запросы, которые соеди няют данные из разных источников, так что вы сможете написать
запрос, который соединяет , например, данные из MySQL, Hadoop
и файлов с разделителями.

Первый шаг состоит в выборе базы данных:
apache drill (information_schema)> use mysql . sakila ;
+

+
+
| summary
+
+
true | Default schema changed to [mysql.sakila] I
+
+
row selected (0.062 seconds)

ok
+

|
+

1

После выбора базы данных можно выполнить команду show tables, чтобы увидеть все таблицы, доступные в выбранной схеме:
apache drill (mysql.sakila)> show tables ;
+
+
| TABLE SCHEMA | TABLE NAME

+

+

+

+

mysql.sakila | actor
mysql.sakila I address
mysql.sakila
category
Глава 18. SQL и большие данные

353

mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila
mysql.sakila

city
country
customer
film
film actor
film category
film text
inventory
language
payment
rental
sales
staff
store

_
_
_

_
_
_
_
_

actor info
customer list
film list
nicer but slower film list
sales by film category
sales by store
staff list

_
_
_
_ _
_

_

+

+

+

24 rows selected (0.147 seconds)

Я начну с выполнения нескольких запросов, продемонстрированных
в других главах. Вот простое соединение двух таблиц из главы 5, “Запросы
к нескольким таблицам”:
apache drill (mysql.sakila)>
)>
)>
)>
)>
+
+
address id I address
+

_

_

+

I

+

city
+

+

+

1121 Loja Avenue
770 Bydgoszcz Avenue
1135 Izumisano Parkway
793 Cam Ranh Avenue
533 al Ayn Boulevard
226 Brest Manor
920 Kumbakonam Loop
1866 al-Qatif Avenue
1895 Zhezqazghan Drive

6
18
55
116
186
218
274
425
599
+

_

SELECT a.address id, a.address, ct.city
FROM address a
INNER JOIN city ct
ON a.city id = ct.city id
WHERE a.district = 'California';

San Bernardino
Citrus Heights

Fontana
Lancaster
Compton
Sunnyvale
Salinas
El Monte
Garden Grove

-

+

+

+

9 rows selected (3.523 seconds)

Следующий запрос взят из главы 8, “Группировка и агрегация”, и включает
в себя как предложение group by, так и предложение having:
354

Глава 18. SQL и большие данные

SELECT fa.actor_id, f.rating,
count(*) num_films
FROM film_actor fa
INNER JOIN film f
ON fa.film id = f.film id
WHERE f.rating IN ( 1 G 1 , 1 PG')
) > GROUP BY fa . actor id , f . rating

apache drill (mysql.sakila)>
)>
)>
)>
)>
)>
)

+

+

_

_

_

_

> HAVING count ( * ) > 9 ;

+

+

actor id | rating | num films
+

+

+

137
37
180
7
83
129
111

44
26
92
17
158
147
14
102
133

+

PG
PG
PG
G
G
G
PG
PG
PG
PG
G
PG
PG
G
PG
PG

10
12

12
10
14
12
15
12
11
12
12

10
10
10
11

10

+
+
+
+
16 rows selected (0.277 seconds)

Наконец, вот запрос из главы 16, “Аналитические функции”, который
включает в себя три разных функции ранжировки:
apache drill (mysql.sakila)>
)>
)>
)>
)>
)>
)>
)>
)>
)>
)>
)>
+

+

+

148
526
144

_
_

_

rank ( )

over (order by count(*) desc) rank_rnk,

_

dense rank ( )

over (order by count(*) desc)
dense rank rnk
FROM rental
GROUP BY customer id
ORDER BY 2 desc;

_

_

_

+

+

I customer id Inum rentals
+

SELECT customer_id, count(*) num_rentals,
row number ( )
over (order by count(*) desc)
row number rnk,

+

+

row number rnk |rank rnk |dense rank rnk |
+

46
45 |
42 |

+

1

I

2 I

3

I

+
1 I
2 I

3

I

+
1 I
2 I

3

Глава 18. SQL и большие данные

I
355

236
75
197

42
41
40

4
5
6

3

3

5
6

4
5

248
61
110
281
318

15
14
14
14

595
596
597
598
599

594
596
596
596
599

30
31
31
31
32

12

+

+

+

+

+

+

599 rows selected (1.827 seconds)

Эти несколько примеров демонстрируют способность Apache Drill выпол нять достаточно сложные запросы к MySQL. Нужно помнить, что Apache Drill
работает со многими реляционными базами данных, а не только с MySQL,
поэтому некоторые возможности языка могут различаться ( например, функции преобразования данных). За дополнительной информацией обратитесь
к документации по реализации SQL в Apache Drill ( https : / / oreile . ly /
d 2 jse ).

Запрос MongoDB с использованием Apache Drill
Теперь, после демонстрации использования Apache Drill для выполнения
запросов базы данных Sakila в MySQL, выполним следующий логический
шаг преобразуем данные Sakila в другой распространенный формат , сохра ним их в нереляционной базе данных и используем Apache Drill для запроса
данных. Я решил преобразовать данные в JSON и сохранить их в MongoDB,
одной из наиболее популярных не-SQL платформ для хранения документов.
Apache Drill включает в себя плагин для MongoDB, а также понимает , как работать с документами JSON, так что загрузить JSON -файлы в MongoDB и начать писать запросы относительно легко.
Перед тем как погрузиться в запросы, давайте рассмотрим структуру
JSON -файлов, поскольку они находятся не в нормированной форме. Первый
из двух файлов JSON films . j son:
{" id":1,



_



"Actors":[
{"First name":"PENELOPE","Last name":"GUINESS","actorId":1},
{"First name":"CHRISTIAN","Last name":"GABLE","actorld":10},
{"First name":"LUCILLE","Last name":"TRACY","actorld":20},
{"First name":"SANDRA","Last name":"PECK","actorld":30},
{"First name":"JOHNNY","Last name":"CAGE","actorld":40},
{"First name":"MENA","Last name":"TEMPLE","actorld":53},
{"First name":"WARREN","Last name":"NOLTE","actorld":108},

356

Глава 18. SQL и большие данные

{"First паше":"OPRAH","Last name":"KILMER","actorld":162},
{"First name":"ROCK","Last name":"DUKAKIS","actorld":188},
{"First name":"MARY","Last name":"KEITEL","actorld":198}],
"Category":"Documentary",
"Description":"A Epic Drama of a Feminist And a Mad Scientist
who must Battle a Teacher in The Canadian Rockies",
"Length":"86",
"Rating":"PG",
"Rental Duration":"6",
"Replacement Cost":"20.99",
"Special Features":"Deleted Scenes,Behind the Scenes",
"Title":"ACADEMY DINOSAUR"},
{" id":2,
"Actors":[
{"First name":"BOB","Last name":"FAWCETT","actorld":19},
{"First name":"MINNIE","Last name":"ZELLWEGER","actorld":85},
{"First name":"SEAN","Last name":"GUINESS","actorld":90},
{"First name":"CHRIS","Last name":"DEPP","actorld":160}],
"Category":"Horror",
"Description":"A Astounding Epistle of a Database Administrator
And a Explorer who must Find a Car in Ancient China",
"Length":"48",
"Rating":"G",
"Rental Duration":"3",
"Replacement Cost":"12.99",
"Special Features":"Trailers, Deleted Scenes",
"Title":"ACE GOLDFINGER"},

_

_

{" id":999,
"Actors":[
{"First name":"CARMEN","Last name":"HUNT","actorld":52},
{"First name":"MARY","Last name":"TANDY","actorld":66},
{"First name":"PENELOPE","Last name":"CRONYN","actorld":104},
{"First name":"WHOOPI","Last name":"HURT","actorld":140},
{"First name":"JADA","Last name":"RYDER","actorld":142}],
"Category":"Children",
"Description":"A Fateful Reflection of a Waitress And a Boat
who must Discover a Sumo Wrestler in Ancient China",
"Length":"101",
"Rating":"R",
"Rental Duration":"5",
"Replacement Cost":"28.99",
"Special Features":"Trailers, Deleted Scenes",
"Title":"ZOOLANDER FICTION"}
{" id":1000,
"Actors":[
{"First name":"IAN","Last name":"TANDY","actorld":155},
{"First name":"NICK","Last name":"DEGENERES","actorld":166},
{"First name":"LISA","Last name":"MONROE","actorld":178}],
"Category":"Comedy",
"Description":"A Intrepid Panorama of a Mad Scientist And a Boy

_

.

Глава 18 SQL и большие данные

| 357

who must Redeem a Boy in A Monastery",
"Length":"50",
"Rating":"NC-17",
"Rental Duration":"3",
"Replacement Cost":"18.99",
"Special Features":
"Trailers,Commentaries,Behind the Scenes",
"Title":"ZORRO ARK"}

В этой коллекции 1000 документов, и каждый документ содержит ряд скалярных атрибутов ( Title, Rating, id), а также включает в себя список под
названием Actors , который содержит от 1 до N элементов, состоящих из
идентификатора актера, атрибутов имени и фамилии каждого актера, появля ющегося в фильме. Таким образом, этот файл содержит все данные из таблиц
actor, film и film actor в базе данных MySQL Sakila.
customer . json , который сочетает в себе данные из таВторой файл
блиц customer , address , city, country, rental и payment базы данных
MySQL Sakila:

_



_

_

{" id":1,
"Address":"1913 Hanoi Way",
"City":"Sasebo",
"Country":"Japan",
"District":"Nagasaki",
"First Name":"MARY",
"Last Name":"SMITH",
"Phone":"28303384290",
"Rentals":[
{"rentalld":1185,
"filmld":611,
"staffld":2,
"Film Title"."MUSKETEERS WAIT",
"Payments":[
{"Payment Id":3,"Amount":5.99,
"Payment Date":"2005-06-15 00:54:12"}],
"Rental Date":"2005 06 15 00:54:12.0",
"Return Date":"2005-06-23 02:42:12.0"},
{"rentalld".1476,
"filmld":308,
"staffld":1,
"Film Title":"FERRIS MOTHER",
"Payments":[
{"Payment Id":5,"Amount":9.99,
"Payment Date":"2005-06-15 21:08:46"}],
"Rental Date":"2005-06-15 21:08:46.0",
"Return Date":"2005-06-25 02:26:46.0"},

-

- -

-

{"rentalld":14825,

358

Глава 18. SQL и большие данные

"filmlcT:317,
"staffId":2,
"Film Title":"FIREBALL PHILADELPHIA",
"Payments":[
{"Payment Id":30,"Amount":1.99,
"Payment Date":"2005-08-22 01:27:57"}],
"Rental Date
2005-08-22 01:27:57.0",
"Return Date":"2005-08-27 07:01:57.0"}

Этот файл содержит 599 записей ( здесь показана только одна ) , которые
загружаются в MongoDB в виде 599 документов в коллекции customers.
Каждый документ содержит информацию об одном клиенте наряду со всеми
платежами, выполненными этим клиентом. Кроме того, документы содержат
вложенные списки, поскольку каждый прокат в списке Rentals также содержит список Payments.
После того как файлы JSON загружены, база данных Mongo содержит две
коллекции(films и customers), а данные в этих коллекциях охватывают
девять разных таблиц базы данных Sakila. Это довольно типичный сцена рий, поскольку прикладные программисты обычно работают с коллекциями
и в общем случае предпочитают не деконструировать данные для хранения
в нормализованных реляционных таблицах. С точки зрения SQL главная
проблема в том, чтобы определить, как сформировать эти данные так, чтобы
они вели себя так, как если бы они хранились в нескольких таблицах.
Для иллюстрации давайте построим следующий запрос к коллекции films:
найти всех актеров, которые появились в 10 или более фильмах, с рейтингом
либо G или PG. Вот как выглядят необработанные данные:
apache drill (mongo.sakila)> SELECT Rating, Actors
)> FROM films
)> WHERE Rating IN ( 1 G 1 ,'PG');
+

+

+

Rating | Actors
+

+

+

PG

[{"First name":"PENELOPE","Last name":"GUINESS",
"actorld":"1"},
{"First name":"FRANCES","Last name":"DAY-LEWIS",
"actorld":"48"},
{"First name":"ANNE","Last name":"CRONYN",
"actorld":"49"},
{"First name":"RAY","Last name":"JOHANSSON",
"actorld":"64"},
{"First name":"PENELOPE","Last name":"CRONYN",
"actorld":"104"},
{"First name":"HARRISON","Last name":"BALE",
Глава 18. SQL и большие данные

359

PG

G

+

"actorld":"115"},
{"First name":"JEFF","Last name":"SILVERSTONE",
"actorld":"180"},
{"First name":"ROCK","Last name":"DUKAKIS",
"actorld":"188"}]
[{"First name":"UMA","Last name":"WOOD",
"actorld":"13"},
{"First name":"HELEN","Last name":"VOIGHT",
"actorld":"17"},
{"First name":"CAMERON","Last name":"STREEP",
"actorld":"24"},
{"First name":"CARMEN","Last name":"HUNT",
"actorld":"52"},
{"First name":"JANE","Last name":"JACKMAN",
"actorld":"131"},
{"First name":"BELA","Last name":"WALKEN",
"actorld":"196"}]
[{"First name":"ED","Last name":"CHASE",
"actorld":"3"},
{"First name":"JULIA","Last name":"MCQUEEN",
"actorld":"27"},
{"First name":"JAMES","Last name":"PITT",
"actorld":"84"},
{"First name":"CHRISTOPHER","Last name":"WEST",
"actorld":"163"},
{"First name":"MENA","Last name":"HOPPER",
"actorld":"170"}]
+

+

372 rows selected (0.432 seconds)

Поле Actors представляет собой список из одного или нескольких документов актера. Для того чтобы взаимодействовать с этими данными, как если
бы это была таблица, для преобразования списка во вложенную таблицу, содержащую три поля, может использоваться команда flatten:
apache drill (mongo.sakila)> SELECT f.Rating, flatten(Actors)
)>
)>
)>

actor_list
FROM films f
WHERE f.Rating IN ( * G', 1 PG');

+

+

+

I Rating | actor_list
+

+

PG

PG
PG

PG

360

+

{"First name":"PENELOPE","Last name":"GUINESS",
"actorld":"1"}
{"First name":"FRANCES","Last name":"DAY-LEWIS",
"actorld":"48"}
{"First name":"ANNE","Last name":"CRONYN",
"actorld":"49"}
{"First name":"RAY","Last name":"JOHANSSON",
Глава 18. SQL и большие данные

"actorld":"64"}
{"First name":"PENELOPE","Last name":"CRONYN",
"actorld":"104"}
{"First name":"HARRISON","Last name":"BALE",
"actorld":"115"}
{"First name":"JEFF","Last name":"SILVERSTONE",
"actorld":"180"}
{"First name":"ROCK","Last name":"DUKAKIS",
"actorld":"188"}
{"First name":"UMA","Last name":"WOOD",
"actorld":"13"}
{"First name":"HELEN","Last name":"VOIGHT",
"actorld":"17"}
{"First name":"CAMERON","Last name":"STREEP",
"actorld":"24"}
{"First name":"CARMEN","Last name":"HUNT",
"actorld":"52"}
{"First name":"JANE","Last name":"JACKMAN",
"actorld":"131"}
{"First name":"BELA","Last name":"WALKEN",
"actorld":"196"}

PG
PG

PG
PG
PG

PG
PG

PG
PG
PG

{"First name":"ED","Last name":"CHASE",
"actorld":"3"}
{"First name":"JULIA","Last name":"MCQUEEN",
"actorld":"27"}
{"First name":"JAMES","Last name":"PITT",
"actorld":"84"}
{"First name":"CHRISTOPHER","Last name":"WEST",
"actorld":"163"}
{"First name":"MENA","Last name":"HOPPER",
"actorld":"170"}

G
G
G

G
G
+

+

+

2119 rows selected (0.718 seconds)

Этот запрос возвращает 2119, а не 372 строк, возвращаемых предыдущим
запросом, что указывает на то, что в среднем в каждом фильме с рейтингом
G или PG появляется 5,7 актера. Затем этот запрос можно использовать в качестве подзапроса для группировки данных по рейтингу и актер, как показано далее:
apache drill (mongo.sakila)> SELECT g pg films.Rating,
)>
g pg films.actor_list. First name

_ _

.
.

_ _

'

_

'

)>
first name,
)>
g_pg_films.actor_list.'Last name'
)>
last name,
)>
count(*) num_films
)> FROM

. )> (SELECT f.Rating, flatten(Actors)
)>
actor list

.

Глава 18 SQL и большие данные

361

. . )>

FROM films f
. . )> WHERE f.Rating IN ('G','PG 1 )
. . )> ) g_pg films
. . )> GROUP BY g_pg_films.Rating,
. . )> g_pg_films.actor_list.'First name',
g_pg films.actor list.'Last name'
• • )>
. . )> HAVING count(*) > 9;

_

_

+

+

+

+

PG
G
PG
PG
PG
PG
PG
G
PG
PG
G
G
G
PG
PG
G
PG
PG
+

+

| num films |
+

+

SILVERSTONE
MOSTEL
TORN
DAVIS
ZELLWEGER
CRAWFORD
PENN
DAVIS
BOLGER
AKROYD
BERGEN
WILLIS
VOIGHT
BASINGER
STALLONE
CRAWFORD
WILLIAMS
WINSLET

JEFF
GRACE
WALTER
SUSAN
CAMERON
RIP
RICHARD
SUSAN
VAL
KIRSTEN
VIVIEN
BEN

HELEN
VIVIEN
NICK
DARYL
MORGAN
FAY
+

+

+

Rating | first_name | last_name
+

_

+

12

10
11

10
15
11
10
13
12
12
10
14
12
10
12
12
10
10
+

+

18 rows selected (0.466 seconds)

Внутренний запрос использует команду flatten для создания одной
строки для каждого актера , который появился в фильме G или PG , а внешний
запрос просто выполняет группировку этого набора данных.
Теперь давайте напишем запрос к коллекции customers в Mongo. Это не много более сложная задача , поскольку каждый документ содержит список
прокатов фильмов, каждый из которых содержит список платежей . Чтобы
сделать задание немного интереснее, давайте также выполним соединение с
коллекцией films, чтобы увидеть, как Apache Drill обрабатывает соединения.
Запрос должен вернуть всех клиентов, которые потратили более 80 долларов
на прокат фильмов с рейтингом G или PG . Вот как выглядит такой запрос:

_

_

apache drill (mongo.sakila)> SELECT first name, last name,
)>
sum(cast(
)>
cust_payments.payment data.Amount
)>
as decimal(4,2))) tot_payments
)> FROM
)> (SELECT cust data.first name,

_

362

Глава 18. SQL и большие данные

)>
)>
)>
)>

)>
)>
)>
)>
)>
)>
)>
)>
)>
)>
)>

)>
)>
)>
)>
)>
)>

+

+

_

_

_

_
_

_

_

_

_

+

+

_

+

85.80
85.86
86.83
86.82
89.83
95.80
85.82
95.82
88.83
81.82

SEAL
LOWE
HICKS
LEONE
CARROLL

STEWART
+

+

+

HUNT
ALLARD
SHAW
LONG

ELEANOR
GORDON
CLARA
JACQUELINE
I KARL
| PRISCILLA
| MONICA
| LOUIS
| JUNE
| ALICE
|
|
|
|

+

_

last_name | tot_payments

first name
+

_

cust data.last_name,
f.Rating,
flatten(
cust data.rental data.Payments)
payment data
FROM films f
INNER JOIN
(SELECT c.'First Name' first_name,
c.'Last Name' last_name,
flatten(c.Rentals) rental_data
FROM customers c
) cust data
ON f. id =
cust data.rental data.filmID
WHERE f.Rating IN ( 1 G 1 , ’ PG ’)
) cust payments
GROUP BY first_name, last name
HAVING
sum(cast(
cust payments.payment data.Amount
as decimal(4,2))) > 80;

+

+

10 rows selected (1.658 seconds)

_

Наиболее глубоко вложенный запрос, который я назвал cust data, преобразует список Rentals, так что запрос cust payments может соединяться
с коллекцией films, а также преобразовывать список Payments. Внешний запрос группирует данные по имени клиента и применяет предложение having,
чтобы отфильтровать клиентов, которые потратили не более 80 долларов на
фильмы с рейтингом G или PG.

_

Apache Drill и несколько источников данных
Пока что я использовал Apache Drill, чтобы работать с несколькими таблицами, хранящимися в одной базе данных. Но что если данные хранятся
Глава 18. SQL и большие данные

363

в разных базах данных ? Например, данные клиента / прокатов / платежные
данные хранятся в MongoDB, а каталог данных фильмов / актеров хранится
в MySQL? Если Apache Drill настроен для подключения к обеим базам дан ных, вам нужно просто указать, где искать данные. Вот запрос из предыдущего раздела, но теперь вместо соединения с коллекцией фильмов, хранящихся
в MongoDB, выполняется соединение с таблицей фильмов из MySQL:

_

_

apache drill (mongo.sakila)> SELECT first name, last name,
)>
sum(cast(
)>
cust_payments.payment_data.Amount
)>
as decimal(4,2))) tot payments
)> FROM
) > (SELECT cust_data.first name,
)>
cust data.last name ,
)>
f.Rating,
)>
flatten(
)>
cust data.rental data.Payments)
payment_data
)>
)>
FROM mysql.sakila.film f
)>
INNER JOIN
)> (SELECT c.'First Name' first name,
)>
c.'Last Name' last name,
)>
flatten(c.Rentals) rental data
)>
FROM mongo.sakila.customers c
)> ) cust data
)> ON f.film id =
)>
cast(cust_data.rental_data.filmID
)>
as integer)
)> WHERE f.rating IN ('G','PG 1 )
)> ) cust payments
)> GROUP BY first_name, last name
)> HAVING
)>
sum(cast(
)>
cust payments.payment data.Amount
)>
as decimal(4,2))) > 80;

_

_

_

_
_

_

_

_

_

_

_

_

+

+

_

_

+

_

+

first name I last name | tot_payments

I LOUIS
| JACQUELINE
| CLARA
| ELEANOR
| JUNE
| PRISCILLA
| ALICE
| MONICA
| GORDON

I KARL

364

+

+

+

LEONE
LONG
SHAW
HUNT
CARROLL
LOWE
STEWART
HICKS
ALLARD
SEAL

Глава 18.SQL и большие данные

+

95.82
86.82
86.83
85.80
88.83
95.80
81.82
85.82
85.86
89.83

_

_

10 rows selected (1.874 seconds)

Поскольку я использую несколько баз данных в одном и том же запросе, я
указываю полный путь к каждой таблице и коллекции, чтобы точно указать
источник данных. В этом Apache Drill действительно очень хорош
с его
помощью легко соединить данные из нескольких источников в одном запросе без необходимости преобразовывать данные и переносить их из одного
источника в другой.



Будущее SQL
Будущее реляционных баз данных несколько туманно. Возможно, технологии больших данных последнего десятилетия будут продолжать развиваться
и отбирать свою долю рынка. Возможно и появление новых технологий, которые обойдут Hadoop и не-SQL базы данных и заберут свою долю рынка у
реляционных баз данных. Однако в настоящее время большинство компаний
все еще выполняют свои бизнес-функции, используя реляционные базы дан ных, и чтобы изменить это положение дел, должно пройти немало времени.
Будущее же SQL представляется немного более ясным. В то время как язык
SQL начинался как механизм для взаимодействия с данными в реляционных
базах данных, такие инструменты, как Apache Drill, действуют в качестве слоя
абстракции, облегчающего анализ данных с использованием различных плат форм баз данных. Мне кажется, что эта тенденция продолжится, a SQL останется критически важным инструментом для анализа данных и генерации
отчетности еще в течение многих лет .

Глава 18. SQL и большие данные

365

ПРИЛОЖЕНИЕ А

Схема базы данных Sakila

На рис. А.1 представлена диаграмма модели данных используемого в кни ге примера базы данных Sakila. На ней отображены объекты, или таблицы,
базы данных вместе с отношениями внешних ключей между таблицами. Вот
несколько подсказок, которые помогут вам разобраться в условных обозначениях.



Каждый прямоугольник представляет таблицу с именем таблицы, указанным над верхним левым углом прямоугольника. Первым в таблице
указан столбец первичного ключа; затем идут столбцы, не являющиеся
ключевыми.



Линии между таблицами представляют собой отношения внешних
ключей. Маркеры на концах линий представляют допустимое множество записей, которое может быть либо 0, либо 1, либо много. Напри мер, если вы посмотрите на отношения между таблицами customer и
rental, то увидите, что rental связан ровно с одним customer, но
customer может иметь нуль, один или несколько записей rental.

Для получения дополнительной информации о диаграммах модели дан ных обратитесь к соответствующей странице Википедии(https://oreil.
ly/hLEeq).

Ш customer

Щ payment
t customerjd SMALLINT(5)
t paymentjd SMALLINT(5)
first name VARCHAR(45) .11 L/ customerjd SMALLINT(5)
last name VARCHAR(45)
О rental_ id INT(11)

amount DECIMAL (5,2)
О email VARCHAR(50)
address id SMALLINT(5)
payment _ date DATETIME
—i
active TINYINT(l)
О last _updateTIMESTAMP
create _ date DATETIME
Ж
О last update TIMESTAMP

__

^

H

Ш actor
t actor id SMALLINT(5)
first name VARCHAR(45)
last name VARCHAR(45)
last update TIMESTAMP

.

ST
Ш address
t address id SMALLINT(5)
address VARCHAR(50)
address2 VARCHAR(50)
( district VARCHAR( 20)
city id SMALLINT(5)
postal code VARCHAR(IO)
phone VARCHAR( 20)
location GEOMETRY
last update TIMESTAMP

>

Ж

Ш rental

Ж

t rentaljd INT(11)
rental date DATETIME
inventory JdMEDIUMINT(8)
customer _ idSMALLINT(5)
О return.date DATETIME
last.update TIMESTAMP

Щ film.actor
actor id SMALLINT(5)
film id SMALLINT(5)

last update TIMESTAMP

ж

Ш inventory
Щ city
t city id SMALLINT(5)
city VARCHAR(50)
country id SMALLINT(5)
last update TIMESTAMP

t inventory id MEDIUMINT(8)
film id SMALLINT (5)
last update TIMESTAMP

Ж
i

It

i
i

Щ country
t country id SMALLINT(5)
country VARCHAR(50)
last update TIMESTAMP

Щ film
T film id SMALLINT( 5)
title VARCHAR( 255)
О description TEXT
О release _yearYEAR(4)
rental_durationTINYINT(3)
rentaljate DECIMAL(4,2)
length SMALLINTC5)
replacement _ cost DECIMAL(5, 2)
О rating ENUM(...)
- О special_featuresSET(...)
last.update TIMESTAMP

Ж

Ш

category
t category_idTINYINT(3)
name VARCHAR(25)

last update TIMESTAMP
.

Puc. A. l . Схема базы данных

368

Приложение А. Схема базы данных Sakila

1

^

Щ film.category
film.id SMALLINT(5)
category.id TINYINT(3)

last update TIMESTAMP
.

ПРИЛОЖЕНИЕ Б

Ответы к упражнениям

Глава 3
УПРАЖНЕНИЕ 3.1
Получите идентификатор актера, а также имя и фамилию для всех актеров. Отсортируйте вывод сначала по фамилии, а затем по имени.



_

mysql> SELECT actor id, first_name, last_name
-> FROM actor
-> ORDER BY 3,2;
+
+
+
+
last name
actor id | first name
+
+
+
+
AKROYD
58 | CHRISTIAN
AKROYD
182 | DEBBIE
AKROYD
92 | KIRSTEN
ALLEN
118 | CUBA
ALLEN
145 ! KIM
194 | MERYL
ALLEN
13
63
111
186
85
+

|
|
|
|
|
+

WOOD
WRAY
ZELLWEGER
ZELLWEGER
ZELLWEGER

UMA
CAMERON
CAMERON
JULIA
MINNIE

+

+

200 rows in set (0.02 sec)

УПРАЖНЕНИЕ 3.2
Получите идентификатор, имя и фамилию актера для всех актеров, чьи
фамилии
' WILLIAMS ' или ' DAVIS ' .
mysql> SELECT actor_id, first_name, last_name



->
->

FROM actor
WHERE last name IN ('WILLIAMS ’, 1 DAVIS');

+

+

+

+

+

first name | last name

actor id
+

+

+

4 | JENNIFER
101 | SUSAN
110 | SUSAN
72 | SEAN
137 | MORGAN
172 | GROUCHO
+

DAVIS
DAVIS
DAVIS
WILLIAMS
WILLIAMS
WILLIAMS
+

+

+

6 rows in set (0.03 sec)

УПРАЖНЕНИЕ 3.3
Напишите запрос к таблице rental, который возвращает идентификаторы
клиентов, бравших фильмы напрокат 5 июля 2005 года ( используйте столбец
rental . rental date ; можете также использовать функцию date ( ) , чтобы
игнорировать компонент времени). Выведите по одной строке для каждого
уникального идентификатора клиента.

_

mysql> SELECT DISTINCT customer_id
-> FROM rental
-> WHERE date(rental date) = 2005-07-05';
+

+

customer id
+

+

8

37
60
111

114
138
142
169
242
295
296
298
322
348
349

369
382
397
421
476
490
520
536
553
565
586
594

370

.

Приложение Б Ответы к упражнениям

+

+

27 rows in set (0.22 sec)

УПРАЖНЕНИЕ 3.4
Заполните пропущенные места ( обозначенные как < # > ) в следующем многотабличном запросе, чтобы получить показанные результаты:
mysql>
->
->
->
->
->

SELECT с.email, г.return_date
FROM customer с
INNER JOIN rental
ON c.customer_id =
WHERE date(r.rental_date) = '2005-06-14
ORDER BY ;

+

+

+

email

return date
+

+

+

2005-06-23
2005-06-23
2005-06-21
2005 06-20
2005-06-19
2005-06-19
2005-06-18
2005-06-18
2005 06 18
2005 06 17
2005 06 17
2005-06-17
2005 06 16
2005 06 16
2005 06-16
2005 06-15

DANIEL.CABRAL@sakilacustomer.org
TERRANCE.ROUSH@sakilacustomer.org
MIRIAM.MCKINNEY@sakilacustomer.org
GWENDOLYN.MAY@sakilacustomer.org
JEANETTE.GREENE ® sakilacustomer.org
HERMAN.DEVORE@sakilacustomer.org
JEFFERY.PINSON@sakilacustomer.org
MATTHEW.MAHAN@sakilacustomer.org
MINNIE.ROMERO@sakilacustomer.org
SONIA.GREGORY@sakilacustomer.org
TERRENCE.GUNDERSON@sakilacustomer.org
ELMER.NOE@sakilacustomer.org
JOYCE.EDWARDS@sakilacustomer.org
AMBER.DIXON@sakilacustomer.org
CHARLES.KOWALSKI@sakilacustomer.org
CATHERINE.CAMPBELL ® sakilacustomer.org

-

-

-

22:00:38
21:53:46
17:12:08
02:40:27
23:26:46
03:20:09
21:37:33
05:18:58
01:58:34
21:44:11
05:28:35
02:11:13
21:00:26
04:02:56
02:26:34
20:43:03

+

+

+

16 rows in set (0.03 sec)

заменяется на г.
< 2 > заменяется r . customer _id.
< 3 > заменяется на 2.
< 4 > заменяется на desc.

Глава 4
В первых двух упражнениях вам потребуется следующее подмножество
строк из таблицы payment:
+

+

payment_id

+

+

customer_id

+

+

+

+

+

+

I amount | date(payment_date)

Приложение 5. Ответы к упражнениям

371

101
102
103
104
105
106
107
108
109
110

5
5
5
5
5
5
5
5
5
5
5
5
5

111
112

113
114
115
116
117
118
119
120
+

+

2005-08-18
2005-08-19
2005 08 20
2005 08 20
2005-08-21
2005-08-22
2005 08-23
2005 05-29
2005-05-31
2005-05-31
2005-06-15
2005 06 16
2005-06 17
2005 06 19
2005-06-20
2005-07-06
2005 07 08
2005 07-09
2005-07 09
2005-07-09

8.99
1.99
2.99
6.99
4.99
2.99
1.99
0.99
6.99

4
4
4
4
4
4
4

- - -

1.99
3.99
2.99
4.99
2.99
4.99
4.99
2.99

- - - -

4.99
5.99
1.99
+

+

+

УПРАЖНЕНИЕ 4.1
Какие из идентификаторов платежей будут возвращены при следующих
условиях фильтрации?

_

customer id 5
AND (amount > 8 OR date(payment date) = '2005-08-23')

_

Идентификаторы платежей 101 и 107.
УПРАЖНЕНИЕ 4.2
Какие из идентификаторов платежей будут возвращены при следующих
условиях фильтрации ?

_

customer id = 5 AND
NOT (amount > 6 OR date(payment_date)

= '2005-06-19')

Идентификаторы платежей 108, 110, 111, 112, 113, 115, 116, 117, 118, 119
и 120.
УПРАЖНЕНИЕ 4.3
Создайте запрос, который извлекает из таблицы payments все строки,
в которых сумма равна 1,98, 7,98 или 9,98.
mysql> SELECT amount
> FROM payment
> WHERE amount IN (1.98, 7.98, 9.98);

-

-

372

Приложение Б. Ответы к упражнениям

+

+

amount
+

+

7.98
9.98
1.98
7.98
7.98
7.98
7.98
+

+

7 rows in set (0.01 sec)

УПРАЖНЕНИЕМ
Создайте запрос, который находит всех клиентов, в фамилиях которых содержатся буква А во второй позиции и буква W в любом месте после А.



_

mysql> SELECT first name, last_name
> FROM customer
> WHERE last name LIKE A%W%';
+
+
+
I first name I last name

-

+

+

+

KAY
JOHN
JILL
LEE
LAURIE
JEANNE
LAWRENCE
SAMUEL
ERICA
+

CALDWELL
FARNSWORTH
HAWKINS
HAWKS
LAWRENCE
LAWSON
LAWTON
MARLOW
MATTHEWS
+

+

9 rows in set (0.02 sec)

Глава 5
УПРАЖНЕНИЕ 5.1
Заполните пропущенные места ( обозначенные как < # > ) в следующем запросе так, чтобы получить показанные результаты:
mysql> SELECT с.first_name, c.last_name, a.address, ct.city

->
->
->
->
->
->

FROM customer c
INNER JOIN address
ON c.address_id = a.address id
INNER JOIN city ct
ON a.city id =
WHERE a.district = 'California';

_

_

Приложение Б. Ответы к упражнениям

373

+

+

| first name
+

+

RENE

1121 Loja Avenue
770 Bydgoszcz Avenue
1135 Izumisano Parkway
793 Cam Ranh Avenue
533 al-Ayn Boulevard
226 Brest Manor
920 Kumbakonam Loop
1866 al-Qatif Avenue
1895 Zhezqazghan Drive
+

+

+

I city
+

+

JOHNSON
WHITE
STEWART
REYNOLDS
LANE
JOHNSTON
WALTERS
LANCE
MCALISTER

PATRICIA
BETTY
ALICE
ROSA
RENEE
KRISTIN
CASSANDRA
JACOB
+

+

+

last name | address

+

San Bernardino
Citrus Heights
Fontana

Lancaster
Compton
Sunnyvale
Salinas
El Monte
Garden Grove
+

+

9 rows in set (0.00 sec)

< 1 > заменяется на a.
< 2 > заменяется ct . city id.
УПРАЖНЕНИЕ 5.2
Напишите запрос, которыйвыводил бы названия всех фильмов, в которых
играл актер с именем JOHN.
mysql> SELECT f.title
-> FROM film f
-> INNER JOIN film_actor fa
-> ON f.film id = fa.film id
-> INNER JOIN actor a
> ON fa.actor id = a.actor id
> WHERE a.first name = 'JOHN';

_

_

-

_

_

+

+

I title
+

+

ALLEY EVOLUTION
BEVERLY OUTLAW
CANDLES GRAPES
CLEOPATRA DEVIL
COLOR PHILADELPHIA
CONQUERER NUTS
DAUGHTER MADIGAN
GLEAMING JAWBREAKER
GOLDMINE TYCOON
HOME PITY
INTERVIEW LIAISONS
ISHTAR ROCKETEER
JAPANESE RUN
JERSEY SASSY
LUKE MUMMY
MILLION ACE
MONSTER SPARTACUS
NAME DETECTIVE

374

Приложение Б. Ответы к упражнениям

NECKLACE OUTBREAK
NEWSIES STORY
PET HAUNTING
PIANIST OUTFIELD
PINOCCHIO SIMON
PITTSBURGH HUNCHBACK
QUILLS BULL
RAGING AIRPLANE
ROXANNE REBEL
SATISFACTION CONFIDENTIAL
SONG HEDWIG
+

+

29 rows in set (0.07 sec)

УПРАЖНЕНИЕ 5.3

Создайте запрос, который возвращает все адреса в одном и том же городе.
Вам нужно будет соединить таблицу адресов с самой собой, и каждая строка
должна включать два разных адреса.
mysql> SELECT al.address addrl, a2.address addr2, al.city_id

->
->

FROM address al
INNER JOIN address a2
> WHERE al.city id = a2.city_id
-> AND al.address id a2.address id;

-

_

+

+

addrl
+

city_id

+

23 Workhaven Lane
1411 Lillydale Drive
47 MySakila Drive
28 MySQL Boulevard
548 Uruapan Street
43 Vilnius Manor
1497 Yuzhou Drive
587 Benguela Manor
+

+

+

+

47 MySakila Drive
28 MySQL Boulevard
23 Workhaven Lane
1411 Lillydale Drive
1497 Yuzhou Drive
587 Benguela Manor
548 Uruapan Street
43 Vilnius Manor

+

+

I addr2

300
576
300
576
312
42
312
42
+

+

8 rows in set (0.00 sec)

Глава 6
УПРАЖНЕНИЕ 6.1
Пусть множество А = {L,M, N,0,P}, а множество В = {P,Q,R,S,T}. Какие множества будут сгенерированы следующими операциями ?




A union В
A union all В
Приложение Б. Ответы к упражнениям

375




A intersect В

A except В

1. A union В = {L M N O P Q R S T}
2. A union all B = {L M N O P P Q R S T}
3. A intersect В = {Р}
4. A except В

= {L М N 0}

УПРАЖНЕНИЕ 6.2
Напишите составной запрос, который находит имена и фамилии всех актеров и клиентов, чьи фамилии начинаются с буквы L.

_

SELECT first name, last_name
FROM actor
WHERE last name LIKE L% 1
UNION
SELECT first name, last name
FROM customer
-> WHERE last name LIKE 'L%';

mysql>
->
->
->
->
->

_

_

+

_

+

+

first name I last name
+

+

MATTHEW
JOHNNY
MISTY
JACOB
RENEE
HEIDI
DARYL
LAURIE
JEANNE
LAWRENCE
KIMBERLY
LOUIS
SARAH
GEORGE
MAUREEN
DWIGHT
JACQUELINE
AMY
BARRY
PRISCILLA
VELMA
WILLARD
LEWIS
JACKIE

376

+

LEIGH
LOLLOBRIGIDA

LAMBERT
LANCE
LANE
LARSON
LARUE
LAWRENCE
LAWSON
LAWTON
LEE
LEONE
LEWIS
LINTON
LITTLE
LOMBARDI
LONG
LOPEZ
LOVELACE
LOWE
LUCAS

LUMPKIN
LYMAN
LYNCH

Приложение Б. Ответы к упражнениям

+

+

+

24 rows in set (0.01 sec)

УПРАЖНЕНИЕ 6.3
Отсортируйте результаты выполнения упражнения 6.2 по столбцу last_
name.
mysql>
->
->
->

->

->>
->

_

SELECT first name, last_name
FROM actor
WHERE last_name LIKE 'L%'
UNION
SELECT first name, last name
FROM customer
WHERE last_name LIKE 'L%'
ORDER BY last name;

_

+

name

I last name
+

+

+

MISTY
JACOB
RENEE
HEIDI
DARYL
LAURIE
JEANNE
LAWRENCE
KIMBERLY
MATTHEW
LOUIS
SARAH
GEORGE
MAUREEN
JOHNNY
DWIGHT
JACQUELINE
AMY
BARRY
PRISCILLA
VELMA
WILLARD
LEWIS
JACKIE
+

+

+

I first

_

LAMBERT
LANCE
LANE
LARSON
LARUE
LAWRENCE
LAWSON
LAWTON
LEE
LEIGH
LEONE
LEWIS
LINTON
LITTLE
LOLLOBRIGIDA
LOMBARDI
LONG
LOPEZ
LOVELACE
LOWE
LUCAS
LUMPKIN
LYMAN
LYNCH
+

+

24 rows in set (0.00 sec)

Приложение Б. Ответы к упражнениям

377

Глава 7
УПРАЖНЕНИЕ 7.1
Напишите запрос, который возвращает символы строки ' Please find
the substring in this string ' с 17- го по 25- й.
mysql> SELECT
- > SUBSTRING ( ' Please find the substring in this string ' , 17 , 9 ) ;

+

+

SUBSTRING('Please find the substring in this string',17,9)
+

+

substring
+

+

1 row in set (0.00 sec)

УПРАЖНЕНИЕ 7.2
Напишите запрос, который возвращает абсолютное значение и знак
(-1, 0 или 1) числа -25,76823. Верните также число, округленное до ближай ших двух знаков после запятой.
mysql> SELECT ABS ( - 25.76823 ) , SIGN ( -25.76823 ) , ROUND ( -25.76823 , 2 ) ;
+
+
+
+
ABS( 25.76823) SIGN(-25.76823) | ROUND(-25.76823, 2) |
+
+
+
+
25.76823 -1
I -25.77
+
+
+
+
1 row in set (0.00 sec)

-

УПРАЖНЕНИЕ 7.3
Напишите запрос, возвращающий для текущей даты только часть, соот ветствующую месяцу.

_

mysql> SELECT EXTRACT ( MONTH FROM CURRENT DATE ( ) ) ;
+

+

EXTRACT(MONTH FROM CURRENT DATE) |
+

+

12
+

+

1 row in set (0.02 sec)

Глава 8
УПРАЖНЕНИЕ 8.1

Создайте запрос, который подсчитывает количество строк в таблице
payment.
378

Приложение Б . Ответы к упражнениям

mysql> SELECT count(*) FROM payment;
+

+

count(*) |
+

+

16049
+

+

1 row in set (0.02 sec)

УПРАЖНЕНИЕ 8.2
Измените запрос из упражнения 8.1 так, чтобы подсчитать количество
платежей, произведенных каждым клиентом. Выведите идентификатор кли ента и общую уплаченную сумму для каждого клиента.

_

mysql> SELECT customer id, count(*), sum(amount)
-> FROM payment
-> GROUP BY customer id;
+
+
+
+
I customer id | count(*) sum(amount) I
+

+

1
2
3
4

+

+

5

32
27
26
22
38

118.68
128.73
135.74
81.78
144.62

595
596
597
598
599

30
28
25
22
19

117.70
96.72
99.75
83.78
83.81

+
+
+
599 rows in set (0.03 sec)

+

УПРАЖНЕНИЕ 8.3
Измените запрос из упражнения 8.2, включив в него только тех клиентов,
у которых имеется не менее 40 выплат .

_

SELECT customer id, count(*), sum(amount)
FROM payment
GROUP BY customer id
HAVING count(*) >= 40;
+
+
+
+
I customer id | count(*) sum(amount) |
+
+
+
+
75
155.59 |
41 I
144
42 |
195.58 |
148
46 |
216.54 |
197
40 !
154.60 |

mysql>
->
>
->

-

_

.

Приложение Б Ответы к упражнениям

379

236
469
526
+

175.58 |
177.60 |
221.55 |

42
40
45

+

+

+

7 rows in set (0.03 sec)

Глава 9
УПРАЖНЕНИЕ 9.1

Создайте запрос к таблице film, который использует условие фильтрации
с некоррелированным подзапросом к таблице category, чтобы найти все боевики ( category , name = ' Action ' ).
mysql>
->
->
->
>
->
->

-

SELECT title
FROM film
WHERE film id IN
(SELECT fc.film id
FROM film_category fc INNER JOIN category c
ON fc.category_id = c.category id
WHERE c.name = 'Action');

_

_

_

+

+

title
+

+

AMADEUS HOLY
AMERICAN CIRCUS
ANTITRUST TOMATOES
ARK RIDGEMONT
BAREFOOT MANCHURIAN
BERETS AGENT
BRIDE INTRIGUE
BULL SHAWSHANK
CADDYSHACK JEDI
CAMPUS REMEMBER
CASUALTIES ENCINO
CELEBRITY HORN
CLUELESS BUCKET
CROW GREASE
DANCES NONE
DARKO DORADO
DARN FORRESTER
DEVIL DESIRE
DRAGON SQUAD
DREAM PICKUP
DRIFTER COMMANDMENTS
EASY GLADIATOR
ENTRAPMENT SATISFACTION
EXCITEMENT EVE
FANTASY TROOPERS
FIREHOUSE VIETNAM

380

Приложение Б. Ответы к упражнениям

FOOL MOCKINGBIRD
FORREST SONS
GLASS DYING
GOSFORD DONNIE
GRAIL FRANKENSTEIN
HANDICAP BOONDOCK
HILLS NEIGHBORS
KISSING DOLLS
LAWRENCE LOVE
LORD ARIZONA
LUST LOCK
MAGNOLIA FORRESTER
MIDNIGHT WESTWARD
MINDS TRUMAN
MOCKINGBIRD HOLLYWOOD
MONTEZUMA COMMAND
PARK CITIZEN
PATRIOT ROMAN
PRIMARY GLASS
QUEST MUSSOLINI
REAR TRADING
RINGS HEARTBREAKERS
RUGRATS SHAKESPEARE
SHRUNK DIVINE
SIDE ARK
SKY MIRACLE
SOUTH WAIT
SPEAKEASY DATE
STAGECOACH ARMAGEDDON
STORY SIDE
SUSPECTS QUILLS
TRIP NEWTON
TRUMAN CRAZY
UPRISING UPTOWN
WATERFRONT DELIVERANCE
WEREWOLF LOLA
WOMEN DORADO
WORST BANGER
+

+

64 rows in set (0.06 sec)

УПРАЖНЕНИЕ 9.2

Переработайте запрос из упражнения 9.1, используя коррелированный подзапрос к таблицам category и f ilm category для получения тех же результатов.
mysql> SELECT f.title

_

->
->

FROM film f
WHERE EXISTS

.

Приложение Б Ответы к упражнениям

381

->
->

(SELECT 1
FROM film category fc INNER JOIN category c
ON fc.category id = c.category id
>
WHERE c.name = 'Action'
>
AND fc.film id = f.film id);
>

_

-

_

_

+

+

| title
+

+

AMADEUS HOLY
AMERICAN CIRCUS
ANTITRUST TOMATOES
ARK RIDGEMONT
BAREFOOT MANCHURIAN
BERETS AGENT
BRIDE INTRIGUE
BULL SHAWSHANK
CADDYSHACK JEDI
CAMPUS REMEMBER
CASUALTIES ENCINO
CELEBRITY HORN
CLUELESS BUCKET
CROW GREASE
DANCES NONE
DARKO DORADO
DARN FORRESTER
DEVIL DESIRE
DRAGON SQUAD
DREAM PICKUP
DRIFTER COMMANDMENTS
EASY GLADIATOR
ENTRAPMENT SATISFACTION
EXCITEMENT EVE
FANTASY TROOPERS
FIREHOUSE VIETNAM
FOOL MOCKINGBIRD
FORREST SONS
GLASS DYING
GOSFORD DONNIE
GRAIL FRANKENSTEIN
HANDICAP BOONDOCK
HILLS NEIGHBORS
KISSING DOLLS
LAWRENCE LOVE
LORD ARIZONA
LUST LOCK
MAGNOLIA FORRESTER
MIDNIGHT WESTWARD
MINDS TRUMAN
MOCKINGBIRD HOLLYWOOD
MONTEZUMA COMMAND
PARK CITIZEN

382

.

Приложение Б Ответы к упражнениям

PATRIOT ROMAN
PRIMARY GLASS
QUEST MUSSOLINI
REAR TRADING
RINGS HEARTBREAKERS
RUGRATS SHAKESPEARE
SHRUNK DIVINE
SIDE ARK
SKY MIRACLE
SOUTH WAIT
SPEAKEASY DATE
STAGECOACH ARMAGEDDON
STORY SIDE
SUSPECTS QUILLS
TRIP NEWTON
TRUMAN CRAZY
UPRISING UPTOWN
WATERFRONT DELIVERANCE
WEREWOLF LOLA
WOMEN DORADO
WORST BANGER
+

+

64 rows in set (0.02 sec)

УПРАЖНЕНИЕ 9.3

_

Соедините следующий запрос с подзапросом к таблице f ilm actor , чтобы показать уровень мастерства каждого актера:
SELECT 'Hollywood Star' level, 30 min_roles, 99999 max roles

_

UNION ALL
SELECT 'Prolific Actor' level, 20 min roles, 29 max roles
UNION ALL
SELECT 'Newcomer' level, 1 min roles, 19 max roles

_

_

Подзапрос к таблице film_actor должен подсчитывать количество строк
для каждого актера с использованием group by actor id, и результат под счета должен сравниваться со столбцами min roles /max roles, чтобы определить, какой уровень мастерства имеет каждый актер.
mysql> SELECT actr.actor_id, grps.level

_

->
->
->
->
->
->
->
->
->

_
_

FROM
(SELECT actor id, count(*) num roles
FROM film_actor
GROUP BY actor id
) actr
INNER JOIN
(SELECT 'Hollywood Star' level, 30 min_roles, 99999 max_roles
UNION ALL
SELECT Prolific Actor' level, 20 min roles, 29 max roles

_

_

Приложение Б. Ответы к упражнениям

| 383

->
->
->

->

UNION ALL
SELECT 'Newcomer level, 1 min roles, 19 max roles
) grps
ON actr.num_roles BETWEEN grps.min roles AND grps.max roles;

_

+

_

+

+

| actor id | level
+
+
+
1
Newcomer
2 Prolific Actor
3 Prolific Actor

6
7

Prolific Actor
Prolific Actor
Prolific Actor
Hollywood Star

195
196
197
198
199
200

Prolific Actor
Hollywood Star
Hollywood Star
Hollywood Star
Newcomer
Prolific Actor

4
5

+

+

+

200 rows in set (0.03 sec)

Глава 10
УПРАЖНЕНИЕ 10.1

Используя следующие определения таблиц и данные, напишите запрос, который возвращает имя каждого клиента вместе с его суммами платежей:
Customer :

Customer id Name

John Smith
Kathy Jones
Greg Oliver

1
2
3

_

Payment :

_

Payment id Customer id Amount

1
3
1

101
102
103

8.99
4.99
7.99

Включите в результирующий набор всех клиентов, даже если для клиента
нет записей о платежах.
mysql> SELECT с.name, sum(р.amount)
> FROM customer c LEFT OUTER JOIN payment p
-> ON c.customer id = p.customer id

-

384

_

.

Приложение Б Ответы к упражнениям

_

->

GROUP BY c.name;

+

+

I name

I

+

+

+

sum(p.amount)
+

| John Smith |
| Kathy Jones |
I Greg Oliver |

16.98
NULL
4.99

+

+

+

3 rows in set (0.00 sec)

УПРАЖНЕНИЕ 10.2
Измените запрос из упражнения 10.1 таким образом, чтобы использо вать другой тип внешнего соединения ( например, если вы использовали левое внешнее соединение в упражнении 10.1, на этот раз используйте правое
внешнее соединение) так, чтобы результаты были идентичны полученным

ранее.
MySQL>
->
->
->

SELECT c.name, sum(р.amount)
FROM payment p RIGHT OUTER JOIN customer c
ON c.customer_id = p.customer id
GROUP BY c.name;

_

sum(p.amount)

I name
+

+

+

+

+

John Smith |
Kathy Jones ! NULL
Greg Oliver |
+

I
+

16.98

4.99

+

+

3 rows in set (0.00 sec)

УПРАЖНЕНИЕ 10.3

Разработайте запрос, который будет генерировать набор {1, 2, 3, ..., 99,
100}. ( Указание: используйте перекрестное соединение как минимум с двумя
подзапросами в предложении from.)
SELECT ones.x + tens.x + 1
FROM
(SELECT 0 x UNION ALL
SELECT 1 x UNION ALL
SELECT 2 x UNION ALL
SELECT 3 x UNION ALL
SELECT 4 x UNION ALL
SELECT 5 x UNION ALL
SELECT 6 x UNION ALL
SELECT 7 x UNION ALL
SELECT 8 x UNION ALL
SELECT 9 x
Приложение Б. Ответы к упражнениям

385

) ones
CROSS JOIN
(SELECT 0 x UNION ALL
SELECT 10 x UNION ALL
SELECT 20 x UNION ALL
SELECT 30 x UNION ALL
SELECT 40 x UNION ALL
SELECT 50 x UNION ALL
SELECT 60 x UNION ALL
SELECT 70 x UNION ALL
SELECT 80 x UNION ALL
SELECT 90 x
) tens;

Глава 11
УПРАЖНЕНИЕ 11.1

Перепишите следующий запрос, в котором используется простое выражение case, так, чтобы получить те же результаты с использованием поискового выражения case. Старайтесь, насколько это возможно, использовать как
можно меньше предложений when.
SELECT name,
CASE name
WHEN 'English i
WHEN 'Italian'
WHEN 'French'
WHEN 'German'
WHEN 'Japanese'
WHEN 'Mandarin'
ELSE 'Unknown
END character set
FROM language;

THEN
THEN
THEN
THEN
THEN
THEN

'latinl
'latinl'
'latinl
'latinl'
'utf8'
'utf8'
i

_

SELECT name,
CASE
WHEN name IN ('English','Italian','French','German')
THEN 'latinl'
WHEN name IN ('Japanese','Mandarin')
THEN 'utf8!
ELSE 'Unknown'
END character_set
FROM language;

УПРАЖНЕНИЕ 11.2
Перепишите следующий запрос так, чтобы результирующий набор содержал одну строку с пятью столбцами ( по одному для каждого рейтинга ). Назовите эти пять столбцов G, PG, PG 13, R и NC 17 .
386

.

Приложение Б Ответы к упражнениям

mysql> SELECT rating, count(*)
> FROM film
-> GROUP BY rating;

-

+

+

+

rating | count(*) |
+

+

+

PG
G
NC 17
PG-13
R

194
178
210
223
195

-

+

+

+

5 rows in set (0.00 sec)
mysql> SELECT
-> sum(CASE
-> sum(CASE
-> sum(CASE
-> sum(CASE
-> sum(CASE
-> FROM film;
+

+

+
g

WHEN
WHEN
WHEN
WHEN
WHEN

rating
rating
rating
rating
rating

'G' THEN 1 ELSE 0 END) g,
'PG' THEN 1 ELSE 0 END) pg,
'PG-13' THEN 1 ELSE 0 END) pg 13,
'R' THEN 1 ELSE 0 END) r,
'NC-17' THEN 1 ELSE 0 END) nc 17

_
_

+

+

+

pg_i3 | r

I pg

=
=
=
=
=

I nc_17 |

| 178 | 194
223 | 195 !
+
+
+
+
+
1 row in set (0.00 sec)

210 |
+

Глава 12
УПРАЖНЕНИЕ 12.1
Создайте логическую единицу работы для перевода 50 долларов со счета
123 на счет 789. Для этого вставьте две строки в таблицу transaction и обновите две строки в таблице account. Используйте следующие определения /

данные таблиц:
Account :

_

account id avail_balance last_activity_date
123
789

500
75

2019-07-10 20:53:27
2019 06 22 15:18:35

- -

Transaction:

txn id

txn date

1001
1002

2019 05-15
2019-06-01

-

_

account_id

txn type_cd amount

123
789

C
C

500
75

Приложение Б. Ответы к упражнениям

387

_

Используйте txn type _cd = ' С ' , чтобы указать операцию кредита (добавление на счет ), и txn type _ cd = ' D ' для обозначения дебета ( снятия со
счета ).

_

START TRANSACTION;
INSERT INTO transaction
(txn id, txn date, account id, txn type cd, amount)
VALUES
(1003, now(), 123, D', 50);

_

_

_

_

_

INSERT INTO transaction
(txn_id, txn_date, account_id, txn_type_cd, amount)
VALUES
(1004, now(), 789, 'C , 50);
UPDATE account
SET avail_balance = available balance
last activity date = now()
WHERE account id = 123;

_

_

_

-

50,

UPDATE account
SET avail balance = available_balance + 50,
last_activity_date = now()
WHERE account id = 789;

_

COMMIT;

Глава 13
УПРАЖНЕНИЕ 13.1

Сгенерируйте инструкцию alter table для таблицы rental так, чтобы
генерировалось сообщение об ошибке, когда строка со значением, имеющим ся в столбце rent . customer id, удаляется из таблицы customer.

_

ALTER TABLE rental
ADD CONSTRAINT fk_rental_customer id FOREIGN KEY (customer id)
REFERENCES customer (customer id) ON DELETE RESTRICT;

_

_

УПРАЖНЕНИЕ 13.2

Создайте многостолбцовый индекс в таблице payment , который может использоваться обоими приведенными далее запросами:

_

_

SELECT customer id , payment date, amount
FROM payment
WHERE payment date > cast('2019-12-31 23:59:59

_

SELECT customer_id, payment_date, amount

388

Приложение Б. Ответы к упражнениям

as datetime);

FROM payment
WHERE payment date > cast('2019-12-31 23:59:59 as datetime)
AND amount < 5;

_

_

SELECT customer id , payment_date, amount
FROM payment
WHERE payment date > cast('2019-12-31 23:59:59 i as datetime);

_

_

_

SELECT customer id, payment date, amount
FROM payment
WHERE payment date > cast('2019-12-31 23:59:59 i as datetime)
AND amount < 5;

_

_

CREATE INDEX idx payment01
ON payment (payment_date, amount);

Глава 14
УПРАЖНЕНИЕ 14.1
Создайте определение представления, которое можно использовать с помощью следующего запроса для генерации приведенного результирующего
набора:

_

_

SELECT title, category name, first_name, last_name
FROM film ctgry actor
WHERE last name = 'FAWCETT';
+

_

+

_

+

_

+

+

category name | first name | last_name |
+
+
+
+
+
ACE GOLDFINGER
FAWCETT
Horror
BOB
ADAPTATION HOLES
FAWCETT
BOB
Documentary
New
CHINATOWN GLADIATOR
FAWCETT
BOB
CIRCUS YOUTH
FAWCETT
Children
BOB
Comedy
CONTROL ANTHEM
FAWCETT
BOB
DARES PLUTO
Animation
BOB
FAWCETT
FAWCETT
DARN FORRESTER
Action
BOB
Games
BOB
FAWCETT
DAZED PUNK
DYNAMITE TARZAN
Classics
BOB
FAWCETT
Comedy
HATE HANDICAP
BOB
FAWCETT
HOMICIDE PEACH
Family
BOB
FAWCETT
JACKET FRISCO
Drama
BOB
FAWCETT
JUMANJI BLADE
New
BOB
FAWCETT
LAWLESS VISION
Animation
BOB
FAWCETT
LEATHERNECKS DWARFS Travel
BOB
FAWCETT
OSCAR GOLD
Animation
BOB
FAWCETT
PELICAN COMFORTS
Documentary
FAWCETT
BOB
PERSONAL LADYBUGS
Music
BOB
FAWCETT
RAGING AIRPLANE
Sci Fi
BOB
FAWCETT
RUN PACIFIC
New
BOB
FAWCETT
RUNNER MADIGAN
BOB
Music
FAWCETT

I title

I

-

Приложение Б. Ответы к упражнениям

389

SADDLE ANTITRUST
SCORPION APOLLO
SHAWSHANK BUBBLE
TAXI KICK
BERETS AGENT
BOILED DARES
CHISUM BEHAVIOR
CLOSER BANG
DAY UNFAITHFUL
HOPE TOOTSIE
LUKE MUMMY
MULAN MOON
OPUS ICE
POLLOCK DELIVERANCE
RIDGEMONT SUBMARINE
SHANGHAI TYCOON
SHAWSHANK BUBBLE
THEORY MERMAID
WAIT CIDER
+

BOB
BOB
BOB
BOB
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA
JULIA

Comedy
Drama
Travel
Music
Action
Travel
Family
Comedy
New
Classics
Animation
Comedy
Foreign
Foreign
New
Travel
Travel
Animation
Animation
+

+

FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
FAWCETT
+

+

40 rows in set (0.00 sec)

CREATE VIEW film_ctgry_actor
AS
SELECT f.title,
c.name category_name,
a.first_name,
a.last name
FROM film f
INNER JOIN film_category fc
ON f.film_id = fc.film id
INNER JOIN category c
ON fc.category id = c.category id
INNER JOIN film actor fa
ON fa.film id = f.film id
INNER JOIN actor a
ON fa.actor id = a.actor id;

_

_

_

_
_

_

_

УПРАЖНЕНИЕ 14.2
Менеджер компании по прокату фильмов хотел бы иметь отчет , который
включает в себя название каждой страны, а также общие платежи всех клиентов, которые живут в данной стране. Создайте определение представления,
которое запрашивает таблицу country и использует для вычисления значения столбца tot payments скалярный подзапрос.

_

CREATE VIEW country_payments
AS
SELECT c.country,
(SELECT sum(p.amount)

390

Приложение Б. Ответы к упражнениям

FROM city ct
INNER JOIN address a
ON ct.city id = a.city id
INNER JOIN customer cst
ON a.address id = cst.address id
INNER JOIN payment p
ON cst.customer id = p.customer_id
WHERE ct.country id = c.country id
) tot payments
FROM country c

_

_

_

_

_

_

_

_

Глава 15
УПРАЖНЕНИЕ 15.1
Напишите запрос, который перечисляет все индексы в схеме Sakila. Не забудьте включить в результаты имена таблиц.

_

mysql> SELECT DISTINCT table_name, index name
-> FROM information schema.statistics
-> WHERE table schema = 'sakila';
+
+
+
TABLE NAME
INDEX NAME
+
+
+
PRIMARY
actor
idx actor last name
actor
address
PRIMARY
address
idx_fk_city_id
idx location
address

_

_

_

_

_

category
city
city
country

film
film
film
film
film actor
film_actor
film category
film_category
film text
film text
inventory
inventory
inventory
language
staff
staff
staff

_
_
_
_

store

PRIMARY
PRIMARY

_

idx_fk_country id
PRIMARY
PRIMARY
idx_title
idx_fk language id
idx fk original_language id
PRIMARY
idx_fk_film_id
PRIMARY
fk_film_category_category
PRIMARY
idx title description
PRIMARY
idx_fk_film_id
idx store id film_id
PRIMARY
PRIMARY
idx_fk store_id
idx fk address_id
PRIMARY

_
_ _

_

_

_

_

_ _

_

_
_ _

Приложение Б. Ответы к упражнениям

391

store
store
customer
customer
customer
customer
customer
customer
rental
rental
rental
rental
rental
payment
payment
payment
payment
payment

_
_
_ _
_
_
_ _
_
_ _

idx unique manager
idx fk address id
PRIMARY
idx email
idx_fk_store_id
idx fk address id
idx last name
idx_full_name
PRIMARY
rental_date
idx fk inventory id
idx_fk_customer_id
idx fk staff id
PRIMARY
idx_fk_staff_id
idx fk customer id
fk payment rental
idx payment01

_ _
_ _

_

_

_ _
_
_
_

_

+
+
45 rows in set (0.00 sec)

+

УПРАЖНЕНИЕ 15.2
Напишите запрос, генерирующий вывод, который можно было бы использовать для создания всех индексов таблицы sakila . customer. Вывод должен
иметь вид
"ALTER TABLE ADD INDEX { )"

Вот одно решение, использующее предложение with:
mysql> WITH idx_info AS
-> (SELECT si.table_name, si.index_name,
-> si.column_name, si.seq_in index,
> (SELECT max(s2.seq in index)
-> FROM information_schema.statistics s2
WHERE s2.table schema = si.table schema
>
AND s2.table_name = si.table_name
->
AND s2.index name = si.index name) num columns
>
>
FROM information schema.statistics si
-> WHERE si.table schema = 'sakila'
AND si.table name = customer i
->
-> )
> SELECT concat(
-> CASE
WHEN seq in index = 1 THEN
->
concat('ALTER TABLE i , table name,
ADD INDEX
->
index name, (', column name)
->
>
ELSE concat(' , , column name)
-> END,
-> CASE

_ _

-

_

_

_

_
_
_

_

_

-

_ _

_

-

392

.

Приложение Б Ответы к упражнениям

_

_

->
->
->

WHEN
ELSE
END
> ) index
-> FROM idx
-> ORDER BY

_ _

_

seq in index = num columns THEN ');
i i

_creation_statement
_info
index_name, seq_in_index;

-

+

+

| index creation statement
+

+

ALTER TABLE customer
ALTER TABLE customer
ALTER TABLE customer
ALTER TABLE customer
, first_name);
ALTER TABLE customer
ALTER TABLE customer

ADD
ADD
ADD
ADD

INDEX
INDEX
INDEX
INDEX

idx_email (email);
idx fk address id (address_id);
idx fk store id (store id);
idx full name (last name

_ _
_
_ _ _
_ _

_

_

ADD INDEX idx_last_name (last_name);
ADD INDEX PRIMARY (customer id);
+

+

7 rows in set (0.00 sec)

Однако после прочтения главы 16, “Аналитические функции”, вы сможете
написать следующее решение:
mysql> SELECT concat('ALTER TABLE I , table name, i ADD INDEX ! r

_
_

->
->
->
->
->
->

index name, 1 I r
group concat(column name order by
seq in index separator

.I

_

_
_ _

', I ),

_

) index creation statement
FROM information schema.statistics
> WHERE table schema = 'sakila'
-> AND table_name = i customer i
-> GROUP BY table name, index name;

_

-

+

+

index creation statement
+

+
_email (email);
_fk_address_id (address_id);
_fk_store_id (store_id);
_full_name (last_name,
first_name);
ALTER TABLE customer ADD INDEX idx_last_name (last_name);

ALTER
ALTER
ALTER
ALTER

TABLE
TABLE
TABLE
TABLE

customer
customer
customer
customer

ADD
ADD
ADD
ADD

INDEX
INDEX
INDEX
INDEX

idx
idx
idx
idx

ALTER TABLE customer ADD INDEX PRIMARY (customer id);
+

+

6 rows in set (0.00 sec)

Глава 16
Для всех упражнений этого раздела используйте набор данных из таблицы

Sales Fact.
Приложение Б. Ответы к упражнениям

| 393

Sales Fact
+

+

_

year no
+

+
+
month no | tot sales |

+

+

+

+

1
2
3
4
5
6
7

2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2019
2020
2020
2020
2020
2020
2020
2020
2020
2020
2020
2020
2020

19228
18554
17325
13221
9964
12658
14233
17342
16853
17121
19095
21436
20347
17434
16225
13853
14589
13248
8728
9378
11467
13842
15742
18636

8

9
10
11

12
1
2
3
4
5
6
7
8

9
10
11
12

+

+

+

24 rows in set (0.00 sec)

УПРАЖНЕНИЕ 16.1
Напишите запрос, который извлекает каждую строку из Sales Fact и добавляет столбец для генерации рейтинга на основе значений столбца tot
sales. Самое высокое значение должно получить рейтинг 1, а самое низкое рейтинг 24.
mysql> SELECT year no, month no, tot sales,

_



+

I

_

_

_

_

_

-> rank() over (order by tot sales desc) sales rank
-> FROM sales fact;
+
+
+
+
year_no
month no | tot sales | sales rank |
2019
2020
2019
2019
2020
2019

394

+

+

+

+

12

1
1
11
12

2

.

21436
20347
19228
19095
18636
18554

Приложение Б Ответы к упражнениям

+

1
2

3
4
5
6

_

2020
2019
2019
2019
2019
2020
2020
2020
2019
2020
2020
2020
2019
2019
2020
2019
2020
2020

2
8
3

10
9
3
11

5
7
4
10
6
4
6
9
5
8

7

+
+
+
24 rows in set (0.02 sec)

7

17434
17342
17325
17121
16853
16225
15742
14589
14233
13853
13842
13248
13221
12658
11467
9964
9378
8728

8

9
10
11

12
13
14

15
16
17
18
19
20
21
22
23
24
+

+

УПРАЖНЕНИЕ 16.2
Измените запрос из предыдущего упражнения так, чтобы генерировались два набора рейтингов от 1 до 12: один
для 2019 года и один
для
2020 года.



_

_



mysql> SELECT year no, month_no, tot sales,
-> rank() over (partition by year no
order by tot sales desc) sales rank
->
-> FROM sales fact;
+
+
+
+
+
| year_no
month no | tot sales sales rank |
+
+
+
+
+
1
12
2019
21436
2
1
2019
19228
3
11
2019
19095
2019
18554
4
2
2019
5
17342
8
2019
3
6
17325
7
2019
10
17121
2019
9
8
16853
7
2019
14233
9
4
10
2019
13221
2019
12658
6
11
2019
5
9964
12
2020
1
1
20347
2020
12
2
18636
2020
17434
2
3
2020
3
4
16225

_

_

_

.

Приложение Б Ответы к упражнениям

395

2020
2020
2020
2020
2020
2020
2020
2020

11
5

4
10
6
9
8

7

+
+
+
24 rows in set (0.00 sec)

5

15742
14589
13853

6
7
8
9
10

13842
13248
11467
9378
8728

11

12
+

+

УПРАЖНЕНИЕ 16.3
Напишите запрос, который извлекает все данные за 2020 год, и включите
столбец, который будет содержать значение tot _sales для предыдущего месяца.
mysql> SELECT year_no, month no, tot sales,

_

_

_

_

_

lag(tot_sales) over (order by month no) prev month sales
> FROM sales fact
-> WHERE year_no = 2020;
+
+
+
+
+
I year no I month no | tot_sales I prev_month sales I
+
+
+
+
NULL
20347
2020
1
2
2020
17434
20347
16225
17434
2020
3
4
16225
2020
13853
2020
14589
13853
5
14589
13248
2020
6
7
2020
8728
13248
2020
9378
8728
8
2020
9
11467
9378
10
2020
11467
13842
11
2020
15742
13842
2020
18636
15742
12
+
+
+
+
+
12 rows in set (0.00 sec)

-

>

_

_

396

_

.

Приложение Б Ответы к упражнениям

_

Предметный указатель
А
all 75, 202
any 203
Apache Drill 34
работа c MySQL 353
asc 87

В
between 98
bigint 45

c
case 239
char 41
clob 44, 146

D
date 47, 169
datetime 47, 169
delete 61
desc 53, 87
describe 53
distinct 74, 185
double 46

E
exists 207
explain 273

F
float 46
from 75

G
group by 84

н
Hadoop 346

I
in 199
insert 57

int 45

interval 173

J

JSON

27, 347

L
like 105
longtext 44

M
mediumint 45
mediumtext 44
MySQL 37, 42, 122, 145
блокировка 253
вставка в строку 158
деление на нуль 247
диапазон дат 48
метаданные 296
механизмы хранения 259
одинарные кавычки 149
создание индекса 265
специальные символы 149
транзакции 256
удаление индекса 267
удаление строк 210

N
NoSQL 347
not 93
null 107, 186

О
Oracle Database 33, 40, 41, 122, 145, 158
блокировка 253
виртуальная частная база данных 285
вставка в строку 159
деление на нуль 247
диапазон дат 48
итоговые данные 192
конкатенация строк 151
метаданные 296
настройка часового пояса 168

одинарные кавычки 149
поиск подстрок 154
символьные данные 44
создание индекса 265
специальные символы 150
транзакции 256
удаление индекса 267
order by 85

where 81

X
XML 27, 60, 347

Y
year 47

A

P
PL /SQL 30

R
regexp 107

S
select 70
smallint 45
SQL
динамическое выполнение 306
SQL Server 23, 33, 44, 122, 145, 158
блокировка 253
вставка в строку 159
диапазон дат 48
конкатенация строк 151
метаданные 296
символьные данные 44
создание индекса 265
специальные символы 149
транзакции 256
удаление индекса 267

Автоинкремент 56
Автофиксация 256
Атомарность 254

Б
База данных 19
секционирование 331
Блокировка 252
гранулярность 253
записи 252
чтения 252
Большие данные 346

В
Взаимоблокировка 258
Выражение case 240
поисковое 240
простое 242

д
Декартово произведение 115, 228

з

т
text 44, 146
time 47, 169
timestamp 47, 169
tinyint 45
tinytext 44
Transact -SQL 30

u
update 61

Запись 26
Запрос
группировка 84
оптимизатор 68
план выполнения 68, 273
подзапрос 76, 195
представление 78
содержащий 77, 195
сортировка 85
составной 133

UTC 167

V
varchar 41, 146

удаление дубликатов 74
фильтрация 81
from 75
select 70

w

и

when 241

Иерархия

398

Предметный указатель

с одним родителем 21
со многими родителями 21
Индекс 264
битовый 270
глобальный 333
локальный 333
многостолбцовый 269
полнотекстовый 272
секционирование 333

создание 264
уникальный 268
B- tree 269
История SQL 26
Итоговые данные 190

К
Каскадное обновление 278
Кластеризация 344
Ключ
внешний 25, 26
естественный 24
первичный 24, 26
составной 24
суррогатный 24
Комментарий 31

Л
Летнее время 166

м
Масштаб 46
Метаданные 28, 295
Механизм хранения 259
Множество 129
объединение 130
пересечение 130
теория 129

н
Набор символов 42
Нормализация 25, 113

О
Обобщенное табличное выражение 217
Ограничение 52, 275
внешнего ключа 55
первичного ключа 52
проверочное 52
типы 275

Окна данных 312
рамки 324
Оптимизатор 29, 68

п
Подзапрос 76, 195

коррелированный 197, 206
некоррелированный 197
скалярный 197, 218
Представление 78, 281
обновляемое 288
Псевдоним столбца 73

Р
Ранжирование 315
Регулярные выражения 106
Результирующий набор 27, 68
Реляционная модель 22

с
Самосоединение 126
Секционирование 331
вертикальное 333
горизонтальное 333
индекса 333
ключ 333
композитное 341
отсечение разделов 343
по диапазону 334
по списку 337
по хешу 339
функция 333
Системный каталог 296
Сканирование таблицы 264
Словарь данных 28, 296
Соединение 25, 113, 114
внешнее 224
левое 225, 226
правое 226
трехстороннее 227
естественное 235
перекрестное 115, 205, 229
Сокрытие таблиц 284
Сортировка 85, 87
Столбец 23, 26
псевдоним 73
Страница 253
Строка 23, 26
СУБД 20

иерархическая 20
Предметный указатель

399

T
Таблица 26, 76

ведущая 122
виртуальная 76, 78
временная 77
постоянная 76
производная 76
псевдоним 80
связь 79
секционирование 332
соединение 80
Тип данных 40
временной 46, 166
символьный 41
строковый 145
текстовый 44
числовой 45, 160
Точность 46, 163
Транзакция 254
точка сохранения 259

у
Управление версиями 252
Условная логика 239

D
Фильтрация 91
групповая 192
условие 91
диапазона 97

неравенства 96
равенства 95
регулярное выражение 106

соответствия 104
членства 102

Функция

агрегатная 180, 183
аналитическая 311
математическая 161
отчетности 321
ранжирования 315
хеширования 339
avg() 183
cast() 170, 177
ceil () 163
ceilingO 163
charindx( ) 154
concat() 157
count() 181, 183
current date() 172

_

400

Предметный указатель

current_time() 172
current_timestamp() 172
date add () 173
datediff() 176
dense_rank() 315
extractO 175
floor() 163
getutcdate() 167
insert( ) 158
lag() 327
last_ day() 174
lead() 327
len() 152
length () 152
locate() 153
max( ) 183
min () 183
mod( ) 161
now() 40
position () 153
pow() 162
power( ) 162
rank() 315
replace() 159
round() 164
row number() 315
strcmpO 154
str_to_date() 171
stuff() 159
substr() 160
substringO 160
sum () 183
to date() 172
trunc() 163
truncate() 163, 164
utc timestamp() 167

_

_

_

_

4
Часовой пояс 166

ш
Шардинг 344

Дана книга вщр1зняеться широким охопленням як тем ( вщ азш SQL до таких
складних питань, як аналггичш функци та робота з великими базами даних) ,
так i конкретних баз даних ( MySQL, Oracle Database, SQL Server ) i особливостей реалЬацй тих чи шших функцюнальних можливостей SQL на цих серверах.
Книга щеально пщходить в якосп пщручника для початк1вця - розробника в об лает ! баз даних. У нш описаш Bci можлив1 застосування мови SQL i найб1льш
поширен1 сервери баз даних.

Науково-попупярне видання
Болье, Алан

Вивчаемо SQL
Генерацш, вибфка та обробка даних
3-е видання
( Рос. мовою)
Зав. редакцию С . М . Тригуб

1з загальних питань звертайтеся до видавництва “Дтлектика
info@ dialektika.com , http:// www.dialektika . com



за адресою:

Пщписано до друку 31.05. 2021 . Формат 60x90/ 16
Ум. друк . арк. 25 , 0. Обл. - вид. арк . 18 , 5
Зам . 21 - 1753

Видавець ТОВ “ Комп’ютерне видавництво “ Дтлектика ”
03164 , м. Кию, вул. Генерала Наумова , буд. 23- Б .
Свщоцтво суб’екта видавничо! справи ДК 6758 вщ 16.05.2019.
'

Надруковано ТОВ “АЛЬФА ГРАФ1К ”
03067, м . Ки1в, вул. Машинобуд1вна , 42
Свщоцтво суб’екта видавничо! справи ДК 6838 вщ 09.07 . 2019.
'

'

SQL ЗА 10 МИНУТ
5-е издание
Эксперт по базам данных Бен
Форта расскажет обо всем , что
касается основ SQL: от простых
запросов на выборку данных до
более сложных тем, таких как соединения, подзапросы, хранимые
Бен Форта
процедуры, курсоры, триггеры
и табличные ограничения . Все
темы последовательно излагаются
в виде простых и коротких уроков,
на каждый из которых уйдет не
более 10 минут . Большинство уроков дополняется упражнениями,
предназначенными для закрепле5-е издание
ния материала.
В книге:
основные инструкции SQL;
создание сложных запросов
за
с множеством предложений
и операторов;
извлечение, сортировка и форматирование данных;
е
фильтрация результатов
W
запроса;
применение итоговых функций
для получения сводных данных;
www. williamspublishing.com
соединение таблиц;
добавление, обновление и удаление данных;
создание и изменение таблиц;
работа с представлениями , хранимыми процедурами и триггерами.

Бен Форта

SQL

10

минут

ISBN 978- 5- 907365-67- 4

в продаже

O'REILLY '

Изучаем SQL
Как только ваша компания сталкивается с большими потоками
данных, их необходимо сразу же заставить работать на пользу
компании (и всего человечества), и лучший инструмент для
этого — язык SQL. В третьем издании данного руководства его
автор, Алан Болье, помогает разработчикам освоить основы
SQL для написания приложений, работающих с базами данных,
выполнения административных задач и создания отчетов. В этом
издании вы найдете новые главы, посвященные аналитическим
функциям, стратегиям работы с большими базами данных и
связям SQL с большими данными.

Каждая глава представляет собой самостоятельный урок по той
или иной ключевой концепции или методике SQL, в котором
используются многочисленные иллюстрации и примеры
с комментариями. Приводимые в конце глав упражнения
позволяют применить изученный материал на практике.
С помощью этой книги вы приобретете необходимые для
эффективной работы с данными знания и сможете быстро
применить всю мощь и гибкость языка SQL для практической
работы. В книге, в частности, раскрыты следующие темы.
Изучение основ SQL и некоторых его расширенных
возможностей
Использование инструкций SQL для генерации, выборки
и обработки данных
ш
Создание объектов баз данных, таких как таблицы,
индексы и ограничения, с помощью инструкций схемы SQL
Взаимодействие наборов данных с запросами;
применение подзапросов и их важность для построения
сложных запросов
Преобразование и манипулирование данными с помощью
встроенных функций SQL и применения условной логики в
инструкциях данных

"От азов SQL до таких сложных

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

— Марк Ричардс,
автор книги Fundamentals of Software
Architecture

Алан Болье более 25 лет
проектировал и создавал
приложения для работы с базами
данных. Он — один из авторов
книги Mastering Oracle SQL
и автор онлайн- курса по SQL
Калифорнийского университета.
Алан создал собственную
консалтинговую компанию,
которая специализируется на
проектировании и разработке
баз данных в области финансов
и телекоммуникаций.

SBN 978 -617-7987-01-6
21

9 786177 987016

93

Комп 'ютерне видавництво
"fllA /lEKTHKA"
www.dialektika.com

http://oreilly.com