SQL и реляционная теория. Как грамотно писать код на SQL [К. Дж. Дейт] (pdf) читать онлайн

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


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

SQL и реляционная теория

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

Спрашивайте
наши книги:

Стефан Фаро,
Паскаль Лерми
Рефакторинг
SQL-приложений

Кевин Кляйн,
Дэниэл Кляйн,
Брэнд Хант
SQL. Справочник,
3-е издание

Алан Бьюли
Изучаем SQL

Энтони Молинаро
SQL. Сборник
рецептов

Êàòåãîðèÿ: базы данных  /  SQL

и реляционная теория

Независимый автор, лектор, исследователь и консультант, начал работать с базами данных в 1970 году
в корпорации IBM. Его книги используются в качестве учебного
пособия в сотнях колледжей и университетов по всему миру. Он считается одним из наиболее фундаментальных исследователей всех
аспектов управления базами данных. В 2004 году имя Дейта было
навечно вписано в Зал славы компьютерной индустрии. Его репутация непревзойденного мастера
объяснять сложные технические
вопросы простым и понятным языком снискала ему широкую известность и уважение среди профессионалов.

SQL

К. Дж. Дейт

Язык SQL распространен повсеместно. Но работать с ним непросто: он сложен, запутан, при написании SQL-команд легко допустить ошибку. Понимание тео­рии, лежащей в основе SQL, – лучший способ гарантировать, что ваш код будет написан правильно,
а сама база данных – надежна и легко сопровождаема.
К. Дж. Дейт – признанный эксперт, начавший заниматься вопросами управления базами данных еще в 1970 году, – демонстрирует, как применить реляционную теорию к повседневной работе
с SQL. Автор подробно объясняет различные аспекты этой модели,
рассуждает и доказывает, приводит многочисленные примеры использования этого языка в соответствии с реляционной теорией.
В книге обсуждаются типичные вопросы, возникающие при
работе с SQL, и их решение:
• Следует ли давать разрешения на доступ к представлениям или
базовым таблицам?
• Почему наличие null-значений в базе данных приводит к неверным ответам и что с этим можно сделать?
• Почему следует избегать «квантифицированных сравнений»
и как это можно реализовать?
• Как разрешить проблему отсутствия поддержки ограничения
целостности в большинстве SQL-систем?

Óðîâåíü ïîäãîòîâêè ÷èòàòåëåé: средний

www.symbol.ru
Издательство «Символ-Плюс»
(812) 324-5353, (495) 945-8100

9 785932 861738

К. ДЖ. ДЕЙТ

ISBN 978-5-93286-173-8

По договору между издательством «Символ-Плюс» и Интернет-мага­
зином «Books.Ru – Книги России» единственный легальный способ
получения данного файла с книгой ISBN 978-5-93286-173-8, назва­ние
«SQL и реляционная теория. Как грамотно писать код на SQL» – покупка в Интернет-магазине «Books.Ru – Книги России». Если Вы
получили данный файл каким-либо другим образом, Вы нарушили
междуна­родное законодательство и законодательство Российской Федерации об охране авторского права. Вам необходимо удалить данный
файл, атакже сообщить издательству «Символ-Плюс» (piracy@symbol.
ru), где именно Вы получили данный файл.

SQL

and Relational
Theory
How to Write Accurate SQL Code

C. J. Date

SQL

и реляционная
теория
Как грамотно писать код на SQL

К. Дж. Дейт

Санкт-Петербург – Москва
2010

К. Дж. Дейт

SQL и реляционная теория
Как грамотно писать код на SQL
Перевод А. Слинкина
Главный редактор
Зав. редакцией
Выпускающий редактор
Редактор
Корректор
Верстка

А. Галунов
Н. Макарова
П. Щеголев
Ю. Бочина
С. Минин
К. Чубаров

К. Дж. Дейт
SQL и реляционная теория. Как грамотно писать код на SQL. – Пер. с англ. –
СПб.: Символ-Плюс, 2010. – 480 с., ил.
ISBN 978-5-93286-173-8
Язык SQL распространен повсеместно. Но работать с ним непросто: он сложен,
запутан, при написании SQL-команд легко допустить ошибку. Понимание тео­
рии, лежащей в основе SQL, – лучший способ гарантировать, что ваш код будет написан правильно, а сама база данных надежна и легко сопровождаема.
В предлагаемой книге К. Дж. Дейт – признанный эксперт, начавший заниматься этими вопросами еще в 1970 году, – демонстрирует, как применить реляционную теорию к повседневной практике работы с SQL. Автор подробно
объясняет различные аспекты этой модели, рассуждает и доказывает, приводит многочисленные примеры использования этого языка в соответствии с реляционной теорией.
Не будучи привязанным ни к какому конкретному продукту, издание опирается на многолетний опыт исследований и представляет наиболее актуальное
на сегодняшний день изложение материала. Всякий, имеющий хоть какой-то
опыт использования SQL – от скромного до весьма обширного, – получит от
прочтения этой книги немалую пользу и удовольствие.
ISBN 978-5-93286-173-8
ISBN 978-0-596-52306-0 (англ)
© Издательство Символ-Плюс, 2010
Authorized translation of the English edition © 2009 O’Reilly Media Inc. This trans­
lation is pub­lished and sold by permission of O’Reilly Media Inc., the owner of all rights
to publish and sell the same.
Все права на данное издание защищены Законодательством РФ, включая право на полное или час­
тичное воспроизведение в любой форме. Все товарные знаки или зарегистрированные товарные зна­
ки, упоминаемые в настоящем издании, являются собственностью соответствующих фирм.

Издательство «Символ-Плюс». 199034, Санкт-Петербург, 16 линия, 7,
тел. (812) 324-5353, www.symbol.ru. Лицензия ЛП N 000054 от 25.12.98.
Налоговая льгота – общероссийский классификатор продукции
ОК 005-93, том 2; 953000 – книги и брошюры.
Подписано в печать 17.02.2010. Формат 70×100 1/16. Печать офсетная.
Объем 30 печ. л. Тираж 1200 экз. Заказ №
Отпечатано с готовых диапозитивов в ГУП «Типография «Наука»
199034, Санкт-Петербург, 9 линия, 12.

Всем считающим,
что подобное упражнение
стоит затраченного времени,
а в особенности Лексу де Хаану,
которого так недостает.

Всякий, кто полагается на практику,
не зная теории, подобен кормчему,
вступающему на судно без руля и компаса, –
он не знает, куда плывет.
Практика всегда должна опираться
на твердые теоретические основания.
Леонардо да Винчи (1452–1519)

Не в том беда, что мы чего-то не знаем,
а в том, что наши знания кажущиеся.
Джош Биллингс (1818–1885)

Языки умирают...
Математические идеи – нет.
Дж. Х. Харди

К сожалению, разрыв между теорией
и практикой в теории не настолько широк,
как на практике.
Аноним

Оглавление
Предисловие................................................................................ 13
Глава 1. Введение......................................................................... 21
Реляционная модель очень плохо понята....................................... 22
Некоторые замечания о терминологии.......................................... 23
Принципы, а не продукты........................................................... 25
Обзор оригинальной модели........................................................ 26
Модель и реализация.................................................................. 35
Свойства от­но­ше­ний................................................................... 39
Базовые и производные от­но­ше­ния............................................... 42
От­но­ше­ния и пе­ре­мен­ные-от­но­ше­ния........................................... 44
Значения и пе­ре­мен­ные.............................................................. 46
Заключительные замечания........................................................ 47
Упражнения.............................................................................. 49

Глава 2. Типы и домены................................................................ 51
Типы и от­но­ше­ния..................................................................... 51
Сравнения на равенство.............................................................. 52
Атомарность значений данных..................................................... 58
Что такое тип?........................................................................... 62
Скалярные и нескалярные типы................................................... 66
Скалярные типы в SQL................................................................ 68
Проверка и приведение типов в SQL.............................................. 70
Схемы упорядочения в SQL......................................................... 72
Тип строки и таб­лицы в SQL........................................................ 74
Заключительные замечания........................................................ 76
Упражнения.............................................................................. 77

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы.......................... 81
Что такое кортеж?...................................................................... 81
Следствия из определений........................................................... 84
Строки в SQL............................................................................. 86
Что такое от­но­ше­ние?................................................................. 88
От­но­ше­ния и их тела.................................................................. 90
От­но­ше­ния n-мерны...................................................................91

8

Оглавление

Сравнение от­но­ше­ний................................................................. 92
TABLE_DUM и TABLE_DEE........................................................ 93
Таб­лицы в SQL........................................................................... 94
Именование столбцов в SQL.........................................................96
Заключительные замечания........................................................ 98
Упражнения..............................................................................99

Глава 4. Нет дубликатам, нет null-значениям.............................. 101
Чем плохи дубликаты?............................................................. 101
Дубликаты: новые проблемы..................................................... 106
Как избежать дубликатов в SQL................................................. 107
Чем плохи null-значения?......................................................... 109
Как избежать null-значений в SQL.............................................. 113
Замечание о внешнем соединении............................................... 115
Заключительные замечания...................................................... 116
Упражнения............................................................................ 117

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы...... 121
Определения данных................................................................ 122
Обновление – это операция над множес­твом................................. 122
Реляционное присваивание....................................................... 125
Принцип присваивания............................................................ 126
Еще о потенциальных ключах.................................................... 127
Еще о внешних ключах............................................................. 130
Пе­ре­мен­ные-от­но­ше­ния и предикаты......................................... 134
От­но­ше­ния и типы................................................................... 137
Упражнения............................................................................ 139

Глава 6. SQL и реляционная алгебра I:
оригинальные операторы........................................................... 142
Предварительные сведения....................................................... 142
Еще о замкнутости................................................................... 145
Ограничение........................................................................... 149
Проекция................................................................................ 149
Соединение............................................................................. 151
Объединение, пересечение и разность......................................... 155
Какие операторы являются примитивными?............................... 157
Пошаговое конструирование выражений..................................... 157
В чем смысл реляционных выражений?...................................... 159
Вычисление таб­личных выражений в SQL................................... 160
Трансформация выражений...................................................... 161
Зависимость от имен атрибутов.................................................. 165
Упражнения............................................................................ 167

Оглавление

9

Глава 7. SQL и реляционная алгебра II:
дополнительные операции......................................................... 171
Полусоединение и полуразность................................................. 171
Расширение............................................................................ 172
От­но­ше­ния-образы................................................................... 174
Деление.................................................................................. 177
Агрегатные операторы.............................................................. 179
Еще об от­но­ше­ниях-образах...................................................... 183
Обобщение.............................................................................. 185
Еще об обобщении.................................................................... 190
Группирование и разгруппирование........................................... 191
Запросы «что если».................................................................. 193
А как насчет ORDER BY?.......................................................... 194
Упражнения............................................................................ 195

Глава 8. SQL и ограничения целостности..................................... 200
Ограничения типа.................................................................... 201
Еще об ограничениях типа........................................................ 204
Ограничения типа в SQL............................................................ 205
Ограничения базы данных......................................................... 206
Ограничения базы данных в SQL................................................ 210
Транзакции............................................................................. 211
Почему ограничения базы данных
должны проверяться немедленно............................................... 213
Но разве можно не откладывать
проверку некоторых ограничений?............................................. 216
Ограничения и предикаты......................................................... 219
Разное.................................................................................... 221
Упражнения............................................................................ 223

Глава 9. SQL и представления...................................................... 228
Представления – это пе­ре­мен­ные-от­но­ше­ния............................... 229
Представления и предикаты...................................................... 233
Операции выборки................................................................... 234
Представления и ограничения................................................... 236
Операции обновления............................................................... 240
Зачем нужны представления?.................................................... 244
Взгляды и снимки.................................................................... 245
Упражнения............................................................................ 247

Глава 10. SQL и формальная логика............................................. 250
Простые и составные высказывания........................................... 251
Простые и составные предикаты................................................ 254

10

Оглавление

Квантификация....................................................................... 256
Реляционное исчисление........................................................... 260
Еще о квантификации.............................................................. 267
Некоторые эквиваленции.......................................................... 274
Заключительные замечания...................................................... 277
Упражнения............................................................................ 278

Глава 11. Использование формальной логики
для формулирования SQL-выражений........................................ 281
Некоторые правила трансформации............................................ 282
Пример 1. Логичес­кая импликация............................................ 284
Пример 2. Добавление квантора всеобщности............................... 285
Пример 3. Импликация и квантор всеобщности............................ 286
Пример 4. Коррелированные подзапросы.................................... 288
Пример 5. Именование подвыражений........................................ 290
Пример 6. Еще об именовании подвыражений.............................. 293
Пример 7. Устранение неоднозначности...................................... 294
Пример 8. Использование COUNT.............................................. 296
Пример 9. Запросы с соединением.............................................. 297
Пример 10. Квантор UNIQUE..................................................... 298
Пример 11. Сравнения с ALL или ANY........................................ 299
Пример 12. GROUP BY и HAVING.............................................. 303
Упражнения............................................................................ 304

Глава 12. Различные вопросы, связанные с SQL............................ 306
SELECT *................................................................................ 307
Явные таб­лицы........................................................................ 307
Квалификация имен................................................................. 307
Пе­ре­мен­ные кортежа................................................................ 308
Подзапросы............................................................................. 311
«Потенциально недетерминированные» выражения..................... 314
Пустые множес­тва................................................................... 315
БНФ-грамматика таб­личных выражений SQL.............................. 315
Упражнения............................................................................ 318

Приложение A. Реляционная модель.......................................... 320
Реляционная и другие модели.................................................... 322
Определение реляционной модели.............................................. 325
Цели реляционной модели........................................................ 331
Некоторые принципы баз данных............................................... 331
Что осталось сделать?............................................................... 333

Приложение B. Теория проектирования баз данных.................... 338
Место теории проектирования................................................... 339

Оглавление

11

Функциональные зависимости
и нормальная форма Бойса/Кодда.............................................. 342
Зависимости соединения и пятая нормальная форма..................... 348
Тост за здоровье нормализации.................................................. 357
Ортогональность...................................................................... 361
Некоторые замечания о физичес­ком проектировании.................... 364
Заключительные замечания...................................................... 366
Упражнения............................................................................ 368

Приложение С. Ответы к упражнениям....................................... 372
Глава 1................................................................................... 372
Глава 2................................................................................... 379
Глава 3................................................................................... 387
Глава 4................................................................................... 392
Глава 5................................................................................... 398
Глава 6................................................................................... 404
Глава 7................................................................................... 413
Глава 8................................................................................... 424
Глава 9................................................................................... 433
Глава 10.................................................................................. 440
Глава 11.................................................................................. 448
Глава 12.................................................................................. 450
Приложение B......................................................................... 450

Приложение D. Дополнительная литература.............................. 460
Алфавитный указатель............................................................... 469

Предисловие
Язык SQL распространен повсеместно. Но работать с SQL трудно: он
сложен, запутан, при написании SQL-команд легко допустить ошибку –
рискну предположить, что куда легче, чем можно судить по уверениям
апологетов этого языка. Поэтому, чтобы уверенно писать правильный
SQL-код (то есть такой, который делает в точности то, что вам нужно, не
больше и не меньше), необходимо четко следовать некоторому правилу.
А основной тезис настоящей книги заключается в том, что таким правилом может стать использование SQL в соответствии с реляционной
теорией. Но что это означает? Разве SQL не является изначально реляционным языком?
Да, конечно, SQL – стандартный язык для всех реляционных баз данных, но сам по себе этот факт не делает его реляционным. Как это ни
печально, SQL слишком час­то отходит от принципов реляционной теории; строки-дубликаты и null-значения – два наиболее очевидных примера, которыми проблема отнюдь не ограничивается. Следовательно,
он предлагает вам путь, который может привести в западню. А если вы
не желаете попадать в западню, то должны понимать реляционную теорию (что она собой представляет и для чего предназначена), знать, в чем
именно SQL отклоняется от этой теории, и уметь избегать проблем, которые могут из этого проистекать. Одним словом, SQL следует использовать в реляционном духе. Тогда вы сможете действовать так, будто
SQL на самом деле реляционный язык, и получать все преимущества
от работы с тем, что по сути является истинно реляционной сис­темой.
В подобной книге не возникло бы необходимости, если бы все и без того
использовали SQL реляционным образом, – но, увы, это не так. Напротив, в современном применении SQL я вижу проявление множества
вредных тенденций. Более того, эти способы применения относятся
к рекомендуемым в различных учебниках и иных публикациях авторами, которые должны были бы относиться к своей работе более ответственно (имен не привожу и на посмешище выставлять не хочу); анализ литературы в этом от­но­ше­нии оставляет удручающее впечатление.
Реляционная модель появилась на свет в 1969 году, и вот – почти сорок
лет спустя – она, похоже, так и не понята по-настоящему сообществом
пользователей СУБД в целом. Отчас­ти поэтому в основу организации
настоящей книги положена сама реляционная модель; подробно объясняются различные аспекты этой модели, и для каждого аспекта демонстрируется, как лучше всего реализовать его средствами SQL.

14

Предисловие

Предварительные требования
Я предполагаю, что вы применяете СУБД в своей работе, поэтому уже
в какой-то мере знакомы с языком SQL. Точнее, я считаю, что вы имеете
практическое представление либо о стандарте SQL, либо (что более вероятно) о каком-либо продукте, где применяется SQL. Однако я не рассчитываю на глубокое знание реляционной теории как таковой (хотя надеюсь, что вы все же согласны с тем, что реляционная теория – вещь хорошая, и по возможности ее следует придерживаться). Поэтому во избежание недопонимания я буду подробно описывать различные свойства
реляционной модели, а также показывать, как SQL согласуется с этими
свойствами. Однако я не стану пытаться обосновывать существование
этих свойств, а буду предполагать, что вы достаточно подкованы в тематике баз данных, чтобы понимать, к примеру, зачем используется понятие ключа, или почему иногда приходится выполнять операцию со­­еди­
нения, или зачем требуется поддержка от­но­ше­ний мно­гие-ко-многим.
(Если бы я решил включать все определения и обоснования, то получилась бы совсем другая книга – гораздо больше по размеру, не говоря уже
обо всем остальном; да и в любом случае такая книга уже написана.)
Я сказал, что ожидаю от вас достаточно близкого знакомства с SQL. Добавлю, однако, что некоторые аспекты SQL я все равно буду объяснять
подробно – особенно те, что на практике применяются нечас­то. (В качестве примера упомяну «потенциально недетерминированные выражения». См. главу 12.)

«Database in Depth»
Эта книга базируется на ранее изданной книге «Database in Depth: Re­
la­tional Theory for Practitioners» (O’Reilly, 2005) и должна заменить ее.
В предыдущей книге я ставил перед собой такую цель (цитата взята из
предисловия):
Посвятив много лет работе с базами данных в разном качестве,
я пришел к выводу, что существует настоятельная потребность
в книге для практиков (не новичков), где принципы реляционной теории излагались бы вне связи с особенностями конкретных существующих продуктов, коммерческими обычаями или
стандартом SQL. Таким образом, в качестве читательской аудитории я вижу опытных пользователей СУБД, достаточно честных, чтобы сознаться в том, что они не понимают теоретических
основ той области, в которой работают, в той мере, в какой могли
бы и должны были бы понимать. Этой теорией, естественно, является реляционная модель, и хотя фундаментальные идеи, положенные в ее основу, очень просты, надо признать, что они чуть ли
не повсеместно неверно представляются, или недооцениваются,

15

Предисловие
или то и другое вместе. Фактически час­то их не понимают вовсе.
Вот, к примеру, несколько вопросов по реляционной теории1...
На сколько из них вы сможете ответить?
Что в точности означает первая нормальная форма?
Какая связь существует между от­но­ше­ниями и предикатами?
Что такое семантическая оптимизация?
Что такое от­но­ше­ние-образ?
Почему так важна полуразность?
Почему отложенная проверка ограничений целостности не имеет смысла?
Что такое пе­ре­мен­ная-от­но­ше­ние?
Что такое предваренная нормальная форма?
Может ли от­но­ше­ние иметь атрибут, значениями которого являются от­но­ше­ния?
Является ли язык SQL реляционно полным?
Почему так важен принцип информации?
Как XML укладывается в реляционную модель?
Настоящая книга дает ответы на эти и многие другие вопросы.
В общем и целом, ее цель – помочь пользователям-практикам баз
данных глубоко разобраться в реляционной теории и применить
полученные знания в повседневной профессиональной деятельности.

Как следует из последнего предложения, я надеялся, что читатели той
книги смогут применить изложенные идеи в своей работе без дальнейшей помощи с моей стороны. Но с тех пор я осознал, что вопреки устоявшемуся мнению язык SQL настолько труден, что вопрос о том, как применять его, не нарушая реляционных принципов, далеко не праздный.
Поэтому я решил дополнить первоначальную книгу, включив в нее явные, конкретные рекомендации именно в этом направлении (то есть
как использовать SQL в реляционном духе). Таким образом, в этой книге я преследовал ту же цель, что и раньше (помочь пользователям-прак­
тикам баз данных глубоко разобраться в реляционной теории и применить полученные знания в повседневной профессиональной деятельности), но постарался представить материал в виде, быть может, более удобном для усвоения и уж точно – для применения. Иными словами, я включил много материала, относящегося конкретно к языку SQL
(и именно поэтому размер так сильно увеличился по сравнению с пре-­­­
ды­­дущим вариантом).
1

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

16

Предисловие

Дополнительные замечания о тексте
Я должен высказать еще несколько предварительных замечаний. Прежде всего, мое собственное понимание реляционной модели с годами
развивалось. В этой книге изложены мои нынешние представления
о предмете; поэтому если вы обнаружите технические расхождения –
а они есть – между этой и другими моими книгами (в час­тности, и той,
которую эта призвана заменить), правильным следует считать написанное здесь. Впрочем, сразу оговорюсь, что расхождения по большей час­ти
не слишком существенны; более того, я всегда соотношу новые термины и понятия со старыми, если считаю, что в этом есть необходимость.
Во-вторых, я, как и обещано, собираюсь говорить о теории, но опираясь на свою уверенность, что нет ничего практичнее хорошей теории.
Я специально выделил этот момент, потому что многие, похоже, придерживаются противоположного мнения и считают: все, что отдает тео­
ретизированием, на практике неприменимо. Но истина в том, что теория (по крайней мере, реляционная, а именно о ней я и веду речь) очень
даже приближена к практике. Она создавалась как теория не ради
тео­­­рии, а ради построения сис­тем, на все сто процентов практических.
Каждая деталь этой теории обусловлена весьма практическими соображениями. Один из рецензентов предыдущей книги, Стефан Фарульт
(Stéphane Faroult), писал: «Приобретя некоторый практический опыт,
вы начинаете осознавать, что без знания теории не обойтись». Более
того, эта теория не только практична, она еще и фундаментальна, проста, понятна, полезна, а иногда еще и забавна (как я надеюсь продемонстрировать в этой книге).
Разумеется, за самой яркой иллюстрацией вышеприведенного тезиса
не нужно ходить дальше самой реляционной модели. Вообще вряд ли
необходимо отстаивать мнение о практичности теории в том контексте,
который мы имеем: существование многомиллиардной индустрии, целиком основанной на одной замечательной теоретической идее. Однако я предвижу и такую циничную позицию: «Ну ладно, а что эта теория сделала лично для меня в последнее время?» Иными словами, те из
нас, кто дейст­вительно убежден в важности теории, должны постоянно
противостоять критикам – и это еще одна причина, по которой я считаю эту книгу полезной.
В-третьих, как я уже говорил, в этой книге детально излагаются различные аспекты SQL и реляционной модели. (Я сознательно почти не затрагивал темы, не имеющие от­но­ше­ния собственно к реляционной теории, в час­тности транзакции.) Я всюду старался ясно отмечать, когда обсуждение относится только к SQL, когда – только к реляционной модели, а когда – к тому и другому. Однако должен подчеркнуть, что не стремился к исчерпывающему рассмотрению всех деталей SQL. Язык SQL
очень сложен и предоставляет много разных способов решения одной

Предисловие

17

и той же задачи, в нем так много исключений и особых случаев, что попытка охватить все – даже если бы это было возможно, в чем я лично сомневаюсь, – оказалась бы непродуктивной, а книга при этом выросла бы
до необозримых размеров. Поэтому я старался уделить внимание тому,
что считаю дейст­вительно важным, не растекаясь при этом мыслью по
древу. Хотелось бы заявить, что если вы будете делать все, что я советую, и не будете делать того, что я не советую, то это станет первым приближением к безопасной работе: вы будете использовать SQL реляционно. Но только вам судить, насколько это заявление оправдано и оправдано ли вообще.
Ко всему сказанному следует добавить, что, к сожалению, существуют ситуации, когда SQL невозможно использовать реляционно. Например, проверку некоторых ограничений целостности приходится откладывать (обычно, чтобы выиграть время), хотя реляционная модель считает такую отложенную проверку логической ошибкой. В этой книге
приводятся рекомендации о том, как поступать в подобных случаях, но
боюсь, что по большей час­ти они сводятся к тривиальному совету: делайте так, как считаете наиболее правильным. Надеюсь, по крайней
мере, что вы будете понимать, в чем состоит опасность отклонения от
модели.
Должен также сказать, что некоторые предлагаемые рекомендации не
относятся к реляционной теории, а имеют общий характер, хотя иногда у них есть и «реляционные» последствия (не всегда очевидные, кстати говоря). Хорошим примером может служить совет избегать приведения типов.
В-четвертых, обратите внимание, что под словом SQL я в этой книге понимаю исключительно стандартную версию языка, а не какой-то конкретный диалект (если только противное не оговорено явно). В час­тности,
в согласии со стандартом я подразумеваю произношение «эс кью эл», а не
«сиквел»1 (хотя на практике последнее широко распространено).
В-пятых, эту книгу следует читать в основном последовательно за немногими исключениями, оговоренными здесь и далее в самом тексте (большинство глав в той или иной мере зависят от ранее изложенного материала, поэтому старайтесь не перескакивать из одного места в другое). Кроме того, в каждую главу включены упражнения. Конечно, выполнять их
необязательно, но мне кажется, что было бы разумно попробовать свои
силы хотя бы на некоторых. Ответы, в которых час­то содержится дополнительная информация по теме, приведены в приложении C.
И наконец, хотелось бы упомянуть о некоторых видеосеминарах, основанных на материалах этой книги. Дополнительную информацию см. по
адресу http://www.clik.to/chris_date или http://www.thethirdmanifesto.com.
1

Поэтому мы пишем «об SQL», а не «о SQL». – Прим. перев.

18

Предисловие

Типографские соглашения
В книге применяются следующие соглашения:
Курсив
Служит для визуального выделения. Этим шрифтом обозначаются
новые термины. Кроме того, он применяется в основном тексте, когда нужно сказать, что вместо чего-то следует подставить некоторую
пе­ре­мен­ную, например x.
Моноширинный шрифт
Применяется для примеров кода.
Моноширинный курсив
В примерах кода обозначает пе­ре­мен­ную или значение, вводимое
пользователем.

О примерах кода
Эта книга призвана помогать вам в работе. Поэтому вы можете использовать приведенный в ней код в собственных программах и в документации. Спрашивать у нас разрешение необязательно, если только вы
не собираетесь воспроизводить значительную час­ть кода. Например,
не требуется разрешение, чтобы включить в свою программу несколько фрагментов кода из книги. Однако для продажи или распространения примеров на компакт-диске нужно получить разрешение. Можно
без ограничений цитировать книгу и примеры в ответах на вопросы. Но
чтобы включить значительные объемы кода в документацию по собственному продукту, нужно получить разрешение.
Мы высоко ценим, хотя и не требуем, ссылки на наши издания. В ссылке обычно указываются название книги, имя автора, издательство
и ISBN, например: «SQL and Relational Theory, C. J. Date», Copyright
2009 C. J. Date, 978-0-596-52306-0.
Если вы полагаете, что планируемое использование кода выходит за
рамки изложенной выше лицензии, пожалуйста, обратитесь к нам по
адресу permissions@oreilly.com.

Замечания и вопросы
Я очень старался, чтобы в книге не было ошибок, но что-то мог упустить.
Если вы найдете какой-то огрех, просьба уведомить издательство по
адресу:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472

19

Предисловие

800-998-9938 (для США и Канады)
707-829-0515 (международный или местный)
707-829-0104 (факс)
Можно также посылать сообщения по электронной почте. Чтобы подписаться на рассылку или заказать каталог, отправьте письмо на адрес:
info@oreilly.com
Замечания и вопросы технического характера следует отправлять по
адресу:
bookquestions@oreilly.com
Для этой книги создана веб-страница, на которой выкладываются примеры и списки замеченных ошибок (ранее отправленные извещения
об ошибках и исправления доступны для всеобщего обозрения). Адрес
страницы:
http://www.oreilly.com/catalog/9780596523060
Дополнительную информацию об этой и других книгах можно найти
на сайте издательства O’Reilly:
http://www.oreilly.com

Доступность на Safari
Если на обложке вашей любимой книги присутствует
значок Safari® Enabled, это означает, что книга доступна в сетeвой биб­лиотеке Safari издательства O’Reilly.
У Safari есть преимущество перед обычными электронными книгами.
Это виртуальная биб­лиотека, которая позволяет легко находить тысячи технических книг, копировать примеры программ, загружать отдельные главы и быст­ро получать точную и актуальную информацию.
Бесплатный доступ по адресу http://safari.oreilly.com.

Благодарности
О том, чтобы переработать предыдущую книгу, включив в нее дополнительный материал по SQL, я подумывал довольно давно, но последним толчком, заставившим меня наконец засесть за эту работу, явилось мое присутствие в 2007 году на одном курсе, предназначенном для
практических пользователей СУБД. Курс вел Тоон Коппелаарс (Toon
Koppelaars) на базе книги, которую он написал совместно с Лексом де
Хааном (Lex de Haan) (рецензентом этой книги), очень неплохой, кстати. Но порази­тельным оказалось то, насколько беспомощно выглядели попытки слушателей применить реляционные и логические принципы к привычному для них использованию SQL. Конечно, у слушателей были какие-то знания по этой теме – в конце концов, все они рабо-

20

Предисловие

тали с базами данных на практике, – но мне показалось, что их определенно нужно направить в сторону применения этих идей в повседневной деятельности. Поэтому я и написал эту книгу. И потому я благодарен в первую очередь Тоону и Лексу, которые придали импульс, необходимый для начала работы над проектом. Я благодарен также рецензентам Хербу Эдельштейну (Herb Edelstein), Шеери Ктитцеру (Sheeri
Ktitzer), Энди Ораму (Andy Oram), Питеру Робсону (Peter Robson) и Бэрону Шварцу (Baron Schwartz) за замечания по первым черновикам рукописи и Хью Дарвену (Hugh Darwen) и Джиму Мелтону (Jim Melton) за
техническую помощь иного характера. Кроме того, я как всегда благодарен своей жене Линди за неизменную многолетнюю поддержку этого и прочих моих проектов. Наконец, спасибо всему коллективу издательства O’Reilly и особенно Изабель Канкл (Isabel Kunkle), Энди Ораму (Andy Oram) и Адаму Уитверу (Adam Witwer) за ценные советы и за
то, что они ободряли и поддерживали меня на протяжении всего времени работы над книгой.
К. Дж. Дейт
Хилдсбург, штат Калифорния,
2008

Глава 1
.

Введение
Реляционный подход к SQL – вот тема или одна из тем настоящей книги. Разумеется, чтобы должным образом раскрыть эту тему, мне придется рассматривать вопросы, касающиеся как реляционной теории,
так и самого языка SQL. И хотя это замечание очевидным образом применимо ко всей книге, к первой главе оно относится в особенности. Поэтому здесь мы почти не будем говорить о языке SQL как таковом. Моя
цель сейчас – дать обзор материала, большая час­ть которого вам, скорее всего, уже известна. Я хочу обозначить некую отправную точку, то
есть заложить фундамент, на котором можно будет возводить здание
книги. Но, даже искренне рассчитывая на то, что вы в основном знакомы с тем, о чем я собираюсь рассказать в этой главе, я все же предлагаю не пропускать ее. Вы должны знать то, что знать надлежит (надеюсь, вы понимаете, что я хочу сказать); в час­тности, вы должны владеть
всем, что понадобится для понимания материала следующих глав. На
самом деле я – со всем уважением – порекомендовал бы вообще не пропускать рассмотрение тем, которые вам кажутся знакомыми. Например, так ли вы уверены, что знаете, что в реляционной терминологии
понимается под ключом? Или что такое соединение?1
1

По крайней мере один ученый муж этого не понимает. Следующий отрывок взят из документа, который (как и эта книга!) имеет целью наставить
пользователей SQL: «Не применяйте соединения… Подход Oracle и SQL
Server к этой концепции принципиально различен… Вы можете получить
неожиданные результирующие наборы… Необходимо понимать основные
типы соединений… Экви-соединение строится путем выборки всех данных
двух отдельных источников и создания из них одной большой таб­лицы…
В случае внутреннего соединения две таб­лицы соединяются по внутренним
столбцам… В случае внешнего соединения две таб­лицы соединяются по
внешним столбцам… (да-да, именно так написано в оригинале. – Прим. перев.). В случае левостороннего соединения две таб­лицы соединяются по левым столбцам. В случае правостороннего соединения две таб­лицы соединяются по правым столбцам».

22

Глава 1. Введение

Реляционная модель очень плохо понята
Профессионалы в любой области должны знать лежащие в ее основе
фундаментальные идеи. Поэтому профессионал в области баз данных
должен знать реляционную теорию, поскольку именно реляционная
модель является фундаментом (уж во всяком случае здоровенной час­
тью фундамента), на котором покоится вся эта отрасль. В наше время
любой курс по управлению базами данных, академический или коммерческий, на словах привержен идее преподавания реляционной модели, но в большинстве случаев преподавание поставлено очень плохо, если судить по результатам; безусловно, в сообществе пользователей баз данных эта модель недопонята. Перечислим некоторые возможные причины такого положения вещей.
•• Модель преподносится в отрыве от всего, в «вакууме». Поэтому начинающему очень трудно уловить значимость материала или понять,
какие проблемы модель призвана решить, или то и другое одновременно.
•• Сами преподаватели не до конца понимают или не в состоянии оценить важность материала.
•• Чаще всего на практике модель как таковая не рассматривается вовсе – вместо нее преподается язык SQL или какой-то его диалект, например принятый в Oracle.
Поэтому эта книга обращена к тем людям, которые на практике работают с базами данных и в особенности с языком SQL и каким-то боком связаны с реляционной моделью, но знают о ней не так много, как
должны или хотели бы знать. Она определенно не рассчитана на начинающих, однако не является курсом повышения квалификации. Конкретно, я уверен, что вы что-то знаете о языке SQL, но – не сочтите за
обиду – если ваши знания о реляционной модели проистекают исключительно из знания SQL, то, боюсь, вы не понимаете ее так хорошо, как
следовало бы, и очень может статься, что какие-то «ваши знания неверны». Не устану повторять: SQL и реляционная модель – вовсе не одно
и то же. В качестве иллюстрации приведу некоторые вопросы реляционной теории, которые в SQL трактуются недостаточно ясно (и это еще
мягко сказано):
•• Что в дейст­вительности представляют собой базы данных, от­но­ше­
ния и кортежи
•• В чем разница между от­но­ше­ниями-значениями и пе­ре­мен­ными-от­
но­ше­ниями
•• Значимость предикатов и высказываний
•• Важность имен атрибутов
•• Важнейшая роль ограничений целостности

Некоторые замечания о терминологии

23

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

Некоторые замечания о терминологии
Вероятно, вы сразу же обратили внимание на то, что в перечне вопросов реляционной теории из предыдущего раздела я употребил формальные термины «от­но­ше­ние», «кортеж» и «атрибут». Но в SQL эти термины не используются, там применяются более «дружественные» слова:
таб­лицы, строка и столбец. Вообще говоря, я с пониманием отношусь
к идее употребления понятных пользователю слов, если это помогает
усвоению идей. Но в данном случае мне кажется, что как раз усвоению
идей такая терминология не способствует – как это ни печально; напротив, она лишь искажает суть и оказывает дурную услугу пониманию
истинного смысла.
А истина заключается в том, что от­но­ше­ние – это не таб­лица, кортеж –
не строка, а атрибут – не столбец. И хотя в неформальном контексте такое словоупотребление может показаться приемлемым – я и сам так час­
то говорю, – я настаиваю, что приемлемо оно лишь в том случае, когда мы ясно понимаем, что эти дружественные термины – не более чем
приближение к истине, они совершенно не ухватывают смысл того, что
происходит на самом деле. Скажу по-другому: если вы понимаете истинное положение вещей, то благоразумное употребление дружественных
терминов может оказаться здравой идеей, но, чтобы понять и оценить
это истинное положение, необходимо освоить более формальную терминологию. Поэтому в этой книге я буду, как правило, пользоваться формальными терминами – по крайней мере тогда, когда говорю о реляционной модели, противопоставляя ее SQL, – и в нужном месте приведу
их строгие определения. Напротив, в контексте SQL я буду употреб­лять
присущую ему терминологию.
И еще одно замечание о терминологии: упомянув, что SQL пытается
упростить один набор терминов, я должен добавить, что он сделал все
возможное для усложнения другого. Я имею в виду использование терминов оператор, функция, процедура, подпрограмма и метод; все они
обозначают по существу одно и то же (быть может, с незначительными
вариациями). В этой книге я всюду буду употреблять слово оператор.
Раз уж зашла речь об SQL, позвольте напомнить, что (как отмечалось
в предисловии) под этим я понимаю исключительно стандартную вер-

24

Глава 1. Введение

сию языка1, если не считать нескольких мест, где по контексту требуется иное.
•• Иногда я употребляю терминологию, отличающуюся от стандартной. Например, я пишу таб­личное выражение (table expression) вместо принятого в стандарте выражение запроса (query expression), потому что (а) значением такого выражения является таб­лица, а не запрос, и (б) запросы – не единственный контекст, в котором такие
выражения используются. (На самом деле, в стандарте применяется термин таб­личное выражение, но в гораздо более узком смысле;
конкретно, он обозначает то, что следует за фразой SELECT в выражении SELECT.)
•• Продолжая предыдущую мысль, должен добавить, что не все таб­
личные выражения допустимы в SQL в любом контексте, где их использование можно было бы предположить. В час­тности, результат
явной операции JOIN, хотя он, безусловно, обозначает таб­лицу, не
может встречаться в качестве «отдельностоящего» таб­личного выражения (то есть на самом внешнем уровне вложенности), а также не
может выступать в качестве таб­личного выражения в скобках, которое составляет подзапрос (см. главу 12). Обратите внимание, что
подобные замечания применимы ко многим обсуждениям в основном тексте книги, но повторять их каждый раз было бы скучно,
поэтому я этого делать не буду. (Однако все это сведено в БНФграмматике в главе 12.)
•• Я игнорирую те аспекты стандарта, которые можно считать чрезмерно эзотерическими, особенно если они не являются час­тью того, что
в стандарте называется Core SQL (Базовый SQL), или имеют мало
общего с реляционной обработкой как таковой. В качестве примеров упомяну так называемые аналитические или оконные функции
(OLAP), динамический SQL, рекурсивные запросы, временные таб­
лицы и детали типов, определенных пользователем.
•• По причинам, отчас­ти связанным с типографским набором, я применяю для комментариев стиль, отличающийся от стандартного.
Точнее, комментарии печатаются курсивом и обрамлены ограничителями «/*» и «*/».
Не забывайте, что все реализации SQL включают средства, не описанные в стандарте. Типичный пример – идентификаторы строк. Моя общая рекомендация относительно таких средств такова: пользуйтесь
ради бога, но только если они не нарушают реляционных принципов
(в конце концов, эта книга посвящена описанию реляционного подхода к SQL). Например, применение идентификаторов строк, скорее всего, нарушит так называемый принцип взаимозаменяемости (см. гла1

International Organization for Standardization (ISO): Database Language SQL,
Document ISO/IEC 9075:2003 (2003).

Принципы, а не продукты

25

ву 9), и если это так, я бы точно не стал их использовать. Но и в этом,
и во всех остальных случаях действует универсальное правило: можете делать все, что хотите, если только знаете, что делаете.

Принципы, а не продукты
Стоит потратить немного времени на вопрос о том, почему профессионалу в области баз данных необходимо (как я уже отмечал выше) знать реляционную модель. Причина в том, что реляционная модель не связана
ни с каким конкретным продуктом; она имеет дело только с принципами. Что я понимаю под принципами? Словарь дает такие определения:
Принцип – основное, исходное положение теории, учения, миро­
воззрения; убеждение, взгляд на вещи; основная особенность
в устройстве чего-либо.1

Важнейшая особенность принципов состоит в их долговечности. Продукты и технологии (и язык SQL в том числе) все время изменяются –
принципы остаются постоянными. Допустим, к примеру, что вы знаете СУБД Oracle; предположим даже, что вы крупный специалист по
Oracle. Но если ваши знания ограничены только Oracle, то они могут
оказаться непригодными, скажем, в среде DB2 или SQL Server (а могут
даже затруднить освоение новой среды). Однако если вы знаете основополагающие принципы – иными словами, реляционную модель, –
ваши знания и навыки переносимы: вы сможете применить их в любой
среде, и они никогда не устареют.
Поэтому в этой книге мы будем иметь дело с принципами, а не с продуктами, с основами, а не с преходящими увлечениями. Но я понимаю,
что в реальном мире иногда приходится идти на компромиссы. Например, иногда имеются веские причины проектировать базу данных способом, далеким от теоретически оптимального. В качестве другого примера снова обратимся к SQL. Хотя нет сомнений, что SQL можно использовать реляционно (по крайней мере, в большинстве случаев), иногда обнаруживается – ведь существующие реализации так далеки от
совершенства, – что это влечет за собой существенное падение производительности… и тогда вы более или менее вынуждены делать что-то не
«истинно реляционное» (например, писать запрос неестественным образом, чтобы заставить реализацию воспользоваться индексом). Однако я абсолютно убежден, что такие компромиссы всегда следует оценивать с концептуальных позиций. Это означает, что:
•• Идя на такой компромисс, вы должны понимать, что делаете.
•• Вы должны понимать, как выглядит теоретически правильное решение, и иметь основательные причины для отхода от него.
1

Перевод дан по изданию Толкового словаря русского языка С. И. Ожегова. –
Прим. перев.

26

Глава 1. Введение

•• Вы должны документировать эти причины; тогда в случае, если в будущем они отпадут (например, из-за того, что в новой версии продукта какая-то функция реализована более удачно), можно будет отказаться от первоначального компромисса.
Следующий афоризм – который приписывается Леонардо да Винчи
(1452–1519), и, стало быть, ему уже около 500 лет – великолепно описывает эту ситуацию:
Всякий, кто полагается на практику, не зная теории, подобен
кормчему, вступающему на судно без руля и компаса, – он не знает, куда плывет. Практика всегда должна опираться на твердые
теоретические основания.

(Курсив добавлен мной.)

Обзор оригинальной модели
В этом разделе я хочу задать отправную точку для последующего обсуждения; здесь приводится обзор некоторых базовых аспектов реляционной модели в том виде, в котором она была определена изначально.
Обратите внимание на уточнение – «была определена изначально»! Бытует широко распространенное заблуждение, будто реляционная модель – вещь абсолютно неизменная. Это не так. В этом от­но­ше­нии она
напоминает математику: математика тоже не статична, а изменяется со
временем. Да ведь и сама реляционная модель – это тоже отрасль математики и как таковая эволюционирует по мере доказательства новых
теорем и получения новых результатов. Более того, новый вклад может
быть внесен любым компетентным специалистом. И, подобно математике, реляционная модель, хотя и была первоначально изобретена одним человеком, ныне стала плодом совместных усилий и принадлежит
всему человечеству.
Кстати говоря, если вы не в курсе, этим человеком был Э. Ф. Кодд, в то
время работавший исследователем в корпорации IBM (Э – это Эдгар, Ф –
Фрэнк, но он всегда подписывался одними инициалами; а для друзей,
к которым я с гордостью отношу и себя, он был просто Тедом). В конце 1968 года Кодд, математик по образованию, впервые понял, как применить математику для закладывания строгих принципов в основание области знания – управления базами данных, – которой в то время
таких качеств остро недоставало. Его первоначальное определение реляционной модели появилось в научно-исследовательском отчете IBM
в 1969 году, и я еще вернусь к этой работе в приложении D.

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

27

Обзор оригинальной модели

ния» очень нестроги; я уточню их в последующих главах, когда это будет уместно.
Итак, начнем со структуры. Конечно, главным структурным свойством
является само от­но­ше­ние, и, как всем известно, от­но­ше­ния принято
изображать на бумаге в виде таб­лиц (пример на рис. 1.1 не нуждается
в пояснениях). От­но­ше­ния определены над типами (их называют также доменами). По существу, тип представляет собой концептуальное
множество значений, которые могут принимать фактические атрибуты фактических от­но­ше­ний. Обращаясь к простой базе данных об отделах и служащих, которая показана на рис. 1.1, мы можем выделить
тип DNO («номера отделов»), представляющий все допустимые номера
отделов, и тогда атрибут DNO в от­но­ше­нии DEPT и атрибут DNO в от­но­
ше­нии EMP будут принимать значения из соответствующего ему концептуального множества. (Кстати, атрибуты не обязательно – хотя иногда это и разумно – называть так же, как соответствующий тип, и час­то
так не делают. Ниже мы увидим много контрпримеров.)
DEPT

EMP

DNO

DNAME

D1
D2
D3

Marketing
Development
Research

BUDGET
10M
12M
5M

ENO

ENAME DNO

E1
E2
E3
E4

Lopez
Cheng
Finzi
Saito

D1
D1
D2
D2

SALARY
40K
42K
30K
35K

DEPT.DNO ссылается на EMP.DNO

Рис. 1.1. База данных об отделах и служащих – тестовые значения

Как я уже сказал, таб­лицы, подобные представленной на рис. 1.1, изображают от­но­ше­ния, точнее n-арные от­но­ше­ния. N-арные от­но­ше­ния
можно нарисовать в виде таб­лицы с n столбцами; столбцы будут соответствовать атрибутам от­но­ше­ния, а строки – кортежам. Здесь n –
произвольное неотрицательное целое число. 1-арное от­но­ше­ние называют унарным, 2-арное – бинарным, 3-арное – тернарным и т. д.
Реляционная модель поддерживает также различные виды ключей.
Начнем с того – и это крайне важно! – что в каждом от­но­ше­нии есть по
меньшей мере один потенциальный ключ1. Потенциальный ключ – это
просто уникальный идентификатор; иными словами, это комбинация
атрибутов – час­то, но не всегда, «комбинация» состоит всего из одного
атрибута, – такая, что значения этой комбинации во всех кортежах от­
но­ше­ния различны. Так, на рис. 1.1 у каждого отдела имеется уникаль1

Строго говоря, это предложение должно выглядеть так: «Каждая пе­ре­мен­
ная-от­но­ше­ние имеет по меньшей мере один потенциальный ключ» (см.
раздел «От­но­ше­ния и пе­ре­мен­ные-от­но­ше­ния» ниже). Аналогичное замечание относится и к другим местам в этой главе. См. упражнение 1.1 в конце главы.

28

Глава 1. Введение

ный номер отдела, а у каждого служащего – уникальный номер служащего, поэтому мы можем сказать, что {DNO} – потенциальный ключ от­
но­ше­ния DEPT, а {ENO} – потенциальный ключ от­но­ше­ния EMP. Кстати, обратите внимание на фигурные скобки; повторю, что потенциальные ключи всегда являются комбинациями, или множествами, атрибутов – даже если конкретное множество состоит всего из одного атрибута, а множества на бумаге традиционно изображают в виде заключенного в фигурные скобки списка элементов, перечисленных через запятую.

Отступление
Здесь я впервые употребил термин список (разделенный запятыми) (com­ma­
list), который далее будет встречаться очень час­то. Его можно определить следующим образом. Пусть xyz – некоторая синтаксическая конструкция (например, «имя атрибута»). Тогда список xyz означает последовательность из нуля
или более элементов xyz, в котором каждая пара соседних элементов разделена запятой (дополнительно перед и после запятой может находиться один или
несколько пробелов). Так, если A, B и C – имена атрибутов, то каждая из показанных ниже конструкций представляет собой список имен атрибутов:
A,B,C
C,A,B
A,C
B
Списком является и пустая последовательность имен атрибутов. Кроме того,
если список заключен в фигурные скобки и, следовательно, обозначает множество, то (а) порядок следования элементов в списке не существенен (так
как множества по определению неупорядочены) и (б) если элемент встречается более одного раза, то считается, что он встретился только один раз (так
как множества не содержат дубликатов).

Далее, первичным ключом называется такой потенциальный ключ, который по каким-то причинам интерпретируется специальным образом.
Если рассматриваемое от­но­ше­ние имеет только один потенциальный
ключ, то его с равным успехом можно назвать первичным. Но если потенциальных ключей несколько, то обычно один из них выбирается в качестве первичного, то есть считается, что он в каком-то от­но­ше­нии «равнее других». Предположим, к примеру, что у каждого служащего имеется как уникальный номер, так и уникальное имя – пример, пожалуй,
не слишком реалистичен, но вполне пригоден для иллюстрации, – тогда
и {ENO}, и {ENAME} являются потенциальными ключами EMP. В этом
случае мы можем назначить, например, {ENO} первичным ключом.
Обратите внимание: я сказал, что первичный ключ обычно выбирается. Обычно – но не обязательно. Если имеется всего один потенциальный ключ, то вопрос о выборе вообще не встает; но если таких ключей два или больше, то выбор одного из них в качестве первичного от-

Обзор оригинальной модели

29

дает произвольностью (по крайней мере, мне так кажется). Разумеется, бывают ситуации, когда нет веских оснований отдать предпочтение
конкретному кандидату. В этой книге я, как правило, буду выбирать
какой-то первичный ключ – и на рисунках, подобных рис. 1.1, обозначать составляющие первичный ключ атрибуты двойным подчеркиванием, – но хочу особо подчеркнуть, что с точки зрения реляционной теории важность представляют именно потенциальные, а не первичные
ключи. Отчас­ти по этой причине я в дальнейшем под словом ключ, без
уточнения, буду понимать любой потенциальный ключ. (Если вам интересно, то скажу, что «специальная интерпретация» первичных ключей по сравнению с потенциальными носит в основном синтаксический
характер; она не является фундаментальной особенностью и вообще не
очень существенна.)
Наконец, внешним ключом называется множество атрибутов одного от­
но­ше­ния, значения которых должны совпадать со значениями некоторого потенциального ключа в каком-то другом (или в том же самом) от­
но­ше­нии. Так, на рис. 1.1 {DNO} – внешний ключ в от­но­ше­нии EMP, значения которого должны совпадать со значениями потенциального ключа {DNO} в от­но­ше­нии DEPT (что я и попытался отразить на рисунке посредством соответственно надписанной стрелочки). Говоря «должны совпадать», я имею в виду, что если от­но­ше­ние EMP содержит, к примеру, кортеж, в котором DNO имеет значение D2, то и DEPT должно содержать кортеж, в котором DNO имеет значение D2, – иначе в от­но­ше­нии
EMP окажется служащий, который работает в несуществующем отделе,
и база данных перестанет быть «верной моделью реальности».

Свойства целостности
Ограничение целостности (или, для краткости, просто ограничение) по
существу представляет собой булево выражение, которое должно принимать значение TRUE. Например, для отделов и служащих мы могли
бы ввести ограничение, которое гласит, что значение атрибута SALARY
(зарплата) должно быть больше нуля. Вообще говоря, на любую базу
данных налагается много разнообразных ограничений, однако все они
по необходимости специфичны для конкретной базы и потому должны
выражаться в терминах от­но­ше­ний в этой базе. Но в оригинальную реляционную модель включены также два обобщенных ограничения целостности – обобщенных в том смысле, что они применимы к любой
базе данных (если говорить неформально). Одно касается первичных
ключей, другое – внешних.
Правило целостности сущностей
Атрибуты, входящие в состав первичного ключа, не могут принимать null-значений.
Правило ссылочной целостности
Не должно быть внешних ключей, не имеющих соответствия.

30

Глава 1. Введение

Сначала я объясню смысл второго правила. Говоря о внешнем ключе, не
имеющем соответствия, я имею в виду значение внешнего ключа, для
которого не существует соответствующего ему потенциального ключа
с таким же значением; например, в базе данных об отделах и служащих
правило ссылочной целостности было бы нарушено, если бы в от­но­ше­
нии EMP встретилось, к примеру, значение D2 атрибута DNO, но в от­но­
ше­нии DEPT не оказалось бы кортежа с таким же значением DNO. Таким образом, правило ссылочной целостности просто выражает семантику внешних ключей, а название «ссылочная целостность» напоминает о том факте, что значение внешнего ключа можно рассматривать как
ссылку на кортеж с таким же значением соответственного потенциального ключа. По существу, правило говорит: если B ссылается на A, то A
должно существовать.
Что же касается правила целостности сущностей, то я, скажем так, испытываю затруднение. Дело в том, что я полностью отвергаю концепцию «null-значений»; по моему глубокому убеждению, им не место в реляционной модели. (Кодд считал иначе, очевидно, но и у меня есть сильные аргументы в защиту собственной позиции.) Поэтому, чтобы объяснить смысл правила целостности сущностей, мне придется (по крайней мере, на время) забыть о своем неверии. Что я и сделаю… но имейте в виду, что я еще вернусь к вопросу о null-значениях в главах 3 и 4.
По сути своей, null – это «маркер», говорящий, что значение неизвестно (подчеркну – и это принципиально важно, что null-значение – и не
значение вовсе, а только маркер, или флаг). Допустим, к примеру, что
мы не знаем величину зарплаты служащего E2. Тогда вместо того, чтобы вводить какое-то реальное значение SALARY в кортеж, описывающий этого служащего в от­но­ше­нии EMP, – по определению, мы не можем этого сделать, так как не знаем нужного значения, – мы помечаем
атрибут SALARY в этом кортеже флагом null:
ENO

ENAME DNO

E2

Cheng

SALARY

D1

Важно понимать, что этот кортеж не содержит ничего в позиции SA­
LARY. Но очень трудно изобразить «ничто» на рисунке! На рисунке
выше я попытался показать, что позиция SALARY пуста, затенением ячейки, но было бы точнее не показывать ее вовсе. Так или иначе,
я и дальше буду придерживаться того же соглашения: обозначать пустые позиции затенением, памятуя о том, что затенение говорит об отсутствии всякого значения. Если вам так удобнее, можете считать, что
она представляет собой маркер, или флаг null.
Возвращаясь к соот­но­ше­нию EMP, правило целостности сущностей говорит (неформально выражаясь), что у любого служащего может быть
неизвестно имя, отдел или зарплата, но не номер служащего, посколь-

Обзор оригинальной модели

31

ку, если неизвестен номер, то мы вообще не знаем, о каком служащем
(сущности) идет речь.
Вот и все, что я хочу сказать о null-значениях на данный момент. И пока
забудем о них.

Средства манипулирования
Раздел модели, относящийся к манипулированию, состоит из двух час­
тей:
•• Реляционная алгебра, в которой определяется набор операторов, например разность (или MINUS), применимых к от­но­ше­ниям.
•• Оператор реляционного присваивания, который позволяет присвоить значение реляционного выражения (например, r1 MINUS r2, где
r1 и r2 – от­но­ше­ния) какому-то от­но­ше­нию.
Оператор реляционного присваивания по существу описывает способ
выполнения обновлений в реляционной модели, и я еще вернусь к нему
ниже в разделе «От­но­ше­ния и пе­ре­мен­ные-от­но­ше­ния». Примечание:
в этой книге я придерживаюсь традиционного соглашения о том, что
общим термином обновление (update) обозначается совокупность реляционных операторов INSERT, DELETE и UPDATE (а также присваивания). Когда речь идет о конкретном операторе UPDATE, я записываю
его заглавными буквами.
Что касается реляционной алгебры, то она состоит из набора операторов, которые – говоря очень нестрого – позволяют порождать «новые»
от­но­ше­ния из «старых». Каждый оператор принимает на входе одно
или несколько от­но­ше­ний и на выходе возвращает новое от­но­ше­ние;
например, оператор разности (MINUS) принимает на входе два от­но­ше­
ния и «вычитает» одного из другого, порождая на выходе третье от­но­
ше­ние. Очень важно, что результатом является от­но­ше­ние: это хорошо
известное свойство замкнутости реляционной алгебры. Именно свойство замкнутости позволяет записывать вложенные реляционные выражения; так как результат любой операции – объект того же вида, что
и входные операнды, то результат одной операции можно подать на
вход другой, – например, разность r1 MINUS r2 может быть объединена с от­но­ше­нием r3, затем результат пересечен с от­но­ше­нием r4 и т. д.
Можно определить сколько угодно операторов, удовлетворяющих простому условию: «одно или несколько от­но­ше­ний на входе, единственное от­но­ше­ние на выходе». Ниже я кратко опишу те операторы, которые принято считать исходными (по существу, те, что были определены Коддом в ранних работах)1; в главах 6 и 7 я остановлюсь на них бо-

1

Исключение составляет дополнительно определенный Коддом оператор деления divide. В главе 7 я объясню, почему опустил его здесь.

32

Глава 1. Введение

лее подробно и определю некоторые дополнительные операторы. На
рис. 1.2 показано графическое представление исходных операторов.
Примечание: если вы незнакомы с этими операторами и не понимаете,
что означают картинки, не расстраивайтесь; как я уже сказал, в последующих главах они будут описаны детально, с множеством примеров.
Ограничение1
Возвращает от­но­ше­ние, содержащее все кортежи заданного от­но­ше­
ния, которые удовлетворяют заданному условию. Например, можно
ограничить от­но­ше­ние EMP только кортежами, для которых атрибут DNO имеет значение D2.
произведение
ограничение

проекция
a
b
c

пересечение

x
y

объединение

a
a
b
b
c
c

x
y
x
y
x
y

разность

(естественное) соединение
a1
a2
a3

b1
b1
b2

b1
b2
b3

c1
c2
c3

a1
a2
a3

b1
b1
b2

c1
c1
c2

Рис. 1.2. Оригинальная реляционная алгебра
1

Слова constraint (ограничение целостности) и restriction (выборка) переводятся на русский язык одним словом ограничение. К счас­тью, из контекста
всегда понятно, о чем именно идет речь. –Прим. перев.

Обзор оригинальной модели

33

Проекция
Возвращает от­но­ше­ние, содержащее все (под)кортежи заданного от­
но­ше­ния, которые остались после исключения из него некоторых
атрибутов. Например, можно спроецировать от­но­ше­ние EMP на атрибуты ENO и SALARY (исключив тем самым атрибуты ENAME и DNO).
Произведение
Возвращает от­но­ше­ние, содержащее все возможные кортежи, которые являются комбинацией двух кортежей, принадлежащих соответственно двум заданным от­но­ше­ниям. Этот оператор известен
также как декартово произведение (иногда он называется расширенным декартовым произведением), перекрестное произведение, перекрестное соединение или декартово соединение; по сути дела, это
просто час­тный случай операции соединения, как будет показано
в главе 6.
Пересечение
Возвращает от­но­ше­ние, содержащее все кортежи, которые принадлежат одновременно двум заданным от­но­ше­ниям. (На самом деле,
пересечение также является час­тным случаем соединения, как будет показано в главе 6.)
Объединение
Возвращает от­но­ше­ние, содержащее все кортежи, которые принадлежат либо одному из двух заданных от­но­ше­ний, либо им обоим.
Разность
Возвращает от­но­ше­ние, содержащее все кортежи, которые принадлежат первому из двух заданных от­но­ше­ний, но не принадлежат
второму.
Соединение
Возвращает от­но­ше­ние, содержащее все возможные кортежи, которые представляют собой комбинации двух кортежей, принадлежащих двум заданным от­но­ше­ниям, при условии, что в комбинируемых кортежах присутствуют одинаковые значения в одном или нескольких общих для исходных от­но­ше­ний атрибутах (причем эти
общие значения появляются в результирующем кортеже один раз,
а не дважды). Примечание: Соединение такого вида изначально называлось естественным. Однако, поскольку естественное соединение несомненно является наиболее важным, то теперь принято называть его просто соединением, опуская уточняющий квалификатор; я тоже буду придерживаться такого соглашения.
И еще одно заключительное замечание: как вы, возможно, знаете, существует еще так называемое реляционное исчисление. Его можно счи-

34

Глава 1. Введение

тать альтернативой реляционной алгебре, то есть с равным успехом
можно сказать, что манипуляционная час­ть реляционной модели состоит из реляционной алгебры (и оператора реляционного присваивания) или из реляционного исчисления (и оператора реляционного присваивания). Они эквивалентны и взаимозаменяемы в том смысле, что
для каждого алгебраического выражения существует логически эквивалентное ему выражение реляционного исчисления и наоборот. Я еще
буду говорить о реляционном исчислении, в основном в главах 10 и 11.

Сквозной пример
Я завершу этот краткий обзор описанием примера, который будет основой при обсуждении большинства, если не всех, тем в этой книге: всем
известной – если не сказать навязшей в зубах – базы данных о поставщиках и деталях. (Прошу прощения за то, что еще раз вывел этого престарелого боевого коня из стойла, но я полагаю, что использование
одного и того же примера в разных публикациях способствует, а не мешает обучению.) На рис. 1.3 приведены тестовые значения.
S SNO
S1
S2
S3
S4
S5

P PNO
P1
P2
P3
P4
P5
P6

SNAME STATUS

CITY

Smith
Jones
Blake
Clark
Adams

London
Paris
Paris
London
Athens

20
10
30
20
30

PNAME COLOR
Nut
Bolt
Screw
Screw
Cam
Cog

Red
Green
Blue
Red
Blue
Red

WEIGHT
12.0
17.0
17.0
14.0
12.0
19.0

SP SNO

CITY
London
Paris
Oslo
London
Paris
London

S1
S1
S1
S1
S1
S1
S2
S2
S3
S4
S4
S4

PNO

QTY

P1
P2
P3
P4
P5
P6
P1
P2
P2
P2
P4
P5

300
200
400
200
100
100
300
400
200
200
300
400

Рис. 1.3. База данных о поставщиках и деталях – тестовые значения

Уточним понятия.
Поставщики
От­но­ше­ние S описывает поставщиков (точнее, поставщиков по контракту). У каждого поставщика имеется уникально определяющий
его номер (SNO) (как видите, я сделал {SNO} первичным ключом), название (SNAME), необязательно уникальный (пусть даже на рис. 1.3
все значения SNAME различаются) статус (STATUS), представляющий некое свойство, позволяющее предпочесть одного поставщика
другому, и местоположение (CITY).

Модель и реализация

35

Детали
От­но­ше­ние P представляет детали (точнее, виды деталей). У каждой детали есть уникальный номер (PNO) ({PNO} – первичный
ключ), одно наименование (PNAME), один цвет (COLOR), один вес
(WEIGHT) и одно местоположение, то есть склад, где хранятся детали этого вида (CITY).
Поставки
От­но­ше­ние SP описывает поставки (то есть показывает, какие детали поставляются какими поставщиками). У каждой поставки имеется один номер поставщика (SNO), один номер детали (PNO) и одно
количество (QTY). В этом примере я буду предполагать, что в любой
момент времени существует не более одной поставки с данным поставщиком и данной деталью ({SNO,PNO} – первичный ключ, кроме
того, {SNO} и {PNO} являются внешними ключами, сопоставляемыми первичным ключам S и P, соответственно). Обратите внимание,
что в базе данных на рис. 1.3 есть один поставщик, S5, для которого
нет ни одной поставки.

Модель и реализация
Прежде чем двигаться дальше, я хочу остановиться на одном моменте,
существенном для всего, что будет обсуждаться ниже. Разумеется, реляционная модель – это модель данных. Но, к сожалению, последний
термин имеет в мире баз данных два совершенно разных значения. Первое и наиболее фундаментальное таково:
Определение: Модель данных (в первом смысле) – это абстрактное, независимое, логическое определение структур
данных, операторов над данными и прочего, что в совокупности составляет абстрактную сис­тему, с которой взаимодействует пользователь.
Именно это значение слова мы имеем в виду, когда говорим о реляционной модели. И, вооружившись этим определением, мы можем провести
полезное, и важное, различие между моделью данных в первом смысле
и ее реализацией, которая определяется следующим образом.
Определение: Реализацией данной модели данных называется физическое воплощение на реальной машине тех компонентов абстрактной сис­темы, которые в совокупности
составляют модель.
Я проиллюстрирую эти определения в терминах реляционной модели.
Прежде всего, само понятие от­но­ше­ния является час­тью модели данных: пользователи должны знать, что такое от­но­ше­ние, понимать, что
оно состоит из кортежей и атрибутов, уметь их интерпретировать и т. д.

36

Глава 1. Введение

Все это час­ти модели. Но пользователям необязательно знать, как от­
но­ше­ния физически хранятся на диске, как физически кодируются
отдельные значения данных, какие существуют индексы или другие
пути доступа к данным – это час­ти реализации, а не модели.
Или возьмем концепцию соединения: пользователи должны знать, что
такое соединение, как его осуществить, как выглядит результат соединения и т. д. Опять-таки, все это час­ти модели. Но совершенно ни
к чему знать, как физически реализуется соединение, какие при этом
производятся трансформации выражений, какие используются индексы или иные пути доступа к данным, какие требуются операции ввода/
вывода – это час­ти реализации, а не модели.
И еще один пример: потенциальные ключи (для краткости просто ключи) – час­ть модели, и пользователям определенно необходимо знать, что
представляют собой ключи. На практике уникальность ключей час­то
гарантируется посредством так называемого уникального индекса; но
индексы вообще и уникальные индексы в час­тности не являются час­
тью модели, это час­ть реализации. Таким образом, не следует путать
индекс с ключом в реляционном смысле, даже если первый применяется для реализации второго (точнее, для реализации некоторого ограничения ключа, см. главу 8).
Короче говоря:
•• Модель (в первом смысле) – это то, что пользователь должен знать.
•• Реализация – это то, что пользователю знать необязательно.
Я вовсе не хочу сказать, что пользователям запрещено знать о реализации; я лишь говорю, что это необязательно. Иными словами, все касающееся реализации должно быть, по крайней мере потенциально, скрыто от пользователя.
Из данных выше определений вытекают некоторые важные следствия.
Прежде всего (и вопреки чрезвычайно распространенному заблуждению), все связанное с производительностью принципиально является
деталью реализации, а не модели. Например, мы час­то слышим, что
«соединение – медленная операция». Но такое замечание лишено всякого смысла! Соединение – это час­ть модели, а модель как таковая не
может быть ни медленной, ни быст­рой; такими качествами может обладать только реализация. Поэтому допустимо сказать, что в некотором продукте X конкретная операция соединения реализована быст­рее
или медленнее, чем в продукте Y, – но это и все.
Я не хочу, чтобы у вас в этом месте сложилось ложное впечатление. Да,
производительность принципиально является деталью реализации;
однако это не означает, что хорошая реализация будет работать быст­ро,
если модель используется неправильно. На самом деле, это как раз одна
из причин, по которым вы должны знать устройство модели: чтобы не
использовать ее неправильно. Написав выражение S JOIN SP, вы впра-

Модель и реализация

37

ве ожидать, что реализация сделает все необходимое и сделает хорошо;
но если вы настаиваете на ручном кодировании соединения, например
таким образом (в виде псевдокода):
do for all tuples in S ;
fetch S tuple into TNO , TN , TS , TC ;
do for all tuples in SP with SNO = TNO ;
fetch SP tuple into TNO , TP , TQ ;
emit tuple TNO , TN , TS , TC , TP , TQ ;
end ;
end ;
то рассчитывать на достижение высокой производительности было бы
глупо. Рекомендация: Не поступайте так. Реляционные сис­темы не
следует использовать в качестве простых методов доступа.1
Кстати, эти замечания по поводу производительности относятся и к
SQL. Утверждения о быст­роте или медленности точно так же бессмысленны в применении к SQL, как и к реляционным операторам (соединению и прочим); в этих терминах имеет смысл говорить только о реализациях. Однако использовать SQL можно и так, что это гарантированно приведет к плохой производительности. Хотя в этой книге тема производительности почти не затрагивается, иногда я все же буду отмечать
последствия своих рекомендаций с этой точки зрения.

Отступление
Я хотел бы ненадолго задержаться на вопросе о производительности. Вообще говоря, приводя в этой книге ту или иную рекомендацию, я не руководствовался соображениями производительности; в конце концов, реляционная модель всегда ставила целью передать заботу о производительности от пользователя сис­теме. Однако не стоит и говорить, что эта задача так и не была решена в полной мере, и потому (как я уже отмечал) поставленной целью – реляционное использование SQL – иногда приходится жертвовать в интересах достижения приемлемой производительности. И это еще одна причина, по которой действует универсальное правило: можете делать все, что хотите, если
только знаете, что делаете.

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

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

38

Глава 1. Введение

жалуй, не слишком удачный, но он уже устоялся, – означает, что мы
можем как угодно изменять способ физического хранения и доступа
к данным, не внося соответствующих изменений в то, как данные представляются пользователю. Причина, по которой может возникнуть
желание изменить детали хранения и доступа, как правило, связана
с производительностью; а тот факт, что такие изменения можно производить, не меняя способа представления данных пользователю, означает, что существующие программы, запросы и прочее будут продолжать
работать. Таким образом, – и это очень важно – физическая независимость от данных позволяет уменьшить капиталовложения в обучение
пользователей и разработку приложений и, добавлю еще, капитало­
вложения в проектирование логической структуры базы данных.
Из всего сказанного следует, что (как отмечалось выше) индексы, да
и вообще все виды физических путей доступа, являются составной час­
тью реализации, а не модели; их место «под капотом», где они не видны
пользователю. (Отмечу, что пути доступа как таковые вообще не упоминаются в реляционной модели.) По тем же причинам их, строго говоря, следовало бы исключить и из SQL. Рекомендация: Избегайте любых конструкций SQL, нарушающих эту заповедь. (Насколько я знаю,
в стандарте нет ничего, вступающего в противоречие с этой рекомендацией, чего нельзя сказать о некоторых продуктах на основе SQL.)
Как бы то ни было, из приведенных выше определений видно, что различие между моделью и реализацией в дейст­вительности является част­
ным – хотя и очень важным – случаем всем знакомого общего различия
между логической и физической организацией. Однако, как это ни печально, в большинстве современных SQL-сис­тем оно не проводится так
четко, как следовало бы. Отсюда немедленно следует, что они в гораздо меньшей степени физически независимы от данных, чем хотелось бы,
и не обеспечивают тех свойств, которые должны присутствовать в реляционной сис­теме. В следующем разделе я еще вернусь к этой теме.
А теперь я хочу обратиться ко второму значению термина модель данных, с которым, смею предположить, вы отлично знакомы. Его можно
определить следующим образом:
Определение: Моделью данных (во втором смысле) называется модель данных (особенно сохраняемых) конкретного
предприятия.
Иными словами, модель данных во втором смысле – это просто (логическая и, возможно, до некоторой степени абстрактная) структура базы
данных. Например, можно говорить о модели данных банка, больницы
или правительственного учреждения.
Объяснив оба смысла, я хочу привлечь ваше внимание к аналогии, которая, как мне кажется, отлично иллюстрирует соот­но­ше­ние между
ними.

Свойства от­но­ше­ний

39

•• Модель данных в первом смысле – аналог языка программирования, конструкции которого применимы для решения многих конкретных задач, но сами по себе не связаны ни с какой отдельной задачей.
•• Модель данных во втором смысле – аналог конкретной программы,
написанной на этом языке, – в ней используются средства, предоставляемые моделью в первом смысле, для решения конкретной
задачи.
Кстати говоря, из сказанного выше следует, что если мы ведем речь
о моделях данных во втором смысле, то можем без опасения говорить
о «реляционных моделях» во множественном числе или о «некоторой»
реляционной модели. Но модель данных в первом смысле только одна,
и это та самая реляционная модель, которую придумал Кодд. Последнюю мысль я еще разовью в приложении A.
Далее в книге я буду употреблять термин модель данных, или для краткости просто модель, исключительно в первом смысле.

Свойства от­но­ше­ний
Теперь вернемся к изучению основных понятий реляционной теории.
В этом разделе я хочу остановиться на некоторых свойствах самих от­
но­ше­ний. Прежде всего, у каждого от­но­ше­ния есть заголовок и тело.
Заголовком называется множество атрибутов (здесь под атрибутом
я понимаю пару имя-атрибута/имя-типа), а телом – множество кортежей, согласованных с заголовком. Так, у от­но­ше­ния «поставщики» на
рис. 1.3 заголовок состоит из четырех атрибутов, а тело – из пяти кортежей. Таким образом, от­но­ше­ние не содержит кортежей – оно содержит
тело, которое, в свою очередь, содержит кортежи, – но обычно мы для
простоты говорим, что кортежи содержит само от­но­ше­ние.
Попутно отмечу, что хотя правильно было бы говорить, что заголовок
состоит из пар имя-атрибута/имя-типа, обычно имена типов на рисунках, подобных рис. 1.3, опускаются, из-за чего создается впечатление,
будто заголовок состоит только из имен атрибутов. Например, атрибут
STATUS имеет тип, – допустим, INTEGER, – но на рис. 1.3 он не показан. Однако не следует забывать, что он все-таки существует!
Далее, количество атрибутов в заголовке называется степенью (или арностью), а количество кортежей в теле – кардинальностью. Например,
для от­но­ше­ний S, P и SP на рис. 1.3 степень равна соответственно 4, 5
и 3, а кардинальность – 5, 6 и 12. Примечание: термин степень применяется также к кортежам. Так, все кортежи в от­но­ше­нии S имеют степень 4 (как и само от­но­ше­ние S).
От­но­ше­ния никогда не содержат кортежей-дубликатов. Это следует
из того, что тело определяется как множество кортежей, а математическое множество по определению не содержит дубликатов. В этом от­

40

Глава 1. Введение

но­ше­нии SQL, как вы, конечно, знаете, отступает от теории: таб­лицы
в SQL могут содержать строки-дубликаты и потому в общем случае не
являются от­но­ше­ниями. Хочу подчеркнуть, что в этой книге термином от­но­ше­ние я всегда называю истинное от­но­ше­ние – без кортежейдубликатов, – а не таб­лицу SQL. Кроме того, вы должны понимать, что
реляционные операции всегда порождают результат без кортежейдубликатов, в полном соответствии с определением. Например, проекция от­но­ше­ния «поставщики» на рис. 1.3 на атрибут CITY дает результат, показанный ниже на левом, а не на правом рисунке:
CITY

CITY

London
Paris
Athens

London
Paris
Paris
London
Athens

(Изображенный на левом рисунке результат можно получить SQL-за­про­
сом SELECT DISTINCT CITY FROM S. Если опустить слово DISTINCT, то
получится нереляционный результат, показанный справа. Особо отметим, что в таб­лице справа нет двойного подчеркивания, поскольку в ней
нет никакого ключа, а уж тем более первичного.)
Далее, кортежи от­но­ше­ния не упорядочены. Это свойство также следует из того, что тело определено как множество, а элементы математического множества не упорядочены (стало быть, {a,b,c} и {c,a,b} в математике считаются одним и тем же множеством, и, естественно, то же
справедливо и для реляционной модели). Конечно, когда мы рисуем
от­но­ше­ние на бумаге в виде таб­лицы, мы должны располагать строки
сверху вниз, но в таком порядке нет ничего реляционного. Так, строки
от­но­ше­ния «поставщики» на рис. 1.3 я мог бы расположить в любом порядке, скажем, сначала S3, потом S1, потом S5, S4 и S2, и это было бы
то же самое от­но­ше­ние. Примечание: Тот факт, что от­но­ше­ния не упорядочены, еще не означает, что в запрос нельзя включать фразу ORDER
BY, но означает, что результат, возвращенный таким запросом, не является от­но­ше­нием. Фраза ORDER BY полезна для представления результатов, но сама по себе не является реляционным оператором.
Аналогично, не упорядочены и атрибуты от­но­ше­ния, поскольку заголовок также является математическим множеством. Изображая от­но­ше­
ние в виде таб­лицы на бумаге, мы по необходимости располагаем столбцы в некотором порядке – слева направо, но в таком порядке нет ничего реляционного. Так, для от­но­ше­ния «поставщики» на рис. 1.3 я мог
бы расположить столбцы в любом другом порядке, скажем, STATUS,
SNAME, CITY, SNO, и с точки зрения реляционной модели это было бы
то же самое от­но­ше­ние (это еще одна причина, по которой таб­лицы SQL
вообще говоря не являются от­но­ше­ниями). Например, на обоих рисунках ниже представлено одно и то же от­но­ше­ние, но разные таб­лицы SQL:

41

Свойства от­но­ше­ний
SNO

CITY

CITY

SNO

S1
S2
S3
S4
S5

London
Paris
Paris
London
Athens

London
Paris
Paris
London
Athens

S1
S2
S3
S4
S5

(В SQL эти представления порождаются соответственно запросами
SELECT SNO, CITY FROM S и SELECT CITY, SNO FROM S. Возможно,
вам кажется, что различие между этими двумя запросами и таб­лицами
несущественно, но на самом деле последствия весьма серьезны, и о некоторых из них я расскажу в последующих главах. См., например, обсуждение SQL-оператора JOIN в главе 6.)
Наконец, от­но­ше­ния всегда нормализованы (или, что то же самое, находятся в первой нормальной форме, 1НФ).1 Неформально это означает,
что в таб­личном представлении от­но­ше­ния на пересечении любой строки и любого столбца мы видим только одно значение. Более формально – каждый кортеж от­но­ше­ния содержит единственное значение соответствующего типа в позиции любого атрибута. В следующей главе
я буду говорить об этом гораздо более подробно.
И в заключение я хотел бы еще раз акцентировать ваше внимание на
моменте, о котором упоминал неоднократно: существует логическое
различие между от­но­ше­нием как таковым и его изображением, например, на рис. 1.1 и 1.3. Повторю, что конструкции, показанные на
рис. 1.1 и 1.3, вообще не являются от­но­ше­ниями, а лишь изображениями от­но­ше­ний, которые я обычно называю таб­лицами, несмотря на то,
что это слово уже наделено определенной семантикой в контексте SQL.
Конечно, между от­но­ше­ниями и таб­лицами есть определенное сходство,
и в неформальном контексте о них обычно говорят, как об одном и том
же, – и, пожалуй, это допустимо. Но если требуется точность – а как раз
сейчас я пытаюсь быть точным, – то необходимо понимать, что эти два
понятия не идентичны.
Попутно отмечу, что и в более общем плане существует логическое различие между произвольной сущностью и изображением этой сущности. Эта мысль замечательно проиллюстрирована на знаменитой картине Магритта. На картине изображена обычная курительная трубка,
но под ней Магриттнаписал Ceçi n’est pas une pipe… – смысл, конечно,
в том, что рисунок – это не трубка, а лишь изображение трубки.
Ко всему вышесказанному я хочу добавить, что на самом деле тот факт,
что основной абстрактный объект реляционной модели – от­но­ше­ние –
1

Мы говорим о «первой» нормальной форме, потому что, как вы наверняка знаете, можно определить последовательность нормальных форм «высших порядков» – вторую нормальную форму, третью нормальную форму
и т. д., – что существенно для проектирования баз данных. См. упражнение 2.9 в главе 2, а также приложение B.

42

Глава 1. Введение

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

Это замечание необычайно полезно; как «мыслительный инструмент»
оно весьма способствует четким и ясным рассуждениям и очень помогает при выявлении и анализе некоторых путаных мест, которые,
к несчас­тью, так час­то встречаются в мире баз данных. Я еще не раз
вернусь к нему на страницах этой книги. А пока позвольте мне выделить те логические различия, с которыми мы уже столкнулись:
•• SQL и реляционная модель
•• Модель и реализация
•• Модель данных (в первом смысле) и модель данных (во втором смысле)
В последующих трех разделах будут описаны и другие различия.

Базовые и производные от­но­ше­ния
Выше я уже объяснял, что операторы реляционной алгебры позволяют,
начав с некоторого набора исходных от­но­ше­ний, – скажем, изображенных на рис. 1.3, – получить новые от­но­ше­ния (например, с помощью запросов). Исходные от­но­ше­ния называются базовыми, остальные – производными. Следовательно, чтобы было от чего отталкиваться, реляционная сис­тема должна предоставлять средства для определения базовых от­но­ше­ний. В SQL эта задача решается предложением CREATE
TABLE (в SQL базовому от­но­ше­нию, естественно, соответствует базовая
таб­лица). Очевидно, что базовые от­но­ше­ния должны быть поименованы, например:
CREATE TABLE S ... ;

Но некоторым производным от­но­ше­ниям, в час­тности так называемым
представлениям, также присваиваются имена. Представлением (или
виртуальным от­но­ше­нием) называется именованное от­но­ше­ние, значением которого в каждый момент времени t является результат вычисления некоторого реляционного выражения в этот момент. Вот как
это может выглядеть на языке SQL:
CREATE VIEW SST_PARIS AS
SELECT SNO , STATUS

Базовые и производные от­но­ше­ния

43

FROM S
WHERE CITY = ‘Paris’ ;

В принципе, представлениями можно оперировать так же, как базовыми от­но­ше­ниями1, но в дейст­вительности они таковыми не являются.
Альтернативно можно считать представление «материализованным»,
то есть мысленно воображать, что в тот момент, когда производится
ссылка на представление, создается базовое от­но­ше­ние, значением которого является результат вычисления заданного реляционного выражения. Однако я должен подчеркнуть, что идея о подобной материализации представлений в момент ссылки является чисто концептуальной; ничего подобного в реальности не происходит, и для операций обновления это в любом случае не сработало бы. Как на самом деле задумывалось функционирование представлений, мы рассмотрим в главе 9.
Попутно хочу сделать важное замечание. Часто приходится слышать
такое описание различий между базовыми от­но­ше­ниями и представлениями:
•• Базовые от­но­ше­ния реально существуют, то есть физически хранятся в базе данных.
•• Напротив, представления «реально не существуют» – это лишь альтернативный способ взглянуть на базовое от­но­ше­ние.
Но реляционная модель ничего не говорит о том, что именно физически
хранится! В час­тности, нигде не утверждается, что базовые от­но­ше­ния
физически хранятся. Требуется лишь, чтобы существовало некоторое
отображение между тем, что физически хранится, и базовыми от­но­ше­
ниями, так чтобы базовое от­но­ше­ние можно было каким-то образом получить, когда в нем возникнет необходимость (по крайней мере, концептуально). Если таким образом можно получить базовое от­но­ше­ние,
то можно получить и все остальное. Например, мы могли бы физически
хранить соединение поставщиков и поставок, но не хранить их по отдельности; тогда базовые от­но­ше­ния S и SP концептуально можно было
бы получить с помощью подходящих проекций этого соединения. Скажем по-другому: с точки зрения реляционной модели, базовые от­но­ше­
ния не более (но и не менее!) «физические», чем представления.
Реляционная модель вполне сознательно ничего не говорит о физическом хранении. Идея заключалась в том, чтобы оставить разработчикам максимум свободы в реализации модели любым удобным для них
способом – в час­тности, наиболее подходящим для обеспечения высокой производительности, – не жертвуя принципом физической неза1

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

44

Глава 1. Введение

висимости от данных. Печально, однако, что поставщики SQL-сис­тем
по большей час­ти, похоже, недопоняли этот аспект; они напрямую отображают базовые таб­лицы на физические хранимые объекты1, и потому (как уже отмечалось выше) созданные ими продукты демонстрируют гораздо меньшую физическую независимость от данных, чем заложено в реляционной модели. Более того, такое положение вещей отражено и в самом стандарте SQL (а также в большинстве других относящихся к SQL документов), где обычно – и на самом деле, в очень многих
местах – употребляются выражения типа «таб­лицы и представления».
Очевидно, что человек, который так говорит, полагает, что таб­лицы
и представления – вещи разные, и, возможно, находится под впечатлением, будто таб­лицы – физические объекты, а представления – нет. Но
весь смысл представления в том, что это такая же таб­лица (хотя я предпочел бы сказать – от­но­ше­ние), поэтому к представлениям применимы те же операции, что к обычным от­но­ше­ниям (по крайней мере, в реляционной модели), так как представления и есть «обычные от­но­ше­
ния». В этой книге я буду употреблять термин от­но­ше­ние для обозначения любого от­но­ше­ния (базового, представления, результата запроса и т. д.); если же мне понадобится (например) уточнить, что речь идет
о базовом от­но­ше­нии, я так и напишу «базовое от­но­ше­ние». Рекомен­
дация: настоятельно рекомендую и вам придерживаться такой же дисциплины. Не делайте распространенную ошибку, мысленно применяя
термин от­но­ше­ние только к базовым от­но­ше­ниям, или – если говорить
в терминах SQL – называя таб­лицами только базовые таб­лицы.

От­но­ше­ния и пе­ре­мен­ные-от­но­ше­ния
Очень может быть, что все, о чем я рассказывал в этой главе до сих пор,
вам уже знакомо; я даже искренне надеюсь, что это так, хотя не в меньшей степени надеюсь, что вы не сочли текст скучным. Но так или иначе, сейчас я подхожу к теме, которая вам, возможно, незнакома. Дело
в том, что исторически возникла серьезная путаница, связанная еще
с одним логическим различием: между от­но­ше­ниями и пе­ре­мен­нымиот­но­ше­ниями.
Давайте на минутку забудем о базах данных и от­но­ше­ниях и рассмотрим пример простого языка программирования. Предположим, что
я написал на некотором языке такое предложение:
DECLARE N INTEGER ... ;

1

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

От­но­ше­ния и пе­ре­мен­ные-от­но­ше­ния

45

Здесь N – не целое число, а пе­ре­мен­ная, которая может принимать целые числа в качестве значений – разных в разные моменты времени.
Все мы это прекрасно понимаем. Точно так же, когда в SQL я пишу:
CREATE TABLE T ... ;

T не является таб­лицей; это таб­личная пе­ре­мен­ная, или (как я предпочитаю говорить, игнорируя такие особенности SQL, как null-значения
и строки-дубликаты) пе­ре­мен­ная-от­но­ше­ние, значениями которой являются от­но­ше­ния (разные в разные моменты времени).
Взгляните еще раз на рис. 1.3, где изображена база данных о поставщиках и деталях. На этом рисунке мы видим три от­но­ше­ния-значения,
а именно те, которые имели место в базе данных в какой-то момент времени. Но, взглянув на ту же базу данных в другой момент, мы, возможно, увидели бы другие три от­но­ше­ния-значения. Иными словами, S, P
и SP в этой базе данных дейст­вительно являются пе­ре­мен­ными – точнее, пе­ре­мен­ными-от­но­ше­ниями. Например, предположим, что пе­ре­
мен­ная-от­но­ше­ние S в настоящий момент времени имеет значение – от­
но­ше­ние-значение, – показанное на рис. 1.3, и мы удаляем кортежи (на
самом деле, только один кортеж) для поставщиков в Афинах:
DELETE S WHERE CITY = ‘Athens’ ;

Вот как будет выглядеть результат:
S

SNO

SNAME STATUS

CITY

S1
S2
S3
S4

Smith
Jones
Blake
Clark

London
Paris
Paris
London

20
10
30
20

Концептуально старое значение S было целиком заменено новым. Конечно, старое значение (с пятью кортежами) и новое (с четырьмя кортежами) в некотором смысле очень похожи, но все-таки это разные значения. Фактически показанная выше операция DELETE логически эквивалентна и по существу является сокращенной записью следующего
реляционного присваивания:
S := S WHERE NOT ( CITY = ‘Athens’ ) ;

Как и при любом присваивании, здесь выполняются два дейст­вия: (а)
вычисляется выражение в правой час­ти и (б) результат вычисления
присваивается пе­ре­мен­ной в левой час­ти. А что получается в результате, я уже объяснил.

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

46

Глава 1. Введение

Дарвен (Hugh Darwen), я использовал его для иллюстрации реляционных идей
в нашей совместной книге «Databases, Types, and the Relational Model: The
Third Manifesto» (см. приложение D) и буду использовать также в этой книге,
когда понадобится объяснить какие-то реляционные концепции. Но поскольку предполагаемая аудитория на этот раз состоит из практических пользователей SQL, то я в большинстве случаев буду приводить эквивалентные конструкции на SQL.

Всем известные предложения INSERT и UPDATE также по существу
являются сокращенной записью реляционного присваивания. Поэтому,
как я уже отмечал в разделе «Обзор оригинальной модели», реляционное присваивание – это фундаментальный оператор обновления в реляционной модели; логически это вообще единственный оператор обновления, который нам нужен.
Таким образом, существует логическое различие между от­но­ше­ниямизначениями и пе­ре­мен­ными-от­но­ше­ниями. Беда в том, что в литературе по базам данных исторически применялся один и тот же термин,
от­но­ше­ние, для обозначения обоих понятий, и такая практика, конечно же, привела к путанице1. Но, начиная с этого момента, я буду скрупулезно различать эти смыслы, четко обозначая, что имеется в виду:
от­но­ше­ние-значение или пе­ре­мен­ная-от­но­ше­ние. Однако выражение
от­но­ше­ние-значение я как правило буду сокращать до просто «от­но­ше­
ние» (точно так же, как вместо целочисленное значение мы обычно говорим целое число).
В качестве упражнения можете еще раз прочитать текст настоящей
главы и отметить те места, где я использовал термин от­но­ше­ние, когда
должен был бы написать пе­ре­мен­ная-от­но­ше­ние.

Значения и пе­ре­мен­ные
Логическое различие между от­но­ше­ниями и пе­ре­мен­ными-от­но­ше­ни­
ями на самом деле является час­тным случаем общего логического различия между значениями и пе­ре­мен­ными, и я хочу немного задержаться на рассмотрении общего случая. (Конечно, это отклонение от темы,
но думаю, что время будет потрачено не зря, поскольку ясное понимание этого вопроса может оказать неоценимую помощь и в других случаях.) Сначала некоторые определения.
Определение: Значение – это то, что в логике называется «индивидуальной константой», например целое число
3. У значения нет позиции во времени и пространстве. Однако значения могут быть представлены в памяти посред1

Разумеется, такая же ошибка присутствует и в языке SQL, где одним и тем
же термином, таб­лица, иногда обозначается таб­личное значение, а иногда – таб­личная пе­ре­мен­ная.

Заключительные замечания

47

ством некоторой кодировки, и у таких представлений, или
видов, имеется позиция во времени и пространстве. Неформально говоря, это означает, что сколько угодно различных пе­ре­мен­ных (см. следующее определение) могут иметь
одно и то же значение одновременно или в разное время.
Особо отметим, что (по определению) значение невозможно модифицировать; если бы это было допустимо, то после
изменения оно перестало бы быть тем же самым значением.
Определение: Пе­ре­мен­ная – это контейнер для представления значения. Пе­ре­мен­ная имеет позицию во времени
и пространстве. Пе­ре­мен­ные, в отличие от значений, можно модифицировать, то есть текущее значение данной пе­
ре­мен­ной можно заменить другим значением, возможно,
отличным от исходного. (Ведь само слово «пе­ре­мен­ная»
подразумевает возможность изменения; иначе говоря, пе­
ре­мен­ной можно присваивать значения.)
Отметим, что допустимы не только такие простые значения, как целое
число 3. Значение может быть сколь угодно сложным: геометрической
точкой, многоугольником, рентгеновским лучом, XML-документом, отпечатком пальца, массивом, стеком, списком, от­но­ше­нием (перечень
можно продолжать до бесконечности). То же самое, конечно, относится и к пе­ре­мен­ным. Я еще буду говорить на эту тему в следующих двух
главах.
Может показаться, что трудно не увидеть различия между такими очевидными и фундаментальными понятиями, как значения и пе­ре­мен­
ные. Но на самом деле в эту ловушку попасть очень просто. Вот, к примеру, цитата из пособия по объектным базам данных (комментарии
курсивом в скобках добавлены мной):
Мы отличаем объявленный тип пе­ре­мен­ной от … типа объекта,
являющегося текущим значением этой пе­ре­мен­ной [следовательно, объект является значением]… Мы различаем объекты и значения [следовательно, объект таки не является значением]…
Изменяющий оператор – [это такой оператор, для которого,]
возможно наблюдать эффект его применения к некоторому объекту [получается, что объект является пе­ре­мен­ной].

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

48

Глава 1. Введение

пользоваться формальной терминологией (от­но­ше­ния, кортежи, ат­
рибуты) вместо «более дружественной» терминологии SQL.
•• Я привел обзор оригинальной модели, затронув, в час­тности, следующие понятия: тип, n-арное от­но­ше­ние, кортеж, атрибут, потенциальный ключ, первичный ключ, внешний ключ, целостность сущностей, ссылочная целостность, реляционное присваивание и реляционная алгебра. Говоря об алгебре, я упомянул свойство замкнутости
и очень коротко остановился на операторах ограничения, проекции,
произведения, пересечения, объединения, разности и соединения.
•• Я рассмотрел различные свойства от­но­ше­ний, ввел понятия заголовка, тела, кардинальности и степени. От­но­ше­ние не может содержать кортежей-дубликатов, кортежи не упорядочены сверху вниз,
а атрибуты не упорядочены слева направо. Мы также обсудили различия между базовыми от­но­ше­ниями (точнее, базовыми пе­ре­мен­
ными-от­но­ше­ниями) и представлениями.
•• Я рассмотрел логические различия между моделью и реализацией, значениями и пе­ре­мен­ными вообще и от­но­ше­ниями и пе­ре­мен­
ными-от­но­ше­ниями в час­тности. Обсуждение различий между моделью и реализацией привело нас к принципу физической независимости от данных.
•• Я высказал утверждение о том, что SQL и реляционная модель – не
одно и то же. Несколько различий между ними мы уже видели, например: SQL допускает null-значения и строки-дубликаты; столбцы в таб­лицах SQL упорядочены слева направо; в SQL не проводится четкое разграничение между таб­личными значениями и таб­
личными пе­ре­мен­ными (поскольку для того и другого употребляется один и тот же термин, таб­лица). А ниже мы встретимся и со многими другими различиями. Все эти вопросы будут подробно рассматриваться в последующих главах.
И последнее замечание (явно я не говорил об этом раньше, но надеюсь, что оно очевидно из всего вышесказанного): реляционная модель
по природе своей декларативная, а не процедурная, то есть всюду, где
возможно, она отдает предпочтение декларативным решениям. Причина понятна: декларативность означает, что работу выполняет сис­тема,
а процедурность – что работа возлагается на пользователя (так что, помимо всего прочего, речь идет и о продуктивности). Вот почему реляционная модель поддерживает декларативные запросы, декларативные
обновления, декларативные определения представлений, декларативные ограничения целостности и т. д.

Примечание
Уже после того как предыдущий абзац был написан, мне сообщили, что по
меньшей мере в одном широко известном SQL-продукте слово «декларативный» употребляется в том смысле, что сис­тема не выполняет предписанной
работы! Иными словами, пользователю разрешается записать некое деклара-

Упражнения

49

тивное предписание (например, указать, что представление имеет ключ), но
сис­тема ничего не делает для проверки этого ограничения, а лишь предполагает, что его будет проверять пользователь. Такая терминологическая неразбериха не способствует правильному пониманию. Caveat lector1.

Упражнения
Упражнение 1.1. (Уже приводилось в тексте главы в слегка измененной формулировке.) Если вы еще этого не сделали, прочитайте главу
с начала и найдите все места, где я употребил термин «от­но­ше­ние»,
хотя должен был бы написать пе­ре­мен­ная-от­но­ше­ние.
Упражнение 1.2. Кто такой Э. Ф. Кодд?
Упражнение 1.3. Что такое домен?
Упражнение 1.4. Что вы понимаете под ссылочной целостностью?
Упражнение 1.5. Термины заголовок, тело, атрибут, кортеж, кардинальность и степень, определенные в тексте главы для от­но­ше­нийзначений, можно очевидным образом интерпретировать и для пе­ре­
мен­ных-от­но­ше­ний. Убедитесь, что это замечание вам понятно.
Упражнение 1.6. Опишите различие между двумя смыслами термина
модель данных.
Упражнение 1.7. Объясните своими словами, (а) что такое физическая
независимость от данных; (б) в чем различие между моделью и реализацией.
Упражнение 1.8. В тексте главы я написал, что таб­лицы, подобные
приведенным на рис. 1.1 и 1.3, являются не от­но­ше­ниями, а лишь
изображениями от­но­ше­ний. Назовите некоторые различия между
такими рисунками и соответствующими от­но­ше­ниями.
Упражнение 1.9. (Попытайтесь выполнить это упражнение, не заглядывая в текст главы.) Какие пе­ре­мен­ные-от­но­ше­ния содержит база
данных о поставщиках и деталях? Какие у них имеются атрибуты?
Какие потенциальные и внешние ключи? (Смысл этого упражнения
в том, чтобы вы как можно лучше освоились со структурой сквозного примера. Помнить фактические данные не так важно, хотя это не
повредило бы.)
Упражнение 1.10. «Существует только одна реляционная модель». Прокомментируйте это утверждение.
Упражнение 1.11. Следующий отрывок взят из недавно вышедшего
учебника по базам данных: «Важно различать хранимые от­но­ше­
ния, то есть таб­лицы, и виртуальные от­но­ше­ния, то есть представления… Мы будем использовать термин от­но­ше­ние только там, где
1

Читатель предупрежден (лат.). – Прим. перев.

50

Глава 1. Введение

можно было бы подставить слово «таб­лица» или «представление».
Если мы захотим подчеркнуть, что речь идет о хранимом от­но­ше­
нии, а не о представлении, то иногда будем употреблять термин базовое от­но­ше­ние, или базовая таб­лица. В этом тексте есть несколько
неправильных интерпретаций реляционной модели. Назовите все,
что сумеете обнаружить.
Упражнение 1.12. Следующий отрывок взят из еще одной недавно вышедшей книги по базам данных: «[Реляционная] модель… определяет простые таб­лицы для каждого от­но­ше­ния и для связей многие-комногим. Таб­лицы связываются между собой с помощью перекрестных ключей, описывающих связи между сущностями. Первичные
и вторичные индексы обеспечивают быст­рый доступ к данным на
основе заданных параметров». Этот текст подается как определение
реляционной модели… Что в нем не так?
Упражнение 1.13. Напишите предложения CREATE TABLE для SQLверсии базы данных о поставщиках и деталях.
Упражнение 1.14. Ниже приведено типичное SQL-предложение IN­SERT
для базы данных о поставщиках и деталях:
INSERT INTO SP ( SNO , PNO , QTY ) VALUES ( ‘S5’ , ‘P6’ , 250 ) ;

Напишите эквивалентную операцию реляционного присваивания.
Примечание: я понимаю, что еще не объяснял подробно синтаксис
реляционного присваивания, поэтому ответ не обязательно должен
быть синтаксически корректным – сделайте, как сможете.
Упражнение 1.15 (более трудное). Ниже приведено типичное SQL-пред­
ло­жение UPDATE для базы данных о поставщиках и деталях:
UPDATE S SET STATUS = 25 WHERE CITY = ‘Paris’ ;

Напишите эквивалентную операцию реляционного присваивания.
(Цель этого упражнения – заставить вас задуматься о том, что не­
обходимо для решения поставленной задачи. В этой главе рассказано
недостаточно для формулировки полного ответа. Дополнительную
информацию см. в главе 7.)
Упражнение 1.16. В тексте главы я сказал, что SQL напрямую не поддерживает реляционное присваивание. А косвенно? Если да, то как?
Попутный вопрос: все ли реляционные присваивания можно выразить в терминах предложений INSERT / DELETE / UPDATE? Если
нет, то почему? Что отсюда следует?
Упражнение 1.17. С практической точки зрения, почему вы думаете,
что кортежи-дубликаты, упорядочение кортежей сверху вниз и упорядочение атрибутов слева направо – крайне неудачные идеи? (На эти
вопросы намеренно не были даны ответы в тексте главы, а само это
упражнение может послужить неплохой основой для коллективной
дискуссии. Мы еще вернемся к этим материям ниже в этой книге.)

Глава 2
.

Типы и домены
Эта глава лишь косвенно связана с основной темой книги. Разумеется, типы – фундаментальное понятие, и обсуждаемые в этой главе идеи,
безусловно, важны (заодно они помогут развеять некоторые широко
распространенные заблуждения). Но теория типов как таковая не относится к реляционной тематике, и связанная с типами проблематика, по крайней мере в первом приближении, имеет мало общего с повсе­
дневной практикой применения SQL. Более того, хотя в SQL несомненно имеются проблемы в этой области, вы, как правило, тут ничего поделать не можете; я хочу сказать, что почти никаких конкретных рекомендаций по реляционному использованию SQL в этой час­ти я предложить не могу (впрочем, как вы увидите, кое-что все же нашлось).
По­этому при первом чтении вы можете просто пробежать эту главу глазами и вернуться к ней после усвоения материала из последующих глав.

Типы и от­но­ше­ния
Типы данных (для краткости просто типы) – фундаментальное понятие информатики. Для реляционной теории поддержка со стороны тео­
рии типов особенно важна, так как от­но­ше­ния определены над типами, то есть для любого атрибута от­но­ше­ния определен некоторый тип
(и то же самое, разумеется, справедливо для пе­ре­мен­ных-от­но­ше­ний).
Например, для атрибута STATUS пе­ре­мен­ной-от­но­ше­ния S (поставщики) можно было бы задать тип INTEGER. В таком случае каждое от­но­
ше­ние, которое потенциально может быть значением пе­ре­мен­ной-от­но­
ше­ния S, должно иметь атрибут STATUS типа INTEGER, а это, в свою
очередь, означает, что в каждом кортеже такого от­но­ше­ния должен
присутствовать атрибут STATUS типа INTEGER, то есть значением
атрибута STATUS такого кортежа будет целое число.
Все эти вопросы я рассмотрю более подробно далее в этой главе. А пока
скажу лишь, что (с некоторыми важными исключениями, о которых

52

Глава 2. Типы и домены

речь пойдет ниже) атрибут от­но­ше­ния может иметь любой тип, в том
числе произвольно сложный. В час­тности, типы могут быть определены как сис­темой, так и пользователем. Однако в этой книге я не собираюсь много распространяться на тему определенных пользователем типов, потому что:
•• Весь смысл определенных пользователем типов (во всяком случае
с точки зрения человека, который ими просто пользуется, в отличие от человека, который их определяет) заключается в том, что они
должны вести себя как сис­темные.
•• Не так уж много есть разработчиков, которым когда-нибудь придется столкнуться с задачей определения нового типа, да и все равно
создание типа не сопряжено с какими-то специфически реляционными проблемами.
Поэтому, начиная с этого места, вы можете считать, что термин тип
означает сис­темный тип, если из контекста не следует противное. Реляционная модель предписывает только один такой тип – BOOLEAN
(наиболее фундаментальный). Тип BOOLEAN может принимать ровно
два значения, точнее, значения истинности, которые обозначаются литералами TRUE и FALSE. Разумеется, любая реальная сис­тема поддерживает много других типов, и для определенности я буду считать, что
определены типы INTEGER (целые числа), FIXED (числа с фиксированной запятой) и CHAR (строки символов произвольной длины). Примечание: В разделе «Скалярные типы в SQL» ниже я рассмотрю сис­темные
типы, поддерживаемые в языке SQL.
Ради исторической точности я должен объяснить, что в исходном определении реляционной модели Кодд говорил, что от­но­ше­ния определены над доменами, а не над типами. Но на самом деле домены и типы –
в точности одно и то же. Если хотите, можете считать это заявление отражением моей личной позиции, но я хотел бы привести ряд аргументов в ее защиту. Начну с реляционной модели в том виде, в котором ее
определил Кодд, то есть буду временно использовать термин домен, а не
тип. Я хочу рассмотреть два важных вопроса, каждому из которых отведу отдельный раздел:
Сравнения на равенство и «подавление проверки доменов»
В этой час­ти обсуждения я надеюсь убедить вас, что домены – это
типы.
Атомарность значений данных и первая нормальная форма
А в этой час­ти я рассчитываю доказать, что типы могут быть сколь
угодно сложными.

Сравнения на равенство
Вопреки сказанному выше об игнорировании определенных пользователем типов, в этом разделе я буду предполагать, что атрибут «номер по-

53

Сравнения на равенство

ставщика» (SNO) в пе­ре­мен­ных-от­но­ше­ниях S и SP объявлен как имеющий некоторый пользовательский тип – прошу прощения, домен, – который для простоты назовем также SNO. (Примечание: В этой книге
я рассматриваю слова объявление и определение как синонимы.) Аналогично, предположим, что атрибут «номер детали» (PNO) в пе­ре­мен­ныхот­но­ше­ниях P и SP также имеет некоторый пользовательский тип (или
домен), PNO. Отмечу, что эти предположения не слишком существенны
для моего рассуждения; мне просто кажется, что они придают ему убедительности и, возможно, делают несколько понятнее.
Начну с того, что, как всем известно (?), в реляционной модели два значения можно сравнивать только, если они принадлежат одному и тому
же домену. Например, следующее сравнение (которое могло бы встретиться во фразе WHERE SQL-запроса), очевидно, допустимо:
SP.SNO = S.SNO

/* правильно

*/

Напротив, это сравнение, очевидно (?), недопустимо:
SP.PNO = S.SNO

/* неправильно */

Почему недопустимо? Потому что номера деталей и номера поставщиков – совершенно разные вещи, они берутся из разных доменов. Таким
образом, общая идея состоит в том, что СУБД1 должна отвергать любую
попытку выполнить реляционную операцию (соединение, объединение
и т. п.), в которой явно или неявно присутствует сравнение на равенство значений из разных доменов. Вот, например, SQL-запрос, в котором пользователь пытается найти поставщиков, не поставляющих никаких деталей:
SELECT
FROM
WHERE
(

S.SNO , S.SNAME , S.STATUS , S.CITY
S
NOT EXISTS
SELECT *
FROM SP
WHERE SP.PNO = S.SNO )
/* неправильно */

(В конце нет точки с запятой, потому что это выражение, а не предложение. См. упражнение 2.23 в конце главы.)
В комментарии говорится, что этот запрос неправилен. Причина в том,
что в последней строке пользователь, вероятно, хотел написать WHERE
SP.SNO = S.SNO, но по ошибке – может быть, просто попал не по той
клавише – написал WHERE SP.PNO = S.SNO. А поскольку мы говорим
о простой опечатке (по всей видимости), то со стороны СУБД было бы
1

СУБД = сис­тема управления базами данных. Кстати, в чем разница между
СУБД и базой данных? (Это не праздный вопрос, поскольку очень час­то говорят база данных, имея в виду либо конкретную СУБД, например Oracle,
либо экземпляр такой СУБД, установленный на конкретном компьютере.
Проблема в том, что если называть СУБД базой данных, то что же такое
база данных?)

54

Глава 2. Типы и домены

любезно в этот момент прервать пользователя, подсветить место, где
он ошибся, и, быть может, спросить, хочет ли пользователь исправить
ошибку, прежде чем продолжить.
Но я не знаю ни одной SQL-сис­темы, которая вела бы себя подобным
образом; современные продукты в зависимости от того, как настроена база данных, либо просто выдают ошибку, либо возвращают неправильный ответ. Ну не то чтобы неправильный, скорее, правильный ответ на неверно заданный вопрос. (Вам от этого легче?)
Еще раз повторю: по идее СУБД должна отвергать сравнение, подобное SP.PNO = S.SNO, если оно недопустимо. Однако Кодд полагал, что
в такой ситуации у пользователя должна быть возможность заставить СУБД продолжить работу и выполнить сравнение, даже если оно
представляется недопустимым, считая, что иногда пользователь знает больше, чем СУБД. Мне такое обоснование принять трудно, потому
что, честно говоря, я не вижу в нем смысла, но я все-таки попытаюсь.
Предположим, что вам поручено спроектировать базу данных, в которой есть, к примеру, заказчики и поставщики, и вы решили определить
домен номеров заказчиков и домен номеров поставщиков. База данных
спроектирована, загружена, и все работает прекрасно год-другой. А затем какой-то пользователь приходит с запросом, о котором вы раньше даже не задумывались: «Есть ли у нас заказчики, которые одновременно являются нашими поставщиками?» Отметим, что запрос вполне
осмысленный. Отметим также, что он подразумевает сравнение на равенство номера заказчика с номером поставщика (кросс-доменное сравнение). А коль скоро это так, сис­тема, конечно, не должна запрещать
подобное сравнение; разумеется, сис­тема не вправе отказать в выполнении осмысленного запроса.
Исходя из таких соображений, Кодд предложил варианты некоторых
реляционных операторов с «подавлением проверки доменов» (domain
check override – DCO). Например, вариант соединения с подавлением проверки доменов должен выполнять соединение, даже если соединяемые атрибуты определены над разными доменами. В терминах
SQL можно было бы реализовать это предложение, введя новую фразу
IGNORE DOMAIN CHECKS, которая включалась бы в SQL-запрос, например, следующим образом:
SELECT
FROM
WHERE
IGNORE

...
...
CUSTNO = SNO
DOMAIN CHECKS

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

55

Сравнения на равенство
SELECT ...
FROM P , SP
WHERE P.WEIGHT = SP.QTY

|
|
|

SELECT ...
FROM P , SP
WHERE P.WEIGHT - SP.QTY = 0

В предположении (достаточно разумном), что вес и количество определены над разными доменами, запрос слева, очевидно, недопустим. Но
что сказать о запросе справа? Согласно Кодду, он является допустимым!
В своей книге «The Relational Model for Database Management Version 2»
(Addison-Wesley, 1990), на странице 47 он говорит, что в такой ситуации
«СУБД [просто] проверяет, что базовые типы данных одинаковы»; в рассматриваемом случае «базовые типы данных» – это числа (говоря нестрого), поэтому проверка проходит.
Мне такой вывод представляется неприемлемым. Очевидно, что выражения P.WEIGHT = SP.QTY и P.WEIGHT – SP.QTY = 0 означают по
существу одно и то же. Поэтому они должны быть допустимы или недопустимы одновременно; мысль о том, что одно может быть допустимо, а другое – нет, просто не имеет смысла. Поэтому мне кажется, что
в проверке доменов по Кодду изначально есть что-то странное, даже
если забыть о ее подавлении. (На самом деле, проверка доменов по Кодду применяется только в очень час­тном случае, когда оба сравниваемых
операнда определены как ссылки на простые атрибуты. Отметим, что
сравнение P.WEIGHT = SP.QTY попадает в эту категорию, а P.WEIGHT –
SP.QTY = 0 – нет.)
Рассмотрим еще более простой пример. Взгляните на следующие сравнения (то и другое могли бы встретиться во фразе WHERE предложения SQL):
S.SNO = ‘X4’

P.PNO = ‘X4’

S.SNO = P.PNO

Надеюсь, вы согласитесь, что по крайней мере первые два выглядят
вполне допустимыми (и выполнятся успешно и даже вернут TRUE),
тогда как третье – нет. Но если так, то я полагаю, что вы согласитесь
с тем, что происходит нечто странное: получается, что у нас есть три
значения a, b и c такие, что a = c истинно, b = c истинно, но что касается a = b, так мы даже не можем выполнить сравнение, что уж говорить
о его истинности! Так в чем же дело?
А теперь я вернусь к тому факту, что атрибуты S.SNO и P.PNO определены соответственно над доменами SNO и PNO, и к своему утверждению
о том, что домены на самом деле являются типами; фактически я уже
предположил ранее, что конкретные домены SNO и PNO – это определенные пользователем типы. Очень может быть, что оба эти типа физически представлены в терминах сис­темного типа CHAR, но, как мы
видели в главе 1, такие представления являются час­тью реализации,
а не модели; для пользователя это несущественно и даже скрыто от него
(по крайней мере, так должно быть)1. В час­тности, операторы, которые
1

В этой книге под словом представление (representation) я понимаю физическое представление, если контекст не подразумевает иной семантики.

56

Глава 2. Типы и домены

применяются к номерам поставщиков и к номерам деталей, – это операторы, входящие в определения соответствующих типов, а не те, что
определены для типа CHAR (см. раздел «Что такое тип?» ниже в этой
главе). Например, мы можем конкатенировать две символьные строки,
но не два номера поставщиков (это можно было бы сделать, если бы для
типа SNO был определен оператор конкатенации).
При определении типа мы должны также определить операторы, которые можно применять к значениям и пе­ре­мен­ным данного типа (снова отсылаю вас к разделу «Что такое тип?»). И одним из подлежащих
определению операторов является так называемый селектор, который
позволяет выбрать, или задать, произвольное значение определяемого типа1. Например, в случае типа SNO селектор (который на практике,
скорее всего, назывался бы тоже SNO) позволяет выбрать конкретное
значение типа SNO, для которого задано представление в виде CHAR.
Например:
SNO(‘S1’)

Это выражение представляет собой селектор SNO и возвращает некоторый номер поставщика (точнее, тот, что представлен символьной строкой ‘S1’). Аналогично выражение:
PNO(‘P1’)

представляет собой селектор PNO и возвращает некоторый номер детали (точнее, тот, что представлен символьной строкой ‘P1’). Иными словами, селекторы SNO и PNO принимают значение типа CHAR и преобразуют его в значение типа SNO или PNO, соответственно.
Вернемся к сравнению S.SNO = ‘X4’. Как видите, сравниваемые величины имеют разные типы (если быть точным, то SNO и CHAR). Так как
типы различны, то величины не могут быть равны (напомним, что два
значения можно сравнить на равенство, «только если они принадлежат
одному домену»). Но сис­теме хотя бы известно, что существует оператор,
а именно селектор SNO, который преобразует тип CHAR в тип SNO. Поэтому она может неявно вызвать этот оператор для преобразования операнда типа CHAR в номер поставщика, заменив тем самым исходное
сравнение таким:
S.SNO = SNO(‘X4’)

Теперь мы сравниваем два номера поставщика, что вполне законно.
Точно так же сис­тема может заменить сравнение P.PNO = ‘X4’ таким:

1

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

Сравнения на равенство

57

P.PNO = PNO(‘X4’)

Но для сравнения S.SNO = P.PNO сис­теме неизвестен (по крайней мере,
мы так предполагаем) оператор, который смог бы преобразовать номер
поставщика в номер детали или наоборот, поэтому сравнение завершается ошибкой типизации: сравниваемые величины имеют разные типы
и не существует способа привести их к одному типу.

Примечание
Неявное преобразование типов в литературе час­то называется приведением
(coercion). Таким образом, в первом примере символьная строка ‘X4’ приводится к типу SNO, а во втором – к типу PNO. Я еще вернусь к теме приведения типов в SQL в разделе «Проверка и приведение типов в SQL» ниже.

Продолжим рассмотрение примера. При определении пользовательского типа, такого как SNO или PNO, мы должны определить специальный оператор THE_, который преобразует значение типа SNO или
PNO в строку символов (или иную форму), служащую для его представления1. Предположим, что в данном примере операторы THE_ для типов SNO и PNO называются THE_SC и THE_PC соответственно. Тогда
у желания сравнить S.SNO и P.PNO на равенство, на мой взгляд, есть
единственная разумная интерпретация – нас интересует, совпадают ли
строковые представления значений. Это можно было бы проверить следующим образом:
THE_SC ( S.SNO ) = THE_PC ( P.PNO )

Другими словами: преобразовать номер поставщика в строку, преобразовать номер детали в строку и сравнить обе строки.
Уверен, вы понимаете, что набросанный только что механизм, в котором учас­твуют селекторы и операторы THE_, эффективно решает обе
проблемы: (а) проверка доменов и (б) подавление этой проверки в случае
необходимости. К тому же он делает это элегантным, полностью ортогональным, а не придуманным для час­тного случая способом. Напротив, механизм подавления проверки доменов на самом деле эту задачу
не решает; фактически он вообще не имеет смысла, так как смешивает
в одну кучу типы и представления (как уже отмечалось, типы – это понятие уровня модели, а представления – понятие уровня реализации).
Вы, вероятно, уже поняли, что я все это время говорил о том, что в языках программирования называется строгой типизацией. Разные авто-

1

Опять-таки это наблюдение справедливо вне зависимости от того, идет ли
речь об SQL или ином контексте, хотя (как и в случае селекторов) «операторы THE_» в SQL не так просты, как кажется, а сам термин «оператор
THE_» в SQL отсутствует. Замечу также, что для данного типа может быть
определено несколько операторов THE_; пример такого рода см. в обсуждении типа POINT в разделе «Что такое тип?».

58

Глава 2. Типы и домены

ры дают слегка отличающиеся определения этого термина, но по существу он означает, что (а) все – в том числе все значения и все пе­ре­мен­
ные – имеет тип и (б) при попытке выполнить любую операцию сис­тема
проверяет, что операнды имеют подходящие для этой операции типы
(или приводятся к подходящим типам). Отметим также, что этот механизм применим ко всем операциям, а не только к сравнению на равенство, которое мы обсуждали; упор именно на операции сравнения в обсуждениях проверки доменов, встречающихся в литературе, обусловлен чисто историческими причинами и, честно говоря, неуместен. Рассмотрим, к примеру, следующие выражения:
P.WEIGHT * SP.QTY
P.WEIGHT + SP.QTY

Первое, по-видимому, допустимо (в результате получается вес, точнее,
полный вес одной поставки). Напротив, второе, скорее всего, недопустимо (как можно складывать вес с количеством?).
Я хотел бы завершить этот раздел, подчеркнув поистине фундаментальную роль оператора сравнения на равенство («=»). Совсем не случайно наше обсуждение сфокусировалось на сравнении двух значений
на равенство. Дело в том, что равенство является важнейшей концепцией, и реляционная модель требует, чтобы она была поддержана во
всех типах. Действительно, коль скоро тип по существу представляет
собой множество значений (см. раздел «Что такое тип?»), то без оператора «=» мы даже не смогли бы сказать, какие значения составляют тип!
Иными словами, если дан тип T и значение v, то без этого оператора мы
не могли бы сказать, совпадает ли v с каким-то элементом множества
значений, составляющих тип T.
Более того, реляционная модель также определяет семантику оператора
«=» следующим образом: если v1 и v2 – значения одного типа, то выражение v1 = v2 равно TRUE, если v1 и v2 – одно и то же значение, и FALSE –
в противном случае. (Напротив, если v1 и v2 – значения разных типов,
то выражение v1 = v2 не имеет смысла – даже не является допустимым
сравнением, – если только не существует приведения v1 к типу v2 или
наоборот, а в таком случае речь уже не идет о сравнении v1 и v2.)

Атомарность значений данных
Надеюсь, что в предыдущем разделе мне удалось убедить вас, что домены – это вдейст­вительности типы, не более и не менее. А теперь я хочу
обратиться к вопросу об атомарности значений данных и связанному
с ним понятию первой нормальной формы (для краткости 1НФ). В главе 1 я сказал, что 1НФ означает, что каждый кортеж любого от­но­ше­
ния содержит единственное значение (соответствующего типа) в позиции каждого атрибута, – и обычно к этому добавляют, что «единствен-

Атомарность значений данных

59

ное значение» предполагается атомарным. Но сразу же возникает вопрос: а что такое атомарные данные?
На странице 6 уже упоминавшейся книги («The Relational Model for Data­
base Management Version 2») Кодд определяет атомарные данные как такие данные, «которые СУБД не может разложить на более мелкие составляющие (исключая некоторые специальные функции)». Даже если не обращать внимания на примечание в скобках, это определение выглядит
несколько загадочным и/или не слишком точным. Например, что сказать о строках символов? Они атомарны? Любой известный мне продукт
предоставляет многочисленные операторы – LIKE, SUBSTR (подстрока),
«||» (конкатенация) и прочие, – которые исходят из того, что для строк
символов СУБД может выполнить разложение на более простые составляющие. Так атомарны строки символов или нет? Как вы думаете?
Приведу еще несколько примеров значений, атомарность которых проблематична, но которые мы тем не менее хотели бы видеть в качестве
значений атрибутов от­но­ше­ний:
•• Целые числа, которые разлагаются в произведение простых множителей (я понимаю, что это не то разложение, которое мы обычно подразумеваем в данном контексте, но просто хочу показать, что самое
понятие разложимости, или декомпозиции, открыто для интерпретации).
•• Числа с фиксированной запятой, которые можно разложить на целую и дробную час­ти.
•• Дата и время, которые можно представить в виде композиции год/
месяц/день или час/минута/секунда соответственно.
И так далее.
А теперь я хочу предложить более удивительный пример. Взгляните
на рис. 2.1. От­но­ше­ние R1 на этом рисунке – это редуцированная версия от­но­ше­ния «поставки» из нашего сквозного примера; оно показывает, что какие-то поставщики поставляют какие-то детали, и содержит по одному кортежу для каждой допустимой комбинации SNO/PNO.
Давайте пока согласимся, что номера поставщиков и номера деталей
дейст­вительно «атомарны», тогда можно признать, что от­но­ше­ние R1
находится по меньшей мере в первой нормальной форме.
Допустим теперь, что мы заменили R1 от­но­ше­нием R2, в котором показано, что какие-то поставщики поставляют некоторые группы деталей (атрибут PNO в R2, по выражению некоторых авторов, многозначный, и значениями этого атрибута являются группы номеров деталей). В этом случае большинство людей наверняка сказали бы, что R2
не приведена к 1НФ; фактически это выглядит как пример «повторяющихся групп», а повторяющиеся группы – это то, что, по мнению большинства, 1НФ как раз и призвана запретить (поскольку такие группы
уж точно не атомарны, правильно?).

60

Глава 2. Типы и домены
R1

R2

R3

SNO

PNO

SNO

PNO

SNO

PNO_SET

S2
S2
S3
S4
S4
S4

P1
P2
P2
P2
P4
P5

S2
S3
S4

P1, P2
P2
P2, P4, P5

S2
S3
S4

{P1, P2}
{P2}
{P2, P4, P5}

Рис. 2.1. От­но­ше­ния R1, R2 и R3

Хорошо, согласимся пока, что R2 не приведена к 1НФ. Но давайте теперь
заменим R2 на R3. Я утверждаю, что от­но­ше­ние R3 находится в 1НФ!
(Я не утверждаю, что это от­но­ше­ние удачно спроектировано, – скорее,
нет, – но не в этом дело. Сейчас меня интересует, что считать допустимым, а не правильные подходы к проектированию. Структура R3 допустима.) В поддержку своего мнения приведу следующие аргументы:
•• Во-первых, отмечу, что я переименовал атрибут в PNO_SET и показал, что группы номеров деталей являются значениями типа PNO_
SET, заключив их в фигурные скобки, чтобы подчеркнуть тот факт,
что каждая такая группа на самом деле представляет собой единственное значение: значение-множество, если быть точным, но множество на определенном уровне абстракции тоже можно считать
единственным значением.
•• Во-вторых (и независимо от того, что вы думаете о моем первом аргументе), множество, подобное {P2,P4,P5}, разложимо СУБД не более и не менее, чем строка символов, – и это неоспоримый факт. Как
и строки символов, множества имеют внутреннюю структуру, но для
определенных целей эту структуру удобно игнорировать. Другими
словами, если строки символов совместимы с требованиями 1НФ, то
есть являются атомарными, значит, то же самое должно быть справедливо в от­но­ше­нии множеств.
А веду я к тому, что понятие атомарности вообще не имеет смысла; оно
зависит от того, что мы собираемся делать с данными. Иногда мы хотим обращаться с множеством номеров деталей, как с единым целым,
а иногда рассматривать отдельные номера деталей из этого множества –
но в этом случае мы опускаемся на более низкий уровень детализации,
или абстракции. Возможно, поможет такая аналогия. В физике (откуда, собственно, и пришло понятие атомарности) прослеживается точная параллель: иногда мы хотим рассматривать отдельные атомы как
неделимые сущности, а иногда рассуждаем об элементарных час­тицах
(протонах, нейтронах и электронах), из которых состоит атом. Более
того, по крайней мере протоны и нейтроны сами не являются неделимыми, они состоят из «субэлементарных» час­тиц, называемых кварками. И возможно, на этом дело не кончается.

Атомарность значений данных

61

Вернемся ненадолго к от­но­ше­нию R3. На рис. 2.1 я показал значения
PNO_SET в виде множеств общего вида. Но на практике было бы полезнее, если бы это были от­но­ше­ния (см. рис. 2.2, где я изменил имя атрибута на PNO_REL). Почему это было бы полезнее? Потому что именно
от­но­ше­ния, а не множества вообще, – предмет реляционной теории1.
Следовательно, к от­но­ше­ниям применим весь аппарат реляционной алгебры – их можно ограничивать, проецировать, соединять и т. д. А если
бы мы использовали множества общего вида, то должны были бы вводить новые операторы (объединение множеств, пересечение множеств
и прочие) для работы с ними… Куда лучше извлечь максимум пользы
из тех операторов, что уже есть!
Атрибут PNO_REL на рис. 2.2 – пример атрибута, значением которого является от­но­ше­ние (relation valued attribute – RVA). Я еще буду говорить об RVA-атрибутах в главе 7, а пока отмечу лишь, что SQL их не
поддерживает. (Точнее, не поддерживается то, что можно было бы назвать аналогом RVA-атрибутов, – столбцы с таб­личными значениями. Как ни странно, SQL все же поддерживает столбцы со значениямимассивами, столбцы со значениями-строками и даже столбцы, значениями которых являются «мультимножества строк», где под мульти­
множеством понимается множество, допускающее дубликаты. Столбцы, значениями которых являются мультимножества строк, немного
напоминают «столбцы с таб­личными значениями», но на самом деле таковыми не являются, так как к содержащимся в них значениям нельзя
применять стандартные SQL-операторы для работы с таб­лицами и потому они, по определению, не могут считаться таб­личными значениями в смысле SQL.
Я намеренно выбрал предыдущий – шокирующий – пример. В конце
концов, от­но­ше­ния с RVA-атрибутами дейст­вительно выглядят, как от­
но­ше­ния с повторяющимися группами, а вы наверняка слышали, что
повторяющиеся группы в реляционном мире находятся под строгим запретом. Но я бы мог проиллюстрировать свою точку зрения на ряде других примеров; я мог бы показать атрибуты (и, следовательно, домены),
которые содержат массивы, мультимножества, списки, фотографии,
аудио- или видеозаписи, рентгеновские лучи, отпечатки пальцев, XMLдокументы – что угодно, «атомарное» или «неатомарное». Атрибуты и,
стало быть, домены, могут содержать произвольные значения.
Кстати говоря, предыдущий абзац – прямой путь к объяснению того,
почему настоящая «объектно-реляционная» сис­тема – не что иное, как
1

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

62

Глава 2. Типы и домены

настоящая реляционная сис­тема, то есть сис­тема, которая поддерживает реляционную модель и все вытекающие из нее требования. Ведь
смысл «объектно-реляционной» сис­темы в том и заключается, что значения атрибутов от­но­ше­ния могут быть сколь угодно сложными. Быть
может, лучше выразить эту мысль по-другому: истинная объектнореляционная сис­тема – это просто реляционная сис­тема с надлежащей
поддержкой типов (в час­тности, типов, определенных пользователем),
а это как раз и есть истинная реляционная сис­тема, не больше, не меньше. И то, что некоторые любят называть «объектно-реляционной моделью» – это просто реляционная модель, ни больше, ни меньше.
R4

SNO

PNO_REL

S2

PNO
P1
P2

S3

PNO
P2

S4

PNO
P2
P4
P5

Рис. 2.2. От­но­ше­ние R4 (пересмотренный вариант от­но­ше­ния R3)

Что такое тип?
Начиная с этого места, я буду употреблять термин тип вместо домен.
Так что же такое тип? По сути дела, это именованное конечное множество значений1 – всех значений данного типа, например: всех возможных целых чисел, всех возможных строк, всех возможных номеров поставщиков, всех возможных XML-документов, всех возможных от­но­
ше­ний с некоторым заголовком и т. д. Кроме того:
•• Любое значение принадлежит некоторому типу – на самом деле, в точности одному типу, если не рассматривать поддержку наследования,
о чем мы в этой книге говорить не будем. Примечание: Из этого определения следует, что типы дизъюнктны (не перекрываются). Однако
на этом моменте, пожалуй, имеет смысл остановиться более подробно.
Как сказал один из рецензентов, типы ТеплокровноеЖивотное и ЧетырехногоеЖивотное, безусловно, перекрываются, разве нет? Конеч1

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

Что такое тип?

63

но, перекрываются, но я хочу сказать, что, допуская перекрытие типов, мы по различным причинам вступаем в область наследования
типов, фактически даже в область множественного наследования.
Поскольку эти причины, да и вообще вся тематика наследования, не
зависят от рассматриваемого нами контекста, реляционного или любого другого, я не собираюсь останавливаться на них в этой книге.
•• Любая пе­ре­мен­ная, любой атрибут, любой оператор, возвращающий результат, и любой параметр любого оператора объявляется
как принадлежащий некоторому типу. И если, например, объявлено, что пе­ре­мен­ная V имеет тип T, то это в точности означает, что
любое значение v, которое допустимо присвоить V, само должно принадлежать типу T.
•• Всякое выражение обозначает некоторое значение и потому имеет
тип, а именно тип своего значения, то есть тип того значения, которое возвращает самый внешний оператор в выражении (под «самым
внешним» я понимаю оператор, который выполняется последним).
Например, типом выражения
( a / b ) + ( x - y )

будет объявленный тип оператора «+», каким бы он ни оказался.
Тот факт, что и параметры имеют тип, подводит нас к вопросу, о котором я уже упоминал, но пока не обсуждал подробно, а именно, что с каждым типом ассоциирован набор операторов, применяемых к значениям и пе­ре­мен­ным данного типа. Здесь слова «оператор Op ассоциирован
с типом T» в точности означают, что у оператора Op имеется параметр
типа T.1 Например, для целых чисел определены обычные арифметические операторы; для даты и времени имеются специальные календарные
арифметические операторы; для XML-документов определены так называемые XPath-операторы; для от­но­ше­ний – операторы реляционной алгебры. И для любого типа определены операторы присваивания («:=»)
и сравнения на равенство («=»). Таким образом, любая сис­тема, обеспечивающая надлежащую поддержку типов, – а «надлежащая поддержка», конечно, предполагает и возможность определения типов пользователями – должна допускать и определение пользователями собственных операторов, поскольку типы без операторов бесполезны. Примеча-

1

Логическое различие между типом и представлением здесь особенно важно. Подчеркну еще раз: оператор, ассоциированный с типом T, ассоциирован именно с типом T, а не с представлением типа T. Например, из того, что
представлением типа SNO является CHAR, вовсе не следует, что мы можем
конкатенировать два номера поставщиков; это возможно лишь в случае,
когда оператор конкатенации («||») определен для типа SNO. (Если помните, я уже мимоходом упоминал именно этот пример в разделе «Сравнение
на равенство».)

64

Глава 2. Типы и домены

ние: Пользовательские операторы можно ассоциировать и с сис­темными
типами, а не только с определенными пользователем.
Отметим, что, по определению, к значениям и пе­ре­мен­ным данного
типа можно применять только операторы, ассоциированные с этим типом. Рассмотрим, к примеру, сис­темный тип INTEGER:
•• Сис­тема предоставляет оператор присваивания «:=», который позволяет присвоить целочисленное значение целочисленной пе­ре­мен­ной.
•• Она также предоставляет операторы сравнения «=», «≠», « ‘E2’
f. ENAME || DNAME
g. LOCATION || ‘burg’
Упражнение 2.16. Иногда говорят, что типы в некотором смысле являются пе­ре­мен­ными. Например, по мере роста предприятия количество цифр в номере служащего может увеличиться с трех до четырех, поэтому может возникнуть необходимость изменить «множество всех возможных номеров служащих». Обсудите этот вопрос.
Упражнение 2.17. Тип – это множество значений, а множество может
быть пустым. Следовательно, мы могли бы определить пустой тип,
как тип с пустым множеством значений. Можете ли вы придумать
применения такому типу? Поддерживается ли такой тип в SQL?
Упражнение 2.18. В реляционном мире оператор сравнения на равенство «=» применим к любому типу. Напротив, в SQL не требуется,
чтобы для любого типа был определен оператор «=» (я не упомянул
об этом в тексте главы, но это справедливо, в час­тности, для определенных пользователем типов), а в тех случаях, когда оператор имеется, его семантика не всегда полностью определена. Какие следствия
вытекают из такого положения вещей?
Упражнение 2.19. Продолжая предыдущее упражнение, мы можем
сказать, что в реляционном мире v1 = v2 дает значение TRUE тогда и только тогда, когда применение оператора Op к v1 и применение того же оператора Op к v2 всегда дает один и тот же результат
для всех возможных операторов Op. Но это еще одно правило, которое в SQL нарушено. Можете ли вы привести примеры такого нарушения? Каковы его последствия?
Упражнение 2.20. Почему из реляционной модели исключены указатели?

80

Глава 2. Типы и домены

Упражнение 2.21. Принцип присваивания – очень простой, но вместе
с тем фундаментальный – гласит, что после присваивания значения
v пе­ре­мен­ной V результатом сравнения V = v должно быть TRUE (см.
главу 5). Но в SQL нарушается и этот принцип (и даже чуть ли не повсеместно). Можете ли вы привести примеры такого нарушения? Каковы его последствия?
Упражнение 2.22. Полагаете ли вы, что типы – это «принадлежность»
базы данных в том же смысле, что пе­ре­мен­ные-от­но­ше­ния?
Упражнение 2.23. В первом примере SQL-выражения SELECT, приведенном в этой главе, я отметил, что завершающая точка с запятой
отсутствует, потому что это выражение, а не предложение. Но в чем
различие между ними?
Упражнение 2.24. Объясните максимально точно, в чем состоит логическое различие между от­но­ше­нием с RVA-атрибутом и «от­но­ше­
нием» с повторяющейся группой.
Упражнение 2.25. Что такое подзапрос?
Упражнение 2.25. В тексте главы я сказал, что оператор «=» по идее
должен быть применим к любому типу. Но как насчет типов строк
и таб­лиц в SQL?

3
Глава
.

Кортежи и от­но­ше­ния,
строки и таб­лицы

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

Что такое кортеж?
Это кортеж?
SNO CHAR SNAME CHAR
S1
Smith

STATUS

INTEGER
20

CITY CHAR
London

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

82

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы
STATUS

INTEGER
20

SNAME CHAR
Smith

CITY CHAR
London

SNO CHAR
S1

Я, конечно, буду пользоваться подобными рисунками и дальше, но
имейте в виду, что это всего лишь рисунки, из которых иногда можно
сделать неверные выводы. Предупредив об этой опасности, я могу теперь точно определить, что такое кортеж:
Определение: Пусть существуют имена типов T1, T2, ..., Tn
(n ≥ 0), необязательно все различные. Сопоставим с каждым Ti имя атрибута Ai; каждая из n получившихся пар
имя-атрибута/имя-типа называется атрибутом. Сопоставим с каждым атрибутом значение атрибута vi типа Ti;
каждая из n получившихся пар атрибут/значение называется компонентом. Множество, состоящее из всех n определенных таким образом компонентов, назовем его t, называется значением кортежа (или для краткости просто кортежем) над атрибутами A1, A2, ..., An. Значение n называется степенью t; кортеж степени 1 называется унарным,
кортеж степени 2 – бинарным, кортеж степени 3 – тернарным, и, более общо, кортеж степени n называется n-арным.
Множество, состоящее из всех n атрибутов, называется заголовком t.
Например, обращаясь к одному из предыдущих рисунков, на которых
изображен наш привычный кортеж для поставщика S1, имеем:
Степень: 4. Считается, что заголовок тоже имеет степень 4.
Имена типов: CHAR, CHAR, INTEGER, CHAR.
Соответствующие имена атрибутов: SNO, SNAME, STATUS, CITY.
Соответствующие значения атрибутов: ‘S1’, ‘Smith’, 20, ‘London’.
Кстати, обратите внимание на кавычки, в которые заключены значения строк символов; я не показывал кавычки на рисунках, хотя,
наверное, стоило бы – это было бы правильнее.

Отступление
Предположим на минутку, как мы делали в разделе «Сравнение на равенство» в главе 2, что атрибут SNO имеет тип SNO (определенный пользователем), а не CHAR. Тогда было бы совсем неправильно говорить, что значение
SNO в рассматриваемом кортеже равно S1 или даже ‘S1’; нужно было бы написать SNO(‘S1’). Значение типа SNO – это значение типа SNO, а не значение
типа CHAR! Несовпадение типов, безусловно, является существенным логическим различием. (Напомню, что в главе 2 говорилось о том, что выражение
SNO(‘S1’) – это вызов селектора – фактически литерал – типа SNO.)

83

Что такое кортеж?

Заголовок: здесь проще всего привести еще один рисунок:
SNO CHAR SNAME CHAR

STATUS

INTEGER

CITY

CHAR

Разумеется, на этом рисунке представлено множество без какого-либо
фиксированного порядка атрибутов. Вот еще одно изображение того же
самого заголовка:
STATUS

INTEGER

SNAME CHAR

CITY

CHAR

SNO CHAR

Упражнение: сколько существует различных рисунков такого вида,
представляющих данный заголовок? (Ответ: 4 * 3 * 2 * 1 = 24)
Итак, кортеж – это значение; поэтому, как и все значения, он имеет
тип (это мы уяснили в главе 2), и этот тип, как и все типы, имеет имя.
В языке Tutorial D такие имена принимают вид TUPLE{H}, где {H} – заголовок. Стало быть, в нашем примере получается такое имя:
TUPLE { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }

(но порядок следования атрибутов произволен).
Еще раз повторю: кортеж – это значение. Следовательно, как и все значения, он должен возвращаться каким-то вызовом селектора (естественно, вызовом селектора кортежа, коль скоро значением является
кортеж). Вот как выглядит вызов селектора кортежа в нашем случае
(опять-таки на языке Tutorial D):
TUPLE { SNO ‘S1’ , SNAME ‘Smith’ , STATUS 20 , CITY ‘London’ }

(порядок записи компонентов произволен). Отметим, что в Tutorial D
каждый компонент задается просто в виде пары имя-атрибута/выражение, где выражение обозначает соответствующее значение атрибута;
тип атрибута опускается, поскольку его всегда можно вывести из типа
выражения.
Вот другой пример (в отличие от предыдущего, это не литерал, потому
что не все его аргументы заданы в виде литералов):
TUPLE { SNO SX , SNAME ‘James’ , STATUS STX , CITY SCX }

Я предполагаю, что SX, STX и SCX – пе­ре­мен­ные типа CHAR, INTEGER
и CHAR соответственно.
Как следует из этих примеров, вызов селектора кортежа в языке
Tutorial D в общем случае состоит из ключевого слова TUPLE, за которым следует список пар имя-атрибута/выражение, разделенных запятыми, причем весь список заключен в фигурные скобки. Таким образом, ключевое слово TUPLE играет в языке Tutorial D двоякую роль:
оно используется в вызовах селекторов кортежей, как мы только что
видели, и в именах типов кортежей, как было показано выше. Анало-

84

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

гичное замечание относится к ключевому слову RELATION (см. раздел
«Что такое от­но­ше­ние» ниже в этой главе).

Следствия из определений
Теперь я хочу остановиться на некоторых важных следствиях, вытекающих из приведенных выше определений. Первое – кортеж никогда
не содержит null-значений. Причина в том, что, по определению, любой кортеж содержит значение (соответствующего типа) каждого из
своих атрибутов, а, как мы видели в главе 1, null вообще не является
значением – вопреки тому факту, что в SQL час­то, хотя и не всегда, он
явно называется null-значением. Рекомендация: Так как словосочетание «null-значение» содержит в себе терминологическое противоречие,
не пользуйтесь им, говорите просто null1.
Итак, если кортеж не содержит null-значений, их тем более не может
содержать от­но­ше­ние; следовательно, мы немедленно получаем по
меньшей мере формальную причину отвергнуть саму концепцию nullзначений. Но в следующей главе я приведу и более прагматичные основания для этого.
Второе следствие – любое подмножество кортежа тоже является кортежем, а любое подмножество заголовка – заголовком. (Примечание:
В соответствии со сложившейся практикой я будут употреблять выражения «В – подмножество A» и «A – надмножество B», подразумевая,
что A и B могут, в час­тности, совпадать. Так, в рассматриваемом случае всякий кортеж является подмножеством себя самого, и то же самое
справедливо для заголовков. Если мне понадобится исключить такую
возможность, то я буду говорить о собственных подмножествах и надмножествах.) Если обратиться к нашему стандартному примеру кортежа для поставщика S1, – то, что можно было бы назвать «значением
{SNO,CITY}» внутри кортежа, само является кортежем (степени 2):
SNO CHAR CITY
S1

CHAR

London

Заголовок этого кортежа показан на рисунке, а его тип – TUPLE {SNO
CHAR, CITY CHAR}. Точно так же кортежем является и следующее
подмножество:
SNO CHAR
S1

1

Несмотря на эту рекомендацию, в переводе употребляется именно словосочетание null-значение в силу особенностей русского языка и устоявшейся
в нем терминологии. – Прим. перев.

Следствия из определений

85

Это кортеж степени 1 типа TUPLE {SNO CHAR}. Таким образом, если мы
имеем подобный кортеж и хотим получить доступ к значению конкретного атрибута – в данном случае ‘S1’, – то должны будем как-то извлечь
его из объемлющего кортежа. Для этой цели язык Tutorial D предлагает синтаксис вида SNO FROM t (где t – произвольное выражение, обозначающее кортеж с атрибутом SNO). В SQL применяется квалификация с помощью точки: t.SNO. Примечание: В главе 2 мы видели, что кортеж t и от­но­ше­ние r, содержащее только данный кортеж t и ничего более, – не одно и то же. Аналогично, значение v и кортеж t, содержащий
только это значение, – разные вещи; в час­тности, не совпадают их типы.
Уверен, вы знаете, что пустое множество является подмножеством любого множества. Отсюда следует, что кортеж с пустым заголовком и,
значит, с пустым набором компонентов, допустим, хотя нарисовать такой кортеж на бумаге было бы несколько затруднительно, я даже пытаться не буду. Кортеж с пустым заголовком имеет тип TUPLE{} (у него
нет компонентов); иногда мы будем называть его просто 0-кортежем,
чтобы подчеркнуть, что его степень равна 0. А иногда будем называть
такой кортеж пустым. Если вы думаете, что у пустых кортежей нет
практического применения, то смею заверить, что есть, – и, как это
ни удивительно, очень важное. Я вернусь к этому вопросу в разделе
«TABLE_DUM и TABLE_DEE».
И последнее, что я хочу обсудить в этом разделе, – понятие равенства
кортежей. (Напомню, в главе 2 говорилось о том, что оператор сравнения «=» определен для любого типа, и типы кортежей – не исключение.)
По сути дела, два кортежа равны тогда и только тогда, когда это один
и тот же кортеж (точно так же, два целых числа равны тогда и только
тогда, когда это одно и то же число). Но имеет смысл остановиться на
семантике равенства кортежей более подробно, потому что она лежит
в основе очень многих вещей в реляционной модели, например потенциальные ключи, внешние ключи и большинство операторов реляционной алгебры определяются в терминах равенства кортежей. Вот как
звучит точное определение:
Определение: Кортежи tx и ty считаются равными тогда
и только тогда, когда они имеют одни и те же атрибуты A1,
A2, ..., An – другими словами, принадлежат одному и тому
же типу – и для любого i (i = 1, 2, ..., n) значение vx атрибута
Ai в кортеже tx равно значению vy атрибута Ai в кортеже ty.
Кроме того (это может показаться очевидным, но уточнить все-таки стоит), два кортежа являются дубликатами тогда и только тогда, когда они
равны.
Кстати, из этого определения сразу же следует, что все 0-кортежи являются дубликатами друг друга. Поэтому мы вправе говорить о единственном 0-кортеже, а не о каком-то из 0-кортежей.

86

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

Наконец, заметим, что операторы сравнения «» к кортежам неприменимы. Причина состоит в том, что кортежи – это множества (точнее, множества компонентов), а для множеств общего вида эти операторы не имеют смысла.

Строки в SQL
В языке SQL поддерживаются не кортежи, а строки; в час­тности, поддержаны типы строк, конструктор типа строки и конструкторы значений строк, которые являются соответственно аналогами типов кортежей, генератора типа TUPLE и селекторов кортежей в языке Tutorial
D. (Типы строк и конструкторы типов строк, но не конструкторы значений строк, обсуждались в главе 2.) Но эти аналогии в лучшем случае приблизительные, поскольку у строк, в отличие от кортежей, есть
очень важное свойство: их компоненты упорядочены слева направо. Например, оба выражения ROW(1,2) и ROW(2,1) – допустимые вызовы конструктора значения строки, но в SQL они представляют две разных строки. Примечание: Ключевое слово ROW при вызове конструктора значения строки в SQL необязательно и на практике чаще всего опускается.
Благодаря наличию упорядоченности слева направо компоненты строки в SQL могут идентифицироваться (и час­то идентифицируются) не по
имени, а по порядковому номеру. Рассмотрим, к примеру, следующий
вызов конструктора значения строки (фактически это строковый литерал, хотя в SQL такой термин не употребляется):
( ‘S1’ , ‘Smith’ , 20 , ‘London’ )

Очевидно, что (среди прочего) у этой строки есть компонент со значением ‘Smith’; однако логически мы не можем сказать, что это «компонент
SNAME», а можем лишь сказать, что это второй компонент.
Следует добавить, что в SQL любая строка содержит по крайней мере
один компонент, в этом языке нет аналога 0-кортежей из реляционной
модели.
Как отмечалось в главе 2, SQL поддерживает также операцию присваивания строк1. В час­тности, такие присваивания производятся (по существу) при выполнении SQL-предложения UPDATE. Например, следующее предложение UPDATE
UPDATE S
SET
STATUS = 20 , CITY = ‘London’
WHERE CITY = ‘Paris’ ;
1

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

87

Строки в SQL

по определению логически эквивалентно такому (обратите внимание на
присваивание строки во второй строчке):
UPDATE S
SET ( STATUS , CITY ) = ( 20 , ‘London’ )
WHERE CITY = ‘Paris’ ;

Что касается операторов сравнения, то большинство булевых выражений в SQL, в том числе (хотите верьте, хотите нет) простые «скалярные» сравнения, фактически определены в терминах строк, а не скаляров. Вот пример выражения SELECT, в котором фраза WHERE содержит явное сравнение строк:
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) = ( 20 , ‘London’ )

Это выражение SELECT логически эквивалентно следующему:
SELECT SNO
FROM S
WHERE STATUS = 20 AND CITY = ‘London’

А такое выражение
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) ( 20 , ‘London’ )

логически эквивалентно следующему:
SELECT
FROM
WHERE
OR

SNO
S
STATUS 20
CITY ‘London’

Обратите внимание на OR в последней строчке!
Далее, поскольку компоненты строк упорядочены слева направо, SQL
может также поддержать операторы «» для сравнения строк, например:
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) > ( 20 , ‘London’ )

Это выражение логически эквивалентно такому:
SELECT
FROM
WHERE
OR (

SNO
S
STATUS > 20
STATUS = 20 AND CITY > ‘London’ )

Однако на практике в подавляющем большинстве случаев сравниваются строки степени 1, например:

88

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы
SELECT SNO
FROM S
WHERE ( STATUS ) = ( 20 )

До сих пор все встречавшиеся в выражениях сравнения операнды были
конструкторами значения строки. Но если конструктор значения строки состоит из единственного скалярного выражения, заключенного
в скобки, то эти скобки можно опустить:
SELECT SNO
FROM S
WHERE STATUS = 20

«Сравнение строк» во фразе WHERE на деле является скалярным сравнением (STATUS и 20 – скалярные выражения). Однако, строго говоря,
в SQL такой вещи, как скалярное сравнение, не существует; выражение STATUS = 20 технически по-прежнему является сравнением строк
(и «скалярные» операнды приводятся к типу строки).
Рекомендация: Если степень сравниваемых строк отлична от единицы
(то есть речь не идет о сравнении скаляров), не пользуйтесь операторами
сравнения «=»; они зависят от упорядочения столбцов
слева направо и, следовательно, не имеют прямого аналога в реляционной модели, да и в любом случае чреваты серьезными ошибками. (В этой
связи нелишне упомянуть, что когда эту функциональность было впервые предложено включить в SQL, авторы стандарта испытывали значительные трудности с надлежащим определением семантики, им даже
потребовалось для этого несколько попыток.)

Что такое от­но­ше­ние?
Все примеры в этом разделе основаны на нашем привычном от­но­ше­нии
«поставщики». Сначала приведу рисунок:
SNO CHAR SNAME CHAR
S1
S2
S3
S4
S5

Smith
Jones
Blake
Clark
Adams

STATUS

INTEGER
20
10
30
20
30

CITY

CHAR

London
Paris
Paris
London
Athens

а затем дам определение:
Определение: Пусть {H} – заголовок кортежа, и пусть t1,
t2, ..., tm (m ≥ 0) – различные кортежи с заголовком {H}.
Комбинация r, состоящая из {H} и множества кортежей
{t1, t2, ..., tm}, называется от­но­ше­нием-значением (или для
краткости просто от­но­ше­нием) над атрибутами A1, A2, ...,
An, где A1, A2, ..., An – атрибуты, содержащиеся в заголовке {H}. Заголовком r является {H}; r имеет те же атрибуты (и, следовательно, те же имена и типы атрибутов), что

89

Что такое от­но­ше­ние?

и заголовок. Телом r называется множество кортежей {t1,
t2, ..., tm}. Величина m называется кардинальностью r.
Оставляю вам в качестве упражнения интерпретацию от­но­ше­ния «поставщики» в терминах приведенного выше определения. Однако я хочу
по крайней мере, объяснить, почему эта штука называется от­но­ше­нием.
По сути дела, любой кортеж в от­но­ше­нии представляет n-арную связь
(relationship) в множестве из n значений (по одному для каждого атрибута кортежа), существующую в некоторый момент времени, а в математике такая связь называется от­но­ше­нием (relation). Таким образом,
час­то встречающееся «объяснение» происхождения названия «реляционная модель» – мол, в ней идет речь о «соотнесении (relating) одной
таб­лицы с другой» – хотя в каком-то смысле правильно, но упускает самое главное. Реляционная модель названа так потому, что имеет дело
с некоторыми абстракциями, которые в математике называются от­но­
ше­ниями, пусть даже неформально мы представляем их в виде таб­лиц.
Итак, от­но­ше­ние, как и кортеж, само является значением и имеет тип,
а у этого типа есть имя. В языке Tutorial D такие имена принимают вид
RELATION{H}, где {H} – заголовок, например:
RELATION { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }

(Атрибуты можно записывать в любом порядке.) Кроме того, любое от­
но­ше­ние-значение возвращается вызовом некоторого селектора от­но­
ше­ния, например:
RELATION
{ TUPLE {
TUPLE {
TUPLE {
TUPLE {
TUPLE {

SNO
SNO
SNO
SNO
SNO

‘S1’
‘S2’
‘S3’
‘S4’
‘S5’

,
,
,
,
,

SNAME
SNAME
SNAME
SNAME
SNAME

‘Smith’
‘Jones’
‘Blake’
‘Clark’
‘Adams’

,
,
,
,
,

STATUS
STATUS
STATUS
STATUS
STATUS

20
10
30
20
30

,
,
,
,
,

CITY
CITY
CITY
CITY
CITY

‘London’
‘Paris’
‘Paris’
‘London’
‘Athens’

}
}
}
}
}

,
,
,
,
}

Порядок перечисления кортежей произвольный. Вот другой пример (в
отличие от предыдущего, это не литерал):
RELATION { tx1 , tx2 , tx3 }

Я предполагаю, что tx1, tx2 и tx3 – выражения-кортежи одного и того
же типа. Как видно из этих примеров, вызов селектора от­но­ше­ния
в Tutorial D в общем случае включает ключевое слово RELATION, за
которым следует список разделенных запятыми выражений-кортежей
в скобках.

Следствия, вытекающие из определений
Большая час­ть свойств от­но­ше­ний, о которых я рассказывал в главе 1,
являются прямыми следствиями приведенных выше определений, но
о некоторых моментах я раньше не говорил вообще, а на других хочу
остановиться подробнее. Первые два свойства, о которых я хочу упомянуть, таковы:

90

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

•• От­но­ше­ния никогда не содержат кортежей-дубликатов, потому что
тело от­но­ше­ния – это множество (кортежей), а математические множества не содержат повторяющихся элементов.
•• От­но­ше­ния никогда не содержат null-значений, потому что тело от­
но­ше­ния – это множество кортежей, а, как мы видели раньше, кортежи не могут содержать null-значений.
Но эти два аспекта настолько важны, и мне надо сказать о них так много,
что я отложу детальное обсуждение до следующей главы. А в следующих
нескольких разделах я рассмотрю ряд не столь существенных вопросов.

От­но­ше­ния и их тела
Первым делом я хочу обсудить такой тезис: любое подмножество тела
от­но­ше­ния само является телом (менее строго, любое подмножество
от­но­ше­ния является от­но­ше­нием). В час­тности, поскольку пустое множество является подмножеством любого множества, то тело от­но­ше­ния
может состоять из пустого множества кортежей (мы называем его пустым от­но­ше­нием). Предположим, к примеру, что в данный момент
нет ни одной поставки. Тогда текущим значением пе­ре­мен­ной-от­но­
ше­ния SP будет пустое от­но­ше­ние «поставки», которое можно изобразить, как показано на рисунке ниже (я снова возвращаюсь к соглашению о том, что в неформальном контексте имена типов не показываются в заголовке; далее в книге я буду то включать, то опускать имена типов – в зависимости от преследуемых целей):
SNO

PNO

QTY

Отметим, что для любого заданного типа от­но­ше­ния существует ровно
одно пустое от­но­ше­ние этого типа, однако пустые от­но­ше­ния разных
типов различаются – именно тем, что принадлежат разным типам. Например, пустое от­но­ше­ние «поставщики» не равно пустому от­но­ше­нию
«детали» (тела равны, а заголовки нет).
Рассмотрим от­но­ше­ние, изображенное на следующем рисунке:
SNO

PNO

QTY

S1

P1

300

Это от­но­ше­ние содержит всего один кортеж (по-другому можно сказать,
что его кардинальность равна 1). Чтобы обратиться к единственному
содержащемуся в нем кортежу, нам необходимо как-то извлечь его из
объемлющего от­но­ше­ния. В языке Tutorial D для этой цели предназначен синтаксис TUPLE FROM rx (где rx – любое выражение, обозначающее от­но­ше­ние кардинальности 1, например выражение RELATION
{TUPLE {SNO ‘S1’, PNO ‘P1’, QTY 300}}, которое на самом деле представляет собой вызов селектора от­но­ше­ния). Напротив, в SQL применяется

От­но­ше­ния n-мерны

91

приведение типа: если (а) tx – таб­личное выражение, которое используется как однострочный подзапрос (то есть встречается в том месте, где
ожидается строчное выражение), то (б) предполагается, что таб­лица t,
обозначаемая выражением tx, содержит всего одну строку r и (в) эта таб­
лица приводится к строке r. Например:
SET S_ROW = ( S WHERE SNO = ‘S1’ ) ;

Нам также необходимо уметь проверять, что заданный кортеж t встречается в от­но­ше­нии r. В языке Tutorial D это выглядит так:
t ∈ r

Это выражение принимает значение TRUE, только если t встречается
в r, и FALSE – в противном случае. Символом «∈» обозначается оператор принадлежности множеству, а выражение t ∈ r произносится как
«t входит в r». Вы, наверное, уже поняли, что «∈» – это по существу SQLоператор IN с тем исключением, что в SQL левый операнд IN обычно
является скаляром, а не строкой, то есть снова производится описанное выше приведение типа (скаляр приводится к типу содержащей его
строки). Приведем пример («получить поставщиков, которые поставляют хотя бы одну деталь»):
SELECT SNO , SNAME , STATUS , CITY
FROM S
WHERE SNO IN ( SELECT SNO
FROM SP )

От­но­ше­ния n-мерны
Я несколько раз подчеркивал, что хотя от­но­ше­ние можно изображать
в виде таб­лицы, на самом деле это не таб­лица. (Еще раз повторю, что
изображение предмета и сам предмет – вещи разные.) Конечно, представлять себе от­но­ше­ние как таб­лицу очень удобно, ведь таб­лицы так
привычны. Более того, в главе 1 отмечалось, что именно тот факт, что
от­но­ше­ние можно неформально рассматривать как таб­лицу – иногда
даже откровенно «плоскую» или «двумерную» таб­лицу, – и делает реляционные сис­темы такими простыми для понимания и использования и позволяет на интуитивном уровне рассуждать о поведении таких
сис­тем. Другими словами, то, что основная структура данных реляционной модели – от­но­ше­ние – имеет такое наглядное графическое представление, является весьма полезным свойством этой модели.
К сожалению, однако, многие люди, введенные в заблуждение обманчивой простотой графического представления, начинают полагать, что
сами от­но­ше­ния являются «плоскими» или «двумерными». Это отнюдь
не так. Раз от­но­ше­ние r имеет n атрибутов, то каждый кортеж r представляет собой точку в n-мерном пространстве (а от­но­ше­ние в целом
оказывается множеством таких точек). Например, каждый из пяти
кортежей в от­но­ше­нии «поставщики» представляет точку в 4-мерном

92

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

пространстве, поэтому от­но­ше­ние в целом можно назвать четырехмерным. Таким образом, от­но­ше­ния n-мерны, а не двумерны.1 Как я уже
писал в другой книге (и даже не в одной): «Давайте все поклянемся никогда больше не говорить «плоские от­но­ше­ния».

Сравнение от­но­ше­ний
Для типов от­но­ше­ний, как и для любых других типов, должен быть
определен оператор сравнения «=»; иначе говоря, если даны два от­но­
ше­ния rx и ry одного и того же типа T, то, как минимум, должна иметься возможность определить, равны они или нет. Могут оказаться полезны и другие сравнения; например, иногда хочется проверить, является ли rx подмножеством ry (это означает, что каждый кортеж, принадлежащий rx, принадлежит также и ry) или собственным подмножеством ry (в этом случае каждый кортеж, принадлежащий rx, принадлежит также и ry, но в ry существует хотя бы один кортеж, не принадлежащий rx). Вот как записывается сравнение от­но­ше­ний на равенство в языке Tutorial D:
S { CITY } = P { CITY }

Здесь в левой час­ти находится проекция поставщиков на CITY, а в правой – проекция деталей на CITY, и сравнение возвращает TRUE, если
обе проекции совпадают, и FALSE – в противном случае. Иными словами, сравнение (которое является булевым выражением) отвечает на
вопрос: совпадает ли множество городов, где находятся поставщики,
с множеством городов, где складируются детали.
Вот еще один пример:
S { SNO } ⊃ SP { SNO }

Символ ⊃ означает «строго включает». Смысл этого сравнения таков:
существует ли хотя бы один поставщик, не поставляющий никаких деталей?
К числу других полезных реляционных операторов относятся «⊇» (включает), «⊆» (включается) и «⊂» (строго включается). Очень час­то возникает необходимость в сравнении «=» заданного от­но­ше­ния rx с пустым от­
но­ше­нием того же типа; иначе говоря, требуется узнать, пусто ли rx. Для
этой цели удобно определить сокращенную нотацию:
IS_EMPTY ( rx )

По определению, это выражение возвращает TRUE, если от­но­ше­ние,
обозначенное реляционным выражением rx, пусто, и FALSE – в про1

На самом деле я полагаю, что одна из причин, по которым мы так час­то слышим о необходимости «многомерных баз данных» (в час­тности, для приложений поддержки принятий решений), как раз и состоит в том, что слишком
многие не понимают, что от­но­ше­ния и так по природе своей многомерны.

TABLE_DUM и TABLE_DEE

93

тивном случае. Я буду постоянно пользоваться этим выражением в последующих главах (особенно в главе 8). Полезен и обратный оператор:
IS_NOT_EMPTY ( rx )

Логически это выражение эквивалентно NOT (IS_EMPTY(rx)).

TABLE_DUM и TABLE_DEE
Вспомните обсуждение кортежей выше в этой главе – мы говорили, что
пустое множество является подмножеством любого множества, и, следовательно, существует такое понятие, как пустой кортеж (он также
называется 0-кортежем), и, разумеется, понятие пустого заголовка. Раз
так, то иот­но­ше­ние может иметь пустой заголовок, ведь заголовок – это
просто множество атрибутов, и почему бы ему не быть пустым? Такое
от­но­ше­ние имеет тип RELATION{}, а его степень равна нулю.
Итак, пусть r – от­но­ше­ние степени 0. Сколько существует таких от­но­
ше­ний? Ответ: ровно два. Во-первых, r может быть пустым (то есть не
содержит ни одного кортежа) – напомним, что существует ровно одно
пустое от­но­ше­ние любого заданного типа. Во-вторых, если r не пусто,
то оно может содержать только 0-кортежи. Но существует всего один
0-кортеж! – или, что то же самое, все 0-кортежи являются дубликатами друг друга, – поэтому r не может содержать более одного из них. Таким образом, дейст­вительно существует только два от­но­ше­ния без атрибутов: первое состоит из одного кортежа, а второе не содержит кортежей
вовсе. По очевидным причинам я не буду изображать эти от­но­ше­ния на
рисунках (на самом деле, это как раз тот случай, когда идея представлять от­но­ше­ния в виде таб­лиц оказывается совершенно несостоятельной).
Ну и что? – наверное, думаете вы. Для чего бы мне могло понадобиться
от­но­ше­ние, не имеющее ни одного атрибута? Даже если с точки зрения
математики они имеют полное право на существование (а это, конечно,
так), то практической-то пользы от них чуть? Однако выясняется, что
практическая польза есть, да еще какая, поэтому для этих от­но­ше­ний
даже придуманы специальные названия: TABLE_DUM и TABLE_DEE,
или просто DUM и DEE (DUM – пустое от­но­ше­ние, DEE – от­но­ше­ние
с одним кортежем).1 А причина в их семантике: FALSE (нет) для DUM
1

Наверное, стоит немного сказать о происхождении этих названий. Вопервых, для читателей, не говорящих по-английски, объясню, что это
игра слов: Tweedledum и Tweedledee первоначально были персонажами
детского стишка, а потом Льюис Кэрролл включил их в свою сказку «Алиса
в Зазеркалье» (в переводе Н. Демуровой они названы «Тру-ля-ля» и «Траля-ля»). Во-вторых, эти названия, пожалуй, не слишком удачны, так как
обозначают как раз те от­но­ше­ния, которые невозможно изобразить в виде
таб­лиц! Но они уже так давно используются в реляционной теории, что
менять что-нибудь поздно.

94

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

и TRUE (да) для DEE. Это самая фундаментальная из всех возможных
семантик. Примечание: Общий вопрос о семантике от­но­ше­ний я буду
рассматривать в главах 5 и 6.
Кстати, есть неплохое мнемоническое правило, помогающее запомнить,
что есть что: в словах DEE и «yes» есть буква «E», а в словах DUM и «no»
ее нет.
Я еще не приводил в этой книге конкретных примеров использования от­но­ше­ний DUM и DEE, но впоследствии они появятся в изобилии.
А сейчас упомяну лишь один момент, который поможет составить хотя
бы интуитивное представление: оба эти от­но­ше­ния (особенно TABLE_
DEE) играют в реляционной алгебре роль, аналогичную нулю в обычной арифметике. Все мы знаем, насколько важен нуль; на самом деле,
трудно вообразить арифметику без него (древние римляне пытались, но
далеко не ушли). Точно так же, трудно представить себе реляционную
алгебру без TABLE_DEE. Что вновь возвращает нас к SQL… поскольку
в SQL нет аналога 0-кортежа, в нем, очевидно (и к величайшему сожалению), нет и аналогов TABLE_DUM и TABLE_DEE.

Таб­лицы в SQL
Примечание
В этом разделе под термином таб­лица я буду понимать таб­личное значение –
в смысле SQL, – а не таб­личную пе­ре­мен­ную (то, что создает предложение
CREATE TABLE). Таб­личные пе­ре­мен­ные будут рассматриваться в главе 5.

В главе 2 я объяснял, что в SQL вообще нет никакого аналога понятию
типа от­но­ше­ния; таб­лица в SQL считается состоящей из строк (в общем
случае это мультимножество, а не множество строк), принадлежащих
некоторому типу строки. Отсюда следует, что в SQL также нет ничего,
аналогичного генератору типа RELATION, хотя поддерживаются другие генераторы типов, в час­тности, ARRAY, MULTISET и уже известный нам из главы 3 ROW. Однако же имеется нечто, именуемое конструктором таб­личных значений, и это можно в какой-то мере считать аналогом селектора от­но­ше­ния. Например:
VALUES ( 1, 2 ), ( 2, 1 ), ( 1, 1 ), ( 1, 2 )

Результатом вычисления этого выражения (в дейст­вительности это таб­
личный литерал, хотя в SQL такой термин не употребляется) является таб­лица из четырех – не трех! – строк и двух столбцов. Причем эти
столбцы не имеют имен. Как я уже объяснял, столбцы в SQL-таб­лице
упорядочены слева направо; следовательно, эти столбцы можно идентифицировать (и час­то так и делается) по порядковому номеру, а не по
имени.
В качестве примера рассмотрим следующий вызов конструктора таб­
личного значения:

95

Таб­лицы в SQL
VALUES (
(
(
(
(

‘S1’
‘S2’
‘S3’
‘S4’
‘S5’

,
,
,
,
,

‘Smith’
‘Jones’
‘Blake’
‘Clark’
‘Adams’

,
,
,
,
,

20
10
30
20
30

,
,
,
,
,

‘London’
‘Paris’
‘Paris’
‘London’
‘Athens’

)
)
)
)
)

,
,
,
,

Заметим, что для того, чтобы это выражение можно было рассматривать
как приближение к реляционному аналогу (то есть к реляционному литералу, обозначающему от­но­ше­ние, которое является текущим значением пе­ре­мен­ной-от­но­ше­ния S, показанной на рис. 1.3), мы должны:
1. Для каждого столбца таб­лицы, определяемой выражением VALUES,
убедиться, что все его значения имеют подходящий тип. (В час­т­
ности, если некоторый реляционный атрибут соответствует i-й позиции в какой-то из заданных строк, то необходимо проверить, что соответствует i-й позиции во всех строках).
2. Убедиться, что одна и та же строка не задана дважды.

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

А как насчет операторов присваивания и сравнения для таб­лиц? Таб­
личное присваивание – обширная тема, детали которой я отложу до
глав 5 и 7. Что касается сравнения таб­лиц, то в SQL прямой поддержки
этой операции нет, но существуют обходные пути. Вот, например, как
выглядит аналог реляционного сравнения в SQL:
NOT EXISTS ( SELECT
EXCEPT
SELECT
AND
NOT EXISTS ( SELECT
EXCEPT
SELECT

CITY FROM S
CITY FROM P )
CITY FROM P
CITY FROM S )

А вот аналог сравнения S{SNO} ⊃ SP{SNO}:
EXISTS ( SELECT SNO
EXCEPT
SELECT SNO
AND
NOT EXISTS ( SELECT
EXCEPT
SELECT

FROM S
FROM SP )
SNO FROM SP
SNO FROM S )

96

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

Именование столбцов в SQL
В реляционной модели (а) любой атрибут любого от­но­ше­ния имеет имя
(то есть анонимные атрибуты не допускаются) и (б) эти имена должны
быть уникальны в пределах одного от­но­ше­ния (то есть дубликаты имен
запрещены). В SQL такие правила тоже действуют, но не всегда. Точнее, они проверяются для таб­лиц, являющихся значениями таб­личных
пе­ре­мен­ных, – определенных с помощью CREATE TABLE или CREATE
VIEW – но не для таб­лиц, которые получаются в результате вычисления некоторого таб­личного выражения1. Настоятельная рекоменда­
ция: Применяйте спецификацию AS, чтобы назначить подходящие
имена столбцам, которые в противном случае (а) вообще не имели бы
имен или (б) имели бы неуникальные имена. Вот несколько примеров:
SELECT DISTINCT SNAME , ‘Supplier’ AS TAG
FROM S
SELECT DISTINCT SNAME , 2 * STATUS AS DOUBLE_STATUS
FROM S
SELECT MAX ( WEIGHT ) AS MBW
FROM P
WHERE COLOR = ‘Blue’
CREATE VIEW SDS AS
SELECT DISTINCT SNAME , 2 * STATUS AS DOUBLE_STATUS
FROM S ;
SELECT
FROM
WHERE
AND

DISTINCT S.CITY AS SCITY , P.CITY AS PCITY
S , SP , P
S.SNO = SP.SNO
SP.PNO = P.PNO

SELECT TEMP.*
FROM ( S JOIN P ON S.CITY > P.CITY ) AS TEMP
( SNO , SNAME , STATUS , SCITY ,
PNO , PNAME , COLOR , WEIGHT , PCITY )

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

Неоспоримо, что в этом случае SQL не проверяет имена столбцов на дубликаты. Однако нельзя сказать, что он вообще не проверяет наличие анонимных столбцов; если столбцу явно не присвоено имя, то предполагается, что
реализация выберет для него имя, уникальное в пределах объемлющей таб­
лицы, но в остальном поведение зависит от реализации. На практике, однако, между «зависит от реализации» и «не определено» разница невелика
(см. главу 12). Поэтому можно, не слишком отклоняясь от истины, назвать
такие столбцы анонимными.

Именование столбцов в SQL

97

ных выше примеров можно было бы в некоторых случаях (скажем, во
фразе WHERE или HAVING) безопасно сократить до:
SELECT MAX ( WEIGHT )
FROM P
WHERE COLOR = ‘Blue’

Отметим еще, что этой рекомендации вообще невозможно следовать
в случае таб­лиц, заданных выражениями VALUES.
Важное замечание: Есть много ситуаций, когда операторы реляционной алгебры полагаются на правильное именование атрибутов. Например, в главе 6 мы увидим, что оператор UNION требует, чтобы его операнды имели одинаковые заголовки (а, стало быть, и имена атрибутов);
результат также будет иметь такой заголовок. Одно из достоинств такого подхода заключается как раз в том, что он позволяет избежать сложностей, связанных с зависимостью от порядковой позиции! По­этому,
если вы хотите использовать SQL реляционно, то должны применять
это правило ко всем аналогам реляционных операторов в SQL. Насто­
ятельная рекомендация: В качестве предварительного условия для
навязывания такой дисциплины старайтесь давать одинаковые имена столбцам, представляющим «одну и ту же информацию». (Вот почему в базе данных о поставщиках и деталях оба столбца, содержащих
номер поставщика, названы SNO, а не, скажем, SNO в одной таб­лице
и SNUM в другой.) Наоборот, если два столбца представляют семантически различную информацию, лучше давать им разные имена.
Единственный случай, когда этой рекомендации невозможно следовать, – это когда в одной таб­лице есть два столбца, представляющих
семантически одинаковую информацию. Рассмотрим, к примеру, таб­
лицу EMP, в которой имеются столбцы «номер служащего» и «номер
начальника», причем номер начальника сам является номером какогото другого служащего. Эти столбцы по необходимости должны называться по-разному, например ENO и MNO. Поэтому иногда приходится прибегать к переименованию столбцов, как в следующем примере:
( SELECT ENO , MNO FROM EMP ) AS TEMP1
NATURAL JOIN
( SELECT ENO AS MNO , ... FROM EMP ) AS TEMP2
/* где «...» столбцы EMP, отличные от ENO и MNO */

Если вы хотите использовать SQL в реляционном духе, то такое пере­
именование имеет смысл производить и тогда, когда столбцы изначально были названы неудачно (то есть вам попалась база данных, спроектированная кем-то другим). В таком случае советую подумать о следующей стратегии:
•• Для каждой базовой таб­лицы определите представление с идентичной структурой, отличающееся разве что тем, что некоторые столбцы переименованы.

98

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

•• Убедитесь, что определенные таким образом представления подчиняются описанному выше правилу именования столбцов.
•• Работайте с представлениями, а не с базовыми таб­лицами (отмечу,
что такие представления, безусловно, допускают обновление).
К сожалению, невозможно полностью игнорировать тот факт, что в SQL
столбцы имеют порядковые номера. (Разумеется, именно поэтому SQL
может мириться с анонимными столбцами и столбцами с одинаковыми именами.) Отметим, в час­тности, что столбцы в SQL имеют порядковые номера даже тогда, когда в этом нет никакой необходимости (то
есть когда у всех столбцов имеются приемлемые имена); это наблюдение
в равной мере относится к столбцам базовых таб­лиц и представлений.
Настоятельная рекомендация: Не пишите на SQL код, зависящий от
порядка столбцов. В качестве примеров ситуаций, в которых SQL придает существенное значение упорядочению, назовем следующие:
•• SELECT *
•• JOIN, UNION, INTERSECT, EXCEPT – особенно, если в последних
трех случаях отсутствует ключевое слово CORRESPONDING (см.
главу 6)
•• В списке имен столбцов, если он задан, вслед за определением пе­ре­
мен­ной кортежа (range variable) (см. главу 12)
•• В списке имен столбцов, если он задан, в CREATE VIEW (см. главу 9)
•• INSERT, если не задан список имен столбцов
•• Сравнения с модификаторами ALL и ANY, если степени операндов
больше 1 (см. главу 11)
•• Выражения VALUES

Заключительные замечания
В этой главе я дал точные определения фундаментальным понятиям
кортежа и от­но­ше­ния. Как я уже говорил, поначалу эти определения
могут немного обескуражить, но надеюсь, что, прочитав первые две
главы, вы смогли в них разобраться. Я также объяснил, что такое типы
кортежа и от­но­ше­ния, селекторы и сравнения, и рассмотрел ряд важных следствий, вытекающих из этих определений (в час­тности, вкратце описал важные от­но­ше­ния TABLE_DUM и TABLE_DEE). Кроме
того, я рассказал об аналогах всех этих понятий в SQL в тех случаях,
когда такие аналоги существуют. В заключение я хотел бы подчеркнуть
важность рекомендаций (в разделе, непосредственно предшествующем
этому) касательно именования столбцов в SQL. В последующих главах
эти рекомендации будут активно применяться.

99

Упражнения

Упражнения
Упражнение 3.1. Дайте точные определения терминам атрибут, тело,
кардинальность, степень, заголовок, от­но­ше­ние, тип от­но­ше­ния
и кортеж.
Упражнение 3.2. Сформулируйте настолько точно, насколько сможете,
что означают выражения: (а) два кортежа равны, (б) два от­но­ше­ния
равны.
Упражнение 3.3. Напишите на языке Tutorial D вызовы селекторов
кортежей для типичного кортежа из (а) пе­ре­мен­ной-от­но­ше­ния «детали», (б) пе­ре­мен­ной-от­но­ше­ния «поставки». Также покажите аналоги этих вызовов селекторов на SQL, если таковые существуют.
Упражнение 3.4. Напишите на языке Tutorial D вызов селектора от­но­
ше­ния. Также покажите аналог этого вызова селектора на SQL, если
таковой существует.
Упражнение 3.5. (По существу, это повторение упражнения 1.8 из главы 1, но теперь вы знаете достаточно, чтобы дать более полный
ответ.) Существует много различий между от­но­ше­нием и таб­лицей.
Перечислите столько, сколько сможете.
Упражнение 3.6. Атрибуты кортежа могут иметь произвольный тип (ну
почти произвольный; можете ли вы назвать какие-нибудь исключения?). Приведите пример (а) кортежа, атрибутом которого является
кортеж; (б) кортежа, атрибутом которого является от­но­ше­ние (RVAатрибут).
Упражнение 3.7. Приведите пример от­но­ше­ния с (а) одним RVA-ат­ри­
бутом, (б) двумя RVA-атрибутами. Также приведите еще два от­но­ше­
ния, которые представляют ту же самую информацию, не используя
RVA-атрибутов. Также приведите пример от­но­ше­ния с RVA-ат­ри­бу­
том такого, что не существует от­но­ше­ния, которое представляет ту
же самую информацию, не используя RVA-атрибута.
Упражнение 3.8. Объясните своими словами смысл от­но­ше­ний TABLE_
DUM and TABLE_DEE. Почему они не поддерживаются в SQL?
Упражнение 3.9. TABLE_DEE означает TRUE, а TABLE_DUM – FALSE.
Означает ли это, что можно обойтись без обычного типа данных
BOOLEAN? Кроме того, DEE и DUM – от­но­ше­ния, а не пе­ре­мен­ныеот­но­ше­ния. Как вы думаете, имеет ли смысл определять пе­ре­мен­
ную-от­но­ше­ние степени 0?
Упражнение 3.10. В чем заключается логическое различие (и существует ли оно) – в отличие от очевидного семантического различия –
между следующими двумя выражениями SQL:
VALUES ( 1, 2 ), ( 2, 1 ), ( 1, 1 ), ( 1, 2 )
VALUES ( ( 1, 2 ), ( 2, 1 ), ( 1, 1 ), ( 1, 2 ) )

100

Глава 3. Кортежи и от­но­ше­ния, строки и таб­лицы

Упражнение 3.11. Каков точный смысл следующего выражения SQL?
SELECT SNO
FROM S
WHERE ( NOT ( ( STATUS , SNO ) B, каким бы ни было значение
B. Отмечу попутно, что именно из этого факта проистекает термин
трехзначная логика (3VL): понятие null в том смысле, в котором оно
трактуется в SQL, неизбежно приводит к логике, в которой есть три
значения истинности вместо двух. (Напротив, реляционная модель
построена на базе традиционной двузначной логики, 2VL.)
2. Я также предполагаю, что вы знакомы с таб­лицами истинности
трехзначной логики для стандартных логичес­ких операторов, или
связок, NOT, AND и OR (T = TRUE, F = FALSE, U = UNKNOWN):

110

Глава 4. Нет дубликатам, нет null-значениям
p

NOT p

T
U
F

F
U
T

p q
T
T
T
U
U
U
F
F
F

T
U
F
T
U
F
T
U
F

p q

p AND q

T
T
T
U
U
U
F
F
F

T
U
F
U
U
F
F
F
F

T
U
F
T
U
F
T
U
F

p OR q
T
T
T
T
U
U
T
U
F

Отметим, в частности, что NOT возвращает UNKNOWN, если его
операндом является UNKNOWN; AND возвращает UNKNOWN,
если один операнд равен UNKNOWN, а второй – либо UNKNOWN,
либо TRUE; OR возвращает UNKNOWN, если один операнд равен
UNKNOWN, а второй – либо UNKNOWN, либо FALSE.
Теперь я готов изложить свой аргумент. Фундаментальное наблюдение заключается в том, что некоторые логичес­кие выражения – и, стало быть, некоторые запросы – порождают результаты, правильные
с точки зрения трехзначной логики, но неправильные в реальном мире.
В качестве примера рассмотрим (нереляционную) базу данных, показанную на рис. 4.2, в которой для детали P1 атрибут CITY «равен» null.
Обратите внимание на пус­тое место там, где должно было бы находиться значение CITY, – оно обозначает отсутствие чего-либо; концептуально в этой позиции нет вообще ничего – ни даже последовательности пробелов или пус­той строки (то есть «кортеж» для детали P1 на самом деле кортежем не является, к этому моменту я еще вернусь в конце раздела).
S SNO
S1

CITY
London

P PNO

CITY

P1

Рис. 4.2. Нереляционная база данных с дубликатами

Рассмотрим теперь следующий (признаю, искусственный) запрос к базе
данных на рис. 4.2: «получить пары (SNO,PNO), в которых либо города поставщика и детали различаются, либо городом детали не является Париж (либо то и другое вместе)». Вот как выглядит формулировка
этого запроса на SQL:
SELECT
FROM
WHERE
OR

S.SNO, P.PNO
S, P
S.CITY P.CITY
P.CITY ‘Paris’

Я хочу обратить ваше внимание на булево выражение во фразе WHERE:
( S.CITY P.CITY ) OR ( P.CITY ‘Paris’ )

Чем плохи null-значения?

111

(Скобки добавлены для ясности.) Для тех данных, что есть в нашей
базе, это выражение сводится к UNKNOWN OR UNKNOWN, что равно UNKNOWN. Но SQL-запросы отбирают те данные, для которых выражение во фразе WHERE принимает значение TRUE, а не FALSE и не
UNKNOWN; следовательно, в данном примере ничего не будет выбрано.
Однако же в реальности деталь P1 все же находится в каком-то городе1; иными словами, то, что для детали P1 атрибут CITY содержит null,
означает, что там есть какое-то, пус­ть неизвестное, значение, скажем,
xyz. Но xyz – это либо Париж, либо нет. Если это Париж, то выражение
( S.CITY P.CITY ) OR ( P.CITY ‘Paris’ )

принимает вид (для тех данных, что у нас есть)
( ‘London’ ‘Paris’ ) OR ( ‘Paris’ ‘Paris’ )

и его вычисление дает TRUE, поскольку первый член равен TRUE. На­обо­
рот, если xyz – не Париж, то выражение принимает такой вид (опять-таки
для имеющихся данных)
( ‘London’ xyz ) OR ( xyz ‘Paris’ )

и, стало быть, снова равно TRUE, так как второй член равен TRUE. Таким образом, в реальном мире это булево выражение всегда равно TRUE,
и запрос должен был бы вернуть пару (S1,P1) независимо от того, что
стоит за null. Иначе говоря, логичес­ки правильный результат (с точки
зрения трехзначной логики) и результат, правильный в реальном мире,
различаются!
В качестве другого примера рассмотрим следующий запрос к той же
таб­лице P, которая показана на рис. 4.2 (я не стал приводить его первым, потому что он еще более искусственный, чем предыдущий, но
в некотором смысле – еще более убедительный):
SELECT PNO
FROM P
WHERE CITY = CITY

Если говорить о реальном мире, то ответом, конечно же, будет множе­
ство номеров деталей, присутствующих в P (другими словами, множество, состоящее только из номера детали P1, если говорить о данных,
представленных на рис. 4.2). Однако SQL не вернет ничего.
Подведем итоги. Если в базе данных имеются null-значения, то на некоторые вопросы вы будете получать неверные ответы. Хуже того, в общем случае невозможно узнать, ответы на какие вопросы верны, а на
1

Должна находиться, потому что в противном случае эта деталь не удовлетворяла бы предикату пе­ре­мен­ной-от­но­ше­ния. Это важное положение, но
я еще не готов объяснить или даже толком сформулировать его. Дальнейшее обсуждение см. в главе 5.

112

Глава 4. Нет дубликатам, нет null-значениям

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

Отступление
Не могу противиться искушению добавить ко всему вышесказанному еще и то,
что хотя SQL поддерживает трехзначную логику и даже поддерживает ключевое слово UNKNOWN, это ключевое слово – в отличие от TRUE и FALSE – не является значением типа BOOLEAN! (Это один из многочисленных изъянов в поддержке трехзначной логики в SQL; есть еще великое множес­тво других, но
они выходят за рамки настоящей книги.) Другими словами, тип BOOLEAN попрежнему содержит всего два значения; «третье значение истинности» представлено – совершенно некорректно – с помощью null. Вот какие следствия
вытекают из этого факта:

•• Присваивание значения UNKNOWN пе­ре­мен­ной X типа BOOLEAN фактичес­ки
превращает X в null.
•• После такого присваивания сравнение X = UNKNOWN дает не TRUE, а null (то
есть SQL то ли дейст­вительно «верит», то ли «притворяется, что верит», будто находится в неведении относительно того, что X равно UNKNOWN).
•• На самом деле сравнение X = UNKNOWN всегда дает null (читай UNKNOWN)
вне зависимости от значения X, потому что логичес­ки оно эквивалентно
сравнению «X = NULL» (хотя это и некорректный синтаксис).
Чтобы в полной мере осознать всю серьезность подобных изъянов, поразмыслите об аналогии с числовым типом, в котором используется null вместо нуля
для представления нуля.

Как и о дубликатах, я мог бы еще много чего сказать по поводу null-зна­
чений, но хотел бы закончить обзором формальных возражений против
них. Напомню, что, по определению, null не является значением. Отсюда следует, что:
•• «Тип», который содержит null, не является типом (так как типы содержат значения).
•• «Кортеж», который содержит null, не является кортежем (так как
кортежи содержат значения).
•• «От­но­ше­ние», которое содержит null, не является от­но­ше­нием (так
как от­но­ше­ния содержат кортежи, а кортеж не может содержать null).
•• Фактичес­ки, null-значения нарушают самый фундаментальный из
реляционных принципов, а именно принцип информации (см. приложение A).
Итак, получается, что если допус­тить существование null-значений, то
мы вообще не можем говорить о реляционной модели (не знаю, о чем мы

Как избежать null-значений в SQL

113

ведем речь, но это точно не реляционная модель); все здание рушится,
и всякая определенность теряется.

Как избежать null-значений в SQL
Реляционная модель запрещает null-значения; поэтому если мы хотим
использовать SQL реляционно, то должны принять меры к тому, чтобы их не было. Прежде всего, для любого столбца во всех базовых таб­
лицах следует задавать ограничение NOT NULL, явно или неявно (см.
главу 5); тогда null-значения в базовых таб­лицах не появятся. К сожалению, однако, SQL-выражения все равно могут порождать таб­лицы,
содержащие null. Вот несколько ситуаций, когда null-значения могут
возникать.
•• Все «функции множес­тв» в SQL, например SUM, возвращают null,
если аргументом является пус­тое множес­тво (за исключением
COUNT и COUNT(*), которые корректно возвращают нуль).
•• Если результатом скалярного подзапроса является пус­тая таб­лица,
то эта таб­лица приводится к null.
•• Если однострочный подзапрос дает пус­тую таб­лицу, то эта таб­лица
приводится к строке, содержащей в каждом столбце null. Примечание: Cтрока из одних null-значений и null-строка – логичес­ки вовсе
не одно и то же (кстати, вот еще одно логичес­кое различие), и тем не
менее SQL полагает, по крайней мере в некоторых случаях, что они
неразличимы. Но попытка разобраться в таких тонкостях завела бы
нас слишком далеко.
•• Внешние соединения и соединения объединением (union join) изначально спроектированы так, что результат включает null-значения.
•• Если в выражении CASE опущена фраза ELSE, предполагается, что
фраза ELSE имеет вид ELSE NULL.
•• Если x = y, то выражение NULLIF(x,y) возвращает null.
•• Оба «ссылочных триггерных дейст­вия» ON DELETE SET NULL и ON
UPDATE SET NULL порождают null-значения (по понятным причинам).
Настоятельные рекомендации:
•• Задавайте ограничение NOT NULL, явное или неявное, для всех
столбцов во всех базовых таб­лицах.
•• Не используйте ключевое слово NULL ни в каких других контекстах
(то есть нигде, кроме ограничения NOT NULL).
•• Не используйте ключевое слово UNKNOWN вообще ни в каких контекстах.

114

Глава 4. Нет дубликатам, нет null-значениям

•• Не опускайте фразу ELSE в выражении CASE, если нет уверенности,
что она никогда не получит управления.
•• Не используйте функцию NULLIF.
•• Не пользуйтесь внешними соединениями и не употребляйте ключевых слов OUTER, FULL, LEFT и RIGHT (кроме, быть может, случаев, описанных в разделе «Замечание о внешнем соединении» ниже).
•• Не используйте соединение объединением.
•• Не употребляйте ключевых слов PARTIAL и FULL в спецификаторе MATCH (они имеют смысл только при наличии null-значений). По
тем же причинам не используйте спецификатор MATCH в ограничениях внешнего ключа и избегайте употребления IS DISTINCT FROM.
(В отсутствие null-значений выражение x IS DISTINCT FROM y эквивалентно выражению x y.)
•• Не используйте предикаты IS TRUE, IS NOT TRUE, IS FALSE и IS
NOT FALSE. Причина в том, что если bx – булево выражение, то при
наличии null-значений следующие эквиваленции ложны:
bx
bx
bx
bx

IS
IS
IS
IS

TRUE
NOT TRUE
FALSE
NOT FALSE






bx
NOT bx
NOT bx
bx

Другими словами, IS TRUE и ему подобные при наличии nullзначений только вводят в заблуждение.
•• Наконец, используйте функцию COALESCE для любого скалярного выражения, которое в противном случае могло бы «принять значение null». (Приношу извинения за кавычки, но фраза «принимать
значение null» внутренне противоречива.)
Последний пункт нуждается в дополнительных пояснениях. По сути
дела, оператор COALESCE позволяет заменить null значением, отличным от null, «прямо в момент возникновения» (то есть до того, как по­
явился шанс нанести заметный вред). Приведем его определение. Пусть
x, y, ..., z – скалярные выражения. Тогда выражение COALESCE (x,y,...,z)
возвращает null, если все его аргументы равны null, а в противном случае – значение первого аргумента, отличного от null. Конечно же, рекомендуется делать так, чтобы хотя бы один из аргументов x, y, ..., z был
отличен от null. Например:
SELECT S.SNO , ( SELECT COALESCE ( SUM ( ALL QTY ) , 0 )
FROM SP
WHERE SP.SNO = S.SNO ) AS TOTQ
FROM S

В этом примере, если результат вызова SUM «принимает значение
null» – а так будет, в частности, для любого поставщика, для которого нет ни одной поставки, – то COALESCE заменит null на нуль. В при-

Замечание о внешнем соединении

115

менении к нашим тестовым данным этот запрос, следовательно, вернет
такой результат:
SNO

TOTQ

S1
S2
S3
S4
S5

1300
700
200
900
0

Замечание о внешнем соединении
Внешнее соединение специально задумано так, чтобы результат содержал в некоторых столбцах null-значения, поэтому, вообще говоря, его
лучше избегать. Говоря реляционным языком, это что-то вроде брака поневоле: он навязывает таб­лицам некий род объединения – да-да,
я имею в виду именно объединение, а не соединение, – даже тогда, когда
рассматриваемые таб­лицы не удовлетворяют обычным условиям, разрешающим объединение (см. главу 6). Делается это, по существу, путем
дополнения одной или обеих таб­лиц null-значениями еще до выполнения объединения, в результате чего они начинают удовлетворять стандартным требованиям, например:
SELECT
FROM
UNION
SELECT
FROM
WHERE

SNO , PNO
SP
SNO , ‘nil’ AS PNO
S
SNO NOT IN ( SELECT SNO FROM SP )

Результат получается такой (обратите особое внимание на последнюю
строчку):
SNO

PNO

S1
S1
S1
S1
S1
S1
S2
S2
S3
S4
S4
S4
S5

P1
P2
P3
P4
P5
P6
P1
P2
P2
P2
P4
P5
nil

Тот же результат можно было бы получить с помощью SQL-оператора
внешнего соединения в сочетании с COALESCE:
SELECT SNO , COALESCE ( PNO , ‘nil’ ) AS PNO
FROM ( S NATURAL LEFT OUTER JOIN SP ) AS TEMP

116

Глава 4. Нет дубликатам, нет null-значениям

Заключительные замечания
Напоследок я хочу сделать еще несколько замечаний касательно nullзначений и трехзначной логики. То и другое задумывалось как решение проблемы «отсутствующей информации», но полагаю, мне удалось
убедить вас, что если они и могут считаться «решением», то крайне неудачным, приводящим к разрушительным последствиям. Однако прежде чем закрыть эту тему, я хотел бы привести аргумент, который часто приходится слышать в этой связи, и возразить на него. Аргумент
звучит примерно так.
Все приводимые вами примеры ситуаций, когда null-значения
оказываются причиной неверных ответов, искусственны. В реальной практике таких запросов не бывает! И вообще, ваша критика по большей части академичес­кая и теоретичес­кая – бьюсь об
заклад, что вы не сможете продемонстрировать реальную ситуацию, в которой null-значения порождают те проблемы, о которых
вы так печетесь, и готов поспорить, что вы не сумеете доказать,
что на практике такие ситуации дейст­вительно встречаются.

Понятно, что у меня есть несколько возражений против этого аргумента. Первое таково: откуда мы знаем, что null-значения никогда не приводили к серьезным практичес­ким ошибкам? Мне кажется, что если
бы было обнаружено, что какая-то серьезная реальная проблема – разлитие нефти, обрушение моста, неверный медицинский диагноз – произошла по вине null-значений, то нашлись бы веские причины (не
техничес­кого свойства), по которым такая информация не вышла бы
наружу. Все мы наслышаны о досадных сбоях, вызванных программными ошибками других видов, даже безо всяких null-значений; на мой
взгляд, последние могут лишь повысить вероятности таких сбоев.
Во-вторых, предположим, кто-то (к примеру, я сам) объявил о том, что
некий программный продукт или приложение содержит серьезную
логичес­кую ошибку из-за null-значений. Только представьте, с каким
воодушевлением его притянут к суду.
В-третьих, и это самое важное, я полагаю, что те из нас, кто выступает с критикой null-значений, вообще не должны находиться в оборонительной позиции. Я полагаю, что мы должны услышать контраргументы противника. В конце концов, нельзя отрицать, что в некоторых случаях null-значения приводят к ошибкам. Поэтому не наше дело доказывать, что в число этих «некоторых случаев» попадают и реальные,
встречающиеся на практике ситуации; пус­ть те, кто защищает nullзначения, доказывают, что такого не бывает. Рискну добавить, что лично мне кажется, что доказать такую вещь будет очень трудно, если вообще возможно.
Конечно, если запретить null-значения, то проблему отсутствующей
информации придется решать какими-то другими способами. К сожалению, эти способы слишком сложны, чтобы подробно обсуждать их

117

Упражнения

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

Упражнения
Упражнение 4.1. «Дубликаты нужны в базах данных, потому что естественным образом встречаются в реальном мире. Например, все копейки – дубликаты друг друга». Как бы вы возразили против такого аргумента?
Упражнение 4.2. Пусть r – некое от­но­ше­ние, и пус­ть bx и by – булевы
выражения. Тогда существует правило (оно используется в реляционных сис­темах для оптимизации), согласно которому (r WHERE bx)
UNION (r WHERE by) ≡ r WHERE bx OR by (где символом ≡ обозначается эквиваленция). Применимо ли это правило, если r – не от­но­ше­
ние, а таб­лица, содержащая дубликаты?
Упражнение 4.3. Пусть a, b и с – множес­тва. Тогда дистрибутивный закон
пересечения относительно объединения (тоже используется в реляционных сис­темах для оптимизации) утверждает, что a INTERSECT
(b UNION c) ≡ (a INTERSECT b) UNION (a INTERSECT c). Выполняется ли этот закон, если a, b и c – не множес­тва, а мультимножес­тва?
Упражнение 4.4. В той части стандарта SQL, где описывается фраза
FROM (в выражениях SELECT - FROM - WHERE), написано:
Результатом (фразы from) является … декартово
произведение таб­лиц, указанных в (ссылках
на таб­лицы) [в этой фразе ]. Декартово произведение, CP, представляет собой мультимножес­тво всех строк r, таких, что r есть конкатенация строк, взятых из каждой из указанных таб­лиц…

Обратите внимание, что CP определено некорректно! – несмотря на
то, что стандарт далее говорит: «кардинальность CP равна произведению кардинальностей указанных таб­лиц». Рассмотрим, к примеру, следующие таб­лицы T1 и T2:
T1

C1
0
0

T2

C2
1
2

Отметим, что любая из следующих таб­лиц удовлетворяет приведенному выше определению декартова произведения CP таб­лиц T1 и T2
(то есть и та, и другая могут быть взяты в качестве упоминаемого
«мультимножес­тва):

118

Глава 4. Нет дубликатам, нет null-значениям

CP1

C1

C2

0
0
0
0

1
1
2
2

CP2

C1

C2

0
0
0
0

1
2
2
2

Можете ли вы исправить приведенную в стандарте формулировку?
Упражнение 4.5. Рассмотрим следующее определение курсора:
DECLARE X CURSOR FOR SELECT SNO , QTY FROM SP ;

Обратите внимание, что (а) курсор X допускает обновление, (б) таб­
лица, видимая посредством курсора X, допускает дубликаты, но (в)
базовая таб­лиц SP дубликатов не содержит. Предположим теперь,
что выполняется операция DELETE ... WHERE CURRENT OF X.
Тогда в общем случае невозможно сказать, какая конкретно строка
таб­лицы SP была удалена. Как бы вы решили эту проблему?
Упражнение 4.6. Пожалуйста, напишите «гугол» (10100) раз: никаких
дубликатов не существует.
Упражнение 4.7. Как вы думаете, есть ли для null-значений естественное место в реальном мире?
Упражнение 4.8. Существует логичес­кое различие между null и третьим значением истинности. Это утверждение истинно или ложно?
(Быть может, следовало бы задать вопрос так: истинно, ложно или
неизвестно?)
Упражнение 4.9. В тексте главы я привел таб­лицы истинности для
одной одноместной (NOT) и двух двуместных (AND и OR) связок трехзначной логики, но есть много других связок (см. следующее упражнение). В частности, весьма полезна одноместная связка MAYBE со
следующей таб­лицей истинности:
p

MAYBE p

T
U
F

F
T
F

Поддерживает ли такую связку SQL?
Упражнение 4.10. Продолжая предыдущее упражнение, ответьте, сколько всего связок имеется в двузначной логике (2VL)? А в трехзначной
(3VL)? Какой вывод можно сделать из ответа на эти два вопроса?
Упражнение 4.11. Логика называется функционально полной, если она
поддерживает прямо или косвенно все возможные связки. Функциональная полнота – чрезвычайно важное свойство; логика, не удовлетворяющая этому условию, была бы похожа на арифметику, в ко-

119

Упражнения

торой отсутствуют некоторые операции, например «+». Является ли
логика 2VL функционально полной? А логика 3VL?
Упражнение 4.12. Пусть bx – булево выражение. Тогда bx OR NOT bx –
тоже булево выражение, и в логике 2VL оно гарантированно дает
значение TRUE (это пример того, что в логике называется тавтологией). Является ли это выражение тавтологией в 3VL? Если нет, то
существует ли в 3VL аналогичная тавтология?
Упражнение 4.13. Пусть, как и в предыдущем упражнении, bx – булево
выражение. Тогда bx AND NOT bx – тоже булево выражение, и в логике 2VL оно гарантированно дает значение FALSE (это пример того,
что в логике называется противоречием). Является ли это выражение противоречием в 3VL? Если нет, то существует ли в 3VL аналогичное противоречие?
Упражнение 4.14. В 2VL r JOIN r равно r, а операторы INTERSECT
и TIMES являются частными случаями JOIN (см. главу 6). Остаются
ли эти наблюдения истинными в 3VL?
Упражнение 4.15. Следующее выражение является допус­тимым вызовом конструктора значения в SQL: ROW (1,NULL). Представляет ли
эта строка null или не‑null?
Упражнение 4.16. Пусть bx – булево SQL-выражение. Тогда NOT (bx)
и (bx) IS NOT TRUE – тоже булевы SQL-выражения. Эквивалентны
ли они?
Упражнение 4.17. Пусть x – SQL-выражение. Тогда x IS NOT NULL
и NOT (x IS NULL) – булевы SQL-выражения. Эквивалентны ли они?
Упражнение 4.18. Пусть DEPT и EMP – SQL-таб­лицы, и пус­ть столбец DNO есть в обеих таб­лицах, а столбец ENO – только в EMP. Рассмотрим выражение DEPT.DNO = EMP.DNO AND EMP.DNO = ‘D1’
(это выражение могло бы быть, например, частью фразы WHERE).
«Хороший» оптимизатор мог бы трансформировать это выражение
в DEPT.DNO = EMP.DNO AND EMP.DNO = ‘D1’ AND DEPT.DNO = ‘D1’,
исходя из того, что если a = b и b = c, то a = c (см. упражнение 6.13
в главе 6). Но корректна ли такая трансформация? Если нет, то почему? И какие отсюда следуют выводы?
Упражнение 4.19. Предположим, что в базе данных о поставщиках
и деталях разрешены null-значения. Рассмотрим тогда следующий
запрос к этой базе, который по причинам, выходящим за рамки данной книги, выражен не на SQL, а на некоторой не вполне правильной форме реляционного исчисления (см. главу 10):
S WHERE NOT EXISTS SP ( SP.SNO = S.SNO AND SP.PNO = ‘P2’ )

Что означает этот запрос? И является ли следующая запись его эквивалентной формулировкой?
S WHERE NOT ( S.SNO IN ( SP.SNO WHERE SP.PNO = ‘P2’ ) )

120

Глава 4. Нет дубликатам, нет null-значениям

Упражнение 4.20. Пусть k1 и k2 – значения одного и того же типа. Что
тогда означают следующие утверждения в SQL?
a. k1 и k2 «одинаковы» с точки зрения сравнения, например во фразе WHERE.
b. k1 и k2 «одинаковы» с точки зрения уникальности ключей.
c. k1 и k2 «одинаковы» с точки зрения исключения дубликатов.
Упражнение 4.21. В тексте главы я сказал, что оператор UNION ALL
может порождать дубликаты. А что можно сказать об операторах
INTERSECT ALL и EXCEPT ALL?
Упражнение 4.22. Являются ли рекомендации «Всегда используйте
DISTINCT» и «Никогда не используйте ALL» дубликатами?
Упражнение 4.23. Если TABLE_DEE соответствует TRUE (да), TABLE_
DUM – FALSE (нет), то что соответствует UNKNOWN (может быть)?

5
Глава
.

Базовые пе­ре­мен­ные-от­но­ше­ния,
базовые таб­лицы
К этому моменту вы уже должны понимать, что между от­но­ше­ниямизна­чениями (для краткости просто от­но­ше­ния) и пе­ре­мен­ными-от­но­
ше­ниями имеется существенное логичес­кое различие. Теперь пришло
время взглянуть на это различие поближе; точнее, мы рассмотрим те
аспекты, которые характерны именно для пе­ре­мен­ных-от­но­ше­ний, но
не для от­но­ше­ний. Предостережение: К сожалению, части последующего обсуждения, относящиеся к SQL, несколько путаны, поскольку
в SQL эти понятия четко не различаются – один и тот термин таб­лица
иногда обозначает таб­личное значение, а иногда – таб­личную пе­ре­
мен­ную. Например, ключевое слово TABLE в предложении CREATE
TABLE, очевидно, относится к таб­личной пе­ре­мен­ной, но когда мы говорим, например, что таб­лица S содержит пять строк, то ясно, что слова
«таб­лица S» относятся к таб­личному значению (то есть к текущему значению таб­личной пе­ре­мен­ной с именем S). Поэтому остерегайтесь путаницы в этом вопросе.
Позвольте также напомнить следующее:
•• Во-первых, пе­ре­мен­ная-от­но­ше­ние – это такая пе­ре­мен­ная, значениями которой являются от­но­ше­ния, и именно к пе­ре­мен­ным-от­но­ше­
ниям, а не к от­но­ше­ниям, применяются операции INSERT, DELETE
и UPDATE (точнее, операции реляционного присваивания, – напомним, что INSERT, DELETE и UPDATE – не более чем сокращенная
запись некоторых реляционных присваиваний).
•• Далее, если R – пе­ре­мен­ная-от­но­ше­ние, а r – от­но­ше­ние, которое
нужно присвоить R, то R и r должны иметь одинаковый тип (от­но­
ше­ния).
•• Наконец, термины заголовок, тело, атрибут, кортеж, кардинальность и степень, формально определенные в главе 3 для от­но­ше­ний,

122

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

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

Определения данных
В качестве основы для примеров я буду использовать следующие определения в базе данных о поставщиках и деталях (слева приведено определение на языке Tutorial D, а справа – на SQL, и такого представления
я буду придерживаться в большинстве примеров в этой главе):
VAR S BASE RELATION
{ SNO
CHAR ,
SNAME CHAR ,
STATUS INTEGER ,
CITY CHAR }
KEY { SNO } ;

|
|
|
|
|
|

CREATE TABLE S
( SNO
VARCHAR(5)
SNAME VARCHAR(25)
STATUS INTEGER
CITY VARCHAR(20)
UNIQUE ( SNO ) ) ;

NOT
NOT
NOT
NOT

NULL
NULL
NULL
NULL

,
,
,
,

VAR P BASE RELATION
{ PNO
CHAR ,
PNAME CHAR
COLOR CHAR ,
WEIGHT FIXED ,
CITY CHAR }
KEY { PNO } ;

|
|
|
|
|
|
|

CREATE TABLE P
( PNO
VARCHAR(6)
PNAME VARCHAR(25)
COLOR CHAR(10)
WEIGHT NUMERIC(5,1)
CITY VARCHAR(20)
UNIQUE ( PNO ) ) ;

NOT
NOT
NOT
NOT
NOT

NULL
NULL
NULL
NULL
NULL

,
,
,
,
,

VAR SP BASE RELATION
{ SNO
CHAR ,
PNO
CHAR ,
QTY
INTEGER }
KEY { SNO , PNO }
FOREIGN KEY { SNO }
REFERENCES S
FOREIGN KEY { PNO }
REFERENCES P ;

|
|
|
|
|
|
|
|
|

CREATE TABLE SP
( SNO
VARCHAR(5) NOT
PNO
VARCHAR(6) NOT
QTY
INTEGER
NOT
UNIQUE ( SNO , PNO ) ,
FOREIGN KEY ( SNO )
REFERENCES S ( SNO )
FOREIGN KEY ( PNO )
REFERENCES P ( PNO )

NULL ,
NULL ,
NULL ,

,
) ;

Обновление – это операция над множес­твом
Сразу же хочу подчеркнуть, что вне зависимости от используемого синтаксиса, реляционное присваивание – это операция над множес­твами.
(Фактичес­ки все операции в реляционной модели производятся над
множес­твами, то есть принимают в качестве операндов от­но­ше­ния или

123

Обновление – это операция над множес­твом

пе­ре­мен­ные-от­но­ше­ния целиком, а не отдельные кортежи). Таким образом, INSERT вставляет множес­тво кортежей в конечную пе­ре­мен­
ную-от­но­ше­ние, DELETE удаляет множес­тво кортежей из пе­ре­мен­нойот­но­ше­ния, а UPDATE обновляет множес­тво кортежей в пе­ре­мен­нойот­но­ше­нии. Конечно, мы часто говорим, например, об обновлении одного кортежа, но вы должны понимать, что:
•• Такое словоупотребление означает лишь, что кардинальность обновляемого множес­тва кортежей равна единице.
•• Более того, обновление множес­тва кортежей кардинальности 1 иногда невозможно в принципе.
Предположим, например, что на пе­ре­мен­ную-от­но­ше­ние S наложено
ограничение целостности (см. главу 8), согласно которому поставщики S1 и S4 всегда должны находиться в одном городе. Тогда любая операция «UPDATE одного кортежа», которая попытается изменить город
только дляодного из этих двух поставщиков, неизбежно завершится
неудачно. Нам придется изменять одновременно обоих поставщиков,
например, так:
UPDATE S
WHERE SNO = ‘S1’
OR SNO = ‘S4’ :
{ CITY := ‘New York’ } ;

|
|
|
|

UPDATE
SET
WHERE
OR

S
CITY = ‘New York’
SNO = ‘S1’
SNO = ‘S4’ ;

В данном примере обновляется именно множес­тво, состоящее из двух
кортежей.
Из сказанного выше вытекает, что в реляционной модели нет никакого соответствия «позиционным обновлениям» в SQL (то еcть предложениям UPDATE или DELETE «WHERE CURRENT OF cursor»), так как
это, по определению, операции над кортежами (точнее, над строками), а не над множес­твами. В современных SQL-продуктах они по большей части работают нормально, но лишь потому, что эти продукты не
очень хорошо поддерживают ограничения целостности. Если они когданибудь будут улучшены в этом от­но­ше­нии, то «позиционные обновления» могут и перестать работать, то есть приложения, которые сегодня
функционируют, завтра могут отказать – не слишком приятная ситуация, на мой взгляд. Рекомендация: Не пользуйтесь в SQL обновлениями на курсоре, если нет абсолютной уверенности, что проблема, подобная той, что описана выше, никогда не возникнет. (Я говорю это, полностью осознавая тот факт, что в настоящее время большая часть обновлений в SQL производится на курсоре.)
А теперь я должен кое в чем сознаться. Дело в том, что фраза «обновление кортежа» – или, точнее, множес­тва кортежей – в любом случае
очень неточна (если не сказать небрежна). Вспомните определения значения и пе­ре­мен­ной из главы 1. Если V – субъект обновления, то V должна быть пе­ре­мен­ной, по определению, но кортежи (как и от­но­ше­ния) являются значениями и не могут быть обновлены – тоже по определению.

124

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

Говоря об обновлении кортежа t1 до t2 в некоторой пе­ре­мен­ной-от­но­
ше­нии R, мы имеем в виду замену кортежа t1 в R другим кортежем t2.
Но даже такое словоупотребление есть небрежность! – на самом деле
мы заменяем от­но­ше­ние r1, являющееся начальным значением R, другим от­но­ше­нием r2. А что такое здесь от­но­ше­ние r2? Объясняю: пус­ть
s1 и s2 – от­но­ше­ния, содержащие только кортеж t1 и t2 соответственно; тогда r2 равно (r1 MINUS s1) UNION s2. Другими словами, «обновление кортежа t1 до t2 в пе­ре­мен­ной-от­но­ше­нии R» можно рассматривать
как две последовательные операции: сначала удаление t1, а затем вставку t2, – если (вопреки всему, что я сказал) вы позволите мне так неформально говорить об удалении и вставке отдельных кортежей.
Точно так же не имеет смысла говорить об «удалении атрибута A из кортежа t» – или из от­но­ше­ния r, или даже из пе­ре­мен­ной-от­но­ше­ния R.
Конечно, на практике мы так говорим, потому что это удобно (и позволяет избежать многословия), но, как и в случае с дружес­твенной к пользователю терминологией, о которой шла речь в главе 1, это приемлемо
лишь при условии, что все мы понимаем, что это лишь приблизительное отображение истины, которое затемняет настоящую суть дела.

Проверка ограничений
Из того, что операторы обновления суть операции над множес­твами,
следует среди прочего, что проверку ограничений целостности нельзя выполнять, пока обновление не будет завершено целиком (дополнительные соображения см. в главе 8). Иначе говоря, обновление множества нельзя рассматривать как последовательность обновлений индивидуальных кортежей (или строк в терминологии SQL). Я полагаю, что
стандарт SQL согласуется с этим требованием, – а, может быть, и нет;
в этой связи вызывают некоторое подозрение «триггеры на уровне строки» (см. следующий подраздел). Как бы то ни было, даже если стандарт
согласуется с теорией, это еще не значит, что с ней согласуются и все
продукты, поэтому всегда нужно быть настороже.

Триггерные дейст­вия
Из того, что операторы обновления суть операции над множес­твами,
вытекает еще одно важное следствие: «ссылочные триггерные дейст­
вия», например ON DELETE CASCADE (см. раздел «Еще о внешних
ключах» ниже в этой главе), да и любые другие триггерные дейст­вия,
тоже нельзя выполнять, пока обновление не будет завершено целиком.
Однако здесь, к сожалению, SQL трактует обновление множес­тва как
последовательность обновлений строк, по крайней мере (как уже отмечалось) в поддержке триггеров на уровне строки, а, может быть, и еще
где-то. Рекомендация: Старайтесь избегать операций, принципиально
относящихся к отдельным строкам. Конечно, эта рекомендация не запрещает операции над множес­твами кардинальности 1, как показано
в следующем примере:

125

Реляционное присваивание
UPDATE S WHERE SNO = ‘S5’ :
|
{ CITY := ‘New York’ } ; |
|

UPDATE S
SET
CITY = ‘New York’
WHERE SNO = ‘S5’ ;

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

Реляционное присваивание
В общем случае реляционное присваивание производится путем присваивания от­но­ше­ния-значения, которое обозначается некоторым реляционным выражением, пе­ре­мен­ной-от­но­ше­нию, обозначаемой своим
именем. Например:
S := S WHERE NOT ( CITY = ‘Athens’ ) ;

В главе 1 мы видели, что такое присваивание логичес­ки эквивалентно
следующему предложению DELETE языка Tutorial D:
DELETE S WHERE CITY = ‘Athens’ ;

Более общо, предложение DELETE
DELETE R WHERE bx ;

(где R – имя пе­ре­мен­ной-от­но­ше­ния, а bx – булево выражение) является
сокращенной записью следующего реляционного присваивания:
R := R WHERE NOT ( bx ) ;

Аналогично предложение INSERT языка Tutorial D
INSERT R rx ;

(где R – снова имя пе­ре­мен­ной-от­но­ше­ния, а rx – реляционное выражение) является сокращенной записью для:
R := R D_UNION rx ;

Примечание
Здесь D_UNION обозначает дизъюнктное объединение. Дизъюнктное объединение похоже на обычное объединение с тем отличием, что его операнды-от­
но­ше­ния не должны иметь общих кортежей (см. главу 6).

126

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

Наконец, предложение UPDATE языка Tutorial D тоже соответствует некоторому реляционному присваиванию, но его детали несколько
сложнее, чем для INSERT и DELETE, поэтому я отложу их рассмотрение до главы 7.

Таб­личное присваивание в SQL
Предложения INSERT, DELETE и UPDATE в SQL являются точными
аналогами соответствующих предложений Tutorial D, и больше о них
сказать почти нечего, разве что привести два мелких замечания по поводу INSERT. В SQL источник операции INSERT задается таб­личным выражением (часто, хотя и необязательно, выражением VALUES – см. главу 3). Таким образом, INSERT в SQL дейст­вительно вставляет таб­лицу,
а не строку, хотя эта таб­лица может содержать всего одну строку или
даже не содержать ни одной строки. Также INSERT в SQL поддерживает
возможность задавать после имени таб­лицы заключенный в скобки список имен столбцов. Рекомендация: Всегда пользуйтесь этой возможностью. Например, следующая форма записи предложения INSERT
INSERT INTO SP ( PNO , SNO , QTY ) VALUES ( ‘P6’ , ‘S5’ , 700 ) ;

предпочтительнее такой:
INSERT INTO SP VALUES ( ‘S5’ , ‘P6’ , 700 ) ;

потому что во втором варианте мы полагаемся на упорядочение слева
направо столбцов в таб­лице SP, а в первом – нет. Вот еще один пример:
INSERT INTO SP ( SNO , PNO , QTY ) VALUES ( ‘S3’ , ‘P1’ , 500 ) ,
( ‘S2’ , ‘P5’ , 400 ) ;

К сожалению, в SQL нет прямого аналога реляционному присваиванию
как таковому. Самое большее, на что он способен в плане обобщенного
присваивания
R := rx ;

– это следующая последовательность предложений:
DELETE FROM T ;
INSERT INTO T ( ... ) tx ;

(здесь T и tx – SQL-аналоги R и rx соответственно). Отметим, в частности, что такая последовательность предложений может завершиться
неудачно в случае, когда ее реляционный аналог (то есть реляционное
присваивание) завершился бы успешно, – например, если на таб­лицу T
наложено ограничение, согласно которому она не должна быть пус­той.

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

Еще о потенциальных ключах

127

ствия. Это принцип присваивания, который гласит, что после присваивания значения v пе­ре­мен­ной V результатом сравнения v = V должно быть TRUE. Примечание: Принцип присваивания – это фундаментальный принцип, справедливый не только для реляционной модели, но и для информатики вообще. Конечно, он применим, в частности,
и к реляционному присваиванию, но (повторюсь) распространяется на
присваивание любого вида. На самом деле, его можно в какой-то мере
считать определением присваивания, и я уверен, что вы это осознаете.
Я еще вернусь к этой теме в главе 8 при обсуждении так называемого
множес­твенного присваивания.

Еще о потенциальных ключах
Основную идею потенциальных ключей я объяснил в главе 1, но сейчас
хочу уточнить это понятие. Сначала определение.
Определение: Пусть K – подмножес­тво заголовка пе­ре­мен­
ной-от­но­ше­ния R. Тогда K называется потенциальным
ключом (или просто ключом) R, если оно обладает следующими свойствами:
1. Уникальность: никакое из возможных значений R не содержит двух разных кортежей с одинаковым значением K.
2. Неприводимость: никакое собственное подмножес­тво K не обладает свойством уникальности.
Если K состоит из n атрибутов, то n называется степенью K.
Свойство уникальности не требует пояснений, а вот о свойстве неприводимости я должен сказать несколько слов. Рассмотрим пе­ре­мен­нуюот­но­ше­ние S и множес­тво атрибутов {SNO,CITY} – назовем его SK – которое, безусловно, является подмножес­твом S, обладающим свойством уникальности (никакое от­но­ше­ние, которое могло бы являться
значением пе­ре­мен­ной-от­но­ше­ния S, не содержит двух разных кортежей с одним и тем же значением SK). Но свойством неприводимости
оно не обладает, поскольку можно отбросить атрибут CITY, а оставшееся множес­тво {SNO} все равно будет обладать свойством уникальности.
Поэтому мы не рассматриваем множес­тво SK как ключ, потому что оно
«слишком велико». Напротив, множес­тво {SNO} неприводимо и является ключом.
Почему мы хотим, чтобы ключи были неприводимы? Одна из важных
причин такова: если бы мы задали «ключ», который не является неприводимым, то СУБД не смогла бы обеспечить выполнение нужного
ограничения уникальности. Предположим, к примеру, что мы сказали
СУБД (солгали!), что {SNO,CITY} – ключ. Тогда СУБД не смогла бы гарантировать, что номера поставщиков «глобально» уникальны, а обеспечила бы лишь более слабое ограничение «локальной» уникальности
номеров, то есть уникальности в пределах одного города. По этой – но

128

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

не только по этой – причине мы требуем, чтобы ключи не содержали
атрибутов, которые не нужны для уникальной идентификации. Реко­
мендация: В SQL никогда не обманывайте сис­тему, определяя в качестве ключа комбинацию столбцов, которая заведомо не является неприводимой.
Все пе­ре­мен­ные-от­но­ше­ния, которые встречались нам до сих пор, имели только один ключ. А теперь для разнообразия приведем несколько
примеров (для простоты только на языке Tutorial D) пе­ре­мен­ных-от­но­
ше­ний с двумя и более ключами. Обратите внимание на перекрывающиеся ключи во втором и третьем примерах.
VAR TAX_BRACKET BASE RELATION
{ LOW MONEY , HIGH MONEY , PERCENTAGE INTEGER }
KEY { LOW }
KEY { HIGH }
KEY { PERCENTAGE } ;
VAR ROSTER BASE RELATION
{ DAY DAY_OF_WEEK , TIME TIME_OF_DAY , GATE GATE , PILOT NAME }
KEY { DAY , TIME , GATE }
KEY { DAY , TIME , PILOT } ;
VAR MARRIAGE BASE RELATION
{ SPOUSE_A NAME , SPOUSE_B NAME , DATE_OF_MARRIAGE DATE }
/* в предположении, что полигамии нет и ни одна пара не */
/* сочетается между собой браком более одного раза ... */
KEY { SPOUSE_A , DATE_OF_MARRIAGE }
KEY { DATE_OF_MARRIAGE , SPOUSE_B }
KEY { SPOUSE_B , SPOUSE_A } ;

Кстати, вы, возможно, заметили здесь мелкую несообразность. По идее,
ключ должен представлять собой множес­тво атрибутов, а атрибут – это
пара имя-атрибута/имя-типа. И тем не менее в синтаксисе KEY в языке
Tutorial D задаются только имена атрибутов, а не пары. Но такой синтаксис годится, потому что имена атрибутов уникальны в пределах заголовка, следовательно, соответствующие им имена типов заданы неявно. Аналогичные замечания применимы ко многим отрывкам на языке
Tutorial D, и я больше не буду их повторять, предлагая отнести сказанное в этом абзаце ко всем остальным случаям.
Я хочу закончить этот раздел несколькими замечаниями на разные
темы. Во-первых, отметим, что понятия ключа применяется к пе­ре­мен­
ным-от­но­ше­ниям, а не к от­но­ше­ниям.1 Почему? Потому что сказать,
1

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

Еще о потенциальных ключах

129

что нечто является ключом, – все равно, что сказать, что дейст­вует некое ограничение целостности – конкретно, ограничение уникальности, – а ограничения целостности применяются к пе­ре­мен­ным, а не
к значениям. (По определению, ограничения целостности налагают
ограничения на обновления, а обновления применяются к пе­ре­мен­ным,
а не к значениям. Продолжение обсуждения см. в главе 8.)
Во-вторых, в случае базовых пе­ре­мен­ных-от­но­ше­ний принято, как отмечалось в главе 1, назначать некий ключ первичным (а все остальные
ключи для рассматриваемой пе­ре­мен­ной-от­но­ше­ния тогда называются
альтернативными). Но выбирать ли какой-то ключ в качестве первичного и, если да, то какой именно, – вопрос прежде всего психологиче­
с­кий, лежащий за рамками самой реляционной модели. На практике большинство базовых пе­ре­мен­ных-от­но­ше­ний, пожалуй, должны
иметь первичный ключ, но, повторюсь, это правило, если его можно назвать правилом, к реляционной теории касательства не имеет.
В-третьих, если R – пе­ре­мен­ная-от­но­ше­ние, то R имеет и даже обязана иметь по меньшей мере один ключ. Причина состоит в том, что любое возможное значение R есть от­но­ше­ние и, следовательно, по определению не содержит кортежей-дубликатов; поэтому уж по крайней мере
комбинация всех атрибутов R – то есть всего заголовка, – безусловно, обладает свойством уникальности. Таким образом, либо сама эта комбинация также обладает свойством неприводимости, либо существует его
собственное подмножес­тво, обладающее этим свойством. В любом случае существует нечто, одновременно уникальное и неприводимое. Примечание: Эти замечания необязательно применимы к SQL-таб­лицам,
поскольку таб­лицы в SQL могут содержать строки-дубликаты, а потому могут вовсе не иметь ключа. Настоятельная рекомендация: По
крайней мере для базовых таб­лиц задавайте спецификаторы PRIMARY
KEY и/или UNIQUE, чтобы у любой такой таб­лицы гарантированно
был хотя бы один ключ.
В-четвертых, отмечу, что значения ключа – это кортежи (строки в SQL),
а не скаляры. Например, в случае пе­ре­мен­ной-от­но­ше­ния S, у которой
есть единственный ключ {SNO}, значением этого ключа для некоторого
конкретного кортежа – скажем, для поставщика S1 – будет
TUPLE { SNO ‘S1’ }

(Вспомните главу 3, где говорилось о том, что любое подмножес­тво кортежа само является кортежем.) Конечно, на практике мы обычно неформально говорим, что значением ключа в этом примере является
просто S1 – или, точнее, ‘S1’, – но фактичес­ки это не так.
Кстати, теперь должно быть ясно, что ключи, как и многое другое в реляционной модели, в сильнейшей мере зависят от понятия равенства
кортежей. Дейст­вительно, чтобы гарантировать соблюдение ограничения целостности, мы должны уметь определять, равны ли два ключа,
а это как раз и есть вопрос о равенстве кортежей – даже тогда, когда,

130

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

как в случае пе­ре­мен­ной-от­но­ше­ния S, соответствующие кортежи имеют степень 1 и «выглядят» как простые скалярные значения.
В-пятых, пус­ть SK – подмножес­тво заголовка пе­ре­мен­ной-от­но­ше­ния
R, обладающее свойством уникальности, но не обязательно свойством
неприводимости. Тогда SK – суперключ R (а суперключ, не являющийся ключом, называется собственным суперключом). Например, {SNO}
и {SNO,CITY} – суперключи пе­ре­мен­ной-от­но­ше­ния S, причем последний – собственный. Отметим, что заголовок любой пе­ре­мен­ной-от­но­ше­
ния R по определению является суперключом R.
И последнее замечание касается понятия функциональной зависимос­
ти. Сейчас я не хочу вдаваться в детали этого понятия, поскольку вернусь к нему в главе 8 и в приложении B, но, надо полагать, вы и так
с ним знакомы. Я лишь хотел бы привлечь ваше внимание к следующему. Пусть SK – суперключ (а, быть может, и ключ) пе­ре­мен­ной-от­но­ше­
ния R, и пус­ть A – произвольное подмножес­тво заголовка R. Тогда R обязательно удовлетворяет функциональной зависимости
SK → A

Поясню. В общем случае функциональная зависимость SK → A означает, что если для двух кортежей R значение SK одинаково, то и значение A для них тоже одинаково. Но если у двух кортежей одинаковое
значение SK, где SK – суперключ, то, по определению, это должен быть
один и тот же кортеж! – поэтому и значение A для них тоже одинаково. Иными словами, если говорить неформально, то всегда существует
«стрелочка функциональной зависимости», направленная от любого суперключа (а значит, и от любого ключа) к любого другому подмножес­
тву заголовка пе­ре­мен­ной-от­но­ше­ния. Повторю, что еще вернусь к этой
теме в главе 8 и приложении B.

Еще о внешних ключах
Напомню (см. главу 1), что внешний ключ – это множес­тво атрибутов
одной пе­ре­мен­ной-от­но­ше­ния, значения которых должны совпадать со
значениями некоторого потенциального ключа другой (или той же самой) пе­ре­мен­ной-от­но­ше­ния. Так, в базе данных о поставщиках и деталях {SNO} и {PNO} – внешние ключи SP, значения которых должны
совпадать со значениями потенциального ключа {SNO} в S и значениями потенциального ключа {PNO} в P соответственно. (Говоря должны
совпадать, я имею в виду, что если, например, пе­ре­мен­ная-от­но­ше­ние
SP содержит кортеж, в котором SNO имеет значение S1, то пе­ре­мен­наяот­но­ше­ние S также должна содержать кортеж, в котором SNO имеет
значение S1, ибо в противном случае в SP присутствовала бы поставка,
произведенная несуществующим поставщиком, и база данных не была
бы «верной моделью реальности».) А теперь приведу более точное определение (еще раз обращаю внимание на зависимость от понятия равенства кортежей):

131

Еще о внешних ключах

Определение: Пусть R1 и R2 – пе­ре­мен­ные-от­но­ше­ния, не
обязательно различные, и пус­ть K – ключ R1. Пусть FK –
подмножес­тво заголовка R2 такое, что существует, возможно пус­тая, последовательность переименований атрибутов
R1, которая переводит K в K′, где K′ и FK содержат в точности одинаковые атрибуты. Пусть далее R2 и R1 в любой
момент времени удовлетворяют ограничению, согласно которому для любого кортежа t2 в R2 значение FK совпадает
со значением K′ для некоторого (необязательно единственного) кортежа t1 в R1 в тот же момент времени. Тогда FK
называет внешним ключом (той же степени, что и K), вышеупомянутое ограничение – ограничением ссылочной целостности, а R2 и R1 – соответственно ссылающейся пе­ре­
мен­ной-от­но­ше­нием и пе­ре­мен­ной-от­но­ше­нием, на которую указывает ссылка, для этого ограничения.
Попутно отмечу, что в первоначальном варианте реляционной модели
требовалось, чтобы внешние ключи соответствовали не какому-то ключу, а именно первичному ключу пе­ре­мен­ной-от­но­ше­ния, на которую
указывает ссылка. Но так как мы не настаиваем на существовании первичных ключей, то, разумеется, не можем и не будем требовать, чтобы
внешние ключи соответствовали только первичным (и SQL с такой позицией согласен).
Еще раз повторю, что в базе данных о поставщиках и деталях {SNO}
и {PNO} – внешние ключи SP, ссылающиеся на единственный потенциальный ключ – на самом деле являющийся также и первичным, – S и P
соответственно. Рассмотрим более сложный пример:
VAR EMP BASE RELATION
{ ENO CHAR ,
MNO CHAR ,
... }
KEY { ENO }
FOREIGN KEY { MNO }
REFERENCES EMP { ENO }
RENAME ( ENO AS MNO ) ;

| CREATE TABLE EMP
|
( ENO VARCHAR(6) NOT NULL ,
|
MNO VARCHAR(6) NOT NULL ,
|
..... ,
|
UNIQUE ( ENO ) ,
|
FOREIGN KEY ( MNO )
|
REFERENCES EMP ( ENO ) ) ;
|

Как видите, имеется существенное различие между этими двумя спе­
цификациями FOREIGN KEY. Сначала я остановлюсь на варианте, написанном на языке Tutorial D1. Атрибут MNO – это номер служащего,
приписанный начальнику служащего, обозначенного атрибутом ENO
(например, в кортеже EMP для служащего E3 значение MNO могло бы
1

Точности ради я должен пояснить, что на момент написания этой книги в языке Tutorial D не было явной поддержки внешних ключей. Однако предложения добавить такую поддержку активно рассматриваются (см.
статью «Inclusion Dependencies and Foreign Keys», упомянутую в приложении D), и мне удобно считать, что они уже приняты.

132

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

быть равно номеру служащего E2, и в результате мы имели бы ссылку
на кортеж EMP, представляющий служащего E2). Поэтому «ссылающаяся пе­ре­мен­ная-от­но­ше­ние» (в определении она названа R2) и «пе­
ре­мен­ная-от­но­ше­ние, на которую указывает ссылка» (в определении
она названа R1) в этом примере совпадают. Но ближе к теме: значения
внешних ключей, как и значения потенциальных ключей, суть кортежи; поэтому мы должны произвести в спецификации внешнего ключа
переименование, чтобы сравнение кортежей на равенство было хотя бы
синтаксичес­ки корректно. (Какое сравнение кортежей на равенство?
Ответ: То, что неявно выполняется в процессе проверки ограничения
внешнего ключа; напомним, что сравнивать на равенство можно только
кортежи одинакового типа, а «одинаковость типа» означает, что атрибуты должны быть одинаковы и, в частности, иметь одинаковые имена.) Вот почему в спецификации на языке Tutorial D целью является не
просто EMP, а EMP{ENO} RENAME (ENO AS MNO). Примечание: Оператор RENAME подробно описан в следующей главе, а пока я надеюсь,
что его назначение вам и так понятно.
Теперь обратимся к SQL. Памятуя о приведенном выше определении внешнего ключа, отметим, что в SQL ключ K и соответствующий
ему внешний ключ FK представляют собой последовательности, а не
множес­тва столбцов. (Иными словами, наличие упорядочения слева направо важно.) Пусть эти столбцы в той последовательности, которая задана в спецификации FOREIGN KEY, будут B1, B2, ..., Bn (для
FK) и A1, A2, ..., An (для K).1 Тогда столбцы Bi и Ai (1 ≤ i ≤ n) должны иметь одинаковые типы, но могут иметь разные имена. Вот почему
в SQL спецификация
FOREIGN KEY ( MNO ) REFERENCES EMP ( ENO )

корректна даже без переименования.
Рекомендация: Несмотря на это последнее замечание, старайтесь по возможности называть столбцы внешнего ключа точно так же, как столбцы соответствующего ключа (вопрос об именовании столбцов обсуждался в главе 3). Однако бывают ситуации – ровно две, если быть точным, –
когда этой рекомендации невозможно следовать на все 100 процентов:
•• Когда в некоторой таб­лице T имеется внешний ключ, ссылающийся
на ключ самой таб­лицы T (как, например, в таб­лице EMP).
•• Когда в некоторой таб­лице T2 есть два разных внешних ключа, ссылающихся на один и тот же ключ K таб­лицы T1.
1

Столбцы A1, A2, ..., An должны встречаться в какой-то спецификации
UNIQUE или PRIMARY KEY для целевой таб­лицы, но не обязательно в том
же порядке, что в спецификации FOREIGN KEY. Однако и сами эти столбцы, и окружающие их скобки в спецификации FOREIGN KEY можно опус­
тить, но в этом случае они должны встречаться в спецификации PRIMARY
KEY, а не UNIQUE для целевой таб­лицы, и, разумеется, должны быть заданы в ней в надлежащем порядке.

Еще о внешних ключах

133

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

Ссылочные дейст­вия
Как вы, наверное, знаете, SQL поддерживает не только внешние ключи,
но также ссылочные дейст­вия, например CASCADE. Такие дейст­вия
можно задавать во фразах ON DELETE и ON UPDATE. Например, предложение CREATE TABLE для таб­лицы поставок могло бы включать такую спецификацию:
FOREIGN KEY ( SNO ) REFERENCES S ( SNO ) ON DELETE CASCADE

В этом случае попытка удалить некоторого поставщика привела бы
к каскадному удалению всех связанных с ним поставок.
Отметим, что ссылочные дейст­вия могут оказаться полезны на практике, однако частью реляционной модели они не являются. Но это не
обязательно считать проблемой! Реляционная модель – это фундамент
всей отрасли баз данных, но и только. Поэтому не видно, почему на
этом фундаменте не разместить дополнительные средства, – при условии, конечно, что они не нарушают предписаний модели (наверное, стоит еще добавить, что они также должны отвечать духу модели и быть
дейст­вительно полезны). Немного разовью эту мысль.
•• Самый очевидный пример дает теория типов. В главе 2 мы видели,
что «типы ортогональны таб­лицам», но видели также и то, что полная и строгая поддержка типов в реляционных сис­темах – быть может, даже с поддержкой наследования – весьма желательна, и это
еще мягко сказано.
•• Триггерные процедуры. Строго говоря, триггерная процедура – это
дейст­вие (триггерное дейст­вие), которое выполняется при возникновении заданного события (триггерного события), но этот термин
часто неформально употребляется и для обозначения самого триггерного события. Ссылочные триггерные дейст­вия, например ON
DELETE CASCADE, – это просто прагматичес­ки важный пример
более общей конструкции, в которой дейст­вием является DELETE
(фактичес­ки, «процедура», определенная в данной случае декларативно), а триггерным событием – ON DELETE.1 Реляционная модель не предписывает никаких триггерных процедур, но и не запрещает их, – а запрещала бы, если бы они нарушали теоретико-мно­
жественную природу модели или принцип присваивания, что, кста1

Если вам интересно, в терминологии SQL ON DELETE CASCADE называется «ссылочным триггерным дейст­вием» (referential triggered action), а само
слово CASCADE – «ссылочным дейст­вием» (referential action).

134

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

ти, на практике весьма вероятно. Примечание: Сочетание триггерного события и соответствующего триггерного дейст­вия часто называют триггером. Рекомендация: Как отмечалось выше, избегайте
имеющихся в SQL триггеров на уровне строк и вообще не пользуйтесь триггерами, если это нарушает принцип присваивания.
•• В качестве третьего примера скажу, что реляционная модель почти
ничего не говорит о восстановлении и управлении параллельным доступом, но это не означает, что реляционные сис­темы могут не предоставлять такие средства. (На самом деле, можно возразить, что реляционная модель все-таки упоминает о таких материях неявно, так
как полагает, что СУБД реализует обновления корректно и без потери данных; тем не менее ничего конкретного она не предписывает.)
И еще одно последнее замечание. Я завел разговор о внешних ключах,
потому что их прагматичес­кая ценность несомненна, а также потому, что
они определены в оригинальной модели. Но хочу подчеркнуть, что ничего фундаментального в них нет, это просто сокращенная запись для некоторого ограничения целостности, которое часто встречается на практике, как мы увидим в главе 8. (На самом деле почти то же самое можно
сказать и о потенциальных ключах, однако в этом случае практичес­кая
польза от наличия такой сокращенной записи неизмеримо больше.)

Пе­ре­мен­ные-от­но­ше­ния и предикаты
Вот мы и подошли к теме, которую в каком-то смысле можно назвать
важнейшей в этой главе. Суть ее в том, что существует другой взгляд
на пе­ре­мен­ные-от­но­ше­ния. Я имею в виду, что многие считают пе­ре­
мен­ные-от­но­ше­ния просто файлами в традиционном для информатики
смысле – быть может, довольно абстрактные файлы, но все-таки файлы.
Но можно взглянуть на них и иначе, и, как мне кажется, это позволит
гораздо глубже понять, что происходит на самом деле.
Рассмотрим пе­ре­мен­ную-от­но­ше­ние S, представляющую поставщиков.
Предполагается, что она, как и все пе­ре­мен­ные-от­но­ше­ния, описывает
какую-то часть реального мира. Но я мог бы выразиться и точнее: заголовок этой пе­ре­мен­ной-от­но­ше­ния представляет некоторый предикат,
то есть обобщенное утверждение о какой-то части реального мира (обобщенное, потому что оно параметризовано, как я вскоре объясню). Предикат выглядит следующим образом:
Поставщик SNO связан контрактом, он называется SNAME,
имеет статус STATUS и находится в городе CITY.
Этот предикат являет собой подразумеваемую интерпретацию, или
смысловое содержание пе­ре­мен­ной-от­но­ше­ния S, называемое также интенцией.
Вообще говоря, предикат можно считать функцией, возвращающей значение истинности. Как и у любой другой функции, у него есть набор

Пе­ре­мен­ные-от­но­ше­ния и предикаты

135

параметров, а при вызове он возвращает результат, который может принимать одно из двух значений: TRUE или FALSE. Например, параметрами вышеупомянутого предиката являются SNO, SNAME, STATUS и CITY
(они соответствуют атрибутам пе­ре­мен­ной-от­но­ше­ния), и обозначают они
значения соответствующих типов (CHAR, CHAR, INTEGER и CHAR).
При вызове этой функции – в логике это называется порождением предиката – мы подставляем вместо параметров аргументы. Предположим,
что мы подставили соответственно аргументы S1, Smith, 20 и London.
Тогда получается следующее предложение:
Поставщик S1 связан контрактом, он называется Smith, имеет статус 20 и находится в городе London.
Фактичес­ки это предложение представляет собой высказывание, так
в формальной логике называют утверждение, которое может быть или
истинным, или ложным и не содержит никаких условий. Вот два примера высказываний:
1. «Банду разводного ключа» (The Monkey Wrench Gang) написал Эдвард Эбби.
2. «Банду разводного ключа» написал Вильям Шекспир.
Первое истинно, второе ложно. Не делайте распространенную ошибку,
считая, что всякое высказывание должно быть истинным! Однако те,
которые я сформулирую ниже, дейст­вительно предполагаются истинными.
•• Прежде всего, с каждой пе­ре­мен­ной-от­но­ше­нием ассоциирован предикат, который называется предикатом пе­ре­мен­ной-от­но­ше­ния.
(Показанный выше предикат является предикатом пе­ре­мен­ной-от­
но­ше­ния S.)
•• Пусть с пе­ре­мен­ной-от­но­ше­нием R ассоциирован предикат P. Тогда
каждый кортеж t, входящий в R в данный момент времени, можно
рассматривать как представление некоторого высказывания p, которое получается путем вызова (или порождения) P в этот момент со
значениями атрибутов t в качестве аргументов.
•• И (очень важно!) мы принимаем соглашение о том, что каждое полученное таким образом высказывание p истинно.
Так, взяв в качестве примера нашу пе­ре­мен­ную-от­но­ше­ние S, мы предполагаем, что каждое из следующих высказываний в данный момент
времени истинно:
Поставщик S1 связан контрактом, он называется Smith, имеет статус 20 и находится в городе London.
Поставщик S2 связан контрактом, он называется Jones, имеет
статус 10 и находится в городе Paris.
Поставщик S3 связан контрактом, он называется Blake, имеет
статус 30 и находится в городе Paris.

136

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

И так далее. Но мы можем пойти еще дальше: если некоторый кортеж
теоретичес­ки мог бы встретиться в некоторой пе­ре­мен­ной-от­но­ше­нии
в какой-то момент времени, но фактичес­ки не встречается, то мы предполагаем, что соответствующее высказывание в данный момент ложно.
Например, кортеж
TUPLE { SNO ‘S6’ , SNAME ‘Lopez’ , STATUS 30 , CITY ‘Madrid’ }

вполне годится на роль поставщика, но в данный момент отсутствует
в пе­ре­мен­ной-от­но­ше­нии S, поэтому мы вправе предположить, что следующее высказывание в данный момент времени ложно:
Поставщик S6 связан контрактом, он называется Lopez, имеет
статус 30 и находится в городе Madrid.
Подведем итог. В любой момент времени данная пе­ре­мен­ная-от­но­ше­
ние R содержит те и только те кортежи, которым соответствуют истинные высказывания (порождения предиката пе­ре­мен­ной-от­но­ше­ния
R возвращают TRUE) в этот момент; по крайней мере, именно это мы
всегда подразумеваем на практике. Иначе говоря, на практике мы принимаем так называемое допущение замкнутости мира (развитие этой
темы см. в приложении A).
Еще немного терминологии. Пусть снова P – предикат пе­ре­мен­ной-от­
но­ше­ния, или интенция пе­ре­мен­ной-от­но­ше­ния R, и пус­ть от­но­ше­ние
r – это значение R в некоторый момент времени. Тогда r – тело r, если
быть точным, – составляет экстенцию P в тот же момент времени. Отметим, однако, что экстенция для данной пе­ре­мен­ной-от­но­ше­ния меняется со временем, а интенция – нет.
И последние два терминологичес­ких замечания:
•• Возможно, вам уже знаком термин предикат, поскольку в SQL он
часто употребляется для обозначения того, что в этой книге называется булевым выражением (то есть в SQL встречаются словосочетания «предикат IN», «предикат EXISTS» и т. д.). Нельзя сказать, что
такое словоупотребление сов­сем уж некорректно, но SQL узурпировал очень общий термин – весьма важный в реляционных контекстах – и придал ему довольно узкое значение, поэтому лично я предпочитаю этой практике не следовать.
•• Если уж зашла речь об узурпировании общих терминов и наделении
их специализированной семантикой, то есть еще один потенциальный источник путаницы. Он связан с термином statement. В логике
этот термин употребляется в смысле, очень близком к естественному языку1. Напротив, в языках программирования ему придается
более ограниченный и специализированный смысл. Так называется
конструкция, приводящая к выполнению некоторого дейст­вия, на1

В контексте логики слово statement переводится как утверждение, суждение, в языках программирования – как предложение, хотя встречаются
также переводы команда, инструкция. – Прим. перев.

От­но­ше­ния и типы

137

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

От­но­ше­ния и типы
В главе 2 среди прочего мы обсуждали типы и от­но­ше­ния. Однако тогда
я еще не мог объяснить самое важное логичес­кое различие между этими понятиями. Теперь могу – и объясню.
Я показал, что в любой момент времени базу данных можно рассматривать как совокупность истинных высказываний, таких, например,
как высказывание Поставщик S1 связан контрактом, он называется
Smith, имеет статус 20 и находится в городе London. Точнее, я показал, что значения аргументов подобного высказывания (в данном случае S1, Smith, 20 и London) – это в точности значения атрибутов, взятых из соответствующего кортежа, причем каждый такой атрибут имеет значение ассоциированного с ним типа. Отсюда следует, что:
Типы – это множес­тва предметов, о которых мы можем рассуж­
дать; от­но­ше­ния – это (истинные) суждения о таких предметах.
Другими словами, типы дают нам словарь (предметы, о которых можно рассуждать), а от­но­ше­ния – возможность высказывать суждения об
этих предметах. (Возможно, вам поможет такая аналогия: между типами и от­но­ше­ниями такая же связь, как между существительными
и грамматичес­кими предложениями.) Например, если для простоты
ограничиться только поставщиками, то мы увидим, что:
•• Предметы, о которых мы можем рассуждать, – это символьные строки и целые числа, и ничего более. (В реальной базе данных наш словарь обычно гораздо шире, особенно если участвуют еще и типы,
определенные пользователем).
•• Возможны суждения вида «поставщик с номером поставщика, обозначенным некоторой символьной строкой, связан контрактом, имеет имя, обозначенное другой символьной строкой, имеет статус, обозначенный целым числом, и находится в городе, обозначенном третьей символьной строкой» – и никакие другие. (Никакие, кроме
логичес­ки вытекающих из тех суждений, которые мы можем сформулировать явно. Например, уже зная, что можно явно сказать о поставщике S1, мы можем также сказать, что Поставщик S1 связан
контрактом, называется Smith, имеет статус 20 и находится
в некотором городе, причем город оставлен не заданным. И если вы
полагаете, что сказанное мной очень напоминает и, пожалуй, даже
имеет глубинную связь с реляционной проекцией… что ж, вы абсолютно правы. См. раздел «В чем смысл реляционных выражений?»
в главе 6.)

138

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

Из всего сказанного выше вытекают по меньшей мере три важных следствия. Конкретно, чтобы «описывать какую-то часть реального мира»
(так я выразился в предыдущем разделе):
1. Необходимы и типы, и от­но­ше­ния – без типов нам не о чем было бы
рассуждать, а без от­но­ше­ний мы ничего не смогли бы сказать.
2. Типы и от­но­ше­ния достаточны и необходимы – логичес­ки говоря,
нам больше ничего не нужно. (Конечно, нам необходимы пе­ре­мен­
ные-от­но­ше­ния, чтобы отразить изменчивость реального мира во
времени, но для представления ситуации в любой конкретный момент времени они ни к чему.)

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

3. Типы и от­но­ше­ния – не одно и то же. Берегитесь тех, кто пытается уверить вас, будто это не так! И ведь есть такие продукты, которые как раз и «делают вид», что тип – это просто частный случай
от­но­ше­ния (хотя, конечно, выражают эту мысль другими словами),
но я надеюсь, что любой продукт, основанный на подобной логической ошибке, обречен на провал. (Фактичес­ки, по крайней мере один
из продуктов, которые я имею в виду, уже прогорел.) Впрочем, продукты, о которых я говорю, не являются реляционными; обычно это
сис­темы, поддерживающие «объекты» в объектно-ориентированном
смысле или пытающиеся тем или иным способом «поженить» объекты и SQL-таб­лицы. Детальное их рассмотрение выходит за рамки
этой книги.
Изложу несколько более формальный взгляд на все вышесказанное.
Как мы видели, базу данных можно представлять себе как совокупность истинных высказываний. В дейст­вительности база данных в сочетании с операторами, которые применяются к представленным в этой
базе высказываниям (точнее, к множес­твам таких высказываний), – это
логичес­кая сис­тема. Под «логичес­кой сис­темой» я понимаю здесь формальную сис­тему – такую, как, скажем, евклидова геометрия, – в которой имеются аксиомы («исходные истины») и правила вывода, позволяющие доказывать теоремы («производные истины») на основе аксиом. Величайшим озарением Кодда, когда в 1969 году он придумал реляционную модель, было осознание того факта, что база данных (вопреки
названию) – это не просто совокупность данных, а совокупность фактов, или истинных высказываний. Эти высказывания – заданные, то
есть представленные кортежами в базовых пе­ре­мен­ных-от­но­ше­ниях, –
являются аксиомами логичес­кой сис­темы. А правила вывода – это те

Упражнения

139

правила, с помощью которых можно из имеющихся высказываний выводить новые; иными словами, правила говорят, как применять операторы реляционной алгебры. Таким образом, вычисляя некоторое реляционное выражение (в частности, отвечая на запрос), сис­тема на самом
деле выводит новые истины из заданных, то есть по существу доказывает теорему!
Осознав это, мы приходим к заключению, что для поиска подходов
к «проблеме баз данных» в нашем распоряжении оказывается весь аппарат формальной логики. Другими словами, следующие вопросы:
•• Как база данных должна выглядеть с точки зрения пользователя?
•• Как должны выглядеть ограничения целостности?
•• Как должен выглядеть язык запросов?
•• Как лучше всего реализовать запросы?
•• Более общо, как лучше всего вычислять выражения?
•• Как следует представлять результаты пользователю?
•• Как вообще следует проектировать базу данных?
(и им подобные) становятся, по сути дела, вопросами, к которым можно применить методы формальной логики и получить логичес­ки безупречные ответы.
Не стоит и говорить, что реляционная модель непосредственно поддерживает только что изложенную интерпретацию, и именно поэтому, на
мой взгляд, она несокрушима, как скала, и ей суждена долгая жизнь.
И именно поэтому, опять-таки на мой взгляд, прочие «модели данных»
просто находятся в другой весовой категории. Я совершенно серьезно
задаю вопрос, можно ли вообще эти «модели данных» называть моделями в том же смысле, в каком таковой является реляционная модель.
Ведь большинство из них в той или иной степени ситуативны, а не возведены, как реляционная модель, на незыблемом фундаменте теории
множес­тв и логики предикатов. Я еще вернусь к этим вопросам в приложении A.

Упражнения
Упражнение 5.1. Иногда предлагают рассматривать пе­ре­мен­ную-от­но­
ше­ние как обычный файл, в котором кортежи играют роль записей,
а атрибуты – роль полей. Обсудите такой подход.
Упражнение 5.2. Объясните своими словами, почему фразы типа «Эта
операция UPDATE обновляет статус всех поставщиков в Лондоне» не
очень точны. Дайте настолько точную переформулировку этой фразы, насколько сможете.
Упражнение 5.3. Почему операции «позиционного обновления» в SQL –
неудачная идея?

140

Глава 5. Базовые пе­ре­мен­ные-от­но­ше­ния, базовые таб­лицы

Упражнение 5.4. Пусть SS – базовая таб­лица с такими же столбцами,
как S. Рассмотрим следующие SQL-предложения INSERT:
INSERT INTO SS ( SNO , SNAME , STATUS , CITY )
( SELECT SNO , SNAME , STATUS , CITY
FROM S
WHERE SNO = ‘S6’ ) ;
INSERT INTO SS ( SNO , SNAME , STATUS , CITY ) VALUES
( SELECT SNO , SNAME , STATUS , CITY
FROM S
WHERE SNO = ‘S6’ ) ;

Являются ли они логичес­ки эквивалентными? Если нет, то в чем их
различие?
Упражнение 5.5. (Это, по существу, повторупражнения 2.21 из главы 2, но теперь вы можете дать более полный ответ.) Сформулируйте принцип присваивания. Можете ли вы назвать ситуации, в которых SQL нарушает этот принцип? Можете ли вы сказать, каковы
негативные последствия такого нарушения?
Упражнение 5.6. Напишите определения базовых таб­лиц SQL, соответствующих пе­ре­мен­ным-от­но­ше­ниям TAX_BRACKET, ROSTER
и MARRIAGE из раздела «Еще о потенциальных ключах».
Упражнение 5.7. Почему бессмысленно говорить о том, что от­но­ше­ние
имеет ключ?
Упражнение 5.8. В тексте главы я назвал одну причину, по которой
неприводимость ключа – здравая идея. Видите ли вы еще какиенибудь?
Упражнение 5.9. «Значения ключа – не скаляры, а кортежи». Прокомментируйте это замечание.
Упражнение 5.10. Пусть степень пе­ре­мен­ной-от­но­ше­ния R равна n. Какое максимальное количество ключей может иметь R?
Упражнение 5.11. В чем различие между ключом и суперключом?
И коль скоро существует понятие суперключа, то имеет ли, по вашему мнению, смысл такая вещь, как подключ?
Упражнение 5.12. Пе­ре­мен­ная-от­но­ше­ние EMP из раздела «Еще о внешних ключах» – это пример так называемой самоссылающейся пе­ре­
мен­ной-от­но­ше­ния. Придумайте какие-нибудь тестовые данные для
такой пе­ре­мен­ной-от­но­ше­ния. Верно ли, что такие примеры с неизбежностью влекут за собой требование о поддержке null-значений?
(Ответ: нет, но они показывают, насколько соблазнительной может быть эта мысль.) Что можно сделать в этом примере, если nullзначения запрещены?
Упражнение 5.13. Почему в SQL нет никакого аналога опции переименования в спецификации внешних ключей в языке Tutorial D?

Упражнения

141

Упражнение 5.14. Можете ли вы придумать ситуацию, в которой каждая из двух пе­ре­мен­ных-от­но­ше­ний R1 и R2 имеет внешний ключ,
ссылающийся на другую? Если да, то каковы последствия?
Упражнение 5.15. В хорошо известном приложении разузлования речь
идет о пе­ре­мен­ной-от­но­ше­нии (назовем ее PP), показывающей для
каждого компонента (узла), из каких компонентов (узлов или простых деталей) он состоит, с указанием количества каждой детали
(например, «деталь P1 состоит из 4 деталей P2»). Дайте определения
PP на языках Tutorial D и SQL. Как вы думаете, какие ссылочные
дейст­вия могут иметь смысл в этом примере?
Упражнение 5.16. Изучите имеющуюся в вашем распоряжении SQL-сис­
тему. Какие ссылочные дейст­вия она поддерживает? Какие, на ваш
взгляд, полезны? Можете ли вы назвать другие дейст­вия, которые не
поддерживаются сис­темой, но могли бы оказаться полезными?
Упражнение 5.17. Определите термины высказывание и предикат.
Приведите примеры.
Упражнение 5.18. Назовите предикаты для пе­ре­мен­ных-от­но­ше­ний P
и SP из базы данных о поставщиках и деталях.
Упражнение 5.19. Как вы понимаете термины интенция и экстенция?
Упражнение 5.20. Пусть DB – какая-нибудь знакомая вам база данных,
и пус­ть R – произвольная пе­ре­мен­ная-от­но­ше­ние в DB. Что есть предикат для R? Примечание: Смысл этого упражнения в том, чтобы
вы применили некоторые из обсуждавшихся в этой главе идей к своим данным и постарались начать думать о данных в таких терминах.
Очевидно, у этого упражнения нет единственного правильного ответа.
Упражнение 5.21. Объясните своими словами допущение замкнутости мира. Возможно ли допущение открытости мира?
Упражнение 5.22. Ключ – это множес­тво атрибутов, а пус­тое множество вполне допус­тимо; таким образом, мы можем определить пус­
той ключ как ключ с пус­тым множес­твом атрибутов. Какие следствия отсюда вытекают? Можете ли вы придумать применения такому ключу?
Упражнение 5.23. У предиката имеется множес­тво параметров, а пус­
тое множес­тво вполне допус­тимо; таким образом, возможен предикат с пус­тым множес­твом параметров. Какие следствия отсюда вытекают?
Упражнение 5.24. Что такое предикат для пе­ре­мен­ной-от­но­ше­ния степени 0? (Имеет ли этот вопрос смысл? Обоснуйте свой ответ.)
Упражнение 5.25. Значением любой пе­ре­мен­ной-от­но­ше­ния является
от­но­ше­ние. Верно ли обратное? Иными словами, всякое ли от­но­ше­
ние является значением некоторой пе­ре­мен­ной-от­но­ше­ния?

Глава

6
.

SQL и реляционная алгебра I:
оригинальные операторы
Это первая из двух глав, посвященных операторам реляционной алгеб­
ры; в ней детально обсуждаются оригинальные операторы (то есть те,
что упоминались в главе 1) и попутно рассматриваются некоторые важные вопросы, например о важности правильного именования атрибутов (или столбцов). Также объясняется, как все это отражается на нашей главной цели – реляционном использовании SQL.

Предварительные сведения
Я хочу начать с повторения изложенного в главе 1. Во-первых, напомню, что любой алгебраичес­кий оператор принимает на входе по меньшей мере одно от­но­ше­ние и на выходе порождает другое от­но­ше­ние.
Во-вторых, напомню и о том, что как исходные данные, так и результат являются от­но­ше­ниями, и именно это свойство, называемое замк­
нутостью алгебры, позволяет записывать вложенные реляционные
выражения. В‑третьих, в главе 1 я дал краткое описание операторов,
которые называю «оригинальными» (ограничение, проекция, произведение, пересечение, объединение, разность и соединение), теперь же
я могу определить эти и другие операторы гораздо более строго. Но сначала необходимо сделать несколько замечаний общего характера:
1. Операторы алгебры являются обобщенными: они применимы к любым от­но­ше­ниям. Например, не нужен один оператор соединения
для того, чтобы соединить служащих с отделами, и другой – для соединения поставщиков с поставками. (Кстати, как вы думаете, применимо ли аналогичное замечание к объектным сис­темам?)
2. Операторы предназначены только для чтения: они «читают» свои
операнды и возвращают значение, но ничего не обновляют. Иными

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

143

словами, они применяются к от­но­ше­ниям, а не к пе­ре­мен­ным-от­но­
ше­ниям.
3. Обратите внимание, что предыдущее замечание не означает, что ре­
ляционные выражения не могут ссылаться на пе­ре­мен­ные-от­но­ше­
ния. Например, если R1 и R2 имена пе­ре­мен­ных-от­но­ше­ний, то R1
UNION R2, конечно, является допус­тимым реляционным выражением в языке Tutorial D (при условии, что эти пе­ре­мен­ные-от­но­ше­ния
имеют один и тот же тип). Однако в этом выражении R1 и R2 обозначают не пе­ре­мен­ные-от­но­ше­ния как таковые, а от­но­ше­ния, являющиеся текущими значениями этих пе­ре­мен­ных-от­но­ше­ний в данный момент времени. Иначе говоря, мы, безусловно, можем указывать имя
пе­ре­мен­ной-от­но­ше­ния для обозначения от­но­ше­ния-операнда, а такая ссылка на пе­ре­мен­ную-от­но­ше­ние сама составляет допус­тимое
реляционное выражение1, но в принципе с равным успехом мы могли
бы на месте этого операнда написать подходящее от­но­ше­ние-литерал.
Следующая аналогия поможет прояснить последнюю мысль. Предположим, что N – пе­ре­мен­ная типа INTEGER, имеющая в момент
времени t значение 3. Тогда N + 2 – допус­тимое выражение, которое в момент времени t означает в точности 3 + 2, не более и не менее.
4. Наконец, коль скоро операторы алгебры способны только читать,
значит, INSERT, DELETE и UPDATE (а также реляционное присваивание), несомненно, являющиеся реляционными операторами, не
являются частью алгебры как таковой, хотя, к сожалению, в литературе часто можно встретить противоположное утверждение.
Необходимо также сказать несколько слов о структуре языка Tutorial D,
поскольку поддержка алгебры в нем устроена совершенно иначе, чем
в SQL. Дело в том, что в операциях типа UNION или JOIN, где необходимо устанавливать соответствие между атрибутами операндов,
Tutorial D решает задачу, требуя, чтобы рассматриваемые атрибуты
формально представляли собой один и тот же атрибут (то есть имели
одинаковые имена и типы). Например, вот как выглядит в Tutorial D
выражение для соединения деталей и поставщиков по городу:
P JOIN S

Здесь операция соединения, по определению, выполняется по городу,
так как CITY – единственный атрибут, общий для P и S. А вот как та
же самая операция записывается в SQL (обратите особое внимание на
последнюю строчку, в которое соответствие между атрибутами – точнее, столбцами – задано явно):
SELECT P.PNO , P.PNAME , P.COLOR , P.WEIGHT , P.CITY /* или S.CITY */ ,
S.SNO , S.SNAME , S.STATUS
1

Но в SQL это необязательно так! Например, если T1 и T2 – имена SQL-таб­лиц,
то, как правило, мы не можем написать нечто вроде T1 UNION T2, а должны
вместо этого писать SELECT * FROM T1 UNION SELECT * FROM T2.

144

Глава 6. SQL и реляционная алгебра I: оригинальные операторы
FROM P , S
WHERE P.CITY = S.CITY

Вообще-то, этот пример можно записать на SQL многими способами.
Ниже приводятся еще три. Как видите, второй и третий по духу несколько ближе к Tutorial D (обратите внимание, в частности, на то, что
результат соединения в этих двух вариантах содержит столбец с именем
просто CITY, а не P.CITY или S.CITY):
SELECT P.PNO , P.PNAME , P.COLOR , P.WEIGHT , P.CITY /* или S.CITY */ ,
S.SNO , S.SNAME , S.STATUS
FROM P JOIN S
ON
P.CITY = S.CITY
SELECT P.PNO , P.PNAME , P.COLOR , P.WEIGHT , CITY ,
S.SNO , S.SNAME , S.STATUS
FROM P JOIN S
USING ( CITY )
SELECT P.PNO , P.PNAME , P.COLOR , P.WEIGHT , CITY ,
S.SNO , S.SNAME , S.STATUS
FROM P NATURAL JOIN S

Однако я выбрал ту формулировку, что выбрал, отчасти потому, что
только она поддерживалась в оригинальном варианте SQL, а отчасти,
и это более важно, потому что она позволяет мне отметить ряд дополнительных различий между SQL и алгеброй в том виде, в каком она реализована в Tutorial D.
•• SQL допускает, а иногда даже требует, квалифицированных имен
с точками, Tutorial D – нет. Примечание: О квалифицированных
именах в SQL я еще буду говорить в главе 12.
•• Tutorial D иногда требует переименовывать атрибуты, чтобы избежать конфликта имен или несоответствия. SQL обычно в этом не
нуждается (хотя и поддерживает некий аналог оператора RENAME,
который используется в Tutorial D для этой цели, как мы увидим
в следующем разделе).
•• Отчасти как следствие предыдущего пункта, Tutorial D не нуждается в понятии «корреляционного имени», существующем в SQL; по
существу, оно заменено идеей о том, что атрибуты иногда необходимо переименовывать, о чем шла речь выше. Примечание: Корреляционные имена в SQL будут подробно рассматриваться в главе 12.
•• Хотя в примере выше этого не видно, SQL иногда устанавливает соответствие столбцов, исходя из того, в каком порядке (слева направо) они упоминаются. Tutorial D так никогда не делает.
•• Помимо явной или неявной поддержки некоторых средств реляционной алгебры, SQL также явно поддерживает некоторые средства
реляционного исчисления (один пример – корреляционные имена,

Еще о замкнутости

145

другой – оператор EXISTS). Tutorial D реляционное исчисление не
поддерживает. Одним из результатов такого различия является тот
факт, что SQL – довольно избыточный язык, то есть часто предлагает много разных способов сформулировать один и тот же запрос,
и это может иметь серьезные негативные последствия для оптимизатора. (Когда-то я написал на эту тему статью под названием «Пятьдесят способов задать вопрос» – см. приложение D, – в которой показал, что для выражения даже такого простого запроса, как «получить имена поставщиков, которые поставляют деталь P2», в SQL
есть больше 50 способов.)
•• В SQL большинство запросов должно строиться по следующему шаб­
лону: SE­LECT - FROM - WHERE. В Tutorial D аналогичного требования нет. Примечание: В следующей главе у меня еще будет что
сказать по этому поводу.
Далее я буду приводить примеры как на Tutorial D, так и на SQL.

Еще о замкнутости
Повторю, что результатом любой реляционной операции является от­но­
ше­ние. И наоборот, любой оператор, результатом которого не является
от­но­ше­ние, по определению не реляционный. Например, оператор, порождающий упорядоченный результат, реляционным не является (см.
обсуждение ORDER BY в следующей главе). А конкретно в SQL то же
самое справедливо для любого оператора, который порождает результат, содержащий строки-дубликаты, или столбцы, упорядоченные слева направо, или null-значения, или анонимные столбцы, или столбцы
с повторяющимися именами. Замкнутость – важнейшее свойство! Как
я уже говорил, именно замкнутость позволяет записывать вложенные
выражения в реляционной модели, и (как мы увидим ниже) она важна
также для трансформации выражений и, следовательно, для оптимизации. Настоятельная рекомендация: Не пользуйтесь операциями, которые нарушают свойство замкнутости, если хотите, чтобы результат
можно было подвергнуть дальнейшей реляционной обработке.

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

Надеюсь, всем ясно, что мои слова о том, что результат любой алгебраичес­
кой операции – от­но­ше­ние, следует понимать концептуально; я не хочу
сказать, что сис­тема должна материализовать результат операции целиком. Рассмотрим, к примеру, следующее выражение (ограничение со­

146

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

единения; слева, как обычно, версия на Tutorial D, справа – на SQL, причем в SQL-версии я сознательно показал квалифицированные имена):
( P JOIN S )
WHERE PNAME > SNAME

|
|
|
|

SELECT
FROM
WHERE
AND

P.* , S.SNO , S.SNAME , S.STATUS
P , S
P.CITY = S.CITY
P.PNAME > S.SNAME

Ясно, что как только очередной кортеж соединения сформирован, сис­
тема в состоянии сразу же проверить, выполняется ли для него ограничение PNAME > SNAME (P.PNAME > S.SNAME в SQL-версии), то есть
должен ли он входить в окончательный результат или его следует отбросить. Следовательно, промежуточный результат операции соединения можно было бы вообще не материализовывать в виде настоящего
от­но­ше­ния. (На практике все сис­темы стремятся во что бы то ни стало
избежать материализации промежуточных результатов, по очевидным
соображениям производительности.)
В данном примере затрагивается еще один важный момент. Рассмот­рим
булево выражение PNAME > SNAME в версии на Tutorial D. Концептуально это выражение применяется к результату операции P JOIN S,
следовательно, имена атрибутов PNAME и SNAME в нем относятся
к атрибутам этого результата, а не к одноименным атрибутам пе­ре­мен­
ных-от­но­ше­ний P и S. Но откуда мы знаем, что результат со­единения
содержит такие атрибуты? Каков заголовок этого результата? И вообще,
откуда нам известно, какой заголовок имеет результат произвольной
алгебраичес­кой операции? Очевидно, необходим какой-то набор правил – конкретно, правил вывода типа от­но­ше­ния, – который позволял
бы по известным заголовкам (и, стало быть, типам) исходных для операции от­но­ше­ний выводить заголовок (а, значит, и тип) результата операции. И в реляционной модели такой набор правил есть. Для случая
соединения эти правила говорят, что результатом операции P JOIN S
является от­но­ше­ние такого типа:
RELATION { PNO CHAR , PNAME CHAR , COLOR CHAR , WEIGHT FIXED ,
CITY CHAR , SNO CHAR , SNAME CHAR , STATUS INTEGER }

Фактичес­ки, для соединения заголовок результата представляет собой объединение заголовков исходных от­но­ше­ний, где под объединением я понимаю обычное теоретико-множес­твенное объединение, а не специальное реляционное объединение, которое будет обсуждаться ниже
в этой главе. Иначе говоря, результат обладает всеми атрибутами исход­
ных от­но­ше­ний, но общие атрибуты – в нашем примере только CITY –
встречаются один раз, а не дважды. Разумеется, эти атрибуты не упорядочены слева направо, поэтому я с тем же успехом мог бы сказать, что
результат операции P JOIN S имеет такой тип:
RELATION { SNO CHAR , PNO CHAR , SNAME CHAR , PNAME CHAR ,
CITY CHAR , STATUS INTEGER , WEIGHT FIXED , COLOR CHAR }

147

Еще о замкнутости

Отмечу, что какие-то правила вывода типа, безусловно, необходимы
для поддержки свойства замкнутости в полном объеме. Это свойство
означает, что результатом любой операции является от­но­ше­ние, а у от­
но­ше­ний есть тело и заголовок; значит, любой результат должен иметь
корректный реляционный заголовок и корректное реляционное тело.
Кстати, оператор RENAME, упоминавшийся во введении к этой главе,
в значительной мере необходим именно из-за правил вывода типа; он
позволяет выполнять, к примеру, соединение даже тогда, когда от­но­ше­
ния-операнды не удовлетворяют требованиям к именованию атрибутов
для данной операции (говоря не слишком строго). Приведу определение.
Определение: Пусть r – от­но­ше­ние, и пус­ть A – атрибут r.
Тогда результатом переименования r RENAME (A AS B) является от­но­ше­ние, для которого (а) заголовок совпадает
с заголовком r, с тем отличием, что атрибут A переименован в B, и (б) тело совпадает с телом r (с тем отличием, что
все ссылки на A в теле заменены ссылками на B, хотя сейчас мы можем не обращать внимания на эту тонкость).
Например:
S RENAME ( CITY AS SCITY )

|
|
|

SELECT SNO , SNAME , STATUS ,
S.CITY AS SCITY
FROM S

Для наших обычных тестовых данных получается такой результат:
SNO

SNAME STATUS

SCITY

S1
S2
S3
S4
S5

Smith
Jones
Blake
Clark
Adams

London
Paris
Paris
London
Athens

20
10
30
20
30

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

Важное замечание: В приведенном выше примере пе­ре­мен­ная-от­но­ше­
ние S в базе данных не изменяется! Оператор RENAME отличается от
SQL-предложения ALTER TABLE тем, что обращение к RENAME – это
просто выражение (точно такое же, как, например, P JOIN S или N + 2),
и, как любое выражение, оно лишь служит для обозначения некоторого значения. Более того, поскольку это выражение, а не предложение,
или «команда», оно может быть вложено в другие выражения. Ниже
мы встретим много примеров такого вложения.

148

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

Так как же SQL справляется с задачей вывода типа результата? Ответ:
не очень хорошо. Во-первых, в главе 3 мы видели, что в SQL вообще
нет понятия «тип от­но­ше­ния» (есть понятие «тип строки»). Во-вторых,
SQL может порождать безымянные столбцы (как, например, в запросе
SELECT DISTINCT 2 * WEIGHT FROM P). В-третьих, он может также
порождать результаты с повторяющимися именами столбцов (как, например, в запросе SELECT DISTINCT P.CITY, S.CITY FROM P, S).
Настоятельная рекомендация: Придерживайтесь дисциплины именования столбцов, описанной в главе 3, если хотите, чтобы SQL максимально полно следовал сформулированным там же реляционным правилам. На всякий случай напомню, что эта дисциплина предполагает использование спецификации AS для присвоения правильных имен
столбцам, которые в противном случае (а) не имели бы имени вовсе или
(б) имели бы неуникальные имена. Игнорировать ее допускается разве
что в случае, когда на столбцы без имени или с неуникальными именами нет никаких других ссылок. В примерах SQL в этой и следующей главах (да, собственно, во всей книге) я буду придерживаться этого правила.
Я еще не закончил разговор о примере в начале этого раздела. Давайте
взглянем на него еще раз:
( P JOIN S )
WHERE PNAME > SNAME

|
|
|
|

SELECT
FROM
WHERE
AND

P.* , S.SNO , S.SNAME , S.STATUS
P , S
P.CITY = S.CITY
P.PNAME > S.SNAME

Как видите, аналогом выражения PNAME > SNAME Tutorial D в языке SQL служит P.PNAME > S.SNAME, что по некотором размышлении
выглядит довольно странно, так как это выражение по идее должно
применяться к результату фразы FROM (см. раздел «Вычисление SQLвыражений» ниже), а пе­ре­мен­ные-от­но­ше­ния P и S уж точно не являются частью этого результата! На самом деле, довольно трудно объяснить, как нечто вроде P.PNAME во фразах WHERE и SELECT (а, быть
может, и в других местах выражения) может иметь хоть какой-то смысл
в терминах результата фразы FROM. Стандарт SQL дает такое объяснение, но уловки, на которые ему пришлось для этого пойти, куда сложнее правил вывода типа в Tutorial D, – настолько, что я даже не буду
пытаться объяснить их здесь, а просто констатирую, что при необходимости это можно объяснить. Эту вольность я оправдываю тем, что вы
предположительно уже знакомы с SQL. Правда, так и тянет спросить,
а задумывались ли вы когда-нибудь над этим вопросом, … но я не стану.
Теперь я готов продолжить описание некоторых алгебраичес­ких операторов. Отмечу, что ни в этой, ни в следующей главе я не пытаюсь дать
исчерпывающую информацию. Я не собираюсь рассматривать «всем известные операторы», и даже те, которые я буду рассматривать, не собираюсь описывать во всей полноте и общности. Фактичес­ки в большин-

149

Ограничение

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

Ограничение
Определение: Пусть r – от­но­ше­ние, и пус­ть bx – булево выражение, в котором все атрибуты, на которые есть ссылки,
являются атрибутами r, а ссылок на пе­ре­мен­ные-от­но­ше­
ния нет вообще. Тогда bx называется условием ограничения, а ограничением r по bx, r WHERE bx, называется такое от­но­ше­ние, что (а) заголовок совпадает с заголовком r
и (б) тело состоит из всех кортежей r, для которых значение
bx равно TRUE.
Например:
P WHERE WEIGHT < 17.5

|
|
|

SELECT *
FROM P
WHERE WEIGHT < 17.5

Пусть r – от­но­ше­ние. Тогда ограничение r WHERE TRUE (или, более
общо, любое ограничение вида r WHERE bx, где bx – произвольное выражение, например 1 = 1, тождественно равное TRUE) возвращает просто r. Такое ограничение называется тождественным.

Примечание
В языке Tutorial D поддерживаются выражения вида r WHERE bx, но эти выражения не обязаны содержать только такие простые ограничения, как приведенное выше, потому что булево выражение bx может быть более общим, чем
условие ограничения. Аналогичное замечание относится и к SQL. Примеры будут приведены в последующих главах.

Попутно отмечу, что операция ограничения иногда называется выборкой (select), но я предпочитаю не употреблять этот термин из-за возможной путаницы со словом SELECT в SQL. В языке SQL SELECT, а точнее
фраза SELECT в выражении SELECT – это вообще не ограничение, а некая комбинация операторов UNGROUP, EXTEND, RENAME и «проекции» (слово «проекция» заключено в кавычки, потому что по умолчанию эта операция не устраняет дубликаты). Примечание: Операторы
UNGROUP и EXTEND описываются в следующей главе.

Проекция
Определение: Пусть r – от­но­ше­ние и пус­ть A, B, ..., C –
атрибуты r. Тогда проекцией r на эти атрибуты r{A,B, ... ,C}
называется от­но­ше­ние, для которого (а) заголовок равен

150

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

{A,B,...,C} и (b) тело состоит из множес­тва всех кортежей x,
таких, что в r существует кортеж t, для которого значение
A равно значению A в x, значение B равно значению B в x,
..., и значение C равно значению C в x.
Например:
P { COLOR , CITY }

|
|

SELECT DISTINCT COLOR , CITY
FROM P

Еще раз повторю, что результат – это от­но­ше­ние, поэтому, как говорят,
«дубликаты устраняются», то есть в формулировке на языке SQL слово DISTINCT обязательно.1 Заголовок результата состоит из атрибутов –
или столбцов – COLOR и CITY (в SQL именно в таком порядке).
Пусть r – от­но­ше­ние. Тогда проекция r{H}, где {H} – множес­тво всех
атрибутов (иными словами, заголовок) r, возвращает просто r. Такая
проекция называется тождественной.
Кстати, язык Tutorial D позволяет также определять проекцию в терминах удаляемых, а не оставляемых атрибутов. Так, следующие выражения в Tutorial D эквивалентны:
P { COLOR , CITY }

и

P { ALL BUT PNO , PNAME , WEIGHT }

Эта возможность иногда позволяет существенно сократить длину вводимого текста (представьте себе проекцию от­но­ше­ния степени 100 на
99 атрибутов).2 Это замечание относится ко всем операторам Tutorial D,
для которых оно имеет смысл.
В реальном синтаксисе удобно приписать оператору проекции высокий
приоритет. Так, в Tutorial D выражение
S JOIN P { PNO }

означает
S JOIN ( P { PNO } )

а не
( S JOIN P ) { PNO }

Упражнение: Продемонстрируйте различие между этими двумя интерпретациями на примере наших тестовых данных.
1

Не могу не отметить, что термин «устранение дубликатов», который употребляется практичес­ки повсеместно (и не только в контексте SQL), не сов­
сем точен, – правильнее было бы говорить об устранении дублирования.

2

В отличие от от­но­ше­ния, существование пе­ре­мен­ной-от­но­ше­ния такой
большой степени маловероятно, поскольку она, скорее всего, нарушала бы
принципы нормализации (см. Приложение B). Но и сказать, что такие пе­
ре­мен­ные-от­но­ше­ния вообще не встречаются, тоже нельзя.

151

Соединение

Соединение
Прежде чем переходить собственно к определению соединения, полезно будет ввести понятие «соединяемости». От­но­ше­ния r1 и r2 называются соединяемыми тогда и только тогда, когда атрибуты с одинаковыми именами имеют одинаковые типы, или, эквивалентно, когда
теоретико-множес­твенное объединение их заголовков само является
допус­тимым заголовком. В следующей главе мы увидим, что это понятие относится не только к соединению как таковому, но и к другим операциям. Так или иначе, вооружившись этим понятием, я могу теперь
определить операцию соединения.
Определение. Пусть r1 и r2 – соединяемые от­но­ше­ния. Тогда их естественным соединением (или для краткос­ти просто
соединением) r1 JOIN r2 называется от­но­ше­ние, для которого (а) заголовком является теоретико-множественное объединение заголовков r1 и r2 и (б) телом является множество
всех кортежей t, таких, что t есть теоретико-множественное
объединение некоторого кортежа из r1 и некоторого кортежа из r2.
Следующий пример повторяет пример из раздела «Предварительные
сведения» с тем отличием, что я опус­тил явные квалификаторы имен
в SQL-версии там, где их наличие необязательно:
P JOIN S

|
|
|
|

SELECT PNO , PNAME , COLOR , WEIGHT ,
P.CITY , SNO , SNAME , STATUS
FROM P , S
WHERE P.CITY = S.CITY

Напомню, однако, что SQL также позволяет записывать соединение
и по-другому, в виде, чуть более близком к Tutorial D (и на этот раз я сознательно вместо длинного списка ссылок на столбцы во фразе SELECT
употребил просто «*»):
SELECT *
FROM P NATURAL JOIN S

В этой формулировке результирующий заголовок состоит из атрибутов, или столбцов CITY, PNO, PNAME, COLOR, WEIGHT, SNO, SNAME
и STATUS (в SQL, но не в Tutorial D, они следуют именно в таком порядке).
В связи с операцией естественного соединения уместно будет сделать
несколько замечаний. Прежде всего, отметим, что его частным случаем является пересечение (то есть r1 INTERSECT r2 – частный случай
r1 JOIN r2 в терминах Tutorial D). Точнее, этот случай возникает, когда
от­но­ше­ния r1 и r2 не просто соединяемые, а имеют в точности один и тот
же тип. Впрочем, к операции INTERSECT я еще вернусь в этой главе.

152

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

Далее, произведение тоже является частным случаем соединения (то
есть r1 TIMES r2 – частный случай r1 JOIN r2 в терминах Tutorial D).
Точнее, этот случай возникает, когда от­но­ше­ния r1 и r2 не имеют общих
имен атрибутов. Почему? Да потому что в этом случае (а) множес­тво общих атрибутов пус­то, (б) любой кортеж с пус­тым множес­твом атрибутов имеет одно и то же значение (а именно, 0-кортеж) и, значит, (в) любой кортеж в r1 соединяется с любым кортежем в r2, то есть мы получаем именно то, что называется произведением. Но для полноты картины
я все же приведу определение.
Определение. Декартовым произведением (или для краткости просто произведением) от­но­ше­ний r1 и r2, r1 TIMES
r2, где r1 и r2 не имеют атрибутов с одинаковыми именами,
называется от­но­ше­ние, для которого (а) заголовком является теоретико-множес­твенное объединение заголовков r1
и r2 и (б) телом является множес­тво всех кортежей t, таких, что t есть теоретико-множес­твенное объединение некоторого кортежа из r1 и некоторого кортежа из r2.
Например:
( P RENAME ( CITY AS PCITY ) )
TIMES /* или JOIN */
( S RENAME ( CITY AS SCITY ) )

|
|
|
|
|

SELECT PNO , PNAME , COLOR ,
WEIGHT , P.CITY AS PCITY,
SNO , SNAME , STATUS ,
S.CITY AS SCITY
FROM P , S

Обратите внимание, что по крайней мере один из двух атрибутов CITY
в этом примере необходимо переименовать. Результирующий заголовок содержит атрибуты, или столбцы, PNO, PNAME, COLOR, WEIGHT,
PCITY, SNO, SNAME, STATUS и SCITY (в SQL именно в таком порядке).
И наконец, соединение – принципиально двуместный оператор, однако возможно и полезно определить n-местный вариант этого оператора
(и в языке Tutorial D это сделано), что позволило бы записывать выражения вида
JOIN { r1 , r2 , ... , rn }

для соединения любого количества от­но­ше­ний r1, r2, ..., rn.1 Например, соединение деталей и поставщиков можно было записать и в таком виде:
JOIN { P , S }

Более того, такой синтаксис можно использовать для «соединения»,
в котором участвует всего одно от­но­ше­ние или даже вовсе нет от­но­ше­
1

Для полноты отмечу, что Tutorial D поддерживает также n-местные варианты операторов INTERSECT и TIMES.

Соединение

153

ний! Соединение одного от­но­ше­ния, JOIN{r}, – по определению, просто
само r; этот случай, наверное, не представляет практичес­кого интереса (?). Однако, как это удивительно, соединение без указания от­но­ше­
ний, JOIN{}, – весьма важная операция, результатом которой является
TABLE_DEE! (Напомню, что TABLE_DEE – единственное от­но­ше­ние,
не имеющее атрибутов и состоящее всего из одного кортежа.) Почему
же результатом является TABLE_DEE? Рассмотрим следующие доводы.
•• В обычной арифметике 0 называется нейтральным элементом относительно операции «+», то есть для любого числа x выражения x + 0
и 0 + x равны x. Отсюда следует, что сумма пус­того множес­тва чисел
равна 0. (Чтобы понять, почему это так, представьте себе программу, которая вычисляет сумму чисел: она присваивает сумме начальное значение 0, а затем в цик­ле перебирает все числа. Ну и что произойдет, если n = 0?)
•• Аналогично 1 является нейтральным элементом относительно операции «*’», то есть для любого числа x выражения x * 1 и 1 * x равны x. Следовательно, произведение пус­того множес­тва чисел равно 1.
•• В реляционной алгебре TABLE_DEE – нейтральный элемент относительно операции JOIN, то есть соединение любого от­но­ше­ния r
с TABLE_DEE равно самому от­но­ше­нию r (см. ниже). Следовательно,
соединение пус­того множес­тва от­но­ше­ний равно TABLE_DEE.
Не тревожьтесь, если сейчас вам трудно переварить эту идею. Но если
вы впоследствии станете перечитывать этот раздел, попытайтесь убедить себя в том, что r JOIN TABLE_DEE и TABLE_DEE JOIN r дейст­
вительно совпадают с r. Может быть, будет проще, если заметить, что
рассматриваемые соединения на самом деле являются декартовыми
произведениями (правильно?).

Явные операторы JOIN в SQL
В SQL ключевое слово JOIN применяется для обозначения различных
операций соединения (хотя их всегда можно записать и без него). Слегка упрощая, можно сказать, что имеются следующие возможности, которые я пронумеровал для удобства ссылок впоследствии (здесь t1 и t2 –
таб­лицы, bx – булево выражение, а C1, C2, ..., Cn – столбцы, общие для
t1 и t2):
1. t1 NATURAL JOIN t2
2. t1 JOIN t2 ON bx
3. t1 JOIN t2 USING ( C1 , C2 , ... , Cn )
4. t1 CROSS JOIN t2
Я ненадолго задержусь на этих четырех случаях, потому что между
ними есть тонкие различия, которые трудно запомнить.

154

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

1. Случай 1 как раз и был рассмотрен выше.
2. Случай 2 логичес­ки эквивалентен такой формулировке:
( SELECT * FROM t1 , t2 WHERE bx )

3. Случай 3 логичес­ки эквивалентен случаю 2, в котором bx принимает вид
t1.C1 = t2.C1 AND t1.C2 = t2.C2 AND ... AND t1.Cn = t2.Cn

с тем отличием, что C1, C2, ..., Cn встречаются в результате только
один раз, а не дважды, а порядок столбцов в заголовке результата
в общем случае иной: сначала идут столбцы C1, C2, ..., Cn (в этом
порядке), затем остальные столбцы t1 в том порядке, в котором они
встречаются в t1, а затем остальные столбцы t2 в том порядке, в котором они встречаются в t2. (Теперь начинаете понимать, сколько
мороки с этим упорядочением слева направо?)
4. Наконец, случай 4 логичес­ки эквивалентен такой формулировке:
( SELECT * FROM t1 , t2 )

Рекомендации:
1. При записи соединений предпочитайте случай 1 (NATURAL JOIN)
всем остальным (но убедитесь, что столбцы с одинаковыми именами
имеют одинаковые типы). Отмечу, что запись с NATURAL JOIN зачастую будет самой краткой, если следовать и другим рекомендациям, предлагаемым в этой книге.
2. Избегайте случая 2 (JOIN ON), поскольку он гарантированно порождает результат с повторяющимися именами столбцов (кроме случая,
когда таб­лицы t1 и t2 изначально не имеют общих столбцов). Но если
вам позарез нужно воспользоваться именно случаем 2, – а так бывает, например, когда требуется сформулировать запрос с оператором
«больше»1, – то не забывайте о переименовании, например:
SELECT TEMP.*
FROM ( S JOIN P ON S.CITY > P.CITY ) AS TEMP
( SNO , SNAME , STATUS , SCITY ,
PNO , PNAME , COLOR , WEIGHT , PCITY )

3. В случае 3 требуется, чтобы столбцы с одинаковыми именами имели одинаковые типы.
4. В случае 4 следите за тем, чтобы не было общих имен столбцов.
Во всех четырех случаях операнды t1 и t2 задаются с помощью конструкции, которая в SQL называется ссылкой на таб­лицу. Пусть tr –
такая ссылка. Если таб­личное выражение в tr представляет собой подзапрос, то tr должна также включать фразу AS, даже если «корреляци1

Соединение с оператором «больше» – частный случай так называемого
θ-соединения, о котором мы будем говорить ниже в этой главе.

155

Объединение, пересечение и разность

онное имя», определяемое этой фразой, больше нигде не упоминается
(дополнительную информацию см. в главе 12). Например:
( SELECT SNO , CITY FROM S ) AS TEMP1
NATURAL JOIN
( SELECT PNO , CITY FROM P ) AS TEMP2

И последнее. Имейте в виду, что явное обращение к JOIN не может
встречаться в SQL ни (а) в качестве «независимого» таб­личного выражения (то есть находящегося на самом внешнем уровне вложенности),
ни (б) в качестве таб­личного выражения в скобках, которое составляет подзапрос.

Объединение, пересечение и разность
Операции объединения, пересечения и разности (UNION, INTERSECT
и MINUS в Tutorial D; UNION, INTERSECT и EXCEPT в SQL) устроены
одинаково. Я начну с объединения.

Объединение
Определение: Пусть от­но­ше­ния r1 и r2 имеют одинаковый
тип; тогда их объединением r1 UNION r2 называется от­но­
ше­ние такого же типа, тело которого состоит из всех кортежей, встречающихся в r1, в r2 или в обоих.
Например (во всех примерах из этого раздела я предполагаю, что в от­но­
ше­нии «детали» есть дополнительный атрибут STATUS типа INTEGER):
P { STATUS , CITY } UNION
S { CITY , STATUS }

|
|
|
|
|

SELECT
FROM
UNION
SELECT
FROM

STATUS , CITY
P
CORRESPONDING
CITY , STATUS
S

Как и в случае проекции, имеет смысл явно подчеркнуть, что из объединения «устраняются дубликаты». Отметим, что для достижения этого эффекта в SQL необязательно указывать DISTINCT; хотя UNION предлагает те же два варианта, что и SELECT (DISTINCT и ALL), по умолчанию в данном случае подразумевается DISTINCT, а не ALL (в главе 4
было сказано, что в случае SELECT дело обстоит «с точностью до на­
оборот»). Результирующий заголовок содержит атрибуты, или столбцы,
STATUS и CITY (в SQL именно в таком порядке). Отмечу, что спецификатор CORRESPONDING в SQL позволяет игнорировать тот факт, что в таб­
лицах-операндах столбцы могут быть расположены в другом порядке.
Рекомендации:
•• Следите за тем, чтобы соответственные столбцы имели одинаковые
имена и типы.

156

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

•• Всюду, где возможно, включайте спецификатор CORRESPONDING.1
Если это невозможно (например, потому что продукт не поддерживает этот спецификатор), то хотя бы располагайте столбцы строго
друг под другом, как показано ниже:
SELECT STATUS , CITY FROM P
UNION
SELECT STATUS , CITY FROM S /* обратите внимание на изменение */
/* порядка */

•• Включайте опцию «BY (список имен столбцов)» в спецификатор COR­
RESPONDING, только если она не изменяет семантики выражения
(например, в данном примере включение BY (STATUS,CITY) ничего
не изменило бы);
•• Никогда не указывайте ALL. Примечание: Обычная причина для
задания ALL в операторе UNION состоит не в том, что пользователь
хочет видеть строки-дубликаты. Напротив, он точно знает, что никаких дубликатов во входных данных нет, то есть объединение дизъюнктное, и просто пытается избавить сис­тему от лишней работы по
устранению дубликатов, которых заведомо не существует. Другим
словами, все упирается в производительность. См. обсуждение этой
тематики в разделе «Как избежать дубликатов в SQL» главы 4.
Язык Tutorial D поддерживает также операцию «дизъюнктного объединения» (D_UNION) – вариант объединения, при котором требуется,
чтобы операнды не содержали одинаковых кортежей. Например:
S { CITY } D_UNION P { CITY }

На наших тестовых данных это выражение привело бы к ошибке во
время выполнения, поскольку «города поставщиков» и «города деталей» не дизъюнктны. В SQL нет прямого аналога оператору D_UNION.
Кроме того, Tutorial D поддерживает n-местные варианты операторов
UNION и D_UNION. Детали я здесь опущу.

Пересечение
Определение: Пусть от­но­ше­ния r1 и r2 имеют одинаковый
тип; тогда их пересечением r1 INTERSECT r2 называется
от­но­ше­ние такого же типа, тело которого состоит из всех
кортежей, встречающихся одновременно в r1 и r2.
Например:
P { STATUS , CITY } INTERSECT
S { CITY , STATUS }

1

|
|
|

SELECT
STATUS , CITY
FROM
P
INTERSECT CORRESPONDING

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

157

Какие операторы являются примитивными?
|
|

SELECT
FROM

CITY , STATUS
S

Все комментарии и рекомендации, приведенные в разделе «Объединение», можно дословно повторить для пересечения. Примечание: Как
мы уже видели, пересечение – всего лишь частный случай соединения. Но и Tutorial D, и SQL поддерживают эту операцию, пус­ть даже
по чисто психологичес­ким причинам. В сноске выше упоминалось, что
Tutorial D поддерживает также n-местный вариант, но детали я опущу.

Разность
Определение: Пусть от­но­ше­ния r1 и r2 имеют одинаковый
тип; тогда их разностью r1 MINUS r2 называется от­но­ше­
ние такого же типа, тело которого состоит из всех кортежей, встречающихся в r1, но не встречающихся в r2.
Например:
P { STATUS , CITY } MINUS
S { CITY , STATUS }

|
|
|
|
|

SELECT
FROM
EXCEPT
SELECT
FROM

STATUS , CITY
P
CORRESPONDING
CITY , STATUS
S

Все комментарии и рекомендации, приведенные в разделе «Объединение», можно дословно повторить для разности.

Какие операторы являются примитивными?
Вот и все операторы, которые я хотел рассмотреть в этой главе. Однако выше я оговорился, что не все они примитивные – некоторые можно определить через другие. Один из возможных примитивных наборов
включает ограничение, проекцию, соединение (или произведение), объединение и разность. Примечание: Возможно, вас удивило отсутствие
в этом списке переименования. Но на самом деле переименование не является примитивом, хотя на данный момент еще рано объяснять, почему (см. упражнение 7.3 в главе 7). Тем не менее этот пример показывает,
что между «примитивным» и «полезным» есть разница! Я определенно
не хотел бы остаться без такого полезного оператора переименования,
пус­ть даже он не является примитивным.

Пошаговое конструирование выражений
Рассмотрим следующее выражение языка Tutorial D (соответствующее
запросу «Получить пары номеров поставщиков, находящихся в одном
городе»):
( ( ( S RENAME ( SNO AS SA ) ) { SA , CITY } JOIN

158

Глава 6. SQL и реляционная алгебра I: оригинальные операторы
( S RENAME ( SNO AS SB ) ) { SB , CITY } )
WHERE SA < SB ) { SA , SB }

Результат содержит два атрибута, SA и SB (отметим, что было бы достаточно одного переименования, я сделал два просто для симметрии).
У условия SA < SB двоякая цель:
•• Исключить пары номеров поставщиков вида (a,a);
•• Гарантировать, что результат не будет содержать одновременно пары
(a,b) и (b,a).
А теперь я приведу другую формулировку этого запроса, чтобы показать, как имеющаяся в языке Tutorial D конструкция WITH позволяет упрощать выражения, которые без нее могли бы оказаться довольно
сложными:
WITH ( S RENAME ( SNO
( S RENAME ( SNO
R1 JOIN R2 AS R3
R3 WHERE SA < SB
R4 { SA, SB }

AS SA ) ) { SA , CITY } AS R1 ,
AS SB ) ) { SB , CITY } AS R2 ,
,
AS R4 :

Как видно из примера, фраза WITH в Tutorial D состоит из ключевого
слова WITH, за которым следует список спецификаций вида expression
AS name, а после списка ставится двоеточие. Для каждой спецификации «expression AS name» вычисляется выражение expression, и результат присваивается (концептуально) временной пе­ре­мен­ной с именем name. Отметим, что любая спецификация в списке может ссылаться на имена, введенные ранее встретившимися спецификациями из
того же списка. Отметим также, что WITH не является оператором реляционной алгебры, это просто способ упростить запись сложных выражений (особенно с общими подвыражениями). Я часто буду пользоваться этой конструкций в последующих главах.
SQL также поддерживает конструкцию WITH со следующими отличиями:
•• Операнды записываются в противоположном порядке: WITH name
AS expression, …, name AS expression.
•• Двоеточие-разделитель не используется.
•• В Tutorial D WITH может использоваться с выражениями любого
вида, а в SQL – только с таб­личными выражениями.
Кроме того, в SQL за частью name спецификации «name AS expression»
может следовать необязательный список имен столбцов в скобках (как
в определении пе­ре­мен­ной кортежа (range variable), см. главу 12), но
если вы будете придерживаться прочих рекомендаций из этой книги,
то этот список вам никогда не понадобится.
Вот как выглядит этот пример на SQL:

В чем смысл реляционных выражений?
WITH T1 AS ( SELECT
FROM
T2 AS ( SELECT
FROM
T3 AS ( SELECT
FROM
T4 AS ( SELECT
FROM
WHERE
SELECT SA , SB
FROM T4

159

SNO AS SA , CITY
S ) ,
SNO AS SB , CITY
S ) ,
*
T1 NATURAL JOIN T2 ) ,
*
T3
SA < SB )

В чем смысл реляционных выражений?
Вспомните, о чем мы говорили в предыдущей главе: каждой пе­ре­мен­
ной-от­но­ше­нию соответствует некоторый предикат пе­ре­мен­ной-от­но­
ше­ния, который и составляет смысл этой пе­ре­мен­ной-от­но­ше­ния. Например, предикат пе­ре­мен­ной-от­но­ше­ния S звучит так:
Поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY.
Но я не упомянул, что это понятие естественным образом обобщается на произвольные реляционные выражения. Рассмотрим, к примеру,
проекцию поставщиков на все атрибуты, кроме CITY:
S { SNO , SNAME , STATUS }

Это выражение обозначает от­но­ше­ние, содержащее все кортежи вида
TUPLE { SNO sno , SNAME sn , STATUS st }

такие, что в текущий момент времени в пе­ре­мен­ной-от­но­ше­нии S существует кортеж вида
TUPLE { SNO sno , SNAME sn , STATUS st , CITY sc }

для некоторого значения sc атрибута CITY. Иначе говоря, результат
представляет экстенцию следующего предиката в текущий момент времени (см. главу 5, если вы забыли, что такое экстенция предиката):
Существует такой город CITY, что поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY.
Этот предикат, следовательно, и представляет смысл реляционного
выражения (проекции) S{SNO,SNAME,STATUS}. Отметим, что у него
всего три параметра, и соответствующее от­но­ше­ние имеет всего три
атрибута; CITY – это не параметр предиката, а то, что в логике называют «связаннойпе­ре­мен­ной» из-за того, что она «квантифицирована»
фразой Существует такой город (о связанных пе­ре­мен­ных и кванто-

160

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

рах см. главу 10).1 Примечание: быть может, мысль о том, что предикат
имеет всего три, а не четыре параметра, станет более понятной, если
заметить, что этот предикат логичес­ки эквивалентен такому:
Поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в некотором городе (а в каком,
мы не знаем).
Аналогичное замечание справедливо для любого реляционного выражения. Точнее: с любым реляционным выражением rx ассоциирован
некий смысл, или предикат, причем предикат для rx можно определить, зная предикаты всех пе­ре­мен­ных-от­но­ше­ний, встречающихся
в выражении, и семантику примененных к ним реляционных операций. В качестве упражнения предлагаю вам вернуться к некоторым реляционным (или записанным на SQL) выражениям в этой главе и определить, как выглядит соответствующий предикат в каждом случае.

Вычисление таб­личных выражений в SQL
Помимо естественного соединения, Кодд с самого начала определил
оператор θ‑соединения, где θ может обозначать любой скалярный оператор сравнения ( «=», «≠», « y AND y > 3

(которое могло бы быть частью запроса) эквивалентно (и, следовательно, может быть трансформировано) следующему:
x > y AND y > 3 AND x > 3

Эквивалентность основывается на том факте, что оператор сравнения «>» транзитивен. Отметим, что эту трансформацию заведомо

170

Глава 6. SQL и реляционная алгебра I: оригинальные операторы

имеет смысл выполнять, если x и y взяты из разных от­но­ше­ний, потому что тогда сис­тема может произвести дополнительное ограничение (x > 3) перед тем, как приступать к соединению с оператором
«больше», которое предполагается условием x > y. В тексте главы мы
видели, что раннее ограничение – это хорошо; когда сис­тема может
вывести дополнительные «ранние» ограничения, это тоже неплохо.
Знаете ли вы какие-нибудь продукты на основе SQL, в которых такая оптимизация дейст­вительно выполняется?
Упражнение 6.14. Рассмотрим следующее выражение языка Tutorial D:
WITH ( P WHERE COLOR = ‘Purple’ ) AS PP ,
( SP RENAME ( SNO AS X ) ) AS T :
S WHERE ( T WHERE X = SNO ) { PNO } ⊇ PP { PNO }

Что оно означает? Покажите, какой результат получается для наших тестовых данных. Согласуется ли этот результат с вашим пониманием смысла выражения? Обоснуйте свой ответ.
Упражнение 6.15. В SQL не существует прямого аналога оператора D_
UNION. Как лучше всего эмулировать этот оператор в SQL?
Упражнение 6.16. Что вы понимаете под термином соединяемые? Как
можно было бы обобщить это понятие на случай произвольного количества от­но­ше­ний (а не только двух – этот случай обсуждался
в тексте главы)?
Упражнение 6.17. Какое именно свойство открывает возможность определить n-местные варианты операторов JOIN и UNION (и D_UNION)?
Есть ли у него аналог в SQL? Почему не имеет смысла n-местный вариант оператора MINUS?
Упражнение 6.18. Ранее я уже говорил, что TABLE_DEE означает TRUE,
а TABLE_DUM – FALSE. Обоснуйте и/или развейте эти утверждения.

7
Глава
.

SQL и реляционная алгебра II:
дополнительные операции

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

Полусоединение и полуразность
Соединение – один из наиболее известных реляционных операторов.
Но на практике часто бывает, что в тех запросах, где вообще необходимо соединение, возникает потребность в обобщенной форме этого оператора, которая называется полусоединением (возможно, вы никогда не
слышали о полусоединении, но это достаточно важный оператор).
Определение: Полусоединение от­но­ше­ний r1 и r2 (в таком
порядке), r1 MATCHING r2, эквивалентно операции (r1
JOIN r2){A,B,...,C}, где A, B, ..., C – все атрибуты r1.
Иначе говоря, r1 MATCHING r2 – это результат соединения r1 и r2, спроецированный на атрибуты r1. Рассмот­рим пример («Получить всех поставщиков, которые в настоящий момент поставляют хотя бы одну деталь»):
S MATCHING SP

|
|
|

SELECT S.* FROM S
WHERE SNO IN
( SELECT SNO FROM SP )

172

Глава 7. SQL и реляционная алгебра II: дополнительные операции

Заголовок результата такой же, как у S. Отметим, что выражения r1
MATCHING r2 и r2 MATCHING r1 в общем случае не эквивалентны. Отметим также, что можно было бы заменить ключевое слово IN в SQL словом MATCH; однако любопытно, что заменить NOT IN на NOT MATCH
в операторе полуразности (см. ниже) не получится, потому что в SQL нет
оператора «NOT MATCH».
Теперь обратимся к полуразности. Если полусоединение в каком-то от­
но­ше­нии важнее соединения, то к полуразности это замечание относится даже в большей мере – на практике в большинстве запросов, где используется разность, на самом деле необходима полуразность.
Определение: Полуразность между от­но­ше­ниями r1 и r2 (в таком
порядке), r1 NOT MATCHING r2, эквивалентна операции r1 MINUS
(r1 MATCHING r2).
Вот пример («Получить всех поставщиков, который в настоящий момент не поставляют ни одной детали»):
S NOT MATCHING SP

|
|
|

SELECT S.* FROM S
WHERE SNO NOT IN
( SELECT SNO FROM SP )

Опять-таки заголовок результата такой же, как у S. Примечание: Если
от­но­ше­ния r1 и r2 одного типа, то r1 NOT MATCHING r2 вырождается в r1 MINUS r2; другими словами, в реляционной теории разность
(MINUS) – это частный случай полуразности. Однако соединение не является частным случаем полусоединения, это совершенно разные операторы, хотя неформально можно сказать, что некоторые соединения
являются полусоединениями, а некоторые полусоединения – соединениями. См. упражнение 7.19 в конце главы.

Расширение
Возможно, вы обратили внимание, что в описанной до сих пор алгебре
нет традиционных средств вычислений. А в SQL они есть; например,
никто не мешает написать запрос такого вида: SELECT A + B AS C ... .
Однако, написав знак «+», мы сразу же вышли за рамки первоначально определенной алгебры. Поэтому, чтобы получить такого рода функциональность, нам необходимо что-то добавить в алгебру, и это «что-то»
как раз и есть оператор EXTEND. Предположим, например, что веса деталей (в от­но­ше­нии P) выражены в фунтах, и что мы хотим получить
их в граммах. Поскольку в фунте 454 грамма, то можно написать такой запрос:
EXTEND P
ADD ( WEIGHT * 454 AS GMWT )

|
|
|

SELECT P.* ,
WEIGHT * 454 AS GMWT
FROM P

173

Расширение

Для наших тестовых данных получится такой результат:
PNO

PNAME COLOR

P1
P2
P3
P4
P5
P6

Nut
Bolt
Screw
Screw
Cam
Cog

Red
Green
Blue
Red
Blue
Red

WEIGHT
12.0
17.0
17.0
14.0
12.0
19.0

CITY

GMWT

London
Paris
Oslo
London
Paris
London

5448.0
7718.0
7718.0
6356.0
5448.0
8626.0

Важно: Пе­ре­мен­ная-от­но­ше­ние P в базе данных не изменяется!
EXTEND – не аналог SQL-предложения ALTER TABLE; выражение
EXTEND – это всего лишь выражение, и, как всякое выражение, служит для обозначения некоторого значения.
Продолжая этот пример, рассмот­рим запрос «Получить номер детали
и вес в граммах для тех деталей, для которых вес в граммах больше
7000 граммов»:
( ( EXTEND P ADD
( WEIGHT * 454 AS GMWT ) )
WHERE GMWT > 7000.0 )
{ PNO , GMWT }

|
|
|
|

SELECT PNO ,
WEIGHT * 454 AS GMWT
FROM P
WHERE WEIGHT * 454 > 7000.0

Как видите, в SQL-версии выражение WEIGHT * 454 встречается дважды, и мы можем лишь надеяться, что реализация достаточно изощренна, чтобы понять, что это выражение нужно вычислять не два,
а один раз для каждого кортежа (или строки). В версии же для языка
Tutorial D выражение встречается только один раз.
Проблема, которую иллюстрирует этот пример, заключается в том, что
конструкция SELECT - FROM - WHERE в SQL слишком жесткая. В дейст­
вительности нам здесь необходимо – из формулировки на Tutorial D это
совершенно очевидно – выполнить ограничение расширения; в терминах
SQL нужно применить оператор WHERE к результату SELECT. Но шаблон SELECT - FROM - WHERE вынуждает применять фразу WHERE
к результату фразы FROM, а не фразы SELECT. Выразим ту же мысль
по-другому: во многих от­но­ше­ниях весь смысл алгебры (благодаря замкнутости) состоит в том, чтобы реляционные операции можно было
произвольным образом сочетать и вкладывать друг в друга. Но шаблон
SELECT - FROM - WHERE в SQL означает по сути дела, что выразить
можно только те запросы, в которых сначала выполняется произведение,
потом ограничение, а потом некоторая комбинация проекции и/или расширения, и/или разгруппирования, и/или переименования. Однако существует много запросов, не укладывающихся в такую схему.
Кстати, возможно, вам интересно, почему я не записал SQL-версию
в таком виде:

174

Глава 7. SQL и реляционная алгебра II: дополнительные операции
SELECT PNO , WEIGHT * 454 AS GMWT
FROM P
WHERE GMWT > 7000.0

(изменена последняя строчка). Причина в том, что GMWT – имя столбца
в конечном результате; в таб­лице P такого столбца нет, поэтому фраза
WHERE не имеет смысла и вызовет ошибку на этапе компиляции.
На самом деле стандарт SQL допускает формулировку рассматриваемого запроса в виде, чуть более близком к Tutorial D (ниже для ясности
я привожу полные имена):
SELECT TEMP.PNO , TEMP.GMWT
FROM ( SELECT P.PNO , ( P.WEIGHT * 454 ) AS GMWT
FROM P ) AS TEMP
WHERE TEMP.GMWT > 7000.0

Но не во всех SQL-сис­темах разрешается употреблять вложенные подзапросы во фразе FROM. Отмечу также, что такая формулировка неизбежно ведет к необходимости ссылаться на некоторые пе­ре­мен­ные (в
данном случае TEMP) еще до того, как они определены, а в реальных
SQL-запросах, быть может, задолго до точки определения.
Я завершаю этот раздел формальным определением:
Определение: Пусть r – от­но­ше­ние. Расширением EXTEND
r ADD (exp AS X) называется от­но­ше­ние, для которого (а)
заголовок совпадает с заголовком r, дополненным атрибутом X, и (б) тело состоит из множес­тва всех кортежей t, таких что t есть кортеж r, дополненный значением атрибута X, которое получается вычислением выражения exp для
этого кортежа r. В от­но­ше­нии r не должно быть атрибута
с именем X и exp не должно ссылаться на X. Отметим, что
кардинальность результата равна кардинальности r, а степень равна степени r плюс 1. Типом X в результате является тип exp.

От­но­ше­ния-образы
От­но­ше­ние-образ – это, неформально говоря, «образ» некоторого кортежа внутри некоторого от­но­ше­ния (обычно кортеж принадлежит
какому-то другому от­но­ше­нию). Например, для базы данных о поставщиках и деталях следующее от­но­ше­ние является образом кортежа поставщика S4 (из от­но­ше­ния «поставщики») в от­но­ше­нии «поставки»:
PNO

QTY

P2
P4
P5

200
300
400

От­но­ше­ния-образы

175

Ясно, что это от­но­ше­ние-образ можно получить с помощью такого выражения на языке Tutorial D:
( SP WHERE SNO = ‘S4’ ) { ALL BUT SNO }

Приведем формальное определение от­но­ше­ния-образа в общем случае.
Определение: Пусть r1 и r2 – соединяемые от­но­ше­ния (то
есть одноименные атрибуты имеют один и тот же тип);
пус­ть t1 – кортеж r1, а t2 – кортеж r2, для которого значения общих с кортежем t1 атрибутов такие же, как в кортеже t1. Пусть от­но­ше­ние r3 – это ограничение r2, которое содержит все такие и только такие кортежи t2, и пус­ть
r4 – проекция r3 на все атрибуты, кроме общих. Тогда r4
называется от­но­ше­нием-образом (относительно r2), соответствующим t1.
Следующий пример иллюстрирует полезность от­но­ше­ний-образов:
S WHERE ( !!SP ) { PNO } = P { PNO }

Отметим попутно, что здесь булево выражение во фразе WHERE является реляционным сравнением.
Объяснение:
•• Во-первых, в качестве r1 и r2 из определения выше выступают от­
но­ше­ния «поставщики» и «поставки» соответственно (под «от­но­ше­
нием “поставщики”» я понимаю текущее значение пе­ре­мен­ной-от­но­
ше­ния S, и аналогично для «от­но­ше­ния “поставки”»).
•• Далее, мы можем представить себе, что булево выражение во фразе
WHERE вычисляется для каждого кортежа t1 из r1 (то есть для каждого кортежа в от­но­ше­нии «поставщики») по очереди.
•• Рассмот­рим один такой кортеж, скажем, для поставщика Sx. Тогда для этого кортежа выражение !!SP обозначает соответствующее
от­но­ше­ние-образ r4 внутри r2; иными словами, это множес­тво пар
(PNO,QTY) от­но­ше­ния SP для деталей, поставляемых поставщиком
Sx. Выражение !!SP представляет собой ссылку на от­но­ше­ние-образ.
•• Выражение (!!SP){PNO}, то есть проекция от­но­ше­ния-образа на
{PNO}, обозначает, следовательно, множес­тво номеров деталей для
деталей, поставляемых поставщиком Sx.
•• Таким образом, все выражение в целом (то есть S WHERE ...) обозначает поставщиков из S, для которых это множес­тво номеров деталей
равно множес­тву всех номеров деталей в проекции P на {PNO}. Иначе говоря, оно представляет запрос «Получить поставщиков, которые поставляют все детали» (допуская некоторую вольность формулировки).

176

Глава 7. SQL и реляционная алгебра II: дополнительные операции

Примечание
Поскольку понятие от­но­ше­ния-образа определяется в терминах некоторого заданного кортежа (в формальном определении он назван t1), то понятно, что ссылка на от­но­ше­ние-образ может встречаться не во всех контекстах,
где допус­тимы общие реляционные выражения, а только в некоторых, точнее
в тех, где кортеж t1 имеет смысл. Примером такого контекста могут служить
фразы WHERE, как показывает приведенный выше пример. Другой пример мы
увидим в разделе «Еще об от­но­ше­ниях-образах».

Отступление
В SQL нет прямой поддержки от­но­ше­ний-образов как таковых. Но для интересующихся покажу аналог приведенного выше выражения Tutorial D (я включил его только для справки и не собираюсь вдаваться в детали, замечу лишь,
что, очевидно (?), есть много способов улучшить его):
SELECT
FROM
WHERE
(

*
S
NOT EXISTS
SELECT PNO
FROM SP
WHERE SP.SNO = S.SNO
EXCEPT
SELECT PNO
FROM P )
AND
NOT EXISTS
( SELECT PNO
FROM P
EXCEPT
SELECT PNO
FROM P
WHERE SP.SNO = S.SNO )

Но вернемся к от­но­ше­ниям-образам. Стоит отметить, что оператор «!!»
можно определить в терминах MATCHING. Например, для рассмот­
ренного выше примера выражение
S WHERE ( !!SP ) { PNO } = P { PNO }

логичес­ки эквивалентно такому выражению:
S WHERE ( SP MATCHING RELATION { TUPLE { SNO SNO } } ) { PNO } = P { PNO }

Объяснение: Снова рассмот­рим некоторый кортеж S, скажем, для поставщика Sx. Для этого кортежа выражение TUPLE{SNO SNO} – вызов
селектора кортежа – обозначает кортеж, содержащий только значение
атрибута SNO кортежа Sx (первое вхождение SNO – имя атрибута, второе – значение атрибута с этим именем в кортеже Sx пе­ре­мен­ной-от­но­
ше­ния S). Таким образом, выражение
RELATION { TUPLE { SNO SNO } }

177

Деление

представляет собой вызов селектора от­но­ше­ния и обозначает от­но­ше­
ние, содержащее только этот кортеж. Следовательно, выражение
SP MATCHING RELATION { TUPLE { SNO SNO } }

обозначает некоторое ограничение SP, а именно ограничение, которое
содержит только те кортежи от­но­ше­ния «поставки», в которых значение SNO такое же, как в кортеже поставщика Sx из от­но­ше­ния «поставщики». Что и требовалось доказать.
Предположим теперь, что база данных о поставщиках несколько изменена (одновременно обобщена и упрощена) и выглядит следующим образом (в общих чертах):
S
SP
PJ
J

{
{
{
{

SNO }
/* поставщики
SNO, PNO } /* поставщик поставляет деталь
PNO, JNO } /* деталь используется в проекте
JNO }
/* проекты

*/
*/
*/
*/

Здесь пе­ре­мен­ная-от­но­ше­ние J представляет проекты (JNO – номер
проекта), а пе­ре­мен­ная-от­но­ше­ние PJ описывает, какие детали в каких проектах используются. Рассмот­рим запрос «Получить все пары
(sno,jno), такие что sno – значение SNO, входящее в данный момент времени в пе­ре­мен­ную-от­но­ше­ние S, jno – значение JNO, входящее в данный момент времени в пе­ре­мен­ную-от­но­ше­ние J, и поставщик sno поставляет все детали, использующиеся в проекте jno». Это сложный запрос! Но с применением от­но­ше­ний-образов он записывается почти тривиально:
( S JOIN J ) WHERE !!PJ ⊆ !!SP

Вернемся к обычной базе данных о поставщиках и деталях и рассмот­
рим еще один запрос («Удалить поставки от поставщиков, находящихся в Лондоне», и на этот раз я приведу также аналог на SQL):
DELETE SP WHERE IS_NOT_EMPTY
( !!(S WHERE
CITY = ‘London’) ) ;

|
|
|
|

DELETE FROM SP
WHERE SNO IN
( SELECT SNO FROM S
WHERE CITY = ‘London’ ) ;

Для данной поставки заданное от­но­ше­ние-образ !!(S WHERE ...) либо
пус­то, либо содержит ровно один кортеж.

Деление
Я включил в эту главу обсуждение операции деления только для того,
чтобы показать, почему (быть может, вразрез с общепринятым мнением) я не считаю ее очень важной; я даже думаю, что от нее следовало бы
отказаться. Если хотите, можете пропус­тить этот раздел.
У меня есть несколько причин (по меньшей мере, три) желать исключения операции деления. Во-первых, любой запрос, который можно

178

Глава 7. SQL и реляционная алгебра II: дополнительные операции

выразить в терминах деления, можно сформулировать, причем гораздо проще, в терминах от­но­ше­ний-образов, что я скоро и продемонстрирую. Во-вторых, существует по меньшей мере семь различных операторов деления, то есть по меньшей мере семь различных операторов, которые претендуют на название «деление», и я, безусловно, не хочу рассказывать о каждом. Вместо этого я ограничусь базовым, самым простым определением.
Определение: Пусть от­но­ше­ния r1 и r2 таковы, что заголовок {Y} от­но­ше­ния r2 является некоторым подмножес­твом
заголовка r1, и множес­тво {X} состоит из остальных атрибутов r1. Тогда результатом деления r1 на r2, r1 DIVIDEBY
r2,1 называется следующее от­но­ше­ние:
r1 { X } NOT MATCHING ( ( r1 { X } JOIN r2 ) NOT MATCHING r1 )

Например, выражение
SP { SNO , PNO } DIVIDEBY P { PNO }

(на наших обычных тестовых данных) дает:
SNO
S1

Таким образом, неформально это выражение можно охарактеризовать
как представление запроса «Получить номера тех поставщиков, которые поставляют все детали» (чуть ниже я объясню, почему «неформально»). На практике, однако, нам обычно интересна вся информация
о поставщиках, а не только номера, и, следовательно, деление должно
сопровождаться соединением:
( SP { SNO , PNO } DIVIDEBY P { PNO } ) JOIN S

Но ведь мы уже знаем, как этот запрос сформулировать проще, воспользовавшись от­но­ше­ниями-образами:
S WHERE ( !!SP ) { PNO } = P { PNO }

Эта формулировка (а) более лаконична, (б) проще для понимания (по
крайней мере, мне так кажется) и (в) корректна. Разумеется, последний пункт важнее всего, и ниже я остановлюсь на нем подробнее. Но
сначала я хочу объяснить, почему эта операция называется делением.
А дело в том, что если r1 и r2 – от­но­ше­ния, не имеющие общих имен
атрибутов, и мы образуем произведение r1 TIMES r2, а затем разделим
его на r2, то получим снова r1.2 Другими словами, произведение и деление – в некотором смысле взаимно обратные операции.
1

Язык Tutorial D не поддерживает этот оператор напрямую, поэтому запись
r1 DIVIDEBY r2 в этом языке синтаксичес­ки недопус­тима.

2

При условии, что r2 не пус­то. А если пус­то, что произойдет?

179

Агрегатные операторы

Как я уже сказал, выражение
SP { SNO , PNO } DIVIDEBY P { PNO }

можно неформально охарактеризовать как формулировку запроса «Получить номера тех поставщиков, которые поставляют все детали». На
самом деле, именно этот пример часто используют для объяснения
и обоснования необходимости оператора деления. Но, к сожалению, такая характеристика не вполне корректна. В дейст­вительности это выражение соответствует запросу «Получить номера поставщиков, которые поставляют хотя бы одну деталь и фактичес­ки поставляют все
детали».1 Иначе говоря, оператор деления не только сложен и недостаточно лаконичен, но еще и не решает ту задачу, для которой изначально задумывался.

Агрегатные операторы
В каком-то смысле этот раздел можно назвать отвлечением от темы, потому что обсуждаемые здесь операторы не реляционные, а скалярные –
они возвращают скаляр в качестве результата.2 Но я все же хочу сказать о них несколько слов, прежде чем вернусь в основной теме главы.
В реляционной модели агрегатным называется оператор, который порождает единственное значение из «агрегата» (то есть множес­тва или
мультимножес­тва) значений некоторого атрибута какого-то от­но­ше­ния.
Для COUNT(*), представляющего особый случай, таким «агрегатом»
является все от­но­ше­ние. Приведу два примера:
X := COUNT ( S ) ;

|
|

Y := COUNT ( S { STATUS } ) ; |
|
|

SELECT COUNT ( * ) AS X
FROM S
SELECT COUNT ( DISTINCT STATUS )
AS Y
FROM S

Сначала рассмот­рим предложения языка Tutorial D в левой части. Для
наших тестовых данных в первом предложении пе­ре­мен­ной X присва1

Если вы не понимаете, в чем тут логичес­кое различие, то давайте рассмот­
рим чуть измененный запрос «Получить поставщиков, которые поставляют все фиолетовые детали» (смысл, конечно, в том, что фиолетовых деталей в базе нет). Если фиолетовых деталей вообще не существует, то каждый
поставщик поставляет их все! – даже поставщик S5, которые не поставляет
никаких деталей и, следовательно, не представлен в пе­ре­мен­ной-от­но­ше­
нии SP, а потому не может быть возвращен аналогичным выражением DIVIDEBY. Если что-то все же осталось непонятно, подождите до главы 11,
где мы продолжим обсуждение этого примера.

2

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

180

Глава 7. SQL и реляционная алгебра II: дополнительные операции

ивается значение 5 (количество кортежей в текущем значении пе­ре­мен­
ной-от­но­ше­ния S), а во втором – пе­ре­мен­ной Y присваивается значение
3 (количество кортежей в проекции текущего значения пе­ре­мен­ной-от­
но­ше­ния S на множес­тво {STATUS}, то есть количество различных значений атрибута STATUS в текущем значении пе­ре­мен­ной-от­но­ше­ния).
В общем случае вызов агрегатного оператора в Tutorial D выглядит так:
( [, ] )

В качестве операции допус­тимы COUNT, SUM, AVG,
MAX, MIN, AND, OR и XOR (последние три применяются к агрегатам,
состоящим из булевых значений). В выражении в любом месте,
где допус­тим литерал, может встречаться ссылка на атрибут . Части не должно быть, если в роли выступает COUNT; в противном случае ее можно опускать, только если реляционное выражение обозначает от­но­ше­ние степени 1, и тогда предполагается, что состоит из ссылки на единственный атрибут этого от­но­ше­ния. Примеры:
1. SUM ( SP , QTY )
Это выражение обозначает сумму всех значений QTY в пе­ре­мен­нойот­но­ше­нии SP (на наших тестовых данных получится 3100).
2. SUM ( SP { QTY } )
Это сокращенная запись SUM(SP{QTY},QTY), которая обозначает
сумму всех различных значений QTY в пе­ре­мен­ной-от­но­ше­нии SP
(то есть 1000).
3. AVG ( SP , 3 * QTY )
Это выражение отвечает на вопрос, какова была бы средняя величина поставки, если бы количество деталей в каждой поставке увеличилось втрое (ответ 775). Более общо, выражение
agg ( rx , x )

(где x – некоторое выражение, более сложное, чем просто ) по существу является сокращенной записью для:
agg ( EXTEND rx ADD ( x AS y ) , y )

Теперь я возвращаюсь к SQL. Для удобства повторю приведенные выше
примеры:
X := COUNT ( S ) ;

|
|

Y := COUNT ( S { STATUS } ) ; |
|
|

SELECT COUNT ( * ) AS X
FROM S
SELECT COUNT ( DISTINCT STATUS )
AS Y
FROM S

Агрегатные операторы

181

Возможно, вам будет странно слышать от меня, что SQL в дейст­
вительности вообще не поддерживает агрегатные операторы! Я заявляю об этом, прекрасно понимая, что многие сочтут выражения справа
в точности вызовами агрегатных операторов в SQL.1 Но это не так. И я
готов объяснить. Как мы знаем, значения этих счетчиков равны соответственно 5 и 3. Но эти SQL-выражения не вычисляют сами счетчики,
как должны были бы делать настоящие агрегатные операторы, а вычисляют таб­лицы, содержащие значения счетчиков. Точнее, каждый
вызов порождает таб­лицу с одной строкой и одним столбцом, и единственным значением в этой строке будет счетчик:
X

Y

5

3

Как видите, выражения SELECT не являются вызовами агрегатных
операторов; в лучшем случае можно сказать, что они дают аппроксимацию таких вызовов. На самом деле агрегирование в SQL рассматривается как частный случай обобщения. Правда, я еще не касался обобщения в этой главе, но пока вы можете считать, что оно представляется в SQL выражением SELECT с фразой GROUP BY. Конечно, в приведенных выше SQL-выражениях фразы GROUP BY нет, но по определению они являются сокращенной записью таких выражений (и потому
все же являются частными случаями обобщения, как и утверждалось):
SELECT COUNT ( * ) AS X
FROM S
GROUP BY ( )
SELECT COUNT ( DISTINCT STATUS ) AS Y
FROM S
GROUP BY ( )

Примечание
На случай, если эти выражения вас смущают, объясню, что в SQL разрешается (а) заключать списки операндов GROUP BY в скобки и (б) включать фразы GROUP BY без операндов. Задание GROUP BY без операндов трактуется так,
будто этой фразы нет вовсе.

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

1

Можно сказать, с несколько большим основанием, что вызовы COUNT
в этих выражениях – это вызовы агрегатных операторов. Но суть в том, что
в SQL такой вызов не может выступать в роли «независимого» выражения;
он обязательно должен быть частью какого-то таб­личного выражения.

182

Глава 7. SQL и реляционная алгебра II: дополнительные операции

в SQL нередко приводят таб­лицу, являющуюся результатом «агрегирования» к одной строке, которую она содержит, или даже к тому единственному значению, которое имеется в этой строке. Две разных ошибки (по крайней мере, концептуальных) – а в результате странное «агрегирование» становится больше похоже на настоящее! Такое двойное
приведение типа происходит, в частности, когда выражение SELECT
заключено в скобки и образует скалярный подзапрос, как, например,
в следующих присваиваниях:
SET X = ( SELECT COUNT ( * ) FROM S ) ;
SET Y = ( SELECT COUNT ( DISTINCT STATUS ) FROM S ) ;

Но присваивание – далеко не единственный контекст, в котором возможно подобное приведение типа (см. главы 2 и 12).

Отступление
На самом деле, с агрегированием в духе SQL связана еще одна странность (я
поместил это замечание сюда, так как оно сюда логичес­ки относится, однако оно опирается на понимание идеи обобщения в SQL, поэтому сейчас можете его пропус­тить):
•• В общем случае выражение вида SELECT - FROM T - WHERE - GROUP BY - HAVING
порождает результат, содержащий по одной строке для каждой «группы» в G,
где G – «сгруппированная таб­лица», получающаяся путем применения фраз
WHERE, GROUP BY и HAVING к таб­лице T.
•• Отсутствие фраз WHERE и HAVING, характерное для типичного агрегирования
в SQL, эквивалентно заданию фраз WHERE TRUE и HAVING TRUE соответственно. Поэтому сейчас нам достаточно рассмот­реть только влияние фразы GROUP
BY на вычисление сгруппированной таб­лицы G.
•• Предположим, что таб­лица T состоит из nT строк. При объединении этих строк
в группы может получиться не более nT групп. Иными словами, сгруппированная таб­лица G состоит из nG групп, где nG ≤ nT, а окончательный результат применения фразы SELECT к G состоит из nG строк.
•• Теперь предположим, что nT равно нулю (то есть таб­лица T пус­та); тогда nG,
очевидно, также должно быть равно нулю (то есть таб­лица G и, стало быть, результат выражения SELECT тоже будут пус­ты).
•• Поэтому, в частности, выражение
SELECT COUNT ( * ) AS X
FROM S
GROUP BY ( )

которое, напомню, является полной записью SELECT COUNT(*) AS X FROM S, по
логике вещей, должно порождать результат, показанный слева, а не справа,
если таб­лица S пус­та.
X

X
0

Еще об от­но­ше­ниях-образах

183

Но на самом деле получается результат, показанный справа. Почему? Ответ:
это особый случай. Вот прямая цитата из стандарта: «Если не заданы столбцы, по которым производится группировка, то результатом фразы является сгруппированная таб­лица, содержащая T в качестве единственной группы». Иными словами, хотя группировка пус­той таб­лицы в SQL
дейст­вительно (как я и доказывал выше) порождает в общем случае пус­
тое множес­тво групп, однако случай, когда множес­тво столбцов группиров­
ки пус­то, считается особым; в этом случае порождается множес­тво, содержащее ровно одну группу, которая идентична пус­той таб­лице T. Таким образом, в нашем примере оператор COUNT применяется к пус­той группе и, значит,
«коррект­но» возвращает значение 0.
Возможно, вам кажется, что в этом несоответствии нет ничего ужасного; может быть, вы даже думаете, что правый результат чем-то «лучше» левого. Но
(и это очевидно) между тем и другим есть логичес­кое различие, а – снова цитируя Виттгенштейна, – «любое логичес­кое различие является существенным
различием». Такого рода логичес­кие ошибки попросту недопус­тимы в сис­
теме, которая, подобно реляционной сис­теме, должна покоиться на твердых
логичес­ких основаниях.

Еще об от­но­ше­ниях-образах
В этом разделе я хотел бы привести ряд примеров, демонстрирующих
полезность агрегатных операторов, рассмот­ренных в предыдущем разделе, в применении к от­но­ше­ниям-образам. Примечание: Во всех примерах в каком-то виде, пус­ть неявном, присутствует обобщение, но
пока я еще не готов обсуждать его детально (это будет темой следующих
двух разделов).
Пример 1. Получить поставщиков, для которых количество деталей
в поставке, просуммированное по всем поставкам данного поставщика, меньше 1000.
S WHERE SUM ( !!SP , QTY ) < 1000

Для любого поставщика выражение SUM(!!SP,QTY) обозначает в точности общее количество деталей, поставляемых этим поставщиком. Эквивалентная формулировка без использования от­но­ше­ния-образа такова:
S WHERE SUM ( SP MATCHING RELATION { TUPLE { SNO SNO } } , QTY ) < 1000

Ради интереса приведу «аналог» на SQL – я взял слово «аналог» в кавычки, потому что в этом примере есть подводный камень: показанное SQL-выражение не является точным эквивалентом выражений
Tutorial D выше (почему?):
SELECT S.*
FROM S , SP
WHERE S.SNO = SP.SNO

184

Глава 7. SQL и реляционная алгебра II: дополнительные операции
GROUP BY S.SNO , S.SNAME , S.STATUS , S.CITY
HAVING SUM ( SP.QTY ) < 1000

Примечание
Не могу не заметить мимоходом, что (как видно из примера выше) SQL допускает запись «S.*» во фразе SELECT, но не во фразе GROUP BY, где она имела бы
ничуть не меньший смысл.

Пример 2. Получить поставщиков, для которых существует меньше
трех поставок.
S WHERE COUNT ( !!SP ) < 3

Пример 3. Получить поставщиков, для которых максимальное поставленное количество менее чем в два раза превышает минимальное поставленное количество (в обоих случаях речь идет обо всех поставках
данного поставщика).
S WHERE MAX ( !!SP , QTY ) < 2 * MIN ( !!SP , QTY )

Пример 4. Получить поставки, для которых существует еще по меньшей мере две поставки с таким же количеством.
SP WHERE COUNT ( !!(SP RENAME ( SNO AS SN , PNO AS PN )) ) > 2

Признаю, что это очень искусственный пример, но смысл его в том, чтобы показать, что иногда при использовании от­но­ше­ний-образов возникает необходимость в переименовании атрибутов. В этом примере переименование необходимо, чтобы нужное нам от­но­ше­ние-образ, связанное с данным кортежем поставки, было определено только в терминах
атрибута QTY. Имена SN и PN выбраны произвольно.
Отмечу попутно, что этот пример иллюстрирует также «множественную» форму RENAME:
SP RENAME ( SNO AS SN , PNO AS PN )

Это выражение – сокращенная запись такого:
( SP RENAME ( SNO AS SN ) ) RENAME ( PNO AS PN )

Похожие сокращения определены и для разных других операторов,
в том числе EXTEND (пример будет приведен ниже).
Пример 5. Для всех поставщиков, для которых общее количество, просуммированное по всем поставкам данного поставщика, меньше 1000,
уменьшить значение статуса в два раза.
UPDATE S WHERE SUM ( !!SP , QTY ) < 1000 :
{ STATUS := 0.5 * STATUS } ;

185

Обобщение

Обобщение
Определение: Пусть от­но­ше­ния r1 и r2 таковы, что заголовок r2 совпадает с заголовком некоторой проекции r1, и пусть
A, B, ..., C – атрибуты r2. Тогда обобщением SUMMARIZE r1
PER (r2) ADD (summary AS X) называется от­но­ше­ние, для
которого (а) заголовком является заголовок r2, дополненный атрибутом X, и (б) тело состоит из множес­тва всех кортежей t таких, что t является кортежем r2, дополненным
значением x атрибута X. Это значение x подсчитывается
путем вычисления некоторого обобщающего выражения по
всем кортежам r1, которые имеют те же значения атрибутов A, B, ..., C, что и кортеж t. От­но­ше­ние r2 не должно содержать атрибута с именем X, а обобщающее выражение не
должно ссылаться на X. Отметим, что кардинальность результата равна кардинальности r2, а степень результата
равна степени r2 плюс единица. Типом X в этом случае будет тип выражения exp.
Вот пример (который я для последующих ссылок назову SX1 – «SUM­
MA­RIZE Example 1»):
SUMMARIZE SP PER ( S { SNO } ) ADD ( COUNT ( PNO ) AS PCT )

Для наших тестовых данных получается такой результат:
SNO
S1
S2
S3
S4
S5

PCT
6
2
1
3
0

Иными словами, результат содержит по одному кортежу для каждого
кортежа в от­но­ше­нии PER – то есть в этом примере по одному кортежу
для каждого из пяти номеров поставщиков, – дополненному соответствующим счетчиком.

Отступление
Обратите особое внимание, что синтаксичес­кая конструкция COUNT(PNO) –
я сознательно не называю ее выражением, потому что она таковым не является (по крайней мере, не в смысле языка Tutorial D), – в предыдущем вызове SUMMARIZE – это не вызов агрегатного оператора с именем COUNT. Агрегатный оператор принимает в качестве аргумента от­но­ше­ние, а аргумент
COUNT – атрибут; конечно, это атрибут некоторого от­но­ше­ния, но само от­но­
ше­ние определено лишь косвенно. На самом деле синтаксичес­кая конструкция COUNT(PNO) представляет собой особый случай – она не имеет никакого

186

Глава 7. SQL и реляционная алгебра II: дополнительные операции

смысла вне контекста подходящего вызова SUMMARIZE и не может встречаться вне такого контекста. Все это наводит на мысль, что операция SUMMARIZE
в каком-то смысле неполноценна и хорошо было бы заменить ее чем-то получше… См. раздел «Еще об обобщении» ниже.

Если от­но­ше­ние r2 не просто имеет такой же заголовок, как некоторая проекция от­но­ше­ния r1, а является такой проекцией, то обобщение
можно записать короче, заменив спецификацию PER спецификацией
BY, как в следующем примере («Пример SX2»):
SUMMARIZE SP BY { SNO } ADD ( COUNT ( PNO ) AS PCT )

Результат получается таким:
SNO
S1
S2
S3
S4

PCT
6
2
1
3

Как видите, результат отличается от предыдущего – он не содержит
кортежа для поставщика S5. Объясняется это тем, что BY {SNO} в этом
примере, по определению, является сокращением для PER (SP{SNO}) –
SP, потому что именно по SP мы и хотим произвести обобщение, – а проекция SP{SNO} не содержит кортежа для поставщика S5.
Пример SX2 можно записать на SQL следующим образом:
SELECT SNO , COUNT ( ALL PNO ) AS PCT
FROM SP
GROUP BY SNO

Мы видим, что обобщения – в противоположность «агрегированию» –
обычно формулируются на SQL посредством выражения SELECT с явной фразой GROUP BY (однако см. ниже!). Вот на какие мысли наводит
этот пример.
•• Можно считать, что такие выражения вычисляются в следующем
порядке. Сначала таб­лица, заданная фразой FROM, разбивается
на множес­тво непересекающихся «групп» – фактичес­ки таб­лиц, –
в соответствии со столбцами группировки во фразе GROUP BY. Затем формируются результирующие строки, по одной для каждой
группы: вычисляется заданное обобщающее выражение (или выражения) для этой группы и в конец дописываются другие элементы, заданные в списке SELECT. Примечание: В SQL аналогом термина обобщающее выражение служит «функция множес­тва» (set
function) – термин вдвойне неподходящий, потому что (а) аргументом такой функции является не множес­тво, а мультимножес­тво,
и (б) результатом тоже является не множес­тво.

187

Обобщение

•• В данном примере можно без опаски употреблять SELECT, а не
SELECT DISTINCT, так как (а) гарантируется, что результирующая
таб­лица содержит в точности одну строку для каждой группы (по
определению) и (б) каждая группа содержит по одному значению для
каждого столбца группировки (опять-таки по определению).
•• В данном примере при вызове COUNT спецификатор ALL можно
опус­тить, так как для функций множес­тв его наличие предполагается по умолчанию. (Фактичес­ки, в этом примере неважно, указан
ли спецификатор ALL или DISTINCT, потому что сочетание номера
поставщика и номера детали является ключом для таб­лицы SP).
•• Функция множес­тва COUNT(*) представляет собой особый случай –
она применяется не к значениям в некотором столбце (как, например, SUM), а к строкам таб­лицы. (В данном примере COUNT(PNO)
можно было бы заменить на COUNT(*), и результат не изменился бы.)
А теперь вернемся к примеру SX1. На SQL его можно записать следующим образом:
SELECT S.SNO , ( SELECT COUNT ( ALL PNO )
FROM SP
WHERE SP.SNO = S.SNO ) AS PCT
FROM S

Здесь важно отметить, что теперь результат содержит строку для поставщика S5, потому что, по определению, результат должен содержать
по одной строке для каждого номера поставщика, присутствующего
в таб­лице S. И, как легко видеть, эта формулировка отличается от примера SX2 – того, где поставщик S5 был пропущен, – тем, что отсутствует фраза GROUP BY и не производится никакая группировка (по крайней мере, явно).

Отступление
Кстати, читателей, не искушенных в SQL, тут поджидает некий подвох. Как видите, второй элемент списка SELECT в приведенном выше SQL-выражении – то
есть подвыражение (SELECT ... S.SNO) AS PCT – имеет вид подзапрос AS имя (и
сам подзапрос скалярный). Если бы точно такое же выражение встретилось
во фразе FROM, то спецификатор AS имя следовало бы понимать как определение пе­ре­мен­ной кортежа (range variable) (см. главу 10). Во фразе же SELECT
этот спецификатор интерпретируется как определение имени столбца результата. Отсюда следует, что следующая формулировка этого примера логичес­ки
не эквивалентна приведенной выше:
SELECT S.SNO , ( SELECT COUNT ( ALL PNO ) AS PCT
FROM SP
WHERE SP.SNO = S.SNO )
FROM S

При такой формулировке таб­лица t, которая возвращается в результате вычисления подзапроса, имеет столбец PCT. Для этой таб­лицы t производится

188

Глава 7. SQL и реляционная алгебра II: дополнительные операции

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

Но вернемся к основной теме обсуждения. Фактичес­ки, пример SX2
можно было бы записать и без использования GROUP BY следующим
образом:
SELECT DISTINCT SPX.SNO , ( SELECT COUNT ( ALL SPY.PNO )
FROM SP AS SPY
WHERE SPY.SNO = SPX.SNO ) AS PCT
FROM SP AS SPX

Как следует из этих примеров, фраза GROUP BY в SQL логичес­ки избыточна – любое реляционное выражение, которое можно представить
с ее помощью, можно записать и без нее. Однако в связи с этим следует
сделать еще одно замечание. Предположим, что в примере SX1 требуется подсчитать не общее число номеров деталей, а суммарное количество,
поставленное каждым поставщиком:
SUMMARIZE SP PER ( S { SNO } ) ADD ( SUM ( QTY ) AS TOTQ )

На наших тестовых данных результат будет такой:
SNO

TOTQ

S1
S2
S3
S4
S5

1300
700
200
900
0

Но SQL-выражение
SELECT S.SNO , ( SELECT SUM ( ALL QTY )
FROM SP
WHERE SP.SNO = S.SNO ) AS TOTQ
FROM S

дает результат, в котором значение TOTQ для поставщика S5 равно null,
а не 0. Так получается потому, что в SQL применение любой функции
множес­тва, кроме COUNT(*) и COUNT, к пус­тому аргументу, по определению (неправильному) дает null. Чтобы получить правильный результат, мы должны воспользоваться функцией COALESCE:
SELECT S.SNO , ( SELECT COALESCE ( SUM ( ALL QTY ) , 0 )
FROM SP
WHERE SP.SNO = S.SNO ) AS TOTQ
FROM S

189

Обобщение

Предположим теперь, что в примере SX1 требуется найти суммарное
количество для каждого поставщика, но только если оно больше 250:
( SUMMARIZE SP PER ( S { SNO } ) ADD ( SUM ( QTY ) AS TOTQ ) )
WHERE TOTQ > 250

Результат:
SNO

TOTQ

S1
S2
S4

1300
700
900

«Естественная» формулировка этого запроса на SQL выглядела бы так:
SELECT
FROM
GROUP
HAVING

SNO , SUM ( ALL QTY ) AS TOTQ
SP
BY SNO
SUM ( ALL QTY ) > 250 /* не TOTQ > 250 !!! */

Но можно переписать его и в таком виде:
SELECT DISTINCT SPX.SNO , ( SELECT SUM ( ALL SPY.QTY )
FROM SP AS SPY
WHERE SPY.SNO = SPX.SNO ) AS TOTQ
FROM SP AS SPX
WHERE ( SELECT SUM ( ALL SPY.QTY )
FROM SP AS SPY
WHERE SPY.SNO = SPX.SNO ) > 250

Как видно из этого примера, фраза HAVING, как и GROUP BY, тоже
логичес­ки избыточна – любое реляционное выражение, которое можно представить с ее помощью, можно записать и без нее. Да, версии
с GROUP BY и HAVING часто более компактны; с другой стороны, верно и то, что иногда они дают неправильные ответы (подумайте, например, что произойдет, если в предыдущем примере требуется, чтобы суммарное количество было не больше, а меньше 250).
Рекомендации: Применяя фразы GROUP BY и HAVING, убедитесь, что
вы обобщаете именно ту таб­лицу, которую хотите обобщить (в примерах из этого раздела – таб­лицу поставщиков, а не поставок). Кроме того,
не забывайте о возможности обобщения по пус­тому множес­тву и включайте COALESCE там, где необходимо.
Есть и еще одна вещь, о которой я хочу сказать в связи с GROUP BY
и HAVING. Рассмот­рим следующее SQL-выражение:
SELECT SNO , CITY , SUM ( ALL QTY ) AS TOTQ
FROM S NATURAL JOIN SP
GROUP BY SNO

Заметим, что CITY встречается с списке SELECT, но не входит в состав столбцов группировки. Но такая ситуация допус­тима, потому что

190

Глава 7. SQL и реляционная алгебра II: дополнительные операции

таб­лица S удовлетворяет некоторой функциональной зависимости (см.
главу 8 или приложение B), согласно которой каждому значению SNO
в этой таб­лице соответствует ровно одно значение CITY (в ней же). Более того, в стандарте SQL есть правила, согласно которым сис­тема знает
о наличии такой функциональной зависимости. Поэтому, несмот­ря на
отсутствие CITY в составе столбцов группировки, известно, что в каждой группе значение CITY одно и то же, так что CITY может встречаться во фразе SELECT, как показано выше (и во фразе HAVING, если бы
таковая присутствовала).
Разумеется, логичес­ки допус­тимо (хотя это может негативно отразиться на производительности) включить этот столбец в состав столбцов
группировки:
SELECT SNO , CITY , SUM ( QTY ) AS TOTQ
FROM S NATURAL JOIN SP
GROUP BY SNO , CITY

Еще об обобщении
Оператор SUMMARIZE входил в состав языка Tutorial D с самого начала. Но с появлением от­но­ше­ний-образов он стал логичес­ки избыточным, и хотя, возможно, есть причины сохранить его (например,
в педагогичес­ких целях), факт остается фактом: как правило, обобщение более компактно записываются с помощью оператора EXTEND.1
Вспомните пример SX1 из предыдущего раздела («Для каждого поставщика получить номер поставщика и количество поставленных им деталей»). Формулировка с применением SUMMARIZE выглядит так:
SUMMARIZE SP PER ( S { SNO } ) ADD ( COUNT ( PNO ) AS PCT )

А вот как выглядит эквивалентное выражение с применением оператора EXTEND:
EXTEND S { SNO } ADD ( COUNT ( !!SP ) AS PCT )

(Поскольку комбинация {SNO,PNO} является ключом пе­ре­мен­ной-от­
но­ше­ния SP, необязательно проецировать от­но­ше­ние-образ на {PNO}
перед вычислением счетчика.) Как видно из этого примера,оператор
EXTEND дает еще один контекст, в котором от­но­ше­ния-образы имеют
смысл; пожалуй, в этом контексте они даже полезнее, чем во фразах
WHERE.
Далее в этом разделе приводятся дополнительные примеры. Я продолжаю нумерацию, начатую в разделе «Еще об от­но­ше­ниях-образах».
1

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

Группирование и разгруппирование

191

Пример 6. Для каждого поставщика получить подробную информацию
о нем и общее количество поставленных им деталей, просуммированное по всем его поставкам.
EXTEND S ADD ( SUM ( !!SP , QTY ) AS TOTQ )

Пример 7. Для каждого поставщика получить подробную информацию
о нем и общее, максимальное и минимальное количество деталей по
всем его поставкам.
EXTEND S ADD ( SUM ( !!SP , QTY ) AS TOTQ ,
MAX ( !!SP , QTY ) AS MAXQ ,
MIN ( !!SP , QTY ) AS MINQ )

Обратите внимание на множес­твенную форму EXTEND в этом примере.
Пример 8. Для каждого поставщика получить подробную информацию
о нем, общее количество деталей, просуммированное по всем его поставкам, и общее количество деталей, просуммированное по всем поставкам всех поставщиков.
EXTEND S ADD ( SUM ( !!SP , QTY ) AS TOTQ ,
SUM ( SP , QTY ) AS GTOTQ )

Результат:
SNO

TOTQ

GTOTQ

S1
S2
S3
S4
S5

1300
700
200
900
0

3100
3100
3100
3100
3100

Пример 9. Для каждого города c получить c и общее и среднее количество деталей в поставке для всех поставок, для которых и поставщик,
и деталь находятся в городе c.
WITH ( S JOIN SP JOIN P ) AS TEMP :
EXTEND TEMP { CITY } ADD ( SUM ( !!TEMP , QTY ) AS TOTQ ,
AVG ( !!TEMP , QTY ) AS AVGQ )

Смысл этого, довольно искусственного, примера – проиллюстрировать
полезность WITH в сочетании с обобщением в случае, когда нужно несколько раз вывести некоторое, возможно, длинное подвыражение.

Группирование и разгруппирование
Напомню, что в главе 2 говорилось о допус­тимости атрибутов, значениями которых являются от­но­ше­ния (RVA-атрибутов). На рис. 7.1 показаны от­но­ше­ния R1 и R4 с рисунков 2.1 и 2.2; R4 имеет RVA-атрибуты,
а R1 – нет, но очевидно, что оба от­но­ше­ния несут в себе одну и ту же информацию.

192

Глава 7. SQL и реляционная алгебра II: дополнительные операции
R1

SNO

PNO

S2
S2
S3
S4
S4
S4

P1
P2
P2
P2
P4
P5

R4

SNO
S2

PNO_REL
PNO
P1
P2

S3

PNO
P2

S4

PNO
P2
P4
P5

Рис. 7.1. От­но­ше­ния R1 и R4 с рис. 2.1 и 2.2 в главе 2

Ясно, что необходим какой-то способ, позволяющий установить соответствие между от­но­ше­ниями с RVA-атрибутами и без них. Именно
в этом и состоит назначение операторов GROUP и UNGROUP. Не хочу
глубоко вдаваться детали, а просто скажу, что для от­но­ше­ний, показанных на рис. 7.1, выражение
R1 GROUP ( { PNO } AS PNO_REL )

порождает R4, а выражение
R4 UNGROUP ( PNO_REL )

порождает R1. В SQL нет прямых аналогов этим операторам.
Кстати, стоит отметить, что выражение
EXTEND R1 { SNO } ADD ( !!R1 AS PNO_REL )

дает в точности такой же результат, как в примере GROUP выше. Иными словами, GROUP можно определить в терминах EXTEND и от­но­ше­
ний-образов. Но я вовсе не призываю отказаться от полезного оператора GROUP; даже если отвлечься от всего остального, язык, в котором
есть явный оператор UNGROUP (как в Tutorial D), но отсутствует явный оператор GROUP, безусловно, стал бы мишенью для критики (хотя
бы из «эргономичес­ких» соображений). Но все равно интересно, а, быть
может, и полезно в педагогичес­ких целях отметить, как легко семантику оператора GROUP можно объяснить в терминах EXTEND и от­но­ше­
ний-образов.
Попутное замечание: если R4 содержит ровно один кортеж для поставщика с номером Sx и значение PNO_REL в этом кортеже пус­то, то результат показанного выше оператора UNGROUP не будет содержать
ни одного кортежа для поставщика с номером Sx. За дополнительной
информацией отсылаю вас к своей книге «Introduction to Database

Запросы «что если»

193

Systems» (см. приложение D) или к книге «Databases, Types, and the
Relational Model: The Third Manifesto» (снова см. приложение D), написанной Хью Дарвеном и мной.
И еще одно попутное замечание: возможно, вам интересно, как выглядят операции над от­но­ше­ниями с RVA-атрибутами. Операции над любым от­но­ше­нием, ссылающиеся на некоторый атрибут A типа T этого
от­но­ше­ния, обычно подразумевают нечто, что можно было бы назвать
«подоперациями» над значениями атрибута A, а точнее, операции, определенные для его типа T. Поэтому если T – тип от­но­ше­ния, то в роли
«подопераций» выступают операции, отпределенные для типов от­но­ше­
ний, то есть те самые реляционные операции (соединение и прочие), которые описаны в этой и предыдущей главах. См. упражнения 7.11 – 7.13
в конце главы.

Запросы «что если»
Поддержка запросов «что если» – часто выдвигаемое требование; они
позволяют исследовать, какой эффект принесло бы некоторое изменение, не выполняя самого изменения (с последующим возвратом в исход­
ное состояние). Вот пример («Что если бы детали, находящиеся в Париже, оказались в Ницце, а их вес удвоился бы?»):
UPDATE P WHERE CITY = ‘Paris’ : | WITH T1 AS
{ CITY := ‘Nice’ ,
| ( SELECT P.*
WEIGHT := 2 * WEIGHT } |
FROM P
|
WHERE CITY = ‘Paris’ ) ,
|
T2 AS
| ( SELECT P.* , ‘Nice’ AS NC ,
|
2 * WEIGHT AS NW
|
FROM T1 )
|
SELECT PNO , PNAME , COLOR ,
|
NW AS WEIGHT ,
|
NC AS CITY
|
FROM T2

Отметим, что несмот­ря на наличие ключевого слово UPDATE, выражение языка Tutorial D слева – это именно выражение, а не предложение
(отсутствует точка с запятой в конце), и, в частности, оно не оказывает
влияния на пе­ре­мен­ную-от­но­ше­ние P. (Таким образом, ключевое слово
UPDATE в данном случае обозначает оператор чтения! Льюис Кэрролл,
где ты?) И это выражение вычисляет от­но­ше­ние, содержащее ровно один
кортеж t2 для каждого кортежа t1 в текущем значении пе­ре­мен­ной-от­
но­ше­ния P, для которого указан город Париж (Paris). Но кортеж t2 отличается от t1 тем, что значение веса (weight) в два раза больше, а значение города (city) равно Nice (Ницца), а не Paris. Иными словами, все это
выражение является сокращенной записью для такого (кстати, полная
форма поможет вам понять версию этого запроса на SQL):

194

Глава 7. SQL и реляционная алгебра II: дополнительные операции
WITH ( P WHERE CITY = ‘Paris’ ) AS R1 ,
( EXTEND R1 ADD ( ‘Nice’ AS NC , 2 * WEIGHT AS NW ) ) AS R2 ,
R2 { ALL BUT CITY , WEIGHT } AS R3 :
R3 RENAME ( NC AS CITY , NW AS WEIGHT )

А теперь я хочу закончить одно дело, начатое в главе 5. Там я сказал,
что UPDATE – я имел в виду предложение UPDATE, а не только что
рассмот­ренный оператор чтения, – соответствует некоторому реляционному присваиванию, но детали несколько сложнее, чем для INSERT
и DELETE. Теперь я могу эти детали объяснить. Рассмот­рим в качестве
примера такое предложение UPDATE:
UPDATE P WHERE CITY = ‘Paris’ :
{ CITY := ‘Nice’ , WEIGHT := 2 * WEIGHT } ;

Логичес­ки это предложение эквивалентно следующему реляционному присваиванию (обратите внимание на обращение к рассмот­ренному
выше оператору чтения UPDATE):
P := ( P WHERE CITY ≠ ‘Paris’ )
UNION
( UPDATE P WHERE CITY = ‘Paris’ :
{ CITY := ‘Nice’ , WEIGHT := 2 * WEIGHT } ) ;

А как насчет ORDER BY?
Напоследок я хочу рассмот­реть в этой главе фразу ORDER BY. Эта конструкция не является частью реляционной алгебры, и как я отмечал
в главе 1, ее вообще нельзя назвать реляционным оператором, потому
что возвращаемый ею результат – не от­но­ше­ние (на входе она принимает от­но­ше­ние, но на выходе порождает нечто иное, а именно последовательность кортежей). Пожалуйста, не поймите меня неправильно, я вовсе не хочу сказать, что конструкция ORDER BY бесполезна; однако
я утверждаю, что в реляционном выражении для нее нет места (разве
что трактовать ее как «пус­тую операцию»).1 Поэтому, по определению,
следующие выражения, хотя и допус­тимы, реляционными выражениями не являются:
S MATCHING SP
ORDER ( ASC SNO )

1

|
|
|
|

SELECT
FROM
WHERE
ORDER

DISTINCT S.*
S , SP
S.SNO = SP.SNO
BY SNO ASC

В частности, поэтому она не может встречаться в определении представления – несмот­ря на то, что один хорошо известный продукт позволяет
это! Примечание: Иногда высказывают мысль, что ORDER BY необходима
для так называемых квотированных запросов, но это распространенное
заблуждение (см. упражнение 7.14).

Упражнения

195

Наведя в этом вопросе ясность, я хотел бы отметить, что есть две причины, из-за которых ORDER BY (просто ORDER в Tutorial D) дейст­
вительно оказывается довольно странным оператором. Во-первых, он
по существу преобразует множес­тво кортежей в отсортированную последовательность, а между тем, как нам известно из главы 3, для кортежей операторы «» не определены. Все операторы реляционной
алгебры – фактичес­ки все операторы чтения в обычном смысле этого
слова – являются функциями, то есть для любых данных на входе возвращают единственное значение на выходе. А вот ORDER BY может порождать различные результаты для одних и тех же исходных данных.
Для иллюстрации взглянем на результат применения операции ORDER
BY CITY к нашему тестовому от­но­ше­нию «поставщики». Понятно, что
эта операция может вернуть любой из четырех различных результатов,
соответствующих показанным ниже последовательностям (для простоты я включил только номера поставщиков):
•• S5 , S1 , S4 , S2 , S3
•• S5 , S4 , S1 , S2 , S3
•• S5 , S1 , S4 , S3 , S2
•• S5 , S4 , S1 , S3 , S2
Кстати, было бы непростительным упущением с мой стороны не упомянуть о том, что SQL-аналоги большинства реляционных операторов
тоже не являются функциями. Такое положение дел объясняется тем,
что, как объяснялось в главе 2, SQL иногда считает, что результат сравнения v1 = v2 равен TRUE, даже если v1 и v2 различаются. Например,
рассмот­рим символьные строки ‘Paris’ и ‘Paris ’ (обратите внимание на
пробел в конце второй строки); ясно, что эти значения различны, и тем
не менее SQL иногда считает их равными. Поэтому в главе 2 и было сказано, что некоторые SQL-выражения «потенциально недерминированы». Вот простой пример:
SELECT DISTINCT CITY FROM S

Если для одного поставщика указан город ‘Paris’, а для другого – ‘Paris
’, то результат может содержать как ‘Paris’, так и ‘Paris ’ (а то и оба сразу),
но что именно, заранее не известно. Можно даже в один день получить
одно, а в другой – другое, хотя в промежутке база данных не изменялась,
и это будет абсолютно законно. Можете сами поразмыслить о том, к каким последствиям способно привести такое положение вещей.

Упражнения
Упражнение 7.1. Что обозначают следующие выражения языка Tu­to­
rial D для нашей базы данных о поставщиках и деталях? В каждом
случае дайте (а) SQL-аналог и (б) неформальную интерпретацию вы-

196

Глава 7. SQL и реляционная алгебра II: дополнительные операции

ражения (то есть соответствующий ему предикат) на естественном
языке.
a. S MATCHING ( SP WHERE PNO = ‘P2’ )
b. P NOT MATCHING ( SP WHERE SNO = ‘S2’ )
c. P WHERE ( !!SP ) { SNO } = S { SNO }
d. P WHERE SUM ( !!SP , QTY ) < 500
e. P WHERE TUPLE { CITY CITY } ∈ S { CITY }
f. EXTEND S ADD ( ‘Supplier’ AS TAG )
g. EXTEND S { SNO } ADD ( 3 * STATUS AS TRIPLE_STATUS )
h. EXTEND ( P JOIN SP ) ADD ( WEIGHT * QTY AS SHIPWT )
i. EXTEND P ADD ( WEIGHT * 454 AS GMWT , WEIGHT * 16 AS OZWT )
j. EXTEND P ADD ( COUNT ( !!SP ) AS SCT )
k. EXTEND S
ADD ( COUNT ( ( SP RENAME ( SNO AS X ) ) WHERE X = SNO )
AS NP )
l. SUMMARIZE S BY { CITY } ADD ( AVG ( STATUS ) AS AVG_STATUS )
m. SUMMARIZE ( S WHERE CITY = ‘London’ )
PER ( TABLE_DEE ) ADD ( COUNT ( SNO ) AS N )
n. UPDATE SP WHERE SNO = ‘S1’ : { SNO := ‘S7’ , QTY = 0.5 * QTY }
Упражнение 7.2. При каких обстоятельствах (если такое вообще возможно) выражения r1 MATCHING r2 и r2 MATCHING r1 эквивалентны?
Упражнение 7.3. Докажите, что оператор переименования не является
примитивным.
Упражнение 7.4. Напишите выражение, содержащее EXTEND вместо
SUMMARIZE, которое было бы эквивалентно такому:
SUMMARIZE SP PER ( S { SNO } ) ADD ( COUNT ( PNO ) AS NP )

Упражнение 7.5. Какие из следующих выражений Tutorial D эквивалентны? В каждом случае приведите SQL-аналог.
a. SUMMARIZE r PER ( r { } ) ADD ( SUM ( 1 ) AS CT )
b. SUMMARIZE r PER ( TABLE_DEE ) ADD ( SUM ( 1 ) AS CT )
c. SUMMARIZE r BY { } ADD ( SUM ( 1 ) AS CT )
d. EXTEND TABLE_DEE ADD ( COUNT ( r ) AS CT )
Упражнение 7.6. В языке Tutorial D, если аргумент при вызове агрегатного оператора – пус­тое множес­тво, то COUNT, как и SUM, воз-

197

Упражнения

вращает 0; MAX и MIN возвращают соответственно минимальное
и максимальное значение соответствующего типа; AND и OR возвращают соответственно TRUE и FALSE, а AVG возбуждает исключение (я сознательно ничего не говорю здесь об агрегатном операторе XOR в Tutorial D). Что в этих случаях возвращает SQL? И почему?
Упражнение 7.7. Пусть от­но­ше­ние R4 на рис. 7.1 обозначает текущее
значение некоторой пе­ре­мен­ной-от­но­ше­ния. Если R4 описано, как
в главе 2, то каков предикат этой пе­ре­мен­ной-от­но­ше­ния?
Упражнение 7.8. Пусть r – от­но­ше­ние, обозначаемое следующим выражением Tutorial D:
SP GROUP ( { } AS X )

Как выглядит r для нашего тестового значения SP? И что дает такое
выражение?
r UNGROUP ( X )

Упражнение 7.9. Напишите на Tutorial D и/или SQL выражения для
следующих запросов к базе данных о поставщиках и деталях:
a. Получить общее количество деталей, поставленных поставщиком S1.
b. Получить номера поставщиков, находящихся в городе, который
идет первым по алфавиту в списке всех городов, где есть поставщики.
c. Получить названия городов, в которых находятся хотя бы два
поставщика.
d. Поскольку оба атрибута SNAME и CITY имеют тип CHAR, имеет смысл (пус­ть неотчетливый) сравнивать название поставщика с названием города… Вернуть в качестве результата ‘Y’, если
название каждого поставщика предшествует названию города,
в котором он находится, при сравнении в алфавитном порядке;
в противном случае вернуть ‘N’.
Упражнение 7.10. Ниже приведены два выражения Tutorial D, в которых участвует от­но­ше­ние R4, изображенное на рис. 7.1. Какие запросы они представляют?
( R4 WHERE TUPLE { PNO ‘P2’ } ∈ PNO_REL ) { SNO }
( ( R4 WHERE SNO = ‘S2’ ) UNGROUP ( PNO_REL ) ) { PNO }

Упражнение 7.11. Что обозначает следующее выражение Tutorial D
для нашей базы данных о поставщиках и деталях?
EXTEND S
ADD ( ( ( SP RENAME ( SNO AS X ) ) WHERE X = SNO ) { PNO }
AS PNO_REL )

198

Глава 7. SQL и реляционная алгебра II: дополнительные операции

Упражнение 7.12. Пусть от­но­ше­ние, которое возвращает выражение
из предыдущего упражнения, сохранено в пе­ре­мен­ной-от­но­ше­нии
SSP. Что делают следующие операции обновления?
INSERT SSP RELATION
{ TUPLE { SNO ‘S6’ , SNAME ‘Lopez’ , STATUS 30 , CITY ‘Madrid’ ,
PNO_REL RELATION { TUPLE { PNO ‘P5’ } } } } ;
UPDATE SSP WHERE SNO = ‘S2’ :
{ INSERT PNO_REL RELATION { TUPLE { PNO ‘P5’ } } } ;

Упражнение 7.13. Считая, что SSP – пе­ре­мен­ная-от­но­ше­ние из предыдущего упражнения, напишите выражения для следующих запросов:
a. Получить номера поставщиков, которые поставляют в точности
одно и то же множес­тво деталей.
b. Получить номера деталей, поставляемых в точности одними
и теми же поставщиками.
Упражнение 7.14. Квотированным называется запрос, в котором задано ограничение сверху на кардинальность результата, или квота,
например: в запросе «Получить две самых тяжелых детали» задана
квота 2. Приведите формулировки этого запроса на Tutorial D и SQL.
Что они возвращают для наших тестовых данных?
Упражнение 7.15. Как бы вы записали следующий запрос с использованием явного оператора SUMMARIZE: «Для каждого поставщика
получить номер поставщика и среднюю величину поставки (количество деталей в ней) для поставок данного поставщика с различными
значениями количества деталей»?
Упражнение 7.16. Рассмот­рим следующую модифицированную версию базы данных о поставщиках и деталях:
S
SP
SJ
J

{
{
{
{

SNO }
/* поставщики
*/
SNO, PNO } /* поставщик поставляет деталь */
SNO, JNO } /* поставщик поставляет проект */
JNO }
/* проекты
*/

Выразите на Tutorial D и SQL такой запрос: «Для каждого поставщика получить всю информацию о нем, номера деталей, поставляемых этим поставщиком, и номера проектов, поставляемых этим поставщиком». На языке Tutorial D дайте формулировки с использованием операторов EXTEND и SUMMARIZE.
Упражнение 7.17. Что означает следующее выражение языка Tuto­ri­
al D?
S WHERE ( !!(!!SP) ) { PNO } = P { PNO }

Упражнение 7.18. Существует ли логичес­кое различие между следующими двумя выражениями языка Tutorial D? Если да, то в чем оно
заключается?

199

Упражнения
EXTEND TABLE_DEE ADD ( COUNT (

SP ) AS NSP )

EXTEND TABLE_DEE ADD ( COUNT ( !!SP ) AS NSP )

Упражнение 7.19. Приведите пример соединения, которое не является
полусоединением, и полусоединения, не являющегося соединением.
Точно сформулируйте условия, при которых выражения r1 JOIN r2
и r1 MATCHING r2 эквивалентны.
Упражнение 7.20. Пусть r1 и r2 – от­но­ше­ния одного и того же типа,
и пус­ть t1 – кортеж r1. Что означает выражение !!r2 для этого кортежа t1? И что будет, если от­но­ше­ния r1 и r2 не просто имеют одинаковый тип, а являются одним и тем же от­но­ше­нием?
Упражнение 7.21. Существует ли логичес­кое различие между следующими SQL-выражениями, и если да, то в чем оно заключается?
SELECT COUNT ( * ) FROM S
SELECT SUM ( 1 ) FROM S

Глава

8
.

SQL и ограничения целостности
В предыдущих главах я несколько раз затрагивал тему ограничений
целостности, а теперь пришло время заняться ею более пристально. Повторю неточное определение из главы 1: ограничением целостности
(для краткости, просто ограничением) называется булево выражение,
вычисление которого должно давать TRUE. Вообще говоря, ограничения получили свое название потому, что ограничивают множес­тво значений, допус­тимых в качестве значения некоторой пе­ре­мен­ной, но нас
здесь интересуют лишь ограничения, применяемые к пе­ре­мен­ным базы
данных. Такие ограничения можно отнести к одной из двух широких
категорий: ограничения типа и ограничения базы данных. По существу, ограничения типа определяют, какие значения составляют данный тип, а ограничения базы данных уточняют, какие значения могут
встречаться в конкретной базе данных (здесь слово «уточняют» означает, что эти ограничения применяются в дополнение к ограничениям
типа). Как обычно, я буду рассматривать все новые понятия в терминах
реляционной теории и SQL.
Кстати, стоит отметить, что в общем случае ограничения можно считать
формальным вариантом того, что иногда называют бизнес-правилами.
У этого термина нет точного определения (по крайней мере, общепринятого), но, вообще говоря, бизнес-правило – это декларативное утверждение (ударение на слове «декларативное») о некотором аспекте деятельности предприятия, которое база данных должна поддерживать.
Утверждения, ограничивающие значения пе­ре­мен­ных базы данных,
безусловно, отвечают этому нестрогому определению. Но я пойду еще
дальше. На мой взгляд, ограничения и составляют самую суть управления базами данных. Задача базы данных – представить некоторый
аспект работы предприятия; это представление должно быть максимально верным, только тогда можно гарантировать, что решения, принимаемые на основе того, что предлагает база данных, правильны.
А ограничения как раз дают наилучшее из имеющихся в нашем распо-

Ограничения типа

201

ряжении средств обеспечить верность представления. Важность ограничений трудно переоценить, как и важность надлежащей их поддержки со стороны СУБД.

Ограничения типа
В главе 2 мы видели, что составной частью определения типа является
задание значений, составляющих этот тип, – именно для этого и предназначены ограничения типа. Если речь идет о сис­темном типе, то
эта задача возлагается на сис­тему, так что говорить тут особо не о чем.
А вот о типах, определенных пользователем, можно сказать больше, гораздо больше. Поэтому предположим – просто для примера, – что количество деталей в поставке описывается не сис­темным типом INTEGER,
а каким-то пользовательским типом, скажем QTY. Вот как может выглядеть его определение на языке Tutorial D:
1 TYPE QTY
2
POSSREP QPR
3
{ Q INTEGER
4
CONSTRAINT Q ≥ 0 AND Q ≤ 5000 } ;

Объяснение:
•• В строке 1 просто говорится, что мы определяем тип с именем QTY.
•• В строке 2 говорится, что количество имеет «допус­тимое представление», которое называется QPR. Из главы 2 мы знаем, что физические представления всегда скрыты от пользователя. Однако в языке
Tutorial D требуется, чтобы предложение TYPE содержало хотя бы
одну спецификацию POSSREP, показывающую, что значения рассматриваемого типа в принципе можно представить каким-то конкретным способом.1 В отличие от физичес­ких, допус­тимые представления, вне всякого сомнения, видны пользователю (в данном
примере пользователю определенно нужно знать, что у количества
есть допус­тимое представление QPR). Однако прошу заметить, что
нигде не говорится, что заданное допус­тимое представление совпадает с физичес­ким; может совпадать, а может и нет, но в любом случае пользователю это безразлично.
•• В строке 3 говорится, что допус­тимое представление QPR состоит из
единственного компонента Q типа INTEGER. Другим словами, значения типа QTY допус­тимо представлять целыми числами (и пользователь об этом знает).
•• Наконец, в строке 4 говорится, что эти целые числа должны лежать
в диапазоне от 0 до 5000 включительно. Таким образом, строки 2–4
вместе определяют, что допус­тимыми являются значения, которые
1

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

202

Глава 8. SQL и ограничения целостности

можно представить целыми числами из указанного диапазона; именно это определение и составляет ограничение типа для типа QTY.
(Заметим, что такие ограничения выражаются не в терминах самого
типа, а в терминах допус­тимого представления этого типа. Собственно, одна из причин, по которым нам вообще нужно понятие допус­
тимого представления, как раз и состоит в том, чтобы получить средство для выражения ограничений типа. Надеюсь, это стало понятно
из рассмотрения примера.)
Вот несколько более сложный пример:
TYPE POINT
POSSREP CARTESIAN { X FIXED , Y FIXED
CONSTRAINT SQRT ( X ** 2 + Y ** 2 ) ≤ 100.0 } ;

Тип POINT описывает точку на двумерной плоскости, поэтому у него
есть допус­тимое представление CARTESIAN с двумя компонентами X
и Y (надо полагать, они соответствуют декартовым координатам). Эти
компоненты имеют тип FIXED, и задана спецификация CONSTRAINT,
смысл которой в том, что нас интересуют только точки, лежащие внутри или на границе круга с центром в начале координат и радиусом 100
(SQRT – неотрицательный квадратный корень). Примечание: В главе 2,
если помните, я пользовался типом POINT, но тогда сознательно не показал спецификации POSSREP и CONSTRAINT; однако я молчаливо предполагал, что допус­тимое представление этого типа называется
POINT, а не CARTESIAN. См. следующий подраздел.

Селекторы и операторы THE_
Прежде чем продолжить обсуждение ограничений типов, я хочу немного отклониться от темы и прояснить несколько вопросов, возникающих при внимательном изучении типов QTY и POINT.
В главе 3 отмечалось, что с любым типом, в том числе с пользовательским, ассоциирован селектор и операторы THE_. Эти операторы тесно
связаны с понятием допус­тимого представления; на самом деле, существует взаимно однозначное соответствие между селекторами и допус­
тимыми представлениями и между операторами THE_ и компонентами
допус­тимого представления. Приведу несколько примеров.
1. QPR ( 250 )
Это вызов селектора типа QTY. Селектор имеет такое же имя QPR,
как единственное допус­тимое представление для этого типа; он принимает аргумент, который соответствует единственному компоненту допус­тимого представления и принадлежит тому же типу, и возвращает количество (то есть значение типа QTY). Примечание: На
практике допус­тимые представления часто именуются так же, как
ассоциированный с ними тип, в примере QTY я взял разные имена, чтобы высветить логичес­кое различие между допус­тимым пред-

Ограничения типа

203

ставлением и типом, но обычно так не делают. В синтаксисе языка
Tutorial D даже есть правило, позволяющее опускать имя допус­
тимого представления в предложении TYPE, и тогда оно по умолчанию будет совпадать с именем ассоциированного типа. Поэтому внесем изменения в определение типа QTY:
TYPE QTY POSSREP { Q INTEGER CONSTRAINT Q ≥ 0 AND Q ≤ 5000 } ;

Теперь и допус­тимое представление, и соответствующий селектор
называются QTY, а показанный выше вызов селектора принимает
вид QTY(250) – именно так я вызывал селекторы в главе 2 (можете
вернуться и убедиться). Далее в этой главе я буду опираться именно на это определение типа QTY, если явно не оговорено противное.
2. QTY ( A + B )
При вызове селектора QTY в качестве аргумента можно передать выражение произвольной сложности (лишь бы оно имело тип
INTEGER). Если выражение является литералом, как в предыдущем примере, то и вызов селектора – литерал; следовательно, литерал – это частный случай вызова селектора, как мы уже знаем из
главы 2.
3. THE_Q ( QZ )
Это вызов оператора THE_ для типа QTY. Оператор называется
THE_Q, потому что Q – имя единственного компонента единственного допус­тимого представления типа QTY; он принимает аргумент
(произвольное выражение) типа QTY и возвращает целое число –
компонент Q допус­тимого представления аргумента.
Что касается типа POINT, то давайте сначала изменим его определение,
так чтобы допус­тимое выражение называлось так же, как сам тип:
TYPE POINT POSSREP { X FIXED , Y FIXED CONSTRAINT ... } ;

А теперь вернемся к примерам:
1. POINT( 5.7, -3.9 )
Это вызов селектора POINT (фактичес­ки литерал типа POINT).
2. THE_X ( P )
Это выражение возвращает значение типа FIXED – координату X
в декартовом допус­тимом представлении точки, являющейся текущим значением пе­ре­мен­ной P (которая должна иметь тип POINT).
Попутно хотел бы привлечь ваше внимание к тому факту, что (как я уже
говорил) язык Tutorial D требует, чтобы в предложении TYPE была по
меньшей мере одна спецификация POSSREP. Но Tutorial D разрешает
задавать для одного типа несколько различных допус­тимых представлений. Хорошим примером может служить тип POINT; мы вполне могли бы определить два допус­тимых представления точки на двумерной
плоскости: в декартовых и в полярных координатах. Дальнейшее углу-

204

Глава 8. SQL и ограничения целостности

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

Еще об ограничениях типа
Вернемся к самим ограничениям типа. Предположим, что тип QTY
определен, как показано ниже, без явной спецификации CONSTRAINT:
TYPE QTY POSSREP { Q INTEGER } ;

По определению это не что иное, как сокращенная запись для
TYPE QTY POSSREP { Q INTEGER CONSTRAINT TRUE } ;

При таком определении все, что можно представить целым числом, будет допус­тимым значением QTY, и хотя с типом QTY по необходимости ассоциировано ограничение типа, оно весьма слабое. Другими словами, заданное допус­тимое представление уже определяет ограничение для типа, а спецификация CONSTRAINT лишь налагает добавочное ограничение помимо априорного. (Но неформально мы часто понимаем под «ограничением типа» именно то, что задано в спецификации
CONSTRAINT.)
Я пока еще ничего не говорил о том, когда проверяются ограничения
типа. Это делается в момент вызова селектора. Предположим снова, что у значений типа QTY есть допус­тимое представление целыми
числами в диапазоне от 0 до 5000 включительно. Тогда выражение
QTY(250) – вызов селектора QTY, который завершается успешно. А вычисление выражения QTY(6000), тоже являющегося вызовом селектора, приводит к ошибке. Полагаю, не вызывает сомнений, что никак
нельзя смириться с выражением, которое должно бы обозначать значение некоторого типа T, но не обозначает; согласитесь, фраза «значение
типа T, не являющееся значением типа T» содержит в себе внутреннее
противоречие. Отсюда следует, что никакой пе­ре­мен­ной – и пе­ре­мен­
ной-от­но­ше­нию, в частности, – невозможно присвоить значение неподходящего типа.
И последнее замечание в этом разделе: объявляя, что нечто имеет тип,
мы тем самым налагаем на это нечто ограничение.1 В частности, объявив, что атрибут QTY пе­ре­мен­ной-от­но­ше­ния SP (к примеру) имеет тип
QTY, мы наложили ограничение – ни один кортеж в пе­ре­мен­ной-от­но­
ше­нии SP не будет содержать в позиции QTY значение, не удовлетворяющее ограничению типа QTY. Примечание: Это ограничение на атрибут QTY – пример так называемого ограничения атрибута.

1

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

205

Ограничения типа в SQL

Ограничения типа в SQL
Уверен, вы заметили, что в предыдущем разделе я не приводил примеров на SQL. А дело в том, что – хотите верьте, хотите нет – SQL вообще не поддерживает ограничения типа, если не считать тривиальные встроенные ограничения. Конечно, SQL позволит создать пользовательский тип QTY и сказать, что количество должно представляться
целым числом, но указать, что эти числа должны принадлежать некоторому диапазону, вы не сможете. Иными словами, определение этого
типа в SQL будет выглядеть примерно так:
CREATE TYPE QTY AS INTEGER FINAL ;

(Ключевое слово FINAL здесь означает, что у типа QTY нет собственных подтипов, но обсуждение подтипов выходит за рамки настоящей
книги.)
При таком определении любое целое число может представлять количество. Если вы хотите, чтобы количество принадлежало заданному диапазону, то должны будете определять подходящее ограничение базы
данных – на практике это, вероятно, будет ограничение на базовую таб­
лицу (см. раздел «Ограничения базы данных в SQL») – при каждом использовании этого типа. Например, определение базовой таб­лицы SP,
возможно, придется расширить следующим образом (обратите внимание на измененную спецификацию типа данных для столбца QTY и на
спецификацию CONSTRAINT в самом конце):
CREATE TABLE SP
( SNO
VARCHAR(5) NOT NULL ,
PNO
VARCHAR(6) NOT NULL ,
QTY
QTY
NOT NULL ,
UNIQUE ( SNO , PNO ) ,
FOREIGN KEY ( SNO ) REFERENCES
FOREIGN KEY ( PNO ) REFERENCES
CONSTRAINT SPQC CHECK ( QTY >=
QTY 100 ) ;

|
|
|
|
|

CREATE ASSERTION
( NOT EXISTS
( SELECT * FROM
WHERE STATUS
OR
STATUS

CX1 CHECK
S
< 1
> 100 ) ) ;

Ограничение CX1 говорит: значения статуса должны принадлежать диапазону от 1 до 100 включительно. В этом ограничении участвует только один атрибут одной пе­ре­мен­ной-от­но­ше­ния. Особо отмечу, что для
проверки ограничения для данного кортежа поставщика достаточно
исследовать только этот кортеж – обращаться к другим кортежам этой
или еще каких-нибудь пе­ре­мен­ных-от­но­ше­ний не нужно. Поэтому такие ограничения иногда неформально называют ограничениями кортежа, или ограничениями строки (в SQL), хотя в SQL этот термин применяется и в более узком смысле – как ограничение на строку, которое невозможно сформулировать в виде ограничения на один столбец (см. раздел «Ограничения базы данных в SQL»). Однако подобных ограничений

1

Хотя я и сказал, что наследование типов не рассматривается в этой книге,
не могу не указать на одно следствие, вытекающее из отсутствия поддержки ограничений типов в SQL, а именно то, что SQL вынужденно допускает
такие нелепости, как «неквадратные квадраты» (точнее, значения типа
SQUARE со сторонами разной длины, которые, следовательно, являются
не квадратами, а прямоугольниками общего вида).

207

Ограничения базы данных

следует избегать, так как они ограничивают обновление и (как мы видели в главе 5) в реляционном мире нет понятия «обновления на уровне кортежа или строки».
Пример 2:
CONSTRAINT CX2 IS_EMPTY
( S WHERE CITY = ‘London’
AND STATUS ≠ 20 ) ;

|
|
|
|
|

CREATE ASSERTION
( NOT EXISTS
( SELECT * FROM
WHERE CITY =
AND
STATUS

CX2 CHECK
S
'London'
20 ) ) ;

Ограничение CX2 говорит: у поставщиков из Лондона должен быть статус 20. В этом ограничении участвуют два атрибута, но, поскольку это
атрибуты одной и той же пе­ре­мен­ной-от­но­ше­ния, то, как и для ограничения CX1, для проверки достаточно исследовать всего один кортеж вне
связи со всеми остальными (следовательно, это тоже «ограничение кортежа», или «ограничение строки»).
Пример 3:
CONSTRAINT CX3
COUNT ( S ) =
COUNT ( S { SNO } ) ;

|
|
|

CREATE ASSERTION CX3 CHECK
( UNIQUE ( SELECT ALL SNO
FROM S ) ) ;

Ограничение CX3 говорит: никакие два кортежа пе­ре­мен­ной-от­но­ше­
ния S не могут иметь одинаковые номера; иными словами, {SNO} – это
суперключ (фактичес­ки, ключ) данной пе­ре­мен­ной-от­но­ше­ния (напомню, что неформально суперключом называется надмножес­тво ключа).
Как и в CX1 и CX2, в этом ограничении тоже участвует только одна
пе­ре­мен­ная-от­но­ше­ние, однако для его проверки недостаточно исследовать только кортеж данного поставщика (поэтому оно не является ограничением «кортежа», или «строки»). Конечно, на практике вряд ли ктонибудь станет записывать ограничение CX3 так, как показано выше, –
та или иная сокращенная форма с ключевым словом KEY почти всегда
предпочтительнее. Я привел «длинную» форму только с одной целью:
показать, что сокращения – это не более чем сокращения.
Кстати, формулировка ограничения CX3 в SQL нуждается в пояснении.
В SQL UNIQUE – это оператор, который возвращает TRUE тогда и только тогда, когда все строки в таб­лице-аргументе различаются. Таким образом, вызов UNIQUE в ограничении возвращает TRUE тогда и только
тогда, когда в таб­лице S нет двух строк с одинаковым номером поставщика. Я еще вернусь к этому оператору в главе 10.
Для тех, кому интересно, приведу SQL-формулировку ограничения
CX3, которая больше напоминает формулировку на Tutorial D:
CREATE ASSERTION CX3 CHECK
( ( SELECT COUNT ( ALL SNO ) FROM S ) =
( SELECT COUNT ( DISTINCT SNO ) FROM S ) ) ;

208

Глава 8. SQL и ограничения целостности

Пример 4:
CONSTRAINT CX4
COUNT ( S { SNO } ) =
COUNT ( S { SNO , CITY } ) ;

|
|
|
|
|
|
|

CREATE ASSERTION CX4 CHECK
( NOT EXISTS ( SELECT *
FROM S AS SX
WHERE EXISTS ( SELECT *
FROM S AS SY
WHERE SX.SNO = SY.SNO
AND SX.CITY SY.CITY ) ) ) ;

Ограничение CX4 говорит: если в двух кортежах пе­ре­мен­ной-от­но­ше­
ния S совпадают номера поставщиков, то должны совпадать и города.
Иначе говоря, между {SNO} и {CITY} существует функциональная зависимость, которую чаще выражают так:
{ SNO } → { CITY }

Приведу определение.
Определение: Пусть A и B – подмножес­тва заголовка пе­ре­
мен­ной-от­но­ше­ния R. Говорят, что R удовлетворяет функциональной зависимости (ФЗ) A → B, если для любого от­
но­ше­ния, являющегося допус­тимым значением R, из того,
что в двух кортежах значения A одинаковы, следует, что
и значения B в них тоже одинаковы.
Обозначение A → B читается как «B функционально зависит от A», или
«A функционально определяет B», или просто «A стрелка B». Однако,
как показывает предыдущий пример, по существу функциональная зависимость – это просто частный случай ограничения целостности (хотя
оно и не является ограничением «кортежа», или «строки»).
В главе 5 отмечалось, что пе­ре­мен­ная-от­но­ше­ние S удовлетворяет этой
конкретной ФЗ просто потому, что {SNO} является для нее ключом. Поэтому, если явно указано, что {SNO} – ключ, то записывать еще и эту ФЗ
явно нет необходимости. Однако не все ФЗ – следствия наличия ключа.
Пусть, например, требуется, чтобы любые два поставщика, находящиеся в одном городе, имели один и тот же статус (отметим, что для наших
тестовых данных это ограничение не удовлетворяется). Очевидно, что
это гипотетичес­кое ограничение – ФЗ:
{ CITY } → { STATUS }

Поэтому его можно записать в том же виде, что ограничение CX4
(упражнение для читателя).
Возможно, вам кажется, что для формулировки ФЗ было бы удобно
иметь сокращенную нотацию, как для ключей. Лично я так не думаю,
поскольку хотя в общем случае не все ФЗ – следствия наличия ключей,
в правильно спроектированной базе данных они должны быть таковыми. Иными словами, раз ФЗ трудно сформулировать для плохо спроектированной базы данных, значит, – что? – проектируйте базу правильно!

209

Ограничения базы данных

Примечание
В предыдущем абзаце под «правильно спроектированной» я имею в виду над­
лежащим образом нормализованную базу данных. Собственно нормализация
обсуждается в приложении B, но я все же отмечу, что реляционные (или SQL)
предложения и выражения работают независимо от того, нормализованы или
нет пе­ре­мен­ные-от­но­ше­ния (или таб­лицы). Однако к этому следует добавить,
что предложения и выражения часто проще сформулировать (и, вопреки распространенному мнению, они и выполняться будут быст­рее), если таб­лицы
полностью нормализованы. Впрочем, нормализация как таковая – это вопрос
проектирования баз данных, а не реляционной модели или SQL.

Пример 5:
CONSTRAINT CX5 IS_EMPTY
( ( S JOIN SP )
WHERE STATUS < 20
AND PNO = ‘P6’ ) ;

|
|
|
|
|
|

CREATE ASSERTION CX5 CHECK
( NOT EXISTS
( SELECT *
FROM S NATURAL JOIN SP
WHERE STATUS < 20
AND
PNO = ‘P6’ ) ) ;

Ограничение CX5 говорит: никакой поставщик со статусом меньше 20
не может поставлять деталь P6. Отметим, что в этом ограничении участвуют две взаимосвязанные пе­ре­мен­ные-от­но­ше­ния: S и SP. В общем
случае в ограничении базы данных может участвовать сколько угодно
пе­ре­мен­ных-от­но­ше­ний. Терминологичес­кое замечание: ограничение,
в котором участвует одна пе­ре­мен­ная-от­но­ше­ние, неформально называют ограничением пе­ре­мен­ной-от­но­ше­ния (или, чтобы подчеркнуть суть
дела, ограничением с одной пе­ре­мен­ной-от­но­ше­нием). Ограничение,
в котором участвуют две или более пе­ре­мен­ных-от­но­ше­ний, называют ограничением с несколькими пе­ре­мен­ными-от­но­ше­ниями. (Таким
образом, CX3 и CX4 – ограничения с одной пе­ре­мен­ной-от­но­ше­нием,
а CX5 – ограничение с несколькими пе­ре­мен­ными-от­но­ше­ниями. CX1
и CX2 – также ограничения с одной пе­ре­мен­ной-от­но­ше­нием.) Впрочем,
все эти термины вряд ли стоит употреблять по причинам, о которых мы
будем говорить в следующей главе.
Пример 6:
CONSTRAINT CX6
SP { SNO } ⊆ S { SNO } ;

|
|
|
|
|
|

CREATE ASSERTION CX6 CHECK
| ( NOT EXISTS
( SELECT SNO
FROM SP
EXCEPT
SELECT SNO
FROM S ) ) ;

Ограничение CX6 говорит: любой номер поставщика, встречающийся
в пе­ре­мен­ной-от­но­ше­нии SP, должен присутствовать и в пе­ре­мен­нойот­но­ше­нии S. Как видите, в формулировке на языке Tutorial D уча-

210

Глава 8. SQL и ограничения целостности

ствует реляционное сравнение. В SQL же реляционные сравнения не
поддерживаются, поэтому приходится прибегать к обходному маневру.
Учитывая, что {SNO} – ключ (на самом деле, единственный ключ) пе­
ре­мен­ной-от­но­ше­ния S, ясно, что CX6 – это, по существу, ограничение
внешнего ключа от SP к S. Поэтому привычный синтаксис FOREIGN
KEY можно считать сокращенной нотацией для ограничений вида CX6.

Ограничения базы данных в SQL
Любое ограничение, которое можно выразить с помощью предложения
CONSTRAINT языка Tutorial D, можно также выразить в виде предложения CREATE ASSERTION в SQL. Приведенных в предыдущем разделе примеров CX1-CX6 должно быть достаточно для иллюстрации этого
утверждения.1 Однако, в отличие от Tutorial D, в SQL любое такое ограничение можно альтернативно записать в виде ограничения базовой
таб­лицы, то есть включить в определение некоторой базовой таб­лицы.
Вот, например, как выглядит SQL-версия ограничения CX5 из предыдущего раздела:
CREATE ASSERTION CX5 CHECK
( NOT EXISTS ( SELECT
FROM
WHERE
AND

*
S NATURAL JOIN SP
STATUS < 20
PNO = ‘P6’ ) ) ;

Этот пример можно было бы также переформулировать несколько иначе, воспользовавшись спецификацией CONSTRAINT в предложении
CREATE TABLE для базовой таб­лицы SP:
CREATE TABLE SP
( ... ,
CONSTRAINT CX5 CHECK
( PNO ‘P6’ OR ( SELECT STATUS FROM S
WHERE SNO = SP.SNO ) >= 20 ) ) ;

Отметим, однако, что логичес­ки эквивалентное ограничение можно
было бы включить в определение базовой таб­лицы S или базовой таб­
лицы P, да и вообще абсолютно любой таб­лицы базы данных (см. упражнение 8.17 в конце главы).
Такая альтернативная запись, безусловно, полезна для «ограничений
строки» (таких, которые можно проверить, исследуя только одну строку), поскольку она проще, чем CREATE ASSERTION. Вот, например,
как можно переписать ограничения CX1 и CX2 из предыдущего раздела в виде ограничений базовой таб­лицы S:
1

С тем исключением (см. главу 2), что в SQL ограничения не должны содержать «потенциально недетерминированных выражений»; на практике нарушение этого правила может привести к серьезным проблемам. Дополнительную информацию см. в главе 12.

Транзакции

211

CREATE TABLE S
( ... ,
CONSTRAINT CX1 CHECK ( STATUS >= 1 AND STATUS 100 ) ;

Здесь пе­ре­мен­ная-от­но­ше­ние «S» – это то, что в формальной логике называется обозначением (designator); в момент проверки ограничения
она обозначает конкретное значение, а именно значение рассматриваемой пе­ре­мен­ной-от­но­ше­ния в этот момент. По определению, это значение является от­но­ше­нием (назовем его s), поэтому ограничение принимает вид:
CONSTRAINT CX1 IS_EMPTY ( s WHERE STATUS < 1 OR STATUS > 100 ) ;

Очевидно, что булево выражение здесь (которое в дейст­вительности
и является ограничением, а слова «CONSTRAINT CX1» – не более чем
украшение) либо истинно, либо ложно, без дополнительных условий,
а это и есть определение высказывания (см. главу 5).
Во-вторых, предположим, что пе­ре­мен­ная-от­но­ше­ние S уже содержит
кортеж, который нарушает ограничение CX1 в момент выполнения

222

Глава 8. SQL и ограничения целостности

предложения CONSTRAINT; тогда это выполнение должно завершиться неудачно. Более общо, всякий раз, как мы пытаемся определить новое ограничение базы данных, сис­тема должна проверить, удовлетворяется ли оно в данный момент; если нет, ограничение следует отвергнуть, иначе принять и в дальнейшем следить за его истинностью.
В-третьих, предполагается, что реляционные базы данных удовлетворяют ограничению ссылочной целостности, которое говорит, что не должно быть внешних ключей, не имеющих соответствия. В главе 1 я назвал
это правило «обобщенным ограничением целостности». Но должно быть
ясно, что это сов­сем не то, что ограничения, рассматриваемые в данной
главе. В дейст­вительности это в некотором смысле метаограничение,
оно говорит, что любая конкретная база данных должна удовлетворять
тем конкретным ограничениям ссылочной целостности, которые применимы к этой базе. Так, в случае базы данных о поставщиках и деталях
оно говорит о том, что должны удовлетворяться ссылочные ограничения
от SP к S и P, поскольку если это не так, то будет нарушено метаограничение ссылочной целостности. Аналогично в случае базы данных об отделах и служащих из главы 1 должно удовлетворяться ссылочное ограничение от EMP к DEPT, поскольку в противном случае снова было бы
нарушено метаограничение ссылочной целостности.
И еще один момент, о котором у меня не было случая упомянуть раньше, – это возможность поддержки ограничений перехода. Так называются ограничения на допус­тимые переходы пе­ре­мен­ных (в частности,
пе­ре­мен­ных-от­но­ше­ний) от одного значения к другому (ограничения
же, которые не являются ограничениями перехода, иногда называют
ограничениями состояния). Например, семейное положение человека
может измениться с «никогда не состоял в браке» на «женат/замужем»,
но не наоборот. Вот пример такого ограничения («статус поставщика не
может уменьшаться»):
CONSTRAINT CX9 IS_EMPTY
( ( ( S′ { SNO , STATUS } RENAME ( STATUS AS OLD ) )
JOIN
( S { SNO , STATUS } RENAME ( STATUS AS NEW ) ) )
WHERE OLD > NEW ) ;

Объяснение: Я следую соглашению о том, что имя пе­ре­мен­ной-от­но­ше­
ния со штрихом (например, S’) относится к указанной пе­ре­мен­ной-от­
но­ше­нию в состоянии до обновления. Таким образом, ограничение CX9
гласит: «если соединить старое значение S с новым и ограничить результат только теми кортежами, для которых старый статус меньше
нового,то должно получиться пус­тое множес­тво». (Так как соединение
производится по атрибуту SNO, то любой принадлежащий соединению
кортеж, для которого старый статус больше нового, представлял бы поставщика с уменьшившимся статусом.)

Упражнения

223

В настоящее время ограничения перехода не поддерживаются ни
в Tutorial D, ни в SQL (разве что процедурно).
И наконец, я надеюсь, что, прочитав эту главу, вы согласитесь, что ограничения необычайно важны, но тем не менее очень слабо поддержаны
в современных продуктах. Создается даже впечатление, что их недооценивают, а то и вовсе не понимают. На практике упор всегда делают на
производительность, производительность и еще раз производительность; прочие цели, например простота использования, независимость
от данных и, в том числе, целостность, похоже приносят в жертву этому всепоглощающему стремлению или в лучшем случае отодвигают на
задворки.1
Я бы не хотел быть понятым неправильно. Разумеется, производительность тоже важна. С функциональной точки зрения, сис­тема, не обеспечивающая хотя бы адекватную производительность, вообще не может считаться сис­темой (по крайней мере, практичес­ки пригодной). Но
какой смысл в быст­ро работающей сис­теме, если нельзя доверять правильности получаемой от нее информации? Откровенно говоря, мне все
равно, насколько быст­ро сис­тема работает, если я не уверен в правильности ответов на свои запросы.

Упражнения
Упражнение 8.1. Определите термины ограничение типа и ограничение базы данных. В какой момент такие ограничения проверяются?
Что произойдет, если проверка завершится неудачно?
Упражнение 8.2. Сформулируйте золотое правило. Верно ли, что это
правило может быть нарушено тогда и только тогда, когда нарушено
какое-то ограничение с одной пе­ре­мен­ной-от­но­ше­нием?
Упражнение 8.3. Что вы понимаете под терминами утверждение (as­ser­
tion), ограничение атрибута, ограничение базовой таб­ли­цы, ограничение столбца, ссылочное ограничение, ограничение пе­ре­мен­ной-от­
но­ше­ния, ограничение строки, ограничение с одной пе­ре­мен­ной-от­
но­ше­нием, ограничение состояния, полное ограничение базы данных, полное ограничение пе­ре­мен­ной-от­но­ше­ния, ограничение перехода, ограничение кортежа? В какие из этих категорий попадают
(а) ограничения ключа, (б) ограничения внешнего ключа?
Упражнение 8.4. В чем различие между допус­тимым и физичес­ким
представлением?
1

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

224

Глава 8. SQL и ограничения целостности

Упражнение 8.5. В предположении, что тип QTY определен на языке
Tutorial D, как в тексте главы, – что возвращают следующие выражения?
a. THE_Q ( QTY ( 345 ) )
b. QTY ( THE_Q ( QTY ) )
Упражнение 8.6. Объясните максимально полно: (а) что такое селектор;
(б) что такое оператор THE_. Примечание: Это упражнение повторяет уже встречавшиеся в предыдущих главах, но теперь вы в состоянии дать более детальный ответ.
Упражнение 8.7. Предположим, что допус­тимы только такие города:
London, Paris, Rome, Athens, Oslo, Stockholm, Madrid и Amsterdam.
Определите на Tutorial D тип CITY, который удовлетворяет этому
ограничению.
Упражнение 8.8. Продолжая предыдущее упражнение, покажите, как
в SQL наложить соответствующее ограничение на столбцы CITY
в базовых таб­лицах S и P. Предложите по меньшей мере два решения. Сравните их между собой и со своим ответом на предыдущее
упражнение.
Упражнение 8.9. Определите номера поставщиков в виде пользовательского типа на Tutorial D. Считайте допус­тимыми только номера поставщиков, которые можно представить строкой, содержащей не менее двух символов, из которых первый равен «S», а остальные обозначают десятичное число от 1 до 9999. Сформулируйте предположения о том, какие операторы необходимы, чтобы выразить определение такого типа.
Упражнение 8.10. Дайте на языке Tutorial D определение отрезка прямой, соединяющего две точки на евклидовой плоскости.
Упражнение 8.11. Можете ли вы придумать тип, для которого желательно иметь два допус­тимых представления? Имеет ли смысл
включать ограничения типа в каждое из нескольких допус­тимых
представлений одного и того же типа?
Упражнение 8.12. Можете ли вы придумать тип, для которого различные допус­тимые представления содержат разное количество компонентов?
Упражнение 8.13. Какие операции могли бы привести к нарушению
ограничений CX1-CX9, приведенных в тексте главы?
Упражнение 8.14. Существует ли в языке Tutorial D какой-нибудь прямой аналог ограничений базовых таб­лиц в SQL?
Упражнение 8.15. Какая именно особенность SQL (дайте формальный ответ) упрощает формулировку ограничений базовых таб­лиц
по сравнению с предложением CREATE ASSERTION? Примечание:

Упражнения

225

Уже изложенного материала недостаточно для ответа на этот вопрос.
Тем не менее, подумайте над ним или обсудите в группе.
Упражнение 8.16. Продолжая тему предыдущего вопроса, скажу, что
ограничение базовой таб­лицы автоматичес­ки считается в SQL выполненным, если таб­лица, к которой оно относится, пус­та. Как вы
думаете, почему принято такое решение (я хотел бы услышать формальную причину)? Аналогично ли поведение в Tutorial D?
Упражнение 8.17. В тексте главы я сформулировал ограничение CX5
в виде ограничения на базовую таб­лицу SP. Однако я отметил, что
можно было бы записать его в виде ограничения на базовую таб­лицу
S или на таб­лицу P, да и вообще на любую базовую таб­лицу. Приведите эти альтернативные формулировки.
Упражнение 8.18. У ограничения CX1 (к примеру) было свойство, для
проверки которого для данного кортежа достаточно было исследовать только этот кортеж; у ограничения CX5 такого свойства не
было. Какая формальная особенность отвечает за это различие? Каково прагматичес­кое значение этого различия (если оно есть)?
Упражнение 8.19. Можете ли вы написать на Tutorial D ограничение
базы данных, в точности эквивалентное спецификации KEY{SNO}
для пе­ре­мен­ной-от­но­ше­ния S?
Упражнение 8.20. Приведите формулировку ограничения CX8 на SQL.
Упражнение 8.21. Напишите на Tutorial D и/или SQL ограничения для
базы данных о поставщиках и деталях, которые бы выражали следующие требования:
a. Все красные детали должны весить меньше 50 фунтов.
b. Каждый поставщик, находящийся в Лондоне, должен поставлять деталь P2.
c. Никакие два поставщика не должны находиться в одном городе.
d. В любой момент времени в Афинах не должно быть более одного
поставщика.
e. В Лондоне должен быть хотя бы один поставщик.
f. Хотя бы одна красная деталь должна весить меньше 50 фунтов.
g. Средний статус поставщика должен быть не меньше 10.
h. Ни в одной поставке количество деталей не должно более чем
вдвое превышать среднее количество деталей по всем поставкам.
i. Поставщик с максимальным статусом не может находиться
в одном городе с любым поставщиком, имеющим минимальный
статус.
j. Любая деталь должна находиться в городе, где есть хотя бы один
поставщик.

226

Глава 8. SQL и ограничения целостности

k. Любая деталь должна находиться в городе, где есть хотя бы один
поставщик этой детали.
l. Поставщики из Лондона должны поставлять больше различных
видов деталей, чем поставщики из Парижа.
m. Суммарное количество деталей, поставленных поставщиками из
Лондона, должно быть больше, чем поставленных поставщиками
из Парижа.
n. Ни для какой поставки общий вес (вес детали, умноженный на
количество в поставке) не должен превышать 20 000 фунтов.
В каждом случае укажите, какие операции могли бы нарушить ограничение. Примечание: Не забывайте про от­но­ше­ния-образы, которые могут помочь при формулировании некоторых ограничений.
Упражнение 8.22. В тексте главы я определил полное ограничение базы
данных как булево выражение вида:
( RC1 ) AND ( RC2 ) AND ... AND ( RCn ) AND TRUE

В чем смысл части «AND TRUE»?
Упражнение 8.23. В сноске в разделе «Ограничения и предикаты» я сказал, что если значения S1 и London встречаются в одном кортеже, то
это может означать (в числе многих других возможных интерпретаций), что поставщик S1 не имеет офиса в Лондоне. На самом деле,
такая интерпретация крайне маловероятна. Почему? Подсказка:
Вспомните о допущении замкнутости мира.
Упражнение 8.24. Предположим, что для поставщиков и поставок задано правило «каскадного удаления». Напишите на языке Tutorial D
предложение, которое удалит указанного поставщика и все его поставки за одну операцию (то есть не давая шанс нарушить ограничение ссылочной целостности).
Упражнение 8.25. Применяя синтаксис, намеченный для ограничений
перехода в разделе «Разное», напишите ограничения перехода, которые выражали бы следующие требования:
a. Поставщики из Афин могут переезжать только в Лондон или Париж, а поставщики из Лондона могут переезжать только в Париж.
b. Общее поставленное количество деталей данного вида не может
уменьшаться.
c. Общее количество деталей, поставленное данным поставщиком,
не может быть уменьшено в одной операции обновления более
чем на половину текущего значения. (Как вы понимаете уточнение «в одной операции обновления»? Почему оно важно? И важно ли оно?)
Упражнение 8.26. В чем различие между правильностью и непротиворечивостью?

Упражнения

227

Упражнение 8.27. Исследуйте любую доступную вам SQL-сис­тему. Поддерживаются ли в ней какие-нибудь семантичес­кие оптимизации?
Упражнение 8.28. Как вы думаете, почему SQL не поддерживает ограничения типа? Каковы последствия такого положения вещей?
Упражнение 8.29. При обсуждении в этой главе типов вообще и ограничений типов в частности молчаливо подразумевалось, что все типы
(а) скалярные и (б) определенные пользователем. В какой мере изложенные положения применимы к нескалярным и/или сис­темным
типам?
Упражнение 8.30. Встречаются ли в каких-нибудь найденных вами решениях упражнений на SQL «потенциально недетерминированные
выражения»? Если да, то составляет ли это проблему? Если составляет, то что с этим можно поделать?

Глава

9
.

SQL и представления
Есть несколько интуитивно понятных способов описать, что такое представление. Все они корректны и могут оказаться полезны в определенных ситуациях.
•• Представление – это виртуальная пе­ре­мен­ная-от­но­ше­ние, то есть
оно «выглядит» как базовая пе­ре­мен­ная-от­но­ше­ние, но, в отличие
от последней, не существует независимо от других пе­ре­мен­ных-от­
но­ше­ний – фактичес­ки, оно и определено-то в терминах других пе­
ре­мен­ных-от­но­ше­ний.
•• Представление – это производная пе­ре­мен­ная-от­но­ше­ние, то есть
оно явно произведено (и пользователям, по крайней мере некоторым,
об этом известно) из других пе­ре­мен­ных-от­но­ше­ний. Примечание:
На всякий случай поясню, что любая виртуальная пе­ре­мен­ная-от­но­
ше­ние является производной, но существуют производные пе­ре­мен­
ные-от­но­ше­ния, не являющиеся виртуальными. См. раздел «Представления и снимки» ниже в этой главе.
•• Представление – это «окно», через которое можно смотреть на те пе­
ре­мен­ные-от­но­ше­ния, из которых оно произведено; таким образом,
операции над представлением на самом деле производятся над базовыми пе­ре­мен­ными-от­но­ше­ниями.
•• Представление – это то, что некоторые авторы называют «готовым
запросом» (или, более точно, именованным реляционным выражением).
Как обычно, я буду рассматривать эти понятия в терминах реляционной
теории и SQL. Но по поводу SQL позвольте напомнить сказанное в главе 1: представление – это таб­лица! – или, как я предпочитаю говорить,
пе­ре­мен­ная-от­но­ше­ние. В документации по SQL часто встречаются выражения типа «таб­лицы и представления», наводящие на мысль, будто таб­лицы и представления – вещи разные. Это не так, вся суть пред-

229

Представления – это пе­ре­мен­ные-от­но­ше­ния

ставлений в том, что это таб­лицы. Поэтому не впадайте в распространенную ошибку и не считайте, что термин таб­лица относится исключительно к базовым таб­лицам. Тот, кто так думает, мыслит не реляционно и, вероятно, из-за этого будет допускать ошибки; собственно, ряд
таких ошибок можно найти в самом языке SQL. Дейст­вительно, можно сказать, что даже названия операторов CREATE TABLE и CREATE
VIEW – ошибка, по крайней мере психологичес­кая, так как тем самым
утверждается (а) мысль о том, что термин таб­лица относится только
к базовым таб­лицам, и (б) мысль о том, что таб­лицы и представления –
разные вещи. Берегитесь путаницы!
Последнее предварительное замечание: вопрос о том, следует ли «всегда» обращаться к базе данных через представления, обсуждается в разделе «Именование столбцов в SQL» главы 3 и в разделе «Зависимость от
имен атрибутов» главы 6.

Представления – это пе­ре­мен­ные-от­но­ше­ния
Из перечисленных выше неформальных характеристик представления
можно извлечь следующее предпочтительное определение, хотя все неформальные характеристики ему, в общем-то, эквивалентны.
Определение: Представление V – это пе­ре­мен­ная-от­но­ше­ние, значением которого в момент времени t является результат вычисления
некоторого реляционного выражения в момент t. Это выражение
(выражение, определяющее представление) задается при определении V и должно содержать хотя бы одну ссылку на пе­ре­мен­ную-от­
но­ше­ние.
Следующие примеры («поставщики в Лондоне» и «поставщики не в Лондоне») уже приводились в главе 8, но сейчас я дам также определения на
SQL:
VAR LS VIRTUAL
| CREATE VIEW LS
( S WHERE CITY = ‘London’ ) ; |
AS ( SELECT *
|
FROM S
|
WHERE CITY = ‘London’ )
|
WITH CHECK OPTION ;
VAR NLS VIRTUAL
( S WHERE CITY ј ‘London’ ) ;

|
|
|
|
|

CREATE VIEW NLS
AS ( SELECT *
FROM S
WHERE CITY ‘London’ )
WITH CHECK OPTION ;

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

230

Глава 9. SQL и представления

•• Скобки в этих примерах необязательны, но и не являются ошибкой.
Я включил их только ради понятности;
•• В SQL-предложении CREATE VIEW после имени представления может находиться заключенный в скобки список имен столбцов, разделенных запятыми, например:
CREATE VIEW SDS ( SNAME , DOUBLE_STATUS )
AS ( SELECT DISTINCT SNAME , 2 * STATUS
FROM S ) ;

Рекомендация: Не делайте так – следуйте рекомендациям из раздела «Именование столбцов в SQL» главы 3. Так, представление SDS
можно с тем же успехом (и даже лучше) определить следующим образом:
CREATE VIEW SDS
AS ( SELECT DISTINCT SNAME , 2 * STATUS AS DOUBLE_STATUS
FROM S ) ;

Отмечу, в частности, что при таком подходе нужно лишь один, а не
два раза сообщить сис­теме о наличии столбца с именем SNAME.
•• Предложение CREATE VIEW в SQL допускает также фразу WITH
CHECK OPTION, если представление рассматривается как обновление. Рекомендация: По возможности старайтесь включать эту фразу. См. также раздел «Операции обновления» ниже.

Принцип взаимозаменяемости
Коль скоро представления – это пе­ре­мен­ные-от­но­ше­ния, практичес­ки
все сказанное в предыдущих главах о пе­ре­мен­ных-от­но­ше­ниях в общем, относится в частности и к представлениям. Ниже мы подробно
обсудим различные аспекты этого наблюдения. Но сначала необходимо
разъяснить более фундаментальный момент.
Снова рассмотрим пример с поставщиками из Лондона и не из Лондона. Здесь S – базовая пе­ре­мен­ная-от­но­ше­ние, а LS и NLS – представления. Но все могло бы быть и наоборот, то есть ничто не мешает сделать
LS и NLS базовыми пе­ре­мен­ными-от­но­ше­ниями, а S – представлением
(для простоты показана только формулировка на Tutorial D):
VAR LS BASE RELATION
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;
VAR NLS BASE RELATION
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;
VAR S VIRTUAL ( LS D_UNION NLS ) ;

Представления – это пе­ре­мен­ные-от­но­ше­ния

231

(Напомню, что D_UNION – «дизъюнктное объединение» – это вариант
объединения, при котором требуется, чтобы у операндов не было общих кортежей.) Примечание: Чтобы гарантировать, что эта конструкция логичес­ки эквивалентна исходной, нам пришлось бы задать и проверять некоторые ограничения, – в частности, что в LS атрибут CITY
в любом кортеже имеет значение London, а в NLS, напротив, кортежей
с таким значением CITY нет, – но эти детали я здесь опущу. В разделе
«Представления и ограничения» мы еще вернемся к этой теме.
Как бы то ни было, смысл этого примера в том, что в общем случае безразлично, какие пе­ре­мен­ные-от­но­ше­ния делать базовыми, а какие –
виртуальными (по крайней мере, с формальной точки зрения). Мы могли бы спроектировать базу данных как минимум двумя разными способами, которые логичес­ки различны, но информационно эквивалентны. (Под информационной эквивалентностью я здесь понимаю, что
в обоих случаях представлена одна и та же информация, то есть для
любого запроса к одной базе найдется логичес­ки эквивалентный ему
запрос к другой базе.) Отсюда естественно вытекает принцип взаимозаменяемости:
Определение: Принцип взаимозаменяемости (базовых и вир­
туальных пе­ре­мен­ных-от­но­ше­ний) утверждает, что не должно быть произвольных, не обусловленных необходимостью
различий между базовыми и виртуальными пе­ре­мен­нымиот­но­ше­ниями, то есть виртуальные пе­ре­мен­ные-от­но­ше­ния
должны «выглядеть» неотличимо от базовых с точки зрения
пользователя.
Вот некоторые следствия этого принципа.
•• Я уже отмечал мимоходом, что представления, как и базовые пе­ре­
мен­ные-от­но­ше­ния, могут быть субъектами ограничений целостности. (Обычно мы считаем, что ограничения целостности применяются только к базовым пе­ре­мен­ным-от­но­ше­ниям, но принцип взаимозаменяемости показывает, что это мнение ни на чем не основано.)
См. раздел «Представления и ограничения» ниже.
•• В частности, у представлений могут быть потенциальные ключи
(поэтому мне, наверное, следовало включить спецификации какихнибудь ключей в примеры представлений; Tutorial D допускает наличие таких спецификаций, а SQL – нет). Они могут иметь также
внешние ключи, и, наоборот, внешние ключи могут ссылаться на
представления. Опять же см. раздел «Представления и ограничения» ниже.
•• Я не упомянул об этом в главе 1, но предполагается, что правило
«целостности сущностей» относится исключительно к базовым пе­
ре­мен­ным-от­но­ше­ниям, а не к представлениям, и потому нарушает
принцип взаимозаменяемости. Конечно, я в принципе отвергаю это

232

Глава 9. SQL и представления

правило, так как оно связано с null-значениями. (Я отвергаю его также потому, что оно касается только первичных ключей, а не потенциальных ключей вообще, но это просто попутное замечание.)
•• Во многих SQL-сис­темах и в стандарте SQL предлагается некоторый
вид «идентификаторов строк». Если эта функция применима только к базовым таб­лицам, а не к представлениям, – а на практике это
весьма вероятно, – то она нарушает принцип взаимозаменяемости.
(Она может нарушать также и принцип информации. См. приложение A.) Идентификаторы строк как таковые не являются частью реляционной модели, но это еще не означает, что их не следует поддерживать. Однако хочу заметить, что если такие идентификаторы
рассматриваются (а, к величайшему сожалению, и в стандарте SQL,
и в большинстве основных SQL-сис­тем так оно и есть) как некая разновидность идентификаторов объектов в объектно-ориентированном
смысле, то их, безусловно, следует запретить! Идентификаторы объектов по сути своей являются указателями, а в реляционной модели
(см. главу 2) указатели явно запрещены.
•• Обсуждавшиеся в предыдущей главе различия между ограничениями с одной и несколькими пе­ре­мен­ными-от­но­ше­ниями скорее кажущиеся, чем реально существующие (как раз поэтому употребление
такой терминологии не поощряется). Как показывает ранее приведенный пример, одно и то же ограничение может включать одну пе­
ре­мен­ную-от­но­ше­ние при одной структуре базы данных и несколько – при другой.
•• И пожалуй, самое главное – должна быть возможность обновлять
представления, потому что, если это не так, то мы имеем очевидное
нарушение принципа взаимозаменяемости. См. раздел «Операции
обновления» ниже.

Константы-от­но­ше­ния
Возможно, вы обратили внимание на то, что в приведенном выше формальном определении требуется, чтобы выражение, определяющее
представление, ссылалось хотя бы на одну пе­ре­мен­ную-от­но­ше­ние. Почему? Потому что в противном случае «виртуальная пе­ре­мен­ная-от­но­
ше­ние» вообще не была бы пе­ре­мен­ной-от­но­ше­нием! Я хочу сказать, что
она не была бы пе­ре­мен­ной и не допускала бы обновления. Например,
следующее предложение CREATE VIEW является допус­тимым в SQL:
CREATE VIEW S_CONST ( SNO , SNAME , STATUS , CITY ) AS
VALUES ( ‘S1’ , ‘Smith’ , 20 , ‘London’ )
( ‘S2’ , ‘Jones’ , 10 , ‘Paris’ )
( ‘S3’ , ‘Blake’ , 30 , ‘Paris’ )
( ‘S4’ , ‘Clark’ , 20 , ‘London’ )
( ‘S5’ , ‘Adams’ , 30 , ‘Athens’ )

,
,
,
,
;

Но такое представление, разумеется, нельзя обновить. Иными словами,
это и не пе­ре­мен­ная вовсе, что уж говорить о виртуальности; скорее, ее

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

233

можно назвать именованной константой-от­но­ше­нием. Позвольте пояснить.
•• Прежде всего, я считаю термины константа и значение синонимами. Отмечу поэтому, что существует логичес­кое различие между
константой и литералом; литерал является не константой, а символом (иногда его называют «самоопределенным» символом), который
обозначает константу.
•• Строго говоря, существует также логичес­кое различие между константой и именованной константой; константа – это значение, а именованная константа похожа на пе­ре­мен­ную с тем отличием, что ее
значение нельзя изменять. Тем не менее, в дальнейшем я буду считать, что термин константа означает именованную константу.
•• Естественно, константы могут иметь любой тип, но меня здесь интересуют только константы-от­но­ше­ния. В настоящее время язык
Tutorial D не поддерживает константы-от­но­ше­ния, но, если бы поддерживал, то определение константы-от­но­ше­ния, наверное, выглядело бы примерно так:
CONST PERIODIC_TABLE INIT ( RELATION
{ TUPLE { ELEMENT ‘Hydrogen’ , SYMBOL ‘H’ , ATOMICNO 1 } ,
TUPLE { ELEMENT ‘Helium’ , SYMBOL ‘He’ , ATOMICNO 2 } ,
....................................................
TUPLE { ELEMENT ‘Uranium’ , SYMBOL ‘U’ , ATOMICNO 92 } } ) ;

Я полагаю, что было бы желательно обеспечить какую-то поддержку
констант-от­но­ше­ний в описанном духе. На самом деле, в языке Tu­to­
ri­al D уже есть две сис­темных константы-от­но­ше­ния: TABLE_DUM
и TABLE_DEE, которые, как мы знаем, весьма важны. Но, если не считать этих двух, то ни Tutorial D, ни SQL пока не поддерживают кон­
стант-от­но­ше­ний. Правда, такую поддержку можно эмулировать с помощью существующего механизма представлений (мы это уже видели), но между константами и пе­ре­мен­ными существует гигантское
логичес­кое различие, поэтому я не думаю, что, притворившись, будто
кон­станты-от­но­ше­ния являются пе­ре­мен­ными-от­но­ше­ниями, мы както проясним ситуацию.

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

234

Глава 9. SQL и представления

но, уже знаете – в главе 6 я объяснял, что с каждым реляционным выражением ассоциирован предикат, и, разумеется, представлению соответствует именно тот предикат, который ассоциирован с определяющим его выражением. Рассмотрим снова представление LS («поставщики в Лондоне»), которое было определено в начале раздела «Представления – это пе­ре­мен­ные-от­но­ше­ния». Это представление является ограничением пе­ре­мен­ной-от­но­ше­ния S, следовательно, его предикат есть
логичес­кое AND предиката S и условия ограничения:
Поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY
AND
город CITY – это London.
Или в более естественной форме:
Поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в Лондоне.
Отметим, однако, что эта более естественная форма затушевывает тот
факт, что CITY – параметр. А ведь это дейст­вительно параметр, но соответствующий ему аргумент всегда равен константе ‘London’. (Именно
по этой причине на практике в представлении LS атрибут CITY, скорее
всего, был бы удален из проекции.)
Аналогично предикат для представления NLS выглядит так:
Поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY, отличном от Лондона.

Операции выборки
Принцип взаимозаменяемости подразумевает, что пользователь может
оперировать представлениями, как будто это базовые пе­ре­мен­ные-от­но­
ше­ния, а СУБД должна уметь отображать эти операции на подходящие
операции над «самыми» базовыми пе­ре­мен­ными-от­но­ше­ниями, в терминах которых определено представление. Примечание: я употребил
слово «самыми», потому что, коль скоро представления во всем подобны базовым пе­ре­мен­ным-от­но­ше­ниям, то мы, в частности, можем определять на их основе новые представления, как в примере ниже:
CREATE VIEW LS_STATUS
AS ( SELECT SNO , STATUS
FROM LS ) ;

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

Операции выборки

235

базовыми пе­ре­мен­ными-от­но­ше­ниями довольно проста. Рассмотрим,
к примеру, такой SQL-запрос к представлению LS:
SELECT LS.SNO
FROM LS
WHERE LS.STATUS > 10

Сначала СУБД заменяет ссылку на представление во фразе FROM выражением, определяющим это представление. В результате получаем:
SELECT LS.SNO
FROM ( SELECT S.*
FROM S
WHERE S.CITY = ‘London’ ) AS LS
WHERE LS.STATUS > 10

Это выражение можно уже вычислить непосредственно. Однако –
и с точки зрения производительности это, пожалуй, существенно – его
можно упростить до:
SELECT
FROM
WHERE
AND

S.SNO
S
S.CITY = ‘London’
S.STATUS > 10

По всей вероятности, именно это выражение и будет реально вычислено.
Важно понимать, что описанная процедура работает лишь благодаря
свойству реляционной замкнутости. В числе прочего замкнутость подразумевает, что всюду, где можно употребить какое-то имя (например,
в запросе), можно подставить и более сложное выражение, при вычислении которого получается нечто подходящего типа.1 Так, во фразе FROM
может находиться имя SQL-таб­лицы, следовательно, может находиться и более общее таб­личное выражение SQL. Именно поэтому мы и смогли подставить выражение, определяющее представление LS, вместо самого имени LS.
По очевидным причинам описанная выше процедура реализации операций чтения над представлениями называется процедурой подстановки. Кстати, стоит отметить, что эта процедура не работала в ранних
версиях SQL; если быть точным, в версиях до 1992 года. Причина в том,
что в ранних версиях не в полной мере поддерживалось свойство замк­
нутости. А в результате безобидные, на первый взгляд, запросы к ничем, казалось бы, не примечательным таб­лицам (а на самом деле представлениям) завершались ошибкой, да еще так, что и объяснить, отку-

1

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

236

Глава 9. SQL и представления

да взялась ошибка, было затруднительно. Вот простой пример. Рассмотрим такое определение представления:
CREATE VIEW V
AS ( SELECT CITY , SUM ( STATUS ) AS ST
FROM S
GROUP BY CITY ) ;

И такой запрос:
SELECT CITY
FROM V
WHERE ST > 25

В версиях SQL до 1992 года этот запрос завершился бы ошибкой, поскольку простая подстановка дала бы синтаксичес­ки недопус­тимое выражение:
SELECT
FROM
WHERE
GROUP

CITY
S
SUM ( STATUS ) > 25
BY CITY

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

Представления и ограничения
Из принципа взаимозаменяемости следует, что представления, как
и базовые пе­ре­мен­ные-от­но­ше­ния, имеют не только предикаты, но
и ограничения – как индивидуальные, так и полные ограничения пе­
ре­мен­ной-от­но­ше­ния (см. главу 8). Но, как и предикаты, ограничения,
применяемые к представлению V, являются производными от ограничений для тех пе­ре­мен­ных-от­но­ше­ний, на основе которых определено
V, и в соответствии с семантикой реляционных операций, которые участвуют в выражении, определяющем представление. В качестве примера снова рассмотрим представление LS. Оно является ограничением пе­ре­мен­ной-от­но­ше­ния S, то есть определяющее его выражение задает операцию ограничения над пе­ре­мен­ной-от­но­ше­нием S, и потому
его (полное) ограничение пе­ре­мен­ной-от­но­ше­ния представляет собой

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

237

логичес­кое AND между (полным) ограничением пе­ре­мен­ной-от­но­ше­
ния S и заданным условием ограничения. Предположим для определенности, что к базовой пе­ре­мен­ной-от­но­ше­нию S применимо только
ограничение ключа, обусловленное тем фактом, что {SNO} – ключ. Тогда полное ограничение пе­ре­мен­ной-от­но­ше­ния для представления LS
является результатом применения AND к этому ограничению ключа
и к ограничению «город должен быть Лондоном», и представление LS
должно в любой момент удовлетворять этому ограничению (другими
словами, золотое правило применимо к представлениям точно так же,
как к базовым пе­ре­мен­ным-от­но­ше­ниям).
Для простоты я буду, начиная с этого места, употреблять термин ограничение представления для обозначения любых ограничений, применимых к некоторому представлению. Из того, что ограничения представления всегда являются производными в описанном выше смысле, еще не следует, что на практике их не нужно объявлять явно. Вопервых, сис­тема не всегда достаточно «разумна», чтобы автоматичес­ки
вывести ограничения, применимые к некоторому представлению. Вовторых, такие явные объявления полезны хотя бы для документирования (то есть помогают понять семантику представления если не сис­теме,
то пользователям). Есть и еще одна причина, о которой я скажу позже.
Таким образом, я утверждаю, что должна иметься возможность явного объявления ограничений для представлений. В частности, должно быть возможно (а) включать явные спецификации KEY и FOREIGN
KEY в определения представлений и (б) разрешить указание представления в качестве целевой пе­ре­мен­ной-от­но­ше­ния в спецификации
FOREIGN KEY. Вот пример, иллюстрирующий требование (а):
VAR LS VIRTUAL ( S WHERE CITY = ‘London’ )
KEY { SNO } ;

Язык Tutorial D допускает такие спецификации, SQL – нет. Рекомен­
дация: В SQL включайте такие спецификации в виде комментариев,
например:
CREATE VIEW LS
AS ( SELECT *
FROM S
WHERE CITY = ‘London’ )
/* UNIQUE ( SNO ) */
WITH CHECK OPTION ;

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

238

Глава 9. SQL и представления

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

Сказав, что должна быть возможность явно объявлять ограничения для
представлений, я должен добавить, что иногда этого лучше не делать,
поскольку это может повлечь за собой избыточные проверки. Например,
я уже говорил, что спецификация KEY{SNO}, очевидно, применяется
к представлению LS, но лишь потому, что она применяется и к базовой
пе­ре­мен­ной-от­но­ше­нию S. Поэтому, если явно объявить ее еще и для
представления LS, то мы просто будем проверять одно и то же ограничение дважды. (Но все равно это ограничение следует как-то включить
в документацию, потому что оно – часть семантики представления.)
Но, пожалуй, важнее тот факт, что существуют-таки ситуации, когда
явное объявление ограничений представления – идея здравая. Вот пример, написанный для определенности в терминах SQL. Пусть даны следующие две базовые таб­лицы (без деталей):
CREATE TABLE FDH
( FLIGHT ... , DESTINATION ... , HOUR ... ,
UNIQUE ( FLIGHT ) ) ;
CREATE TABLE DFGP
( DAY ... , FLIGHT ... , GATE ... , PILOT ... ,
UNIQUE ( DAY , FLIGHT ) ) ;

Предикаты этих таб­лиц можно сформулировать так: рейс FLIGHT в город DESTINATION вылетает в час HOUR (для FDH) и в день DAY посадка на рейс FLIGHT с пилотом PILOT производится из выхода GATE
(для DFGP). На таб­лицы наложены следующие ограничения (выраженные в виде псевдокода):
IF ( f1,n1,h ), ( f2,n2,h ) IN FDH AND
( d,f1,g,p1 ), ( d,f2,g,p2 ) IN DFGP
THEN f1 = f2 AND p1 = p2
IF ( f1,n1,h ), ( f2,n2,h ) IN FDH AND
( d,f1,g1,p ), ( d,f2,g2,p ) IN DFGP
THEN f1 = f2 AND g1 = g2

Объяснение: Первое ограничение говорит, что если (а) в двух строках
FDH одинаковы значения часа h и (б) в двух строках DFGP, по одной
для рейсов f1 и f2 из указанных выше строк FDH, одинаковы значения дня d и выхода g, то обе строки FDH должны совпадать и обе строки DFGP должны совпадать. Иными словами, если известны час, день
и выход, то рейс и пилот однозначно определены. Второе ограничение
аналогично: если (а) в двух строках FDH одинаковы значения часа h
и (б) в двух строках DFGP, по одной для рейсов f1 и f2 из указанных

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

239

выше строк FDH, одинаковы значения дня d и пилота p, то обе строки
FDH должны совпадать и обе строки DFGP должны совпадать. Иными
словами, если известны час, день и пилот, то рейс и выход однозначно
определены.
Сформулировать эти ограничения непосредственно в терминах базовых
таб­лиц отнюдь не тривиально:
CREATE ASSERTION BTCX1 CHECK
( NOT ( EXISTS ( SELECT * FROM FDH AS FX WHERE
EXISTS ( SELECT * FROM FDH AS FY WHERE
EXISTS ( SELECT * FROM DFGP AS DX WHERE
EXISTS ( SELECT * FROM DFGP AS DY WHERE
FY.HOUR = FX.HOUR AND
DX.FLIGHT = FX.FLIGHT AND
DY.FLIGHT = FY.FLIGHT AND
DY.DAY = DX.DAY AND
DY.GATE = DX.GATE AND
( FX.FLIGHT FY.FLIGHT OR
DX.PILOT DY.PILOT ) ) ) ) ) ) ) ;
CREATE ASSERTION BTCX2 CHECK
( NOT ( EXISTS ( SELECT * FROM FDH AS FX WHERE
EXISTS ( SELECT * FROM FDH AS FY WHERE
EXISTS ( SELECT * FROM DFGP AS DX WHERE
EXISTS ( SELECT * FROM DFGP AS DY WHERE
FY.HOUR = FX.HOUR AND
DX.FLIGHT = FX.FLIGHT AND
DY.FLIGHT = FY.FLIGHT AND
DY.DAY = DX.DAY AND
DY.PILOT = DX.PILOT AND
( FX.FLIGHT FY.FLIGHT AND
DX.GATE DY.GATE ) ) ) ) ) ) ) ;

Зато формулировка в виде ограничений ключей в определении представления, будь она разрешена, выглядела бы весьма лаконично:
CREATE VIEW V AS
( FDH NATURAL JOIN DFGP ,
UNIQUE ( DAY , HOUR , GATE ) ,
/* гипотетичес­кий */
UNIQUE ( DAY , HOUR , PILOT ) ) ; /* синтаксис !!!! */

Поскольку такое решение нам недоступно, нужно хотя бы задать эти
гипотетичес­кие ограничения представления в виде утверждений:
CREATE VIEW V AS FDH NATURAL JOIN DFGP ;
CREATE ASSERTION VCX1
CHECK ( UNIQUE ( SELECT DAY , HOUR , GATE FROM V ) ) ;
CREATE ASSERTION VCX2
CHECK ( UNIQUE ( SELECT DAY , HOUR , PILOT FROM V ) ) ;

240

Глава 9. SQL и представления

Для задания этих ограничений даже не обязательно определять представление V – достаточно заменить ссылки на V в выражении UNIQUE
выражением, определяющим представление V:
CREATE ASSERTION VCX1
CHECK ( UNIQUE ( SELECT DAY , HOUR , GATE
FROM FDH NATURAL JOIN DFGP ) ) ;
CREATE ASSERTION VCX2
CHECK ( UNIQUE ( SELECT DAY , HOUR , PILOT
FROM FDH NATURAL JOIN DFGP ) ) ;

Операции обновления
Из принципа взаимозаменяемости следует, что представления должны допускать обновление (то есть присваивание). Я уже слышу возражения читателей: «Но ведь представление же возможно обновить, разве не так?» Взять, к примеру, представление, которое определено как соединение – многие ко многим, заметим, – пе­ре­мен­ных-от­но­ше­ний S и P
по атрибуту {CITY}; разумеется, для него мы не можем ни вставить, ни
удалить кортеж, так? Примечание: Приношу извинения за небрежность
формулировок; как мы знаем из главы 5, в реляционной модели нельзя говорить о «вставке или удалении кортежа». Но излишняя педантичность в данном случае только помешала бы пониманию сути дела.
Что ж, даже если мы дейст­вительно не можем вставить или удалить кортеж для S JOIN P (впрочем, это еще вилами на воде писано), позвольте
заметить, что некоторые обновления некоторых базовых пе­ре­мен­ныхот­но­ше­ний тоже невозможны. Например, вставить кортеж в пе­ре­мен­
ную-от­но­ше­ние SP не получится, если содержащееся в этом кортеже
значение SNO отсутствует в пе­ре­мен­ной-от­но­ше­нии S. Таким образом,
обновление базовых пе­ре­мен­ных-от­но­ше­ний может завер­шаться неудачно из-за нарушения ограничений целостности, и то же самое справедливо для обновления представлений. Иными словами, дело не в том,
что некоторые представления принципиально не обновляемы, а в том,
что некоторые обновления некоторых представлений не проходят из-за
нарушения ограничений целостности (то есть нарушений золотого пра­
вила). Примечание: На самом деле обновления – как базовых пе­ре­мен­
ных-от­но­ше­ний, так и представлений, – могут завершаться и ошибкой
и вследствие нарушения принципа присваивания. Но для простоты я не
стану обращать на это внимания.
Итак, пус­ть V – представление. Чтобы корректно поддержать обновление V, сис­тема должна знать полное ограничение для V, назовем его VC.
Иначе говоря, она должна уметь выводить ограничения, то есть, зная,
какие ограничения применяются к пе­ре­мен­ным-от­но­ше­ниям, в терминах которых определено V, уметь вычислить VC. Но вы наверняка знаете, что современные SQL-сис­темы очень плохо справляются с задачей

Операции обновления

241

вывода ограничений. Поэтому и поддержка обновления представления
разработана так слабо (и это относится не только к основным продуктам, но и к самому стандарту). Фактичес­ки SQL-сис­темы обычно позволяют обновлять лишь представления, которые определены как простое
сочетание ограничения и проекции для одной базовой таб­лицы (и даже
тут есть сложности). Обратимся снова к представлению LS. Это всего
лишь ограничение базовой таб­лицы S, поэтому мы можем выполнить
для него следующую операцию DELETE:
DELETE
FROM LS
WHERE LS.STATUS > 15 ;

Эта операция отображается на такую:
DELETE
FROM S
WHERE S.CITY = ‘London’
AND
S.STATUS > 15 ;

Однако, как я уже отметил, немногие продукты поддерживают обновление более сложных представлений. Примечание: я еще вернусь к вопросу об обновляемости представлений в SQL в подразделе «Еще об SQL».

Опция CHECK
Рассмотрим следующее SQL-предложение INSERT для представления
LS:
INSERT INTO LS ( SNO , SNAME , STATUS , CITY )
VALUES ( ‘S6’ , ‘Lopez’ , 30 , ‘Madrid’ ) ;

Эта операция отображается на такую:
INSERT INTO S ( SNO , SNAME , STATUS , CITY )
VALUES ( ‘S6’ , ‘Lopez’ , 30 , ‘Madrid’ ) ;

(Изменено лишь имя обновляемой базовой таб­лицы.) Заметим, что «новая строка» нарушает полное ограничение для представления LS, потому что город отличен от Лондона. Так что же произойдет? По умолчанию
SQL вставит строку в базовую таб­лицу S, но поскольку она не удовлетворяет выражению, определяющему представление (именно представление LS), то в этом представлении строка не будет видна. Следовательно,
с точки зрения представления LS новая строка просто пропала из виду
(можно также сказать, что с той же точки зрения операция INSERT оказалась «пус­той»). Однако это нарушение принципа присваивания.
Полагаю, никого не нужно убеждать, что такое поведение логичес­ки
некорректно. В языке Tutorial D оно попросту невозможно. Что же касается SQL, то для решения этой проблемы в нем существует опция
CHECK: если (и только в этом случае) для представления задана спецификация WITH CASCADED CHECK OPTION, то любое его обновле-

242

Глава 9. SQL и представления

ние должно быть согласовано с выражением, определяющим представление. Рекомендация: Всюду, где возможно, включайте в определения
представлений спецификацию WITH CASCADED CHECK OPTION. Но
имейте в виду, что SQL допускает наличие такой спецификации лишь
в том случае, когда считает представление обновляемым, а (как уже
отмечалось) не все представления, логичес­ки допускающие обновление, считаются таковыми в SQL. Примечание: Альтернативой слову
CASCADED служит LOCAL, но не пользуйтесь этой возможностью.1
Впрочем, можно вообще не указывать ни CASCADED, ни LOCAL, так
как CASCADED подразумевается по умолчанию.

Еще об SQL
Как мы видели, поддержка обновления представлений в SQL ограничена. К тому же в ней очень трудно разобраться – стандарт в этом вопросе
еще более непостижим, чем обычно! Следующая цитата (лишь немного
подредактированная) дает представление о возникающих сложностях:
(query expression) QE1 допускает обновление
тогда и только тогда, когда для любого (query
expression) или (query specification) QE2, которое просто содержится (simply contained) в QE1:

1

a)

QE1 содержит QE2 без промежуточного (non join query expression) вида UNION DISTINCT,
EXCEPT ALL или EXCEPT DISTINCT.

b)

Если QE1 просто содержит
(non join query expression) NJQE вида UNION ALL, то:
i)

NJQE непосредственно содержит (query expression) LO и (query term)
RO такие, что никакая листовая обобщенно исходная
таб­лица LO (leaf generally underlying table) не является
также листовой обобщенно исходной таб­лицей RO.

ii)

Для каждого столбца NJQE исходные столбцы (under­
lying columns) таб­лиц, определяемые соответственно по
LO и RO, одновременно допускают или не допускают обновление.

c)

QE1 содержит QE2 без промежуточного (non join query term) вида INTERSECT.

d)

QE2 допускает обновление.

Семантика WITH LOCAL CHECK OPTION слишком причудлива, чтобы
описывать ее здесь во всех подробностях. Но в любом случае непонятно,
кому может понадобиться такая семантика; невольно закрадывается подозрение, что она была включена в стандарт только для того, чтобы производители каких-то существовавших в то время некорректных реализаций
могли заявить о соответствии стандарту.

Операции обновления

243

Вот что лично я думаю об этой цитате…
•• Это лишь одно из многих правил, которые нужно учитывать при определении того, допускает ли данное представлениеобновление в SQL.
•• Правила, относящиеся к этому вопросу, приведены не в одном месте,
а разбросаны по разным частям документа.
•• Формулировки этих правил зависят от различных дополнительных
понятий и конструкций, например: обновляемые столбцы, листовые
обобщенно исходные таб­лицы, , которые сами определены в других частях документа.
Поэтому я даже не буду пытаться точно описать, какие представления
в SQL считаются допускающими обновление. Но, говоря нестрого, к ним
относятся, по крайней мере, следующие:
1. Представления, определенные как ограничение и/или проекция
одной базовой таб­лицы.
2. Представления, определенные как соединение один-к-одному или
один-ко-многим двух базовых таб­лиц (в случае соединения один-комногим обновление допускает только таб­лица на стороне «многие»).
3. Представления, определенные как результат операции UNION ALL
или INTERSECT для двух различных базовых таб­лиц.
4. Некоторые комбинации пунктов 1–3 выше.
Но даже эти случаи обрабатываются некорректно из-за того, что SQL
«не понимает», что такое вывод ограничений, золотое правило и принцип присваивания, а также из-за наличия в SQL null-значений и строкдубликатов. Картина становится еще запутаннее вследствие того, что
в SQL выделено четыре разных случая, когда представление допускает обновление: обновляемое, потенциально обновляемое, просто обновляемое и допускающее вставку. В стандарте эти термины определены
формально, но нет даже намека на то, что они означают на интуитивном уровне и почему так названы. Но я хотя бы могу сказать, что «обновляемое» относится к UPDATE и DELETE, а «допускающее вставку» – к INSERT, и что представление не может допускать вставку, не будучи обновляемым. Отмечу, однако, что могут существовать представления, которые допускают одни операции обновления, но не допускают другие (например, DELETE, но не INSERT), и что операции DELETE
и INSERT не обязательно являются взаимно обратными. Оба эти факта,
если их можно назвать фактами, также являются нарушениями принципа взаимозаменяемости.
Что касается случая 1 выше, то я могу немного уточнить ситуацию. Конкретно, SQL-представление, безусловно, допускает обновление, если одновременно удовлетворяются следующие условия:
•• Выражение, определяющее представления, – это либо (а) простое выражение SELECT (без UNION, INTERSECT и EXCEPT), либо (б) «явная таб­лица» (см. главу 12), которая логичес­ки эквивалентна такому

244

Глава 9. SQL и представления

выражению. Примечание: Для простоты я в дальнейшем буду предполагать, что случай (б) автоматичес­ки преобразуется в случай (а).
•• Во фразе SELECT этого выражения SELECT задан спецификатор
ALL (явно или неявно).
•• После раскрытия всех элементов, заданных звездочками, каждый
элемент в списке является простым именем столбца (возможно, квалифицированным или содержащим спецификатор AS), и ни один
элемент не встречается более одного раза.
•• Фраза FROM в этом выражении SELECT содержит ровно одну ссылку
на таб­лицу (см. главу 12), и таб­личное выражение в этой ссылке представляет собой только имя (скажем, T) таб­лицы, допускающей обновление (либо базовой таб­лицы, либо обновляемого представления).
•• Если в этом выражении SELECT присутствует фраза WHERE, то она
не содержит подзапросов, в которых фраза FROM ссылается на T.
•• В этом выражении SELECT нет фраз GROUP BY или HAVING.
Рекомендация: Настаивайте на том, чтобы производители SQL-сис­тем
как можно скорее улучшили поддержку в части обновления представлений.

Зачем нужны представления?
До сих пор в этой главе я молчаливо предполагал, что вам известно,
для чего нужны представления, но теперь все же хочу сказать несколько слов на эту тему. Фактичес­ки представления служат двум совершенно разным целям.
•• Пользователь, который определяет представление V, очевидно, знает
соответствующее выражение X, определяющее представление. По­
этому он может использовать имя V всюду, где подразумевается выражение X. Но такое применение – не более чем сокращенная запись,
и автор представления именно так это и понимает.
•• Напротив, пользователь, которому известно лишь, что V существует и доступно для применения, предположительно (по крайней мере,
в идеале) не знает о выражении X. Для такого пользователя V должно выглядеть и вести себя, как базовая пе­ре­мен­ная-от­но­ше­ние, о чем
я уже подробно говорил в этой главе. Именно этот второй случай понастоящему важен, и именно его я имел в виду до сих пор.

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

Взгляды и снимки

245

ятия данных пользователем. Неудивительно поэтому, что под логиче­с­кой независимостью от данных понимается возможность изменить
способ логичес­кого хранения данных и доступа к ним, не меняя восприятия данных пользователем. И обеспечить такую логичес­кую независимость призваны именно представления.
Предположим, что по какой-то причине (по какой именно, нам сейчас не важно) мы хотим следующим образом заменить базовую пе­ре­
мен­ную-от­но­ше­ние S двумя базовыми пе­ре­мен­ными-от­но­ше­ниями LS
и NLS:
VAR LS BASE RELATION
/* поставщики в Лондоне */
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;
VAR NLS BASE RELATION
/* поставщики не в Лондоне */
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;

Мы уже говорили в этой главе о том, что старая пе­ре­мен­ная-от­но­ше­ние
S представляет собой дизъюнктное объединение LS и NLS (и LS, и NLS –
ограничения S). Поэтому можно определить представление, объединяющее эти пе­ре­мен­ные-от­но­ше­ния, и назвать его S:
VAR S VIRTUAL ( LS D_UNION NLS ) KEY { SNO } ;

Любое выражение, которое раньше ссылалось на базовую пе­ре­мен­нуюот­но­ше­ние S, теперь ссылается на представление S. В предположении,
что сис­тема корректно поддерживает операции над представлениями, –
к сожалению, слишком смелое предположение, принимая во внимание
современное состояние дел, – пользователи не заметят этого изменения
в логичес­кой структуре базы данных.
Попутно замечу, что замена исходной пе­ре­мен­ной-от­но­ше­ния S («поставщики») двумя ограничениями LS и NLS не сов­сем тривиальное дейст­вие.
В частности, нужно что-то делать с пе­ре­мен­ной-от­но­ше­нием SP («поставки»), так как для нее задан внешний ключ, который ссылается на исходную пе­ре­мен­ную-от­но­ше­ние. См. упражнение 9.11 в конце главы.

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

246

Глава 9. SQL и представления

существует не только как определение в терминах других пе­ре­мен­ныхот­но­ше­ний, но и как отдельная копия данных (по крайней мере, концептуально). Например (изобретаю синтаксис на ходу):
VAR LSS SNAPSHOT ( S WHERE CITY = ‘London’ )
KEY { SNO }
REFRESH EVERY DAY ;

Определение снимка аналогично выполнению запроса со следующими
отличиями:
•• Результат запроса сохраняется в базе данных под указанным именем (в данном примере LSS) в виде константы-от­но­ше­ния или «пе­ре­
мен­ной-от­но­ше­ния, доступной только для чтения» (если не считать
периодичес­кого обновления, см. следующий пункт).
•• Периодичес­ки (в примере каждый день – EVERY DAY) снимок обновляется, то есть текущее значение отбрасывается и заменяется новым значением снимка.
Следовательно, в этом примере LSS представляет данные в том виде,
в котором они находились не более 24 часов назад.
Снимки важны в хранилищах данных, распределенных сис­темах и во
многих других контекстах. Но во всех случаях за ними стоит одно и то
же допущение (а иногда это даже требование) – что сис­тема может
примириться с данными на конкретный момент времени в прошлом.
Типичные приложения такого рода – составление отчетов и бухгалтерия; здесь обычно необходимо, чтобы данные были в нужный момент
заморожены (например, в конце отчетного периода), а снимки позволяют осуществить такое замораживание, не блокируя работу других
приложений.
Пока все хорошо. Беда в том, что снимки получили известность (по
крайней мере, в определенных кругах) под названием материализованные представления. Но это не представления! Представления никогда и не предполагалось материализовывать1; как мы видели, предполагается, что операции над представлениями реализуются посредством отображения их на подходящие операции с исходными пе­ре­мен­
ными-от­но­ше­ниями. Таким образом, «материализованное представление» – внутренне противоречивый термин. Хуже того, сам термин представление безо всяких прилагательных все чаще воспринимают как
«материализованное представление» (опять же в определенных кругах),
поэтому нам грозит опасность утратить хорошее слово для обозначения представлений в их первоначальном смысле. В этой книге я употребляю термин представление только в исконном смысле, но имейте
в виду, что в других местах под ним могут понимать нечто иное. Реко­
1

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

247

Упражнения

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

Упражнения
Упражнение 9.1. Определите представление, состоящее из номеров поставщиков и номеров деталей для тех поставщиков и деталей, которые находятся в разных городах. Приведите варианты на SQL и Tu­
to­rial D.
Упражнение 9.2. Пусть представление LSSP определено следующим
образом (на SQL):
CREATE VIEW LSSP
AS ( SELECT S.SNO , S.SNAME , S.STATUS , SP.PNO , SP.QTY
FROM S NATURAL JOIN SP
WHERE S.CITY = ‘London’ ) ;

Вот запрос к этому представлению:
SELECT
FROM
WHERE
(

DISTINCT STATUS , QTY
LSSP
PNO IN
SELECT PNO
FROM P
WHERE CITY ‘London’ )

Как мог бы выглядеть реально выполняемый запрос с участием базовых таб­лиц?
Упражнение 9.3. Какой ключ (или ключи) имеет представление LSSP
из предыдущего упражнения? Как можно сформулировать предикат для этого представления?
Упражнение 9.4. Пусть дано такое определение представления на языке Tutorial D:
VAR HEAVYWEIGHT VIRTUAL
( ( P RENAME ( WEIGHT AS WT , COLOR AS COL ) )
WHERE WT > 14.0 ) { PNO , WT , COL } ;

Для каждого из следующих выражений и предложений напишите,
во что оно преобразуется после применения процедуры подстановки:
a. HEAVYWEIGHT WHERE COL = ‘Green’
b. ( EXTEND HEAVYWEIGHT ADD ( WT + 5.3 AS WTP ) ) { PNO , WTP }
c. INSERT HEAVYWEIGHT RELATION { TUPLE { PNO ‘P99’ , WT 12.0 , COL
‘Purple’ } } ;
d. DELETE HEAVYWEIGHT WHERE WT < 9.0 ;
e. UPDATE HEAVYWEIGHT WHERE WT = 18.0 : { COL := ‘White’ } ;

248

Глава 9. SQL и представления

Упражнение 9.5. Пусть определение представления HEAVYWEIGHT
из упражнения 9.4 модифицировано следующим образом:
VAR HEAVYWEIGHT VIRTUAL
( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) )
RENAME ( COLOR AS COL ) ) WHERE WT > 6356.0 )
{ PNO , WT , COL } ;

(то есть атрибут WT теперь обозначает вес в граммах, а не в фунтах).
Повторите задания из упражнения 9.4.
Упражнение 9.6. Приведите решения упражнений 9.4 и 9.5 на SQL.
Упражнение 9.7. Для каких SQL-представлений из предыдущих упражнений следует задавать спецификацию WITH CHECK OPTION?
Упражнение 9.8. Какие ключи есть у представлений из упражнений
9.4 и 9.5? Какие у них предикаты? Каковы полные ограничения пе­
ре­мен­ных-от­но­ше­ний?
Упражнение 9.9. Приведите столько причин, сколько сможете придумать, в обоснование желательности объявления ключей для представления.
Упражнение 9.10. Пользуясь базой данных о поставщиках и деталях
или любой другой знакомой вам базой, приведите дополнительные
примеры (помимо примера с поставщиками из Лондона и не из Лондона), доказывающие, что решение о том, какие пе­ре­мен­ные-от­но­
ше­ния делать базовыми, а какие – виртуальными, носит в значительной мере произвольный характер.
Упражнение 9.11. Обсуждая вопрос о логичес­кой независимости от
данных, я рассмотрел возможность реструктуризации (то есть изменения логичес­кой структуры) базы данных о поставщиках и деталях путем замены базовой пе­ре­мен­ной-от­но­ше­ния S двумя ее ограничениями (LS и NLS). Однако я заметил, что такая замена – не
вполне тривиальное дейст­вие. Почему?
Упражнение 9.12. Исследуйте какой-нибудь доступный вам продукт на
основе SQL.
a. Существуют ли какие-нибудь допус­тимые на первый взгляд запросы, которые в этом продукте не работают? Если да, то сформулируйте максимально точно, что это за запросы. Какие доводы
поставщик приводит в обоснование неполной поддержки?
b. Какие обновления и каких именно представлений поддерживаются в этом продукте? Постарайтесь ответить как можно точнее.
Совпадают ли правила обновления с теми, что сформулированы
в стандарте?
c. Более общо, как именно этот продукт нарушает принцип взаимозаменяемости (а нарушает обязательно!)?

249

Упражнения

Упражнение 9.13. Опишите различие между представлениями и снимками. Поддерживаются ли снимки в SQL? А в каком-нибудь известном вам продукте?
Упражнение 9.14. Что такое «материализованное представление»? Почему этот термин не следует употреблять?
Упражнение 9.15. Рассмотрим базу данных о поставщиках и деталях,
но для простоты забудем о пе­ре­мен­ной-от­но­ше­нии «детали». Вот набросок двух возможных способов описать поставщиков и поставки:
a. S { SNO , SNAME , STATUS , CITY }
SP { SNO , PNO , QTY }
b. SSP { SNO , SNAME , STATUS , CITY , PNO , QTY }
XSS { SNO , SNAME , STATUS , CITY }
Вариант (a) – стандартный. В варианте (b) пе­ре­мен­ная-от­но­ше­ние
SSP содержит кортеж для каждой поставки, в котором хранятся номер детали, количество ее в поставке и полная информация о поставщике, а пе­ре­мен­ная-от­но­ше­ние XSS содержит информацию о тех поставщиках, которые не поставили ни одной детали. (Можно ли сказать, что эти варианты информационно эквивалентны?) Напишите
определения представлений, выражающих вариант (b) через вариант (a) и наоборот. Кроме того, покажите ограничения, применимые
к каждому варианту. Есть ли у какого-нибудь варианта очевидные
преимущества по сравнению с другим? Если да, то в чем они состоят.
Упражнение 9.16. Предполагается, что представления обеспечивают ло­
гичес­кую независимость от данных. Но не говорил ли я в главе 6, что
для решения этой задачи предназначен гипотетичес­кий механизм
«открытых таб­лиц»? Как вы относитесь к такому несоответствию?

Глава

10
.

SQL и формальная логика
В главе 1 я упоминал, что реляционной алгебре есть альтернатива – реляционное исчисление. Это означает, что все запросы, ограничения,
определения представлений и так далее можно сформулировать не
только в терминах реляционной алгебры, но и реляционного исчисления. Иногда последняя формулировка оказывается даже проще, хотя
бывает и наоборот.
Что такое реляционное исчисление? По существу, это прикладная форма исчисления предикатов (известного также под названием логика
предикатов), приспособленная под нужды реляционных баз данных.
Поэтому цель настоящей главы – познакомить вас с некоторыми средствами логики предикатов (для краткости будем называть ее просто
логикой), показать, как эти средства реализуются конкретно в реляционном исчислении, и по ходу дела рассмотреть соответствующие средства SQL.
Кстати говоря, из сказанного выше следует, что реляционный язык может быть основан как на алгебре, так и на исчислении. Например, язык
Tutorial D явным образом основан на алгебре (поэтому на него в этой
главе не так уж много ссылок), а язык Query-By-Example (см. приложение D) столь же явно основан на исчислении. А на чем основан SQL? Ответ, к сожалению, неоднозначен: серединка на половинку… Когда SQL
только проектировался, ставилась задача сделать его отличным и от алгебры, и от исчисления; к слову сказать, именно из-за этого в языке появился «подзапрос IN».1 Но со временем выяснилось, что без некоторых
1

Эта цель базировалась на фундаментальном заблуждении, будто и реляционная алгебра, и реляционное исчисление в какой-то мере «враждебны
пользователю». Но такая характеристика, как мне кажется, основана на
смешении синтаксиса и семантики. Конечно, синтаксис в работах Кодда
способен напугать человека, не искушенного в математичес­ком формализме. Но семантика – сов­сем другое дело; и у алгебры, и у исчисления семан-

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

251

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

Простые и составные высказывания
Напомню (см. главу 5), что в логике высказыванием называется суждение, которое безусловно принимает значение TRUE или FALSE. Вот несколько примеров (1, 4 и 5 – истинные высказывания, 2 и 3 – ложные):
1. 2 + 3 = 5
2. 2 + 3 > 7
3. Юпитер – звезда
4. У Марса два спутника
5. Венера расположена между Землей и Меркурием

Связки
Имея некоторое множес­тво высказываний, мы можем строить из них
новые с помощью связок. Чаще всего на практике встречаются связки
NOT, AND, OR, IF ... THEN ... (известная также под названием IMPLIES,
или «⇒») IF AND ONLY IF (известная также под названием IFF, или BIIMPLIES, или IS EQUIVALENT TO, или «⇔», или «≡»). Вот несколько
примеров высказываний, которые можно образовать из высказываний
3, 4 и 5 в предыдущем списке:
6. ( Юпитер – звезда ) OR ( У Марса два спутника )
7. ( Юпитер – звезда ) AND ( Юпитер – звезда )
8. ( Венера расположена между Землей и Меркурием ) AND NOT (Юпитер –
звезда)
9. IF ( У Марса два спутника ) THEN ( Венера расположена между Землей
и Меркурием )
10. IF ( Юпитер – звезда ) THEN ( У Марса два спутника )

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

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

252

Глава 10. SQL и формальная логика

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

Вообще говоря, связки можно рассматривать как логичес­кие операторы – они принимают некоторые высказывания на входе и возвращают
новое высказывание на выходе. NOT – одноместный оператор, остальные – двуместные. Если в высказывании нет связок, оно называется
простым, в противном случае – составным. Значение истинности составного высказывания можно определить, зная значения истинности
составляющих его простых высказываний и применяя следующие таб­
лицы истинности (из соображений экономии места я сократил TRUE
и FALSE до T и F сответственно):
p

NOT p

T
F

F
T

p q
T
T
F
F

p AND q

p OR q

T
F
F
F

T
T
T
F

T
F
T
F

IF p THEN q

p IFF q
T
F
F
T

T
F
T
T

Кстати говоря, таб­лицы истинности можно изображать и несколько подругому (опять же для экономии места я сократил IF ... THEN ... до IF):
NOT
T
F

AND T F
F
T

T
F

T F
F F

OR

T F

IF

T F

IFF

T F

T
F

T T
T F

T
F

T F
T T

T
F

T F
F T

Иногда удобнее один способ, иногда – другой. Как бы то ни было, рассмотрим внимательнее одно из приведенных выше составных высказываний.
9. IF ( У Марса два спутника ) THEN (Венера расположена между Землей
и Меркурием)
Это высказывание имеет вид IF p THEN q (или, эквивалентно, p IMPLIES
q), где p – посылка, а q – следствие. Так как и посылка, и следствие равны TRUE, то и все высказывание принимает значение TRUE, как легко
видеть из таб­лицы истинности. Но очевидно, что расположение Венеры
между Землей и Меркурием не имеет ничего общего с тем фактом, что
у Марса два спутника! Так что же тут происходит?
Предыдущий пример иллюстрирует проблему, с которой часто сталкиваются люди, не имеющие опыта работы с формальной логикой, а именно трудности усвоения импликации. Поэтому я хотел бы привести следующее рассуждение, надеясь, что оно немного прояснит суть дела.

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

253

•• Прежде всего, отметим, что существует ровно 16 двуместных связок,
которые соответствуют 16 возможным таб­лицам истинности (из которых выше показано только четыре).
•• Некоторые, но не все из этих 16 двуместных связок имеют названия,
например AND и OR. Но эти названия – не более чем мнемоники;
у них нет никакой внутренней семантики и выбраны они лишь потому, что поведение связок похоже (но не обязательно идентично) на тот
смысл, который принято придавать соответствующим союзам в естественном языке. Дейст­вительно, легко видеть, что даже AND означает не в точности то же самое, что «и» в естественном языке. В логике
p AND q и q AND p – эквивалентные высказывания, а в естественном
языке это необязательно. Вот простой пример: предложения
«Я был сильно разочарован и голосовал за смену лидера»
и
«Я голосовал за смену лидера и был сильно разочарован».
очевидно, не эквивалентны! Другими словами, связка AND – своего рода логичес­ки очищенный идеал союза «и»; очень важно, что ее
смысл не зависит от контекста (чего никак нельзя сказать о союзе
«и»). Аналогичные соображения применимы и к остальным связкам.
•• Из 16 двуместных связок поведение IMPLIES наиболее близко напоминает то, что называется импликацией (следствием) в естественном языке. Например, «если Марс имеет два спутника, то, конечно же, он имеет хотя бы один спутник» – правильная импликация
как в логике, так и в естественном языке. Но никто не стал бы, да
и не должен утверждать, что логичес­кая и «естественная» импликация – одно и то же. На самом деле логичес­кая импликация, как
и все остальные связки, по необходимости имеет формальное определение, то есть она определена исключительно в терминах значений
истинности, а не семантики операндов. Сказать такое об аналоге импликации в естественном языке, очевидно, нельзя.
•• Рассмотрим еще один пример (десятый в списке выше):
IF ( Юпитер – звезда ) THEN ( У Марса два спутника )

Хотя и вразрез с интуицией, значением этого высказывания тоже
является TRUE, поскольку посылка ложна (сверьтесь с таб­лицей истинности); но очевидно же, что наличие у Марса двух спутников не
имеет ничего общего с тем, звезда Юпитер или нет. Как и раньше, отчасти обоснованием того, что импликация в этом случае принимает
значение TRUE, является формальное определение связки IMPLIES.
Однако в данном случае есть и еще один довод (фактичес­ки, из области баз данных), который может показаться вам более убедительным. Предположим, что на базу данных о поставщиках и деталях
наложено ограничение, согласно которому красные детали долж-

254

Глава 10. SQL и формальная логика

ны находиться на складе в Лондоне (я сознательно формулирую это
ограничение в слегка упрощенном виде):
IF ( COLOR = ‘Red’ ) THEN ( CITY = ‘London’ )

Понятное дело, мы не хотим, чтобы это ограничение нарушалось изза деталей другого цвета. А значит, высказывание в целом (логичес­
кая импликация) должно принимать значение TRUE, если посылка
равна FALSE.
Из всего сказанного выше следует, что высказывание p IMPLIES q (или,
что то же самое, IF p THEN q) логичес­ки эквивалентно высказыванию
(NOT p) OR q – оно принимает значение FALSE тогда и только тогда, когда p равно TRUE и q равно FALSE. И это наблюдение иллюстрирует тот
факт, что не все связки NOT, AND, OR, IMPLIES и IF AND ONLY IF –
примитивные; некоторые можно выразить через другие. Фактичес­ки
все возможные одноместные и двуместные связки можно выразить через NOT и либо AND, либо OR в подходящих сочетаниях. (Упражнение:
проверьте это утверждение.) Но, пожалуй, еще интереснее, что любую
связку можно выразить всего через один примитив. Сумеете ли вы отыскать его?

Замечание о коммутативности
Связки AND и OR коммутативны; то есть составные высказывания p
AND q и q AND p эквивалентны. Следовательно, никогда не следует писать содержащий такие высказывания код, в котором предполагается,
что p будет вычислено прежде q или наоборот. Например, пус­ть функция SQRT (неотрицательный квадратный корень) определена так, что
в случае отрицательного аргумента возбуждается исключение. Рассмотрим следующее SQL-выражение:
SELECT ...
FROM ...
WHERE X >= 0 AND SQRT ( X ) ...

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

Простые и составные предикаты
Рассмотрим следующие суждения:
11. x – звезда
12. x имеет два спутника
13. x имеет m спутников
14. x расположен между Землей и y
15. x расположен между y и z

Простые и составные предикаты

255

Здесь x, y, z и m – параметры, которые иногда называют метками-за­
ме­нителями (place­holders). Следовательно, эти суждения не являются высказываниями (нельзя сказать, что они безусловно истинны или
ложны) – именно потому, что содержат параметры. Например, суждение «x – звезда» зависит от параметра x, и потому мы не можем сказать, истинно оно или ложно, пока не будем знать, что скрывается за x.
А узнав это, мы уже будем иметь дело не с данным суждением, а с неким другим, что станет ясно из следующего абзаца.
Мы можем подставлять аргументы вместо параметров и тем самым
получать высказывания из параметризованных суждений. Например,
если подставить аргумент Солнце вместо параметра x в суждение «x –
звезда», то получится «Солнце – звезда». И это суждение уже является
высказыванием, потому что оно безусловно истинно или ложно (на самом деле, конечно, истинно). Но исходное суждение («x – звезда»), повторю, не является высказыванием. А является оно предикатом, который (см. главу 5) представляет собой функцию, возвращающую значение истинности. Как и у любой функции, у предиката имеется множес­
тво параметров; при вызове вместо параметров подставляются аргументы, в результате чего предикат превращается в высказывание. Говорят,
что аргументы удовлетворяют предикату, если это высказывание истинно. Например, аргумент Солнце удовлетворяет предикату «x – звезда», а аргумент Луна – не удовлетворяет.
Попутно напомню, что в формальной логике говорят не о вызове, а о порождении предиката (на самом деле, есть причины, которые здесь нас
не интересуют, по которым понятие порождения несколько более общее, чем хорошо знакомое понятие вызова функции). Однако в этой главе я все же буду употреблять термин вызов. Отмечу еще, что в упражнении 5.23 из главы 5 было показано, что высказывание можно считать
вырожденным предикатом с пус­тым множес­твом параметров (а функция, которой этот предикат и является, при каждом вызове возвращает один и тот же результат – TRUE или FALSE). Иными словами, все высказывания – предикаты, но большинство предикатов высказываниями не является.
Теперь рассмотрим предикат «x имеет m спутников» с двумя парамет­
рами: x и m. Подстановка вместо x аргумента Марс, а вместо m – 2 дает
истинное высказывание; подстановка вместо x аргумента Земля, а вместо m – 2 дает ложное. На самом деле предикаты удобно классифицировать по кардинальности множес­тва параметров. N-местным называется предикат, имеющий ровно n параметров; например, «x расположен
между y и z» – 3‑местный предикат, а «x имеет m спутников» – 2‑местный. Высказывание – это 0‑местный предикат. Примечание: иногда
n-местный предикат называют также n‑адичес­ким. Если n = 1, то предикат называется монадичес­ким, если n = 2 – диадичес­ким.
Если задано некое множес­тво предикатов, то входящие в него предикаты можно комбинировать для получения новых, применяя логичес­кие

256

Глава 10. SQL и формальная логика

связки (NOT, AND, OR и т. д.); другими словами, связки – это логиче­
ские операторы, которые применяются к предикатам общего вида, а не
только к высказываниям, являющимся частными случаями предикатов. Предикат без связок называется простым; предикат, не являющийся простым, называется составным. Вот пример составного предиката:
16. ( x – звезда ) OR ( x расположен между Землей и y )
Это двуместный предикат – не потому, что он состоит из двух простых
предикатов, а потому, что имеет два параметра: x и y.

Квантификация
В предыдущем разделе я показал, что один из способов получить высказывание из предиката – вызвать его с подходящими аргументами.
Но есть и другой способ – квантификация. Пусть p(x) – одноместный
предикат (для ясности я решил показать его единственный параметр).
Тогда:
•• Выражение
EXISTS x ( p ( x ) )

является высказыванием и означает: «Существует хотя бы одно
допус­тимое значение аргумента a, соответствующего параметру x,
такое, что p(a) обращается в TRUE» (иными словами, значение аргумента a удовлетворяет предикату p). Например, если p – предикат «x
является логиком», то
EXISTS x ( x является логиком )

является высказыванием, принимающим значение TRUE (возьмите,
например, в качестве a Бертрана Рассела).
•• Выражение
FORALL x ( p ( x ) )

является высказыванием и означает: «Все допус­тимые значения аргумента a, соответствующего параметру x, таковы, что p(a) обращается в TRUE» (иными словами, все такие значения аргумента a удовлетворяют предикату p). Например, взяв в качестве p все тот же
предикат «x является логиком», получим высказывание
FORALL x ( x является логиком )

которое принимает значение FALSE (в качестве примера, возьмите
Джорджа Буша).
Отметим, что для доказательства истинности высказывания EXISTS
достаточно предъявить один пример, а для доказательства ложности высказывания FORALL – один контрпример. Отметим также, что
в обоих случаях параметр должен быть ограничен множес­твом допус­

Квантификация

257

тимых значений (в нашем примере множес­твом всех людей). К этой
мысли я еще вернусь в разделе «Реляционное исчисление» ниже.
В формальной логике выражения вида EXISTS x и FORALL x называются кванторами (термин происходит от глагола quantify, который
означает «давать количественное выражение» – каков объем чеголибо или сколько экземпляров чего-либо). EXISTS называется квантором существования, а FORALL – квантором всеобщности. В текстах
по математичес­кой логике квантор EXISTS обычно представляется отраженной буквой E (∃), а квантор FORALL – перевернутой буквой A (∀).
Я же буду для удобства восприятия употреблять ключевые слова EXISTS
и FORALL.

Отступление
В этом месте один из рецензентов спросил, а не является ли квантор просто
еще одной связкой. Нет, не является. Пусть p и q – предикаты с одним параметром x. Тогда p и q можно разными способами комбинировать с помощью связок, но в результате всегда будет получаться предикат с тем же единственным
параметром x. С другой стороны, квантификация по x, то есть образование такого выражения, как EXISTS x (p) или FORALL x (q), преобразует рассматриваемый предикат в высказывание. Следовательно, существует четкое логичес­кое
различие между двумя этими понятиями. (Хотя должен добавить, что, по крайней мере, в контексте баз данных кванторы можно определить в терминах некоторых связок. Я вернусь к этому моменту позже, в разделе «Еще о квантификации».)

В качестве еще одного примера рассмотрим двуместный предикат «x
выше, чем y». Если предварить его квантором существования по x, то
получим:
EXISTS x ( x выше, чем y )

Это суждение не является высказыванием, поскольку у него нет безусловного значения истинности; на самом деле, это одноместный предикат с единственным параметром y. Вызовем этот предикат с аргументом Стив, получим:
EXISTS x ( x выше, чем Стив )

Это суждение является высказыванием (и если существует хотя бы
один человек, скажем Арнольд, который выше Стива, то его значение
равно TRUE). Но есть и другой способ получить высказывание из исходного предиката: квантифицировать по обоим параметрам, например:
EXISTS x ( EXISTS y ( x выше, чем y ) )

Это суждение является высказыванием; оно принимает значение
FALSE тогда и только тогда, когда не существует ни одного человека,
который был бы выше любого другого, и TRUE в противном случае (подумайте над этим!).

258

Глава 10. SQL и формальная логика

Из этого примера можно извлечь несколько уроков:
•• Чтобы получить высказывание из n-местного предиката с помощью
одной лишь квантификации, необходимо выполнить квантификацию по каждому параметру. Более общо, результатом квантификации по m параметрам (m ≤ n) является k-местный предикат, где k =
n – m.
•• Забудем ненадолго о кванторе всеобщности. Очевидно, что существуют два, на первый взгляд, различных высказывания, которые
можно получить с помощью «квантификации по всему»:
EXISTS x ( EXISTS y ( x is taller than y ) )
EXISTS y ( EXISTS x ( x is taller than y ) )

Однако должно быть понятно, что смысл этих высказываний одинаков: «Существуют два человека x и y такие, что x выше, чем y». Более общо, легко видеть, что последовательность одинаковых кванторов (только существования или только всеобщности) можно записать в любом порядке, не изменяя смысла высказывания. Напротив,
для разных кванторов порядок важен (см. следующий пункт).
•• Когда мы «квантифицируем по всему», каждый отдельный квантор
может быть как квантором существования, так и квантором всеобщности. Поэтому в примере выше существует шесть различных высказываний, которые можно получить в результате полной квантификации; все они перечислены ниже. (На самом деле их восемь, но
два можно игнорировать в силу замечания в предыдущем пункте.)
В каждом случае я привел интерпретацию на естественном языке.
Обратите внимание, что все интерпретации логичес­ки различаются!
И еще заметьте, что в большинстве интерпретаций я был вынужден
предположить, что во «вселенной» существует хотя бы два человека. Я вернусь к этому допущению в разделе «Еще о квантификации».
EXISTS x ( EXISTS y ( x выше, чем y ) )

Семантика: Кто-то выше, чем кто-то еще; TRUE, если только все
люди не одинакового роста.
EXISTS x ( FORALL y ( x выше, чем y ) )

Семантика: Кто-то выше всех (включая и самого «кого-то»!); очевидно FALSE.
FORALL x ( EXISTS y ( x выше, чем y ) )

Семантика: каждый выше кого-то; очевидно FALSE.
EXISTS y ( FORALL x ( x выше, чем y ) )

Семантика: Кто-то ниже всех (включая и самого «кого-то»); очевидно FALSE. Примечание: Здесь я немного схитрил, потому что не сказал, что имеется в виду под «ниже»! Но я мог бы это сделать, явно объ-

Квантификация

259

явив, что предикаты «x выше, чем y» и «y ниже, чем x» логичес­ки эквивалентны, – и далее в этом разделе именно так и буду считать.
FORALL y ( EXISTS x ( x выше, чем y ) )

Семантика: Каждый ниже кого-то; очевидно FALSE.
FORALL x ( FORALL y ( x выше, чем y ) )

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

Свободные и связанные пе­ре­мен­ные
То, что я называю параметрами, в формальной логике чаще называют
свободными пе­ре­мен­ными, а квантификация по свободной пе­ре­мен­ной
превращает ее в связанную пе­ре­мен­ную. Снова рассмотрим двуместный
предикат из предыдущего раздела:
x выше, чем y

Здесь x и y – свободные пе­ре­мен­ные. Если теперь применить к x квантор существования, то получим:
EXISTS x ( x выше, чем y )

Теперь пе­ре­мен­ная y свободна (все еще), а пе­ре­мен­ная x связана. А в
полностью квантифицированной формуле
EXISTS x EXISTS y ( x выше, чем y )

связаны и x, и y, а свободных пе­ре­мен­ных не осталось (предикат выродился в высказывание).
Мы знаем, что свободные пе­ре­мен­ные соответствуют параметрам
в смысле, принятом в программировании. Но у связанных пе­ре­мен­ных
в традиционном программировании вообще нет аналогов; они служат
лишь для того, чтобы связать предикат внутри скобок с квантором вне
скобок. Рассмотрим, к примеру, следующий простой предикат (на самом деле, высказывание):
EXISTS x ( x > 3 )

В этом высказывании утверждается, что существует целое число, большее трех. (Я предполагаю, что областью значений x является множес­
тво целых чисел. Позже я еще вернусь к этому моменту.) Поэтому смысл
высказывания не изменится, если заменить оба вхождения x какойнибудь другой пе­ре­мен­ной y. Иными словами, высказывание
EXISTS y ( y > 3 )

260

Глава 10. SQL и формальная логика

семантичес­ки идентично предыдущему.
Теперь рассмотрим такой предикат:
EXISTS x ( x > 3 ) AND x < 0

Здесь есть три вхождения x, но они означают разные вещи. Первые два
связаны и могут быть заменены пе­ре­мен­ной y (например) без изменения
семантики. Третья же пе­ре­мен­ная свободна и безнаказанно заменить ее
не получится. Таким образом, из следующих двух предикатов первый
эквивалентен показанному выше, а второй – нет:
EXISTS y ( y > 3 ) AND x < 0
EXISTS y ( y > 3 ) AND y < 0

Как видно из этого примера, термины «свободные и связанные пе­ре­
мен­ные» обозначают не столько пе­ре­мен­ные, сколько вхождения пе­ре­
мен­ных, точнее, вхождения ссылок на пе­ре­мен­ные в некоторый предикат. Так, во втором из приведенных выше предикатов второе вхождение ссылки на y связано, а третье вхождение свободно. Несмотря на это,
обычно (и об этом можно лишь сожалеть) говорят о свободных и связанных пе­ре­мен­ных как таковых1, пус­ть даже такое словоупотребление отдает легкой небрежностью. Будьте начеку и не путайтесь!
В завершение этого раздела отмечу, что теперь мы можем определить
(переопределить, если хотите) высказывание как предикат, в котором
все пе­ре­мен­ные связаны, или, что эквивалентно, нет ни одной свободной пе­ре­мен­ной.

Реляционное исчисление
Все сказанное в этой главе до сих пор имеет самое прямое от­но­ше­ние
к реляционному исчислению. Рассмотрим простой пример – представление в реляционном исчислении запроса «Получить номера поставщиков и статусы поставщиков в Париже, которые поставляют деталь P2».
Сначала для сравнения приведем алгебраичес­кую формулировку:
( S WHERE CITY = ‘Paris’ ) { SNO , STATUS }
MATCHING ( SP WHERE PNO = ‘P2’ )

А вот эквивалентная формулировка в реляционном исчислении:
RANGEVAR SX RANGES OVER S ;
RANGEVAR SPX RANGES OVER SP ;
{ SX.SNO , SX.STATUS }
WHERE SX.CITY = ‘Paris’ AND
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = ‘P2’ )
1

Иногда даже в учебниках по математичес­кой логике, где такую практику
следует осудить.

Реляционное исчисление

261

Объяснение:
•• В первых двух строках даются определения пе­ре­мен­ных кортежа (range variable) с множес­твами значений S и SP соответственно.
Смысл этих определений в том, что в любой момент времени допус­
тимыми значениями SX являются кортежи от­но­ше­ния, являющегося значением пе­ре­мен­ной-от­но­ше­ния S в этот момент времени. Аналогично допус­тимыми значениями SPX являются кортежи от­но­ше­
ния, являющегося значением пе­ре­мен­ной-от­но­ше­ния SP в этот момент времени.
•• Оставшиеся строки содержат сам запрос. Они имеют такую общую
форму:
прототип кортежа WHERE предикат

Это аналог реляционного выражения (т. е. выражения, обозначающего от­но­ше­ние) в реляционном исчислении, и значением его является
от­но­ше­ние, содержащее все возможные значения прототипа кортежа,
для которых предикат принимает значение TRUE, и больше никаких
кортежей не содержащее. (Термин прототип кортежа удачный, но
не стандартный, однако стандартного термина для этого понятия, похоже, не существует). Таким образом, в нашем примере результатом
является от­но­ше­ние степени 2, содержащее все пары (SNO,STATUS)
из пе­ре­мен­ной-от­но­ше­ния S такие, что (а) городом является Париж
и (б) существует поставка в пе­ре­мен­ной-от­но­ше­нии SP с тем же номером поставщика, что в этой паре, и номером детали P2.
Обратите внимание на имена с точками (и в прототипе кортежа, и в предикате); я не стану уделять этому много внимания, поскольку квалифицированные имена должны быть вам знакомы по SQL. На самом
деле формулировка этого запроса в SQL очень напоминает показанную
выше формулировку в реляционном исчислении:
SELECT
FROM
WHERE
AND
(

SX.SNO , SX.STATUS
S AS SX
SX.CITY = ‘Paris’
EXISTS
SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = ‘P2’ )

Отметим, что в SQL поддерживаются пе­ре­мен­ные кортежа, хотя сам
термин там обычно не используется (в главе 12 я еще вернусь к теме пе­
ре­мен­ных кортежа в SQL). Но важнее тот факт, что SQL поддерживает также квантор EXISTS; впрочем, эта поддержка не сов­сем прямая.1
1

Наверное, будет полезно подчеркнуть, что EXISTS в SQL похож на конструкцию IS_NOT_EMPTY в Tutorial D (см. главу 3). См. раздел «Некоторые эквиваленции» ниже.

262

Глава 10. SQL и формальная логика

Конкретно, пус­ть sq – некоторый подзапрос, тогда EXISTS sq – булево
выражение (и потому представляет собой предикат), которое принимает значение FALSE, если таб­лица, обозначенная подзапросом sq, пус­та,
и TRUE – в противном случае. (Таб­личное выражение tx в скобках, составляющее подзапрос sq, обычно, хотя и не всегда, имеет вид SELECT *
FROM ... WHERE ..., а фраза WHERE опять обычно, но не обязательно,
включает ссылку на некоторую «внешнюю» таб­лицу, то есть sq является коррелированным подзапросом. В примере выше такой внешней таб­
лицей является S, а ссылка на нее производится с помощью пе­ре­мен­ной
кортежа SX. Дополнительные пояснения см. в главе 12.)

Отступление
Здесь таится некая ирония. В главе 4 мы видели, что SQL в силу того, что поддерживает null-значения, основан на так называемой трехзначной логике –
3VL (а не на традиционной двузначной логике, которая рассматривается в этой
главе и лежит в основе реляционной модели). В 3VL квантор существования
может возвращать три разных результата: TRUE, FALSE и UNKNOWN (опять-таки
см. главу 4). Но в SQL оператор EXISTS возвращает только TRUE или FALSE и никогда UNKNOWN. Например, EXISTS(tx) вернет TRUE, а не UNKNOWN, если значением tx окажется таб­лица, не содержащая ничего, кроме null-значений (тут
я позволил себе некоторую вольность в словоупотреблении), хотя логически правильно было бы вернуть именно UNKNOWN. Следовательно, (a) EXISTS
в SQL – не вполне корректная реализация квантора существования в 3VL и (b)
SQL иногда возвращает неверные ответы (как мы уже неоднократно отмечали).
Конкретный пример такой ситуации я приведу в следующейглаве.

Рассмотрим еще один пример – запрос «Получить названия тех поставщиков, которые поставляют все детали». Я предполагаю, что уже определены те же пе­ре­мен­ные кортежа, что и выше, а также еще одна – PX:
RANGEVAR PX RANGES OVER P ;
{ SX.SNAME } WHERE FORALL PX ( EXISTS SPX ( SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

На естественном языке это, несколько коряво, звучит так: «Получить
названия поставщиков таких, что для всех деталей существует поставка
с таким же номером поставщика, как у поставщика, и с таким же номером детали, как у детали». Примечание: Как вы, наверное, знаете, в SQL
вообще нет прямой поддержки квантора FORALL. Поэтому я сейчас не
покажу SQL-аналог этого примера, но вернусь к этому вопросу позже,
в разделе «Еще о квантификации». Однако я все же отмечу логичес­кое
различие между приведенным выше выражением реляционного исчисления и следующим, в котором кванторы поменяны местами:
{ SX.SNAME } WHERE EXISTS SPX ( FORALL PX ( SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

Реляционное исчисление

263

Упражнение: Что означает это выражение? И как вы думаете, «разумен» ли этот запрос?
Еще один пример («Получить названия тех поставщиков, которые поставляют все красные детали»):
{ SX.SNAME } WHERE FORALL PX ( IF PX.COLOR = ‘Red’ THEN
EXISTS SPX ( SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

Обратите внимание на логичес­кую импликацию в этом примере. Отмечу еще, что я предполагаю то же самое множес­тво значений пе­ре­мен­
ных, что и раньше; собственно, это предположение будет дейст­вовать
до конца главы.
Кстати, предыдущий запрос можно сформулировать и по-другому:
{ SX.SNAME } WHERE FORALL PX
( EXISTS SPX ( IF PX.COLOR = ‘Red’ THEN
SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

В этой формулировке предикат во фразе WHERE записан в так называемой предваренной нормальной форме. Неформально говоря, это означает, что все кванторы находятся в начале. Вот точное определение:
Определение: Говорят, что предикат записан в предваренной нормальной форме (PNF), если либо (а) он не содержит кванторов вовсе, либо (б) имеет вид EXISTS x (p) или
FORALL x (p), где предикат p записан в предваренной нормальной форме. Иными словами, PNF-предикат имеет вид:
Q1 x1 ( Q2 x2 ( ... ( Qn xn ( q ) ) ... ) )

где n ≥ 0, все Qi (i = 1, 2, ..., n) – либо EXISTS, либо FORALL,
и предикат q (который иногда называют матричным) не содержит кванторов.
Предваренная нормальная форма не более и не менее правильна, чем
любая другая, но при некоторой практике она во многих случаях дает
наиболее естественную формулировку.

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

264

Глава 10. SQL и формальная логика

ре­мен­ной-от­но­ше­ния). Примечание: Отсюда следует, что данная пе­ре­
мен­ная кортежа всегда обозначает некоторый кортеж. Поэтому реляционное исчисление иногда называют исчислением кортежей, а вместо
термина range variable употребляют tuple variable. Но последнее может
приводить к путанице, так как термин tuple variable (пе­ре­мен­ная кортежа) уже имеет несколько иное значение.1
Теперь я могу сказать еще несколько слов о синтаксисе выражений реляционного исчисления.
•• Прежде всего, прототип кортежа – это заключенный в круглые скобки список элементов, разделенных запятыми, в котором каждый
элемент является либо ссылкой на атрибут кортежа (возможно,
в сочетании с ключевым словом AS, которое вводит новое имя атрибута), либо ссылкой на пе­ре­мен­ную кортежа. (Существуют и другие возможности, но пока я ограничусь только этими случаями. См.
пример 5 ниже.) Примечание: принято опускать скобки, если список
состоит всего из одного элемента, но я для ясности буду включать их
даже тогда, когда без этого можно было бы обойтись.
•• Ссылка на атрибут кортежа – это выражение вида R.A, где A – атрибут от­но­ше­ния, являющегося множес­твом значений пе­ре­мен­ной
кортежа R (в примере выше SX.SNO). А ссылка на пе­ре­мен­ную кортежа – это просто имя пе­ре­мен­ной кортежа, например SX, которое
есть не что иное, как сокращенная запись списка ссылок на атрибуты кортежа, по одной для каждого атрибута от­но­ше­ния, являющегося множес­твом значений пе­ре­мен­ной кортежа.
•• Пусть в прототипе кортежа встречается некая ссылка на атрибут
кортежа, в которой участвует, явно или неявно, пе­ре­мен­ная кортежа R. Тогда предикат в соответствующей фразе WHERE может содержать и обычно содержит по крайней мере одну свободную ссылку на атрибут кортежа, в которой участвует R, где под выделенной курсивом фразой понимается ссылка на атрибут кортежа вида
R.A, который не находится в области дейст­вия какого-либо квантора, в котором R является связанной пе­ре­мен­ной.
•• Фраза WHERE необязательна; если она опущена, подразумевается
WHERE TRUE.

1

Тем не менее в переводе мы остановились на термине «пе­ре­мен­ная кортежа» отчасти потому, что в русской терминологии не нашлось адекватного перевода range variable (предлагались разные варианты: от совершенно неприемлемой «ранжированной пе­ре­мен­ной» до не слишком вразумительной и длинной «пе­ре­мен­ной с множес­твом значений»), а отчасти потому, что именно так этот термин переведен в издании книги К. Дж. Дейта
«Введение в сис­темы баз данных». – Пер. с англ. – СПб.: Вильямс, 2001. –
Прим. перев.

Реляционное исчисление

265

Дополнительные примеры запросов
Я приведу еще несколько примеров запросов реляционного исчисления, чтобы проиллюстрировать ряд особенностей; однако я не ставлю
своей целью дать исчерпывающее изложение вопроса. Для простоты
я опускаю определения RANGEVAR, которые на практике необходимы,
и предполагаю, что SX, SY, SZ и т. д. определены как пе­ре­мен­ные кортежа над множес­твом значений S; PX, PY, PZ и т. д. – пе­ре­мен­ные кортежа над множес­твом значений P; SPX, SPY, SPZ, и т. д. – пе­ре­мен­ные
кортежа над множес­твом значений SP. Обращаю ваше внимание, что
в общем случае приведенные ниже формулировки не являются единственно возможными. Оставляю в качестве упражнения для читателя
формулирование эквивалентных запросов на SQL.
Пример 1. Получить все пары номеров поставщиков такие, что оба поставщика находятся в одном городе.
{ SX.SNO AS SA , SY.SNO AS SB } WHERE SX.CITY = SY.CITY
AND SX.SNO < SY.SNO

Обратите внимание на использование фраз AS в этом примере.
Пример 2. Получить названия поставщиков, которые поставляют хотя
бы одну красную деталь.
{ SX.SNAME } WHERE EXISTS SPX ( EXISTS PX ( SX.SNO = SPX.SNO AND
SPX.PNO = PX.PNO AND
PX.COLOR = ‘Red’ ) )

Пример 3. Получить названия поставщиков, которые поставляют хотя
бы одну деталь, поставляемую поставщиком S2.
{ SX.SNAME } WHERE EXISTS SPX ( EXISTS SPY ( SX.SNO = SPX.SNO AND
SPX.PNO = SPY.PNO AND
SPY.SNO = ‘S2’ ) )

Пример 4. Получить названия поставщиков, которые не поставляют деталь P2.
{ SX.SNAME } WHERE NOT EXISTS SPX ( SPX.SNO = SX.SNO AND
SPX.PNO = ‘P2’ )

На практике выражение, следующее за NOT, иногда приходится заключать в скобки.
Пример 5. Для каждой поставки получить всю информацию о поставке,
а также ее общий вес.
{ SPX , PX.WEIGHT * SPX.QTY AS SHIPWT } WHERE PX.PNO = SPX.PNO

Обратите внимание на математичес­кое выражение в прототипе кортежа. В алгебраичес­ком варианте этого примера следовало бы воспользоваться оператором EXTEND.

266

Глава 10. SQL и формальная логика

Пример 6. Для каждой детали получить номер детали и общее поставленное количество.
{ PX.PNO , SUM ( SPX WHERE SPX.PNO = PX.PNO , QTY ) AS TOTQ }

В этом примере иллюстрируется вызов агрегатного оператора в прототипе кортежа (это также первый пример, в котором опущена фраза WHERE). Попутно отмечу, что следующее выражение, хотя оно
синтаксичес­ки допус­тимо, не является правильной формулировкой
того же запроса (почему?):
{ PX.PNO , SUM ( SPX.QTY WHERE SPX.PNO = PX.PNO ) AS TOTQ }

Ответ: Потому что перед вычислением суммы устраняются дубликаты, то есть одинаковые значения количества деталей в поставке.
Пример 7. Получить города, в которых находится более пяти красных
деталей.
{ PX.CITY } WHERE
COUNT ( PY WHERE PY.CITY = PX.CITY AND PY.COLOR = ‘Red’ ) > 5

Примеры ограничений
Теперь я хотел бы на примерах продемонстрировать применение реляционного исчисления для формулировки ограничений. Примеры пронумерованы так же, как в главе 8, и описывают те же самые ситуации.
Предполагается, что пе­ре­мен­ные кортежа определены, как в предыдущем разделе. Еще раз замечу, что приведенные формулировки, вообще
говоря, не являются единственно возможными.
Пример 1. Значения статуса должны принадлежать диапазону от 1 до
100 включительно.
CONSTRAINT CX1 FORALL SX ( SX.STATUS > 0 AND SX.STATUS < 101 ) ;

Примечание
SQL позволяет упрощать подобные ограничения за счет исключения явного
использования пе­ре­мен­ной кортежа и, что важнее, явного квантора всеобщности. Точнее, мы можем следующим образом задать ограничение базовой
таб­лицы (см. главу 8) в самом определении базовой таб­лицы S:
CONSTRAINT CX1 CHECK ( STATUS > 0 AND STATUS < 101 )

Аналогичное замечание применимо и к последующим примерам.

Пример 2. Поставщики из Лондона должны иметь статус 20.
CONSTRAINT CX2 FORALL SX ( IF SX.CITY = ‘London’
THEN SX.STATUS = 20 ) ;

Пример 3. Ни в каких двух кортежах пе­ре­мен­ной-от­но­ше­ния S не должно быть одинакового номера поставщика.

Еще о квантификации

267

CONSTRAINT CX3 FORALL SX ( FORALL SY ( IF SX.SNO = SY.SNO THEN
SX.SNAME = SY.SNAME AND
SX.STATUS = SY.STATUS AND
SX.CITY = SY.CITY ) ) ;

Пример 4. Если в двух кортежах пе­ре­мен­ной-от­но­ше­ния S одинаковы
номера поставщиков, то должны быть одинаковы и города.
CONSTRAINT CX4 FORALL SX ( FORALL SY ( IF SX.SNO = SY.SNO
THEN SX.CITY = SY.CITY ) ) ;

Пример 5. Ни один поставщик со статусом меньше 20 не может поставлять деталь P6.
CONSTRAINT CX5 FORALL SX ( IF SX.STATUS < 20 THEN
NOT EXISTS SPX ( SPX.SNO = SX.SNO AND
SPX.PNO = ‘P6’ ) ) ;

Пример 6. Любой номер поставщика, встречающийся в пе­ре­мен­ной-от­
но­ше­нии SP, должен встречаться и в пе­ре­мен­ной-от­но­ше­нии S.
CONSTRAINT CX6 FORALL SPX ( EXISTS SX ( SX.SNO = SPX.SNO ) ) ;

Об этом примере у меня еще будет случай кое-что сказать в следующем
разделе.
Пример 7. Ни один номер поставщика не может встречаться в обеих пе­
ре­мен­ных-от­но­ше­ниях LS и NLS.
CONSTRAINT CX7 FORALL LX ( FORALL NX ( LX.SNO ≠ NX.SNO ) ) ;

Множес­твами значений LX и NX являются LS и NLS соответственно.
Пример 8. Поставщик S1 и деталь P1 не могут находиться в разных городах.
CONSTRAINT CX8 NOT EXISTS SX ( EXISTS PX ( SX.SNO = ‘S1’ AND
PX.PNO = ‘P1’ AND
SX.CITY ≠ PX.CITY ) ) ;

Пример 9. Должен существовать хотя бы один поставщик. (В главе 8 нет
соответствующего примера.)
CONSTRAINT CX9 EXISTS SX ( TRUE ) ;

Выражение EXISTS SX (TRUE) принимает значение FALSE тогда и только тогда, когда множес­твом значений SX является пус­тое от­но­ше­ние.

Еще о квантификации
Я бы хотел обсудить еще ряд вопросов, относящихся к квантификации.

Нам не нужны оба квантора
На практике нам не нужны оба квантора EXISTS и FORALL, поскольку любой предикат, выражаемый в терминах EXISTS, можно выразить

268

Глава 10. SQL и формальная логика

в терминах FORALL и наоборот. В качестве примера еще раз рассмотрим следующий предикат:
EXISTS x ( x выше, чем Стив )

(«Кто-то выше, чем Стив»). По-другому то же самое можно сказать следующим образом:
NOT ( FORALL x ( NOT ( x выше, чем Steve ) ) )

(«Неверно, что нет никого выше Стива»). Более общо, предикат
EXISTS x ( p ( x ) )

логичес­ки эквивалентен предикату
NOT ( FORALL x ( NOT ( p ( x ) ) )

(где предикат p может иметь и другие параметры, помимо x). Аналогично предикат
FORALL x ( p ( x ) )

логичес­ки эквивалентен предикату
NOT ( EXISTS x ( NOT ( p ( x ) ) )

(где опять-таки предикат p может иметь и другие параметры, помимо x).
Отсюда следует, что формальный язык не обязан явно поддерживать
оба квантора – EXISTS и FORALL. Но на практике их поддержка крайне желательна. Причина в том, что некоторые задачи «более естественно» формулируются в терминах EXISTS, тогда как другие – в терминах
FORALL. Например, как мы знаем, SQL поддерживает EXISTS, но не
FORALL; поэтому формулировка некоторых запросов выглядит в SQL
неуклюже. Рассмотрим еще раз запрос «Получить поставщиков, которые поставляют все детали», который в реляционном исчислении выражается сов­сем просто:
{ SX } WHERE FORALL PX ( EXISTS SPX
( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) )

А в SQL этот запрос приходится записывать следующим образом:
SELECT
FROM
WHERE
(

SX.*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX
WHERE NOT EXISTS
( SELECT *
FROM SP AS SPX
WHERE SX.SNO = SPX.SNO
AND
SPX.PNO = PX.PNO ) )

(«Получить поставщиков SX таких, что не существует детали PX такой,
что не существует поставки SPX, связывающей поставщика SX с дета-

Еще о квантификации

269

лью PX»). Уже одно отрицание – это плохо (многие пользователи испытывают при этом трудности), а уж двойное отрицание, как в этом запросе, многократно хуже.

Пустые множес­тва значений
Снова обратимся к тому факту, что предикаты
EXISTS x ( p ( x ) )

и
NOT ( FORALL x ( NOT ( p ( x ) ) ) )

логичес­ки эквивалентны. Как мы знаем, связанная пе­ре­мен­ная x
в каждом из этих предикатов должна принимать значения из некоторого множес­тва допус­тимых значений. Предположим теперь, что это
множес­тво пус­то; например, оно может состоять из людей ростом выше
пятнадцати метров (в контексте баз данных было бы уместнее говорить
о множес­тве кортежей пе­ре­мен­ной-от­но­ше­ния, которое в данный момент пус­то). Тогда:
•• Выражение EXISTS x (p(x)) принимает значение FALSE, потому что
«не существует ни одного x», то есть не существует ни одного значения, которое можно было бы подставить вместо x, чтобы сделать это
выражение истинным. Отмечу, что это замечание справедливо вне
зависимости от того, что представляет собой p(x). Например, высказывание «существует человек ростом выше пятнадцати метров,
работающий в IBM» ложно (что и не удивительно).
•• Отсюда следует, что отрицание NOT EXISTS x (p(x)) принимает значение TRUE, –опять-таки вне зависимости от того, что представляет собой p(x). Например, высказывание «не существует человека ростом выше пятнадцати метров, работающего в IBM» (или в более разговорной форме «в IBM не работают люди ростом выше пятнадцати
метров») истинно (и снова не удивительно).
•• Но выражение NOT EXISTS x (p(x)) экивалентно FORALL x (NOT
(p(x)), и, значит, это последнее тоже принимает значение TRUE –
и снова вне зависимости от природы p(x).
•• Но если предикат p(x) произволен, то произволен и предикат NOT
(p(x)). Поэтому мы получаем следующий, быть может, удивительный результат; суждение FORALL x (...) принимает значение TRUE,
если никаких допус­тимых x не существует, вне зависимости от
того, что находится в скобках. Например, суждение «Все люди ростом выше пятнадцати метров работают в IBM» также истинно, потому что, повторюсь, людей выше пятнадцати метров не существует.
Следствием всего вышеизложенного является тот факт, что некоторые
запросы могут порождать неожиданные результаты (для тех, кто незнаком с формальной логикой). Например, рассмотренный выше запрос

270

Глава 10. SQL и формальная логика
{ SX } WHERE FORALL PX ( EXISTS SPX ( SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

(«Получить поставщиков, которые поставляют все детали») вернет всех
поставщиков, если не одной детали не существует.

Определение EXISTS и FORALL
Как вы, наверное, поняли, кванторы EXISTS и FORALL можно определить в терминах повторяющихся OR и AND соответственно. Сначала рассмотрим EXISTS. Пусть p(x) – предикат с параметром x, и пус­ть x
пробегает множес­тво значений X = {x1, x2, ..., xn}. Тогда
EXISTS x ( p ( x ) )

является предикатом, который, по определению, эквивалентен (и потому является сокращенной записью) предикату
p ( x1 ) OR p ( x2 ) OR ... OR p ( xn ) OR FALSE

Обратите внимание, что это выражение обращается в FALSE, если
множес­тво X пус­то (как мы уже знаем). Например, пус­ть p(x) имеет вид
«у x есть спутник», и пус­ть X – множес­тво {Меркурий, Венера, Земля,
Марс}. Тогда предикат EXISTS x (p(x)) принимает вид «EXISTS x (у x
есть спутник)» и является сокращенной записью для
( у Меркурия есть спутник ) OR ( у Венеры есть спутник ) OR
( у Земли есть спутник )
OR ( у Марса есть спутник ) OR FALSE

и равен TRUE, например, потому что высказывание «у Марса есть спутник» истинно. Аналогично
FORALL x ( p ( x ) )

является предикатом, который, по определению, эквивалентен (и потому является сокращенной записью) предикату
p ( x1 ) AND p ( x2 ) AND ... AND p ( xn ) AND TRUE

И это выражение (как мы уже знаем) обращается в TRUE, если множес­
тво X пус­то. Например, пус­ть p(x) и X такие же, как определено выше
для квантора EXISTS. Тогда предикат FORALL x (p(x)) принимает вид
«FORALL x (у x есть спутник)» и является сокращенной записью для
( у Меркурия есть спутник ) AND ( у Венеры есть спутник ) AND
( у Земли есть спутник )
AND ( у Марса есть спутник ) AND TRUE

Это выражение принимает значение FALSE, например, потому что высказывание «у Венеры есть спутник» ложно.
Попутно отмечу, что, как видно из этих примеров, возможность определения EXISTS и FORALL в терминах повторяющихся OR и AND, соответственно, означает, что всякий предикат с кванторами эквивалентен некоторому предикату без кванторов. Но тогда возникает не лишен-

Еще о квантификации

271

ный основания вопрос, а зачем вообще нужна вся эта возня с кванторами? Ответ таков: мы можем определить EXISTS и FORALL в терминах
повторяющихся OR и AND только потому, что множес­тва, с которыми
нам приходится иметь дело, к счастью, конечны (так как мы работаем
с компьютерами, а те по природе своей конечны). В чистой логике, где
такого ограничения нет, эти определения уже не дейст­вуют.1
Быть может, следует добавить, что пус­ть даже мы всегда имеем дело
с конечными множес­твами, а EXISTS и FORALL – просто сокращения,
но зато какие полезные сокращения! Лично я точно не хотел бы формулировать запросы исключительно в терминах AND и OR, не прибегая
к кванторам. А если спус­титься с небес на землю, то кванторы позволяют формулировать запросы, не зная точного содержимого базы данных
в каждый момент времени (что было бы невозможно в случае явного повторения OR или AND).

Другие виды кванторов
Хотя кванторы EXISTS и FORALL, безусловно, самые важные на практике, кроме них есть и другие. Нет никаких причин, запрещающих,
например, кванторы вида
существуют по меньшей мере три x, такие что
или
большинство x таковы, что
или
нечетное количество x таковы, что
(и так далее). Один довольно важный частный случай – существует
ровно одно x, такое что. Для него я буду использовать ключевое слово
UNIQUE. Вот несколько примеров:
UNIQUE x ( x выше, чем Арнольд )

Семантика: существует ровно один человек, который выше, чем Арнольд; скорее всего, FALSE.
UNIQUE x ( x имеет номер социального страхования y )

1

Поясню: рассмотрим, к примеру, высказывание EXISTS x (p), где p – предикат с единственным параметром x. Если x пробегает бесконечное множес­тво,
то любая попытка сформулировать алгоритм «с повторяющимися OR» для
вычисления значения такого высказывания обречена на провал, так как
вычисление может никогда не закончиться (не найдется такое значение x,
которое удовлетворяло бы p). Аналогично любая попытка сформулировать
алгоритм «с повторяющимися AND» для FORALL x (p) также обречена на
провал, так как вычисление может никогда не закончиться (не найдется такое значение x, которое не удовлетворяло бы p).

272

Глава 10. SQL и формальная логика

Семантика: Ровно один человек имеет номер социального страхования
y (y – параметр). Мы не можем сопоставить этому суждению значение
истинности, потому что это (одноместный) предикат, а не высказывание.
FORALL y ( UNIQUE x ( x имеет номер социального страхования y ) )

Семантика: Каждый человек имеет уникальный номер социального
страхования. (Я предполагаю, что y пробегает множес­тво всех реально присвоенных, а не потенциально возможных номеров социального
страхования. Упражнение: Верно ли, что этот предикат, а фактичес­ки
высказывание, принимает значение TRUE?)
Еще одно упражнение: что означает следующий предикат?
FORALL x ( UNIQUE y ( x имеет номер социального страхования y ) )

Теперь вспомним следующее ограничение: «Любой номер поставщика,
встречающийся в пе­ре­мен­ной-от­но­ше­нии SP, должен встречаться в пе­
ре­мен­ной-от­но­ше­нии S». Вот как я формулировал его раньше:
CONSTRAINT CX6 FORALL SPX ( EXISTS SX ( SX.SNO = SPX.SNO ) ) ;

Но я полагаю, вы согласитесь, что более точна такая формулировка:
CONSTRAINT CX6 FORALL SPX ( UNIQUE SX ( SX.SNO = SPX.SNO ) ) ;

Другими словами, мы хотим, чтобы для любого кортежа пе­ре­мен­нойот­но­ше­ния SP существовал не хотя бы один (EXISTS), а ровно один
(UNIQUE) соответствующий ему кортеж пе­ре­мен­ной-от­но­ше­ния S.
Предыдущая формулировка «работает», потому что имеется дополнительное ограничение, состоящее в том, что {SNO} – ключ пе­ре­мен­нойот­но­ше­ния S. Но пересмотренная формулировка ближе к тому, что мы
хотим сказать в дейст­вительности.
SQL поддерживает UNIQUE (в какой-то мере), хотя поддержка и не
такая прямая, как для EXISTS. Точнее, пус­ть sq – подзапрос; тогда
UNIQUE sq – булево выражение, которое принимает значение FALSE,
если таб­лица, обозначенная sq, содержит строки-дубликаты, и TRUE
в противном случае. Таким образом, если логичес­кое выражение
UNIQUE x ( p ( x ) )

означает «Существует ровно одно значение аргумента a, соответствующее параметру x, при котором p(a) равно TRUE», то его SQL-аналог
(очень приблизительный!)
UNIQUE ( SELECT * FROM T WHERE p ( x ) )

означает «При данном значении аргумента a, соответствующего параметру x, в таб­лице T существует не более одной строки такой, что p(a)
равно TRUE». В частности, если выражение, являющееся аргументом
UNIQUE, – не произвольный, а однострочный подзапрос (см. главу 12),
то вызов UNIQUE возвращает TRUE, если обозначенная этим подзапросом таб­лица содержит только одну или не содержит ни одной строки.

Еще о квантификации

273

Вот несколько натянутый пример SQL-запроса с ключевым словом
UNIQUE («Получить названия поставщиков, которые поставляют по
меньшей мере две различных детали в одном и том же количестве»):
SELECT DISTINCT SNAME
FROM S
WHERE NOT UNIQUE ( SELECT QTY
FROM SP
WHERE SP.SNO = S.SNO )

Повторю, что это натянутый пример; точнее, в нем используется тот
факт, что SQL сохраняет дубликаты в результате выражения SELECT,
если отсутствует DISTINCT.1 Однако, как вы помните, в главе 4 я говорил, что слово DISTINCT следует употреблять «всегда». Поэтому, пус­ть
даже этот пример близок к реальности, не делайте отсюда вывод, что
сис­тема должна допускать строки-дубликаты во имя поддержки таких
запросов. Вот формулировка того же запроса, в которой UNIQUE не используется:
SELECT DISTINCT SNAME
FROM S
WHERE ( SELECT COUNT ( DISTINCT QTY )
FROM SP
WHERE SP.SNO = S.SNO ) <
( SELECT COUNT ( * )
FROM SP
WHERE SP.SNO = S.SNO )

В SQL ключевое слово UNIQUE применяется также в определениях
ключа. Например, в предложении CREATE TABLE для таб­лицы S есть
такая спецификация:
UNIQUE ( SNO )

Но на самом деле эта спецификация, по определению, является сокращенной записью следующей (которая могла бы быть частью более общего ограничения таб­лицы или предложения CREATE ASSERTION):
CHECK ( UNIQUE ( SELECT SNO FROM S ) )

Заметим, что выражение SELECT в этом ограничении CHECK точно не
должно содержать ключевого слова DISTINCT! (Почему?)
Кроме того, ключевое слово UNIQUE используется в SQL и в выражениях MATCH. Вот пример («Получить поставщиков, которые поставляют
ровно одну деталь»):
SELECT SNO , SNAME , STATUS , CITY
1

Это также первый пример в этой главе, где используются неявные пе­ре­
мен­ные кортежа (хотя в предыдущих главах таких примеров было сколько угодно). Если вам требуется формальное объяснение происходящего,
см. главу 12.

274

Глава 10. SQL и формальная логика
FROM S
WHERE SNO MATCH UNIQUE ( SELECT SNO FROM SP )

Однако и это не более чем сокращенная запись. Так, только что приведенный пример эквивалентен следующему:
SELECT SNO , SNAME , STATUS , CITY
FROM S
WHERE EXISTS ( SELECT * /* существует по меньшей мере один ... */
FROM SP
WHERE SP.SNO = S.SNO )
AND
UNIQUE ( SELECT *
/* ... и не существует двух
*/
FROM SP
WHERE SP.SNO = S.SNO )

который, в свою очередь, эквивалентен такому:
SELECT SNO , SNAME , STATUS , CITY
FROM S
WHERE ( SELECT COUNT ( * )
FROM SP
WHERE SP.SNO = S.SNO ) = 1

Некоторые эквиваленции
Я закончу эту главу несколькими замечаниями об эквиваленциях, которые, возможно, вам уже приходили в голову. Прежде всего, вспомним оператор IS_EMPTY, с которым мы познакомились в главе 3 и активно использовали в главе 8. Если сис­тема поддерживает этот оператор, то отпадает логичес­кая необходимость в поддержке кванторов благодаря следующим эквиваленциям:
EXISTS x ( p ) ≡ NOT ( IS_EMPTY ( X WHERE p ) )

и
FORALL x ( p ) ≡ IS_EMPTY ( X WHERE NOT ( p ) )

(Я предполагаю, что пе­ре­мен­ная x пробегает множес­тво X.)
На самом деле, имеющаяся в SQL поддержка EXISTS (и FORALL в том
виде, в котором она существует) основана именно на этих эквиваленциях. Факт в том, что EXISTS в SQL вообще не является квантором как
таковым, потому что отсутствуют связанные пе­ре­мен­ные. Это оператор
в общепринятом смысле слова: одноместный оператор типа BOOLEAN,
если быть точным. Как и для любого одноместного оператора, вызов
оператора EXISTS состоит из двух этапов: сначала вычисляется выражение, обозначающее его единственный аргумент, а потом сам оператор применяется к результату вычисления. Таким образом, видя выражение EXISTS (tx), где tx – таб­личное выражение, сис­тема сначала вычисляет tx, чтобы получить таб­лицу t, а затем применяет к t оператор
EXISTS, который возвращает TRUE, если t не пус­та, и FALSE в против-

Некоторые эквиваленции

275

ном случае. (Так, по крайней мере, алгоритм выглядит концептуально;
возможны многочисленные оптимизации, но они к теме данного обсуждения не имеют от­но­ше­ния.)
А теперь я объясню, почему SQL не поддерживает квантор FORALL.
Причина состоит в том, что представление квантора всеобщности
с помощью оператора, имеющего синтаксис FORALL(tx), где tx – таб­
личное выражение, не имеет никакого смысла. Рассмотрим, к примеру, гипотетичес­кое выражение FORALL (SELECT * FROM S WHERE
CITY = ‘Paris’). Что оно могло бы означать? Очевидно, что оно не могло
бы означать «Все поставщики в Париже», потому что – говоря нестрого – аргумент, к которому применяется этот гипотетичес­кий оператор, –
не все поставщики вообще, а только поставщики в Париже.
Но на самом деле нам вообще не нужны кванторы, если сис­тема поддерживает агрегатный оператор COUNT, благодаря следующим эквиваленциям:
•• EXISTS x ( p ) ≡ COUNT ( X WHERE p ) > 0
•• FORALL x ( p ) ≡ COUNT ( X WHERE p ) = COUNT ( X )
•• UNIQUE x ( p ) ≡ COUNT ( X WHERE p ) = 1

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

Отступление
В этой книге речь редко заходит о производительности, но я должен хотя бы
отметить, что приведенные выше эквиваленции могут порождать проблемы
с производительностью. Рассмотрим, к примеру, следующее выражение, которое выражает на SQL запрос «Получить поставщиков, которые поставляют по
меньшей мере одну деталь»:
SELECT
FROM
WHERE
(

*
S
EXISTS
SELECT *
FROM SP
WHERE SP.SNO = S.SNO )

Вот другая формулировка, логичес­ки эквивалентная предыдущей:
SELECT *
FROM S
WHERE ( SELECT COUNT ( * )
FROM SP
WHERE SP.SNO = S.SNO ) > 0

Но нам совершенно ни к чему, чтобы сис­тема вычисляла истинное значение
счетчика, который здесь упоминается, только чтобы потом проверить, боль-

276

Глава 10. SQL и формальная логика

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

Реляционная полнота
У каждого оператора реляционной алгебры есть точное определение
в терминах формальной логики. (Я не останавливался на этом раньше, но легко видеть, что данные в главах 6 и 7 определения соединения и других операторов легко переформулировать в терминах логики,
как описано в этой главе.) Отсюда сразу следует, что для каждого выражения реляционной алгебры существует логичес­ки эквивалентное (то
есть обладающее той же семантикой) ему выражение реляционного исчисления. Иными словами, реляционное исчисление по меньшей мере
столь же «мощно» (или, точнее, столь же выразительно), сколь и реляционная алгебра: любая задача, выразимая средствами алгебры, может быть выражена и средствами исчисления.
Обратное также верно, хотя это и не столь очевидно, – реляционная алгебра по меньшей мере столь же выразительна, сколь и реляционное исчисление. Другими словами, оба формализма логичес­ки эквивалентны,
и оба, как говорят, реляционно полные. В приложении A я еще скажу
несколько слов об этом понятии.

Важность непротиворечивости
Мне необходимо поставить точку в одном незаконченном деле. Если
помните, в главе 8 я заявил, что в противоречивой сис­теме можно доказать истинность любого высказывания (даже очевидно ложного). Поговорим об этом утверждении более подробно.
Начну с простого примера. Предположим, что (а) пе­ре­мен­ная-от­но­ше­
ние S в данный момент не пус­та; (б) имеется ограничение, требующее,
чтобы всегда существовала хотя бы одна деталь, но в данный момент
пе­ре­мен­ная-от­но­ше­ние P пус­та (налицо противоречие). Теперь рассмотрим такой запрос реляционного исчисления:
SX WHERE EXISTS PX ( TRUE )

Или, если вы предпочитаете SQL:
SELECT
FROM
WHERE
(

*
S
EXISTS
SELECT *
FROM P )

Заключительные замечания

277

Если вычислять это выражение непосредственно, то получится пус­той
результат. Альтернативно, если сис­тема (или пользователь) замечает,
что существует ограничение, которое говорит, что EXISTS PX (TRUE)
всегда должно иметь значение TRUE, то фразу WHERE можно свести просто к WHERE TRUE, и тогда результатом будет все от­но­ше­
ние «поставщики». По крайней мере один из этих результатов обязан
быть ложным! Фактичес­ки в некотором смысле ложны оба; в противоречивой базе данных просто не существует – и не может существовать – корректно определенного понятия правильности, и любой ответ
столь же хорош (или плох), как любой другой. Собственно, это и так
очевидно: если я говорю вам, что высказывание p одновременно истинно и ложно, а затем спрашиваю, истинно ли p, то вы просто не можете
дать правильный ответ.
Если я вас еще не убедил, давайте рассмотрим следующий чуть более
реалистичный пример на SQL (в тех же предположениях):
SELECT DISTINCT
CASE WHEN EXISTS ( SELECT * FROM P ) THEN x ELSE y END
FROM S

Это выражение вернет либо x, либо y – точнее, таб­лицу, содержащую
либо x, либо y – в зависимости от того, будет или не будет вызов EXISTS
заменен простым TRUE. А теперь примите во внимание, что x и y могут
быть чем угодно… Например, x может быть SQL-выражением, обозначающим суммарный вес всех деталей, а y – литералом 0. И в таком случае выполнение этого запроса легко может привести к неверному выводу о том, суммарный вес деталей равен null, а не 0.

Заключительные замечания
Я глубоко убежден, что профессионалы в области баз данных вообще
и специалисты, применяющие SQL на практике, в частности, должны
быть знакомы хотя бы с основами логики предикатов (или реляционного исчисления – что в конечном итоге одно и то же). Я хотел бы завершить эту главу обоснованием своей точки зрения.
Моя основная мысль заключается в том, что знание формальной логики приучает точно мыслить (а в нашей области точность мышления имеет особенно большое значение). В частности, оно заставляет ценить значимость надлежащей квантификации. Естественный язык часто бывает неточен, но тщательный анализ того, какая именно квантификация нужна, позволяет ухватить смысл высказывания, которое на
естественном языке звучало бы очень расплывчато. Например, можете
поразмыслить над тем, что хотел сказать Авраам Линкольн – или мог
бы хотеть, или думал, что хочет сказать, или мог бы думать, что хочет
сказать – в своем знаменитом афоризме: «Можно всё время дурачить

278

Глава 10. SQL и формальная логика

некоторых, можно некоторое время дурачить всех, но нельзя всё время
дурачить всех».
Я прекрасно осознаю, что многие со мной не согласятся, то есть найдется немало людей, считающих, что простым смертным нет необходимости вгрызаться в такой неподатливый предмет, каким может показаться формальная логика. По сути дела, они заявляют, что логика слишком трудна для большинства людей. Возможно, в целом это и так (логика – обширный предмет). Но для наших узких целей вовсе не обязательно разбираться во всей формальной логике; я даже сомневаюсь, что
потребуется существенно больше того, что уже изложено в этой главе.
Но сколь велика награда! Я уже высказывал эту мысль в другой своей
книге «Logic and Databases: The Roots of Relational Theory» (Trafford,
2007) и хотел бы процитировать слова из нее:
Безусловно, имеет смысл с самого начала потратить определенные
усилия на ознакомление с материалом, изложенным в этой главе, чтобы впоследствии избежать проблем с неоднозначно трактуемыми бизнес-правилами. Неоднозначность бизнес-правил ведет
к задержкам реализации в лучшем случае и к ошибкам реализации – в худшем (или к тому и другому сразу). А такие задержки и ошибки, конечно, стоят денег, возможно, куда больших, чем
пришлось бы потратить на начальное обучение. Другими словами, правильное оформление бизнес-правил – серьезное дело, требующее определенного уровня техничес­кой грамотности.

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

Упражнения
Упражнение 10.1. В тексте главы я сказал, что существует ровно 16
двуместных связок. Выпишите все соответствующие таб­лицы истинности. Сколько существует одноместных связок?
Упражнение 10.2. (Уже приводилось в тексте главы, но здесь сформулировано по-другому). (а) Докажите, что все одноместные и двуместные связки можно выразить в терминах подходящих комбинаций
NOT и либо AND, либо OR; (б) докажите, что все связки можно выразить в терминах одной-единственной связки.
Упражнение 10.3. Рассмотрим предикат «x – звезда». Если подставить
вместо x Солнце, станет ли этот предикат высказыванием? Если нет,
то почему? А если в качестве аргумента берется Луна?
Упражнение 10.4. Снова рассмотрим предикат «x – звезда». Если подставить вместо x Солнце, будет ли удовлетворяться этот предикат?
Если нет, то почему? А если в качестве аргумента берется Луна?

279

Упражнения

Упражнение 10.5. Еще раз приведем ограничение CX1 из главы 8:
CONSTRAINT CX1 IS_EMPTY ( S WHERE STATUS < 1 OR STATUS > 100 ) ;

Здесь выражение IS_EMPTY (...), очевидно, является предикатом.
А в главе 8 я говорил, что имя пе­ре­мен­ной-от­но­ше­ния «S» в этом предикате играет роль обозначения. Но разве это не параметр? Если нет,
то в чем разница?
Упражнение 10.6. (Уже приводилось в тексте главы.) Что означает следующее выражение:
{ SX.SNAME } WHERE EXISTS SPX ( FORALL PX ( SPX.SNO = SX.SNO AND
SPX.PNO = PX.PNO ) )

Упражнение 10.7. (Уже приводилось в тексте главы.) Приведите SQLаналоги выражений реляционного исчисления из подраздела «Дополнительные примеры запросов».
Упражнение 10.8. Докажите, что операции AND и OR ассоциативны.
Упражнение 10.9. Пусть p(x) и q – предикаты, в которых x соответственно встречается и не встречается в качестве свободной пе­ре­мен­
ной. Какие из следующих утверждений верны? (Напоминаю, что
символом «⇒» обозначается импликация, а символом «≡» – эквиваленция. Отмечу также, что одновременная истинность A ⇒ B и B ⇒
A – то же самое, что A ≡ B.)
a. EXISTS x ( q ) ≡ q
b. FORALL x ( q ) ≡ q
c. EXISTS x ( p(x) AND q ) ≡ EXISTS x ( p(x) ) AND q
d. FORALL x ( p(x) AND q ) ≡ FORALL x ( p(x) ) AND q
e. FORALL x ( p(x) ) ⇒ EXISTS x ( p(x) )
f. EXISTS x ( TRUE ) ≡ TRUE
g. FORALL x ( FALSE ) ≡ FALSE
h. UNIQUE x ( p(x) ) ⇒ EXISTS x ( p(x) )
i. UNIQUE x ( p(x) ) ⇒ FORALL x ( p(x) )
j. FORALL x ( p(x) ) AND EXISTS x ( p(x) ) ⇒ UNIQUE x ( p(x) )
k. FORALL x ( p(x) ) AND UNIQUE x ( p(x) ) ⇒ EXISTS x ( p(x) )
Упражнение 10.10. Пусть p(x,y) – предикат со свободными пе­ре­мен­
ными x и y. Какие из следующих утверждений верны?
a. EXISTS x EXISTS y ( p(x,y) ) ≡ EXISTS y EXISTS x ( p(x,y) )
b. FORALL x FORALL y ( p(x,y) ) ≡ FORALL y FORALL x ( p(x,y) )
c. FORALL x ( p(x,y) ) ≡ NOT EXISTS x ( NOT p(x,y) )

280

Глава 10. SQL и формальная логика

d. EXISTS x ( p(x,y) ) ≡ NOT FORALL x ( NOT p(x,y) )
e. EXISTS x FORALL y ( p(x,y) ) ≡ FORALL y EXISTS x ( p(x,y) )
f. EXISTS y FORALL x ( p(x,y) ) ⇒ FORALL x EXISTS y ( p(x,y) )
Упражнение 10.11. Пусть p(x) и q(y) – предикаты со свободными пе­ре­
мен­ными x и y соответственно. Какие из следующих утверждений
верны?
a. EXISTS x ( p(x) ) AND EXISTS y ( q(y) ) ≡
EXISTS x EXISTS y ( p(x) AND q(y) )
b. EXISTS x ( IF p(x) THEN q(x) ) ≡
IF FORALL x ( p(x) ) THEN EXISTS x ( q(x) )
Упражнение 10.12. Там, где это возможно и разумно, дайте решения
упражнений из глав 8–9 в терминах реляционного исчисления.
Упражнение 10.13. Рассмотрим запрос: «Получить города, в которых
находится либо какой-то поставщик, либо какая-то деталь». Можно
ли выразить этот запрос в терминах реляционного исчисления? Если
нет, то почему?
Упражнение 10.14. Покажите, что язык SQL реляционно полный (то
есть докажите, что для каждого выражения реляционной алгебры
или реляционного исчисления существует семантичес­ки эквивалентная формулировка на SQL).
Упражнение 10.15. Ниже приведена цитата из главы 8:
•• Если база данных содержит только истинные высказывания, то
она непротиворечива, но обратное может быть неверно.
•• Если база данных противоречива, то она содержит хотя бы одно
ложное утверждение, но обратное опять-таки неверно.
Верно ли, что эти два утверждения логичес­ки эквивалентны? Другими словами, нет ли здесь дублирования?
Упражнение 10.16. Всегда ли достижима предваренная нормальная
форма?

Глава 11. Использование формальной
логики для SQL-выражений

11
Глава
.

Использование формальной логики
для формулирования SQL-выражений

В главе 6 я описал процедуру трансформации выражений, применяемую к выражениям реляционной алгебры; я показал, как одно выражение можно трансформировать в другое, логичес­ки эквивалентное,
применяя различные правила трансформации. Среди рассмотренных
правил были такие:
a. Дистрибутивность ограничения относительно объединения, пересечения и разности
b. Дистрибутивность проекции относительно объединения, но не
пересечения и разности
и ряд других. (Как вы, наверное, догадываетесь, аналогичные правила применимы и к выражениям реляционного исчисления, хотя в главе 10 я о них не говорил.)
Я отметил, что цель таких трансформаций – в основном оптимизация;
задача состоит в том, чтобы найти выражение с такой же семантикой,
как у исходного, но с лучшими показателями производительности. Однако сама идея трансформации выражений – или переписывания запросов, как ее иногда (не сов­сем правильно) называют, – находит применение и в других областях. В частности, что очень важно, ею можно
воспользоваться для трансформации точных логичес­ких выражений,
представляющих запросы, в SQL-эквиваленты. Именно этому и посвящена настоящая глава: будет показано, как, имея формулировку некоторого запроса или ограничения (к примеру) в терминах формальной
логики или реляционного исчисления, применить сис­тематичес­кую
процедуру преобразования ее в эквивалент на языке SQL. И хотя полученная таким образом формулировка на SQL иногда с трудом поддается
пониманию, мы можем быть уверены в ее правильности благодаря фор-

282

Глава 11. Использование формальной логики для SQL-выражений

мализованному способу получения. Отсюда и подзаголовок книги: Как
грамотно писать код на SQL.

Некоторые правила трансформации
Правила трансформации, подобные упомянутым выше, известны и под
другими названиями:
•• Эквиваленции, потому что в общем виде они записываются так: exp1 ≡
exp2 (напомню, что символ «≡» означает «эквивалентно»).
•• Тождества, потому что правило вида exp1 ≡ exp2 можно произнести
так: exp1 и exp2 «тождественно равны», то есть обладают одинаковой семантикой.
•• Правила перезаписи, потому из правила вида exp1 ≡ exp2 следует,
что выражение, в которое входит exp1, можно переписать в виде выражения, в которое входит exp2, не изменяя смысла.
О последнем пункте я хотел бы поговорить подробнее, так как он особенно важен для того, чем мы займемся в этой главе. Итак, пус­ть X1 –
выражение, содержащее вхождение x1 в качестве подвыражения, пус­
ть x2 эквивалентно x1, и пус­ть X2 – выражение, полученное подстановкой x2 вместо x1 в X1. Тогда X1 и X2 логичес­ки и семантичес­ки эквивалентны, следовательно, X1 можно переписать как X2.
Вот простой пример. Рассмотрим следующее SQL-выражение
SELECT
FROM
WHERE (
OR
(

SNO
S
STATUS > 10 AND CITY = ‘London’ )
STATUS > 10 AND CITY = ‘Athens’ )

Булево выражение во фразе WHERE, очевидно, эквивалентно такому:
STATUS > 10 AND ( CITY = ‘London’ OR CITY = ‘Athens’ )

Поэтому все выражение можно переписать в виде
SELECT
FROM
WHERE
AND
(

SNO
S
STATUS > 10
CITY = ‘London’ OR CITY = ‘Athens’ )

Перечислю некоторые правила трансформации, которыми мы будем
пользоваться в этой главе.
•• Правило импликации
IF p THEN q ≡ ( NOT p ) OR q

Я сформулировал это правило в главе 10, но не привел примеров. Потратьте некоторое время (если вам это требуется) на то, чтобы проверить таб­лицы истинности и убедиться, что это правило дейст­
вительно справедливо. Примечание: Символами p и q обозначают-

283

Некоторые правила трансформации

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

Это правило очевидно (но тем не менее важно).
•• Законы де Моргана
NOT ( p AND q ) ≡ ( NOT p ) OR ( NOT q )
NOT ( p OR q ) ≡ ( NOT p ) AND ( NOT q )

Я ничего не говорил об этих законах в предыдущей главе, но полагаю, что интуитивно они понятны. Например, первый утверждает
следующее: если неверно, что p и q одновременно истинны, значит,
либо не истинно p, либо не истинно q (либо оба вместе). Так или иначе, справедливость обоих законов без труда проверяется по таб­лицам
истинности. Вот, например, таб­лица истинности, соответствующая
первому закону:
p q
T
T
F
F

T
F
T
F

p AND q

NOT (p AND q)

(NOT p) OR (NOT q)

T
F
F
F

F
T
T
T

F
T
T
T

Справедливость первого закона де Моргана следует из того, что столбцы NOT (p AND q) и (NOT p) OR (NOT q) одинаковы. Доказательство
второго закона аналогично.
•• Дистрибутивные законы
p AND ( q OR r ) ≡ ( p AND q ) OR ( p AND r )
p OR ( q AND r ) ≡ ( p OR q ) AND ( p OR r )

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

284

Глава 11. Использование формальной логики для SQL-выражений

•• Правило квантификации
FORALL x ( p ( x ) ) ≡ NOT EXISTS x ( NOT p ( x ) )

Это правило я тоже обсуждал в предыдущей главе. Надеюсь, вы заметили, что в дейст­вительности это просто применение законов де
Моргана к выражениям EXISTS и FORALL (напомню, что в предыдущей главе было показано, что EXISTS и FORALL – это, по существу, последовательность повторяющихся OR и AND соответственно).
Еще одно замечание об этих законах: поскольку законы де Моргана часто применяются к результату применения правила импликации, то
удобно хотя бы первый из них переписать в таком виде (где вместо q подставлено NOT q и молчаливо применен закон двойного отрицания):
NOT ( p AND NOT q ) ≡ ( NOT p ) OR q

Или в таком (логичес­ки эквивалентном):
( NOT p ) OR q ≡ NOT ( p AND NOT q )

Или, что то же самое:
IF p THEN q ≡ NOT ( p AND NOT q )

По большей части ссылки на законы де Моргана в данной главе будут
относиться к этой последней формулировке.
Далее в этой главе будут предложены практичес­кие рекомендации по
применению этих законов к формулировке «сложных» SQL-выражений.
Я начну с простых примеров и буду двигаться в сторону усложнения.

Пример 1. Логичес­кая импликация
Рассмотрим еще раз упомянутое в предыдущей главе ограничение, требующее, чтобы все красные детали хранились в Лондоне. Для конкретной детали этому ограничению соответствует бизнес-правило, формулируемое (более-менее формально) следующим образом:
IF COLOR = ‘Red’ THEN CITY = ‘London’

Иначе говоря, это логичес­кая импликация. Правда, в SQL логичес­кая
импликация в чистом виде не поддерживается, но правило импликации позволяет трансформировать это выражение в такое:
( NOT ( COLOR = ‘Red’ ) ) OR CITY = ‘London’

(Скобки я добавил для ясности.) А в этом выражении встречаются только операторы, поддерживаемые SQL, поэтому его уже можно сформулировать как ограничение базовой таб­лицы:
CONSTRAINT BTCX1 CHECK ( NOT ( COLOR = ‘Red’ ) OR CITY = ‘London’ )

Пример 2. Добавление квантора всеобщности

285

Или, быть может, несколько более естественно (учитывая тот факт,
что NOT (a = b) можно трансформировать в a ≠ b (в нотации SQL a b)
и опус­тить некоторые скобки):
CONSTRAINT BTCX1 CHECK ( COLOR ‘Red’ OR CITY = ‘London’ )

Пример 2. Добавление квантора всеобщности
Сознаюсь, что в примере 1 я немного схитрил, притворившись, будто понятно, к какой конкретной детали применяется ограничение. Но
природа ограничений базовых таб­лиц в SQL такова, что они применяются к каждой строке базовой таб­лицы, в определении которой встречаются. Предположим, однако, что мы хотим явно сформулировать,
что ограничение должно применяться к каждой детали, которая присутствует в таб­лице P. Иными словами, для любой детали PX, если цвет
PX красный, городом PX является Лондон:
FORALL PX ( IF PX.COLOR = ‘Red’ THEN PX.CITY = ‘London’ )

Примечание
Имя PX и подобные ему в этой главе специально выбраны так, чтобы напоминать о пе­ре­мен­ных кортежа, которые использовались в предыдущей главе. Фактичес­ки, начиная с этого места, я буду предполагать, что имена вида
PX, PY, PZ и т. д. обозначают пе­ре­мен­ные, областью значений которых является таб­лица P; для пе­ре­мен­ных с именами вида SX, SY, SZ и т. д. областью значений является таб­лица S и так далее. Детали того, как эти пе­ре­мен­ные определены (в формальной логике, а не в SQL), сейчас для нас несущественны и потому опускаются. В SQL они определяются с помощью фразы AS, как я продемонстрирую, когда мы дойдем до SQL-формулировок.

В SQL квантор FORALL не поддерживается, однако правила квантификации позволяют трансформировать приведенное выше выражение
в такое:
NOT EXISTS PX ( NOT ( IF PX.COLOR = ‘Red’
THEN PX.CITY = ‘London’ ) )

(И снова я для ясности добавил скобки. Начиная с этого места, я буду
добавлять скобки или убирать уже имеющиеся без специальных комментариев.) Теперь применим правило импликации:
NOT EXISTS PX ( NOT ( NOT ( PX.COLOR = ‘Red’ )
OR PX.CITY = ‘London’ ) )

Это выражение уже можно было бы отобразить на SQL, но, пожалуй,
сначала стоит еще немного поработать над ним. Применяем закон де
Моргана:
NOT EXISTS PX ( NOT ( NOT ( ( PX.COLOR = ‘Red’ )
AND NOT ( PX.CITY = ‘London’ ) ) ) )

286

Глава 11. Использование формальной логики для SQL-выражений

Применяем закон двойного отрицания и опускаем некоторые скобки:
NOT EXISTS PX ( PX.COLOR = ‘Red’ AND NOT ( PX.CITY = ‘London’ ) )

И окончательно
NOT EXISTS PX ( PX.COLOR = ‘Red’ AND PX.CITY ≠ ‘London’ )

Все примененные до сих пор трансформации были очень простыми, вы,
наверное, даже заскучали. Однако отображение последнего логического выражения на SQL не так уж тривиально. Опишу подробности этого отображения:
•• Во-первых (что не удивительно), NOT отображается на NOT.
•• Во-вторых, «EXISTS PX (bx)» отображается на «EXISTS (SELECT *
FROM P AS PX WHERE (sbx))», где sbx – SQL-аналог булевого выражения bx.
•• В-третьих, скобки, окружающие sbx, можно опус­тить, хотя это и не
обязательно.
•• И наконец, все выражение целиком следует погрузить в подходящее
предложение CREATE ASSERTION.
Вот как выглядит окончательный результат:
CREATE ASSERTION ... CHECK
( NOT EXISTS ( SELECT
FROM
WHERE
AND

*
P AS PX
PX.COLOR = ‘Red’
PX.CITY ‘London’ ) ) ;

Пример 3. Импликация и квантор всеобщности
На этот раз рассмотрим запрос «Получить названия деталей, для которых вес отличается от веса любой детали, находящейся в Париже». Вот
как выглядит прямолинейная формулировка в терминах формальной
логики (то есть реляционного исчисления):
{ PX.PNAME } WHERE FORALL PY ( IF PY.CITY = ‘Paris’
THEN PY.WEIGHT ≠ PX.WEIGHT )

Это выражение можно интерпретировать следующим образом: «получить значения PNAME для деталей PX таких, что для всех деталей
PY, если PY находится в Париже, то веса PY и PX различны». Обратите внимание, что в подобных интерпретациях на естественном языке
я считаю обороты где и такой что синонимами и употребляю тот, что
лучше звучит в конкретном контексте.
В качестве первой трансформации применим правило квантификации:
{ PX.PNAME } WHERE NOT EXISTS PY ( NOT ( IF PY.CITY = ‘Paris’
THEN PY.WEIGHT ≠ PX.WEIGHT ) )

Пример 3. Импликация и квантор всеобщности

287

Затем – правило импликации:
{ PX.PNAME } WHERE
NOT EXISTS PY ( NOT ( NOT ( PY.CITY = ‘Paris’ )
OR ( PY.WEIGHT ≠ PX.WEIGHT ) ) )

Теперь – закон де Моргана:
{ PX.PNAME } WHERE
NOT EXISTS PY ( NOT ( NOT ( ( PY.CITY = ‘Paris’ )
AND NOT ( PY.WEIGHT ≠ PX.WEIGHT ) ) ) )

Наведем красоту, воспользовавшись законом двойного отрицания
и тем, что NOT (a ≠ b) эквивалентно a = b:
{ PX.PNAME } WHERE NOT EXISTS PY ( PY.CITY = ‘Paris’ AND
PY.WEIGHT = PX.WEIGHT )

Отображаем на SQL:
SELECT
FROM
WHERE
(

DISTINCT PX.PNAME
P AS PX
NOT EXISTS
SELECT *
FROM P AS PY
WHERE PY.CITY = ‘Paris’
AND
PY.WEIGHT = PX.WEIGHT )

Кстати, слово DISTINCT в первой фразе SELECT необходимо! Вот что
получается в результате:1
PNAME
Screw
Cog

К сожалению, в эту бочку меда попала ложка дегтя. Предположим, что
в Париже есть хотя бы одна деталь, но у всех таких деталей вес равен
null. Тогда мы попросту не знаем – не можем сказать, – существуют ли
какие-нибудь детали, вес которых отличается от веса любой детали, находящейся в Париже (то есть, на исходный вопрос, строго говоря, нельзя дать ответ). Однако в SQL подзапрос, следующий за ключевым словом EXISTS, возвращает пус­тую таб­лицу для любой детали PX, представленной в P, поэтому NOT EXISTS будет принимать значение TRUE
для любой такой детали PX, и выражение в целом вернет названия всех
деталей в таб­лице P, что совершенно неправильно.
1

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

288

Глава 11. Использование формальной логики для SQL-выражений

Отступление
Это, пожалуй, самая серьезная практичес­кая проблема, связанная с nullзначениями, – они ведут к неверным ответам. Еще хуже то, что в общем случае
мы даже не знаем, какие ответы верны, а какие – нет! Более подробное освещение этой темы см. в статье «Почему не работает трех- и четырехзначная логика», упомянутой в приложении D.

Скажу больше – неправилен не только результат предыдущего SQLзапроса, но и любой определенный результат следовало бы рассматривать как ложь со стороны сис­темы. Еще раз повторю, единственный
логичес­ки правильный результат в данном случае – «Я не знаю», или,
если быть более точным и честным: «У сис­темы нет достаточной информации, чтобы дать определенный ответ на этот запрос».
Еще более отягчает ситуацию тот факт, что при тех же самых условиях
(в Париже есть хотя бы одна деталь, и все такие детали имеют вес null)
SQL-выражение
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE PX.WEIGHT NOT IN ( SELECT PY.WEIGHT
FROM P AS PY
WHERE PY.CITY = ‘Paris’ )

которое вроде бы должно быть эквивалентно показанному выше (и так
оно и есть в отсутствие null-значений), возвращает пус­той результат:
иной, хотя и столь же неправильный, ответ.
Мораль очевидна: избегайте null-значений! И тогда все трансформации
будут работать корректно.

Пример 4. Коррелированные подзапросы
Рассмотрим запрос «Получить названия поставщиков, которые поставляют одновременно детали P1 и P2». Вот его формально-логичес­кая
формулировка:
{ SX.SNAME } WHERE
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = ‘P1’ ) AND
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = ‘P2’ )

Эквивалентная SQL-формулировка записывается без труда:
SELECT DISTINCT SX.SNAME
FROM S AS SX
WHERE EXISTS ( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = ‘P1’ )
AND
EXISTS ( SELECT *
FROM SP AS SPX

Пример 4. Коррелированные подзапросы

289

WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = ‘P2’ )

И приводит к такому результату:
SNAME
Smith
Jones

Однако легко видеть, что SQL-выражение содержит два коррелированных подзапроса. (На самом деле, в примере 3 тоже встречался коррелированный подзапрос. Дополнительные сведения см. в главе 12.) Но
коррелированные подзапросы часто противопоказаны с точки зрения
производительности, потому что (во всяком случае концептуально) их
необходимо повторно вычислять для каждой строки внешней таб­лицы,
а не раз и навсегда. Поэтому стоит подумать, нельзя ли их устранить.
В рассматриваемом случае (когда коррелированные подзапросы встречаются внутри вызова EXISTS) существует простое преобразование,
позволяющее добиться указанной цели. В результате получается такое
выражение:
SELECT DISTINCT SX.SNAME
FROM S AS SX
WHERE SX.SNO IN ( SELECT
FROM
WHERE
AND
SX.SNO IN ( SELECT
FROM
WHERE

SPX.SNO
SP AS SPX
SPX.PNO = ‘P1’ )
SPX.SNO
SP AS SPX
SPX.PNO = ‘P2’ )

Более общо, SQL-выражение
SELECT sic /* «SELECT item commalist» – список элементов SELECT */
FROM T1
WHERE [ NOT ] EXISTS ( SELECT *
FROM T2
WHERE T2.C = T1.C
AND
bx )

можно трансформировать в
SELECT sic
FROM T1
WHERE T1.C [ NOT ] IN ( SELECT T2.C
FROM T2
WHERE bx )

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

290

Глава 11. Использование формальной логики для SQL-выражений

случаев, когда эта трансформация попросту неприменима. Как показывает пример 3, одной из причин является наличие null-значений (кстати, а являются ли null-значения проблемой в примере 4?), но есть ситуации, когда ее не удается применить, даже если никаких null-значений
нет и в помине. В качестве упражнения можете попытаться понять,
к каким из следующих далее примеров эта трансформация применима.

Пример 5. Именование подвыражений
Рассмотрим запрос «Получить всю информацию о поставщиках, которые поставляют все фиолетовые детали». Примечание: такой или похожий запрос часто служит для демонстрации изъяна в той форме реляционного оператора деления, которая была предложена изначально.
См. замечание по этому поводу в конце раздела.
Вот первая формулировка в терминах логики:
{ SX } WHERE FORALL PX ( IF PX.COLOR = ‘Purple’ THEN
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) )

(«названия поставщиков SX таких, что для всех деталей PX, если PX
фиолетовая, то существует поставка SPX, в которой SNO равно номеру
поставщика SX, а PNO равно номеру детали PX»). Сначала применим
правило импликации:
{ SX } WHERE FORALL PX ( NOT ( PX.COLOR = ‘Purple’ ) OR
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) )

Затем – закон де Моргана:
{ SX } WHERE
FORALL PX ( NOT ( ( PX.COLOR = ‘Purple’ ) AND
NOT EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) ) )

Теперь – правило квантификации:
{ SX } WHERE
NOT EXISTS PX ( NOT ( NOT ( ( PX.COLOR = ‘Purple’ ) AND
NOT EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) ) ) )

Правило двойного отрицания:
{ SX } WHERE
NOT EXISTS PX ( ( PX.COLOR = ‘Purple’ ) AND
NOT EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) )

Опускаем лишние скобки и отображаем на SQL:
SELECT
FROM
WHERE
(

*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX

Пример 5. Именование подвыражений

291

WHERE PX.COLOR = ‘Purple’
AND
NOT EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO ) )

Результатом является все от­но­ше­ние «поставщики»:1
SNO

SNAME STATUS

CITY

S1
S2
S3
S4
S5

Smith
Jones
Blak e
Clark
Adams

London
Paris
Paris
London
Athens

20
10
30
20
30

Возможно, вы не сов­сем понимаете, как были выполнены трансформации в этом примере и почему получился такой результат. Когда выражения становятся слишком сложными, имеет смысл применить полезный прием, заключающийся в том, чтобы ввести символичес­кие имена
для подвыражений. Обозначим exp1 подвыражение
PX.COLOR = ‘Purple’

а exp2 – такое подвыражение:
EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO )

(отметим, что оба подвыражения можно более или менее непосредственно представить на SQL). Тогда исходное выражение реляционного
исчисления принимает вид:
{ SX } WHERE FORALL PX ( IF exp1 THEN exp2 )

Теперь за деревьями стал виден лес, и мы можем приступить к применению обычных трансформаций, хотя сейчас представляется более ра­
зумным применить их в другом порядке, именно потому что мы лучше
ухватываем всю картину в целом. Итак, сначала правило квантификации:
{ SX } WHERE NOT EXISTS PX ( NOT ( IF exp1 THEN exp2 ) )

Затем правило импликации:
{ SX } WHERE NOT EXISTS PX ( NOT ( NOT ( exp1 ) OR exp2 ) )

Закон де Моргана:
{ SX } WHERE NOT EXISTS PX ( NOT ( NOT ( exp1 AND NOT ( exp2 ) ) )
1

Напомню (см. главу 7), что, поскольку фиолетовых деталей нет вообще, то
каждый поставщик поставляет их все, – даже поставщик S5, который не
поставляет никаких деталей. См. обсуждение пус­тых множес­тв значений
в главе 10.

292

Глава 11. Использование формальной логики для SQL-выражений

Двойное отрицание:
{ SX } WHERE NOT EXISTS PX ( exp1 AND NOT ( exp2 ) )

И наконец, разворачиваем exp1 и exp2 и отображаем все выражение на
SQL:
SELECT
FROM
WHERE
(

*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX
WHERE PX.COLOR = ‘Purple’
AND
NOT EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO ) )

Я полагаю, этот пример убедительно демонстрирует, что SQL-вы­ра­же­
ния, получаемые с помощью обсуждаемых методов, часто оказываются сложны для восприятия, но, как я уже говорил, они заведомо корректны, потому что для их вывода сис­тематичес­ки применялись строгие правила.
Не могу противиться искушению привести версию этого примера на
языке Tutorial D – просто для сравнения:
S WHERE ( !!SP ) { PNO } ⊇ ( P WHERE COLOR = ‘Purple’) { PNO }

А теперь я хотел бы вернуться к замечанию об операторе деления, сделанному в начале этого раздела. Для простоты обозначим символом PP
оператор ограничения P WHERE COLOR = ‘Purple’. Кроме того, упростим запрос – «Получить всю информацию о поставщиках, которые поставляют все фиолетовые детали» – так, чтобы запрашивалась не вся
информация, а только номера поставщиков. Тогда можно подумать, что
этот запрос представляется следующим выражением:
SP { SNO , PNO } DIVIDEBY PP { PNO }

Примечание
Здесь DIVIDEBY представляет оператор деления, определенный в оригинальной модели. Если вам нужны пояснения по этому поводу, обратитесь к главе 7.

Однако на наших тестовых данных от­но­ше­ние PP, а, стало быть, и проекция PP на {PNO}, пус­ты (поскольку фиолетовых деталей не существует), и это выражение возвращает номера поставщиков S1, S2, S3, S4.
Но если фиолетовых деталей нет, то любой поставщик поставляет их
все (см. обсуждение пус­тых множес­тв значений в предыдущей главе) –
даже поставщик S5, который не поставляет вообще никаких деталей.
В то же время приведенная выше операция деления не может вернуть
номер поставщика S5, так как номера поставщиков извлекаются из SP,

Пример 6. Еще об именовании подвыражений

293

а не из S, а поставщик S5 в SP не представлен. Поэтому неформальное
описание такой операции деления как «Получить номера поставщиков,
которые поставляют все фиолетовые детали» некорректно, правильнее
было бы сказать «Получить номера поставщиков, которые поставляют хотя бы одну фиолетовую деталь и при этом поставляют все такие детали». Поэтому, как показывает этот пример (и повторяя сказанное в главе 7), оператор деления не решает задачу, для решения которой задумывался.

Пример 6. Еще об именовании подвыражений
Я приведу еще один пример, иллюстрирующий полезность введения
символичес­ких имен для подвыражений. Мы рассмотрим запрос «Получить поставщиков таких, что каждая поставляемая ими деталь находится в том же городе, где и сам поставщик». Вот его формулировка
в терминах формальной логики:
{ SX } WHERE FORALL PX
( IF EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO )
THEN PX.CITY = SX.CITY )

(«поставщики SX такие, что для любой детали PX, если существует поставка PX поставщиком SX, то PX.CITY = SX.CITY»).
На этот раз я покажу трансформации, не называя применяемых на
каждом шаге правил (оставляю это в качестве упражнения для читателя):
{ SX } WHERE FORALL PX ( IF exp1 THEN exp2 )
{ SX } WHERE NOT EXISTS PX ( NOT ( IF exp1 THEN exp2 ) )
{ SX } WHERE NOT EXISTS PX ( NOT ( NOT ( exp1 ) OR exp2 ) )
{ SX } WHERE NOT EXISTS PX ( NOT ( NOT ( exp1 AND NOT ( exp2 ) ) ))
{ SX } WHERE NOT EXISTS PX ( exp1 AND NOT ( exp2 ) )

Теперь разворачиваем exp1 и exp2 и отображаем на SQL:
SELECT
FROM
WHERE
(

*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX
WHERE EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO )
AND
PX.CITY SX.CITY )

294

Глава 11. Использование формальной логики для SQL-выражений

Результат:
SNO

SNAME STATUS

CITY

S3
S5

Blake
Adams

Paris
Athens

30
30

Кстати, если этот результат кажется вам странным, обратите внимание,
что поставщик S3 поставляет только одну деталь P2, а поставщик S5 не
поставляет никаких деталей, поэтому, с точки зрения логики, оба эти
поставщика удовлетворяют условию, что «каждая поставляемая ими
деталь» находится в том же городе, где и сам поставщик.
Для интересующихся приведу версию этого примера на языке Tu­to­ri­
al D:
S WHERE RELATION { TUPLE { CITY CITY } } = ( ( !!SP ) JOIN P ) { CITY }

Пример 7. Устранение неоднозначности
Естественный язык часто неоднозначен. Рассмотрим, к примеру, следующий запрос: «Получить поставщиков таких, что каждая поставляемая ими деталь находится в том же городе». Во-первых, обратите внимание на тонкое (?) различие между этим примером и предыдущим. Вовторых, и это важнее, заметьте, что эта формулировка на естественном
языке дейст­вительно неоднозначна! Для определенности буду предполагать, что она означает следующее:
Получить поставщиков SX таких, что для любых деталей PX
и PY, если SX поставляет обе детали, то PX.CITY = PY.CITY.
Отметим, что поставщик, который поставляет всего одну деталь, удовлетворяет этой интерпретации условия. (Как, кстати, и поставщик, не
поставляющий ни одной детали.) Альтернативно запрос мог бы означать следующее:
Получить поставщиков SX таких, что для любых деталей
PX и PY, если SX поставляет обе детали и они различны, то
PX.CITY = PY.CITY.
Такому условию поставщик, который поставляет всего одну деталь или
не поставляет ни одной детали, уже не удовлетворяет.
Я уже сказал, что для определенности остановлюсь на первой интерпретации. Но замечу, что такого рода неоднозначности – обычное дело
в сложных запросах и в сложных бизнес-правилах, и одно из достоинств формальной логики в том и заключается, что она помогает выявлять и разрешать их.
Вот формулировка первой интерпретации в терминах логики:
{ SX } WHERE FORALL PX ( FORALL PY
( IF EXISTS SPX ( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO )

Пример 7. Устранение неоднозначности

295

AND EXISTS SPY ( SPY.SNO = SX.SNO AND SPY.PNO = PY.PNO )
THEN PX.CITY = PY.CITY ) )

А вот и трансформации (снова оставляю читателю выяснение того, какое правило применено на каждом шаге):
{ SX } WHERE FORALL PX ( FORALL PY
( IF exp1 AND exp2 THEN exp3 ) )
{ SX } WHERE NOT EXISTS PX ( NOT FORALL PY
( IF exp1 AND exp2 THEN exp3 ) )
{ SX } WHERE NOT EXISTS PX ( NOT ( NOT EXISTS PY ( NOT
( IF exp1 AND exp2 THEN exp3 ) ) ) )
{ SX } WHERE NOT EXISTS PX ( EXISTS PY ( NOT
( IF exp1 AND exp2 THEN exp3 ) ) )
{ SX } WHERE NOT EXISTS PX ( EXISTS PY ( NOT
( NOT ( exp1 AND exp2 ) OR exp3 ) ) )
{ SX } WHERE NOT EXISTS PX ( EXISTS PY ( NOT
( NOT ( exp1 ) OR NOT ( exp2 ) OR exp3 ) ) )
{ SX } WHERE NOT EXISTS PX ( EXISTS PY (
( exp1 AND exp2 AND NOT ( exp3 ) ) ) )

SQL-эквивалент:
SELECT
FROM
WHERE
(

*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX
WHERE EXISTS
( SELECT *
FROM P AS PY
WHERE EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO )
AND
EXISTS
( SELECT *
FROM SP AS SPY
WHERE SPY.SNO = SX.SNO
AND
SPY.PNO = PY.PNO )
AND
PX.CITY PY.CITY ) )

Кстати, в этом примере я использовал две пе­ре­мен­ных кортежа SPX
и SPY, обе с множес­твом значений SP, просто для ясности; я вполне
мог бы дважды воспользоваться одной и той же пе­ре­мен­ной (скажем,

296

Глава 11. Использование формальной логики для SQL-выражений

SPX) – никакого логичес­кого различия тут нет! Вот какой получается результат:
SNO SNAME STATUS

CITY

S3
S5

Paris
Athens

Blake
Adams

30
30

В этом месте я хотел бы упомянуть еще об одном правиле трансформации, которое иногда бывает полезно: правиле контрапозиции. Рассмотрим импликацию IF NOT q THEN NOT p. По определению, это выражение эквивалентно NOT (NOT q) OR NOT p, то есть q OR NOT p, то есть
NOT p OR q, то есть IF p THEN q. Таким образом, имеем:
IF p THEN q ≡ IF NOT q THEN NOT p

Отметим, что этот закон интуитивно очевиден: если из истинности p
следует истинность q, то из ложности q должна следовать ложность p.
Например, если из утверждения «Идет дождь» следует, что «Улицы мокрые», то из утверждения «Улицы не мокрые» должно следовать, что
«Дождь не идет».
Значит, в рассматриваемом примере мы можем переформулировать ранее принятую интерпретацию (Получить поставщиков SX таких, что
для любых деталей PX и PY, если SX поставляет обе детали, то PX.CITY =
PY.CITY) следующим образом:
Получить поставщиков SX таких, что для любых деталей
PX и PY, если PX.CITY ≠ PY.CITY, то SX не поставляет обе
детали. (Кстати, так ли очевидно, что этот вариант эквивалентен предыдущему?)
Такая интерпретация запроса вполне может привести к другой (хотя
и логичес­ки эквивалентной) формулировке его на SQL. Детали оставляю читателю в качестве упражнения.

Пример 8. Использование COUNT
О предыдущем примере сказано еще не все. Повторю сам запрос: «Получить поставщиков таких, что каждая поставляемая ими деталь находится в том же городе». Вот еще одна возможная его интерпретация
на естественном языке:
Получить поставщиков SX таких, что количество городов
для деталей, поставляемых SX, меньше или равно единице.
Кстати, обратите внимание на слова «меньше или равно», если бы я написал просто «равно», то получил бы другую интерпретацию запроса.
Вот формулировка в терминах логики:
{ SX } WHERE COUNT ( PX.CITY WHERE EXISTS SPX
( SPX.SNO = SX.SNO AND SPX.PNO = PX.PNO ) ) ≤ 1

Пример 9. Запросы с соединением

297

Это первый пример в этой главе, где встречается агрегатный оператор.
Но, как вы наверняка понимаете, отображение не вызывает сложностей. Эквивалентная формулировка на SQL выглядит так:
SELECT *
FROM S AS SX
WHERE ( SELECT COUNT ( DISTINCT PX.CITY )
FROM P AS PX
WHERE EXISTS ( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO ) ) ALL ( SELECT PY.WEIGHT
FROM P AS PY
WHERE PY.COLOR = ‘Blue’ )

Результат:
PNAME
Bolt
Screw
Cog

Как видно из этого примера, «строковое выражение» rx в сравнении
с модификатором ALL или ANY rx θ sq часто – на самом деле, почти
всегда – представляет собой простое скалярное выражение, и в таком
случае скалярное значение, обозначаемое этим выражением, приводится к типу строки, содержащей только это значение. (Кстати, отмечу, что
даже если rx не сводится к простому скалярному выражению, а обозначает строку степени больше 1, то в качестве θ все равно могут выступать
не только операторы «=» или «», хотя на практике это не рекомендуется. Более подробное обсуждение этого вопроса см. в главе 3.)
Рекомендация: Не пользуйтесь сравнениями с модификаторами ALL
или ANY – они могут стать причиной ошибок, да и в любом случае того
же результата можно достичь иными способами. В качестве иллюстрации первого положения примите во внимание тот факт, что в формулировке предыдущего запроса на естественном языке вполне можно было
бы употребить слово любой вместо каждой – «Получить названия деталей, вес которых больше веса любой синей детали» – и тогда мы бы неправильно использовали сравнение >ANY вместо >ALL. А в качестве
другого примера, иллюстрирующего сразу оба положения, рассмотрим
такое SQL-выражение:
SELECT DISTINCT SNAME
FROM S
WHERE CITY ANY ( SELECT CITY FROM P )

Это выражение легко было бы прочитать так: «Получить названия поставщиков, для которых город местонахождения не равен городу местонахождения никакой детали», однако означает оно сов­сем не то.
Логичес­ки это выражение эквивалентно1 следующему запросу: «Получить названия поставщиков, для которых существует хотя бы одна деталь, находящаяся в другом городе»:

1

А так ли это? Что если город, в котором находится поставщик или деталь,
окажется равным null?

Пример 11. Сравнения с ALL или ANY

301

SELECT DISTINCT SNAME
FROM S
WHERE EXISTS ( SELECT *
FROM P
WHERE P.CITY S.CITY )

Результат:
SNAME
Smith
Jones
Blake
Clark
Adams

На самом деле, сравнение с модификатором ALL или ANY всегда можно
преобразовать в эквивалентное выражение, содержащее EXISTS, как
видно из предыдущего примера. Обычно такие сравнения можно также трансформировать в выражения, содержащие оператор MAX или
MIN, потому что (к примеру) некоторое значение больше всех значений в множес­тве тогда и только тогда, когда оно больше максимального значения в этом множес­тве. А выражения с MAX и MIN часто проще
для понимания, чем эквивалентные сравнения с ALL или ANY. В следующую таб­лицу сведены различные возможности в этом плане. (Чуть
ниже я приведу пример.)
ANY

ALL

=


IN

<
MAX

>=

>=MIN

>=MAX

NOT IN

Замечу, в частности, что =ANY и ALL эквивалентны IN и NOT IN
соответственно.1 У конструкций =ALL и ANY нет аналогичных эквивалентов, но выражения, содержащие эти операторы, всегда можно заменить выражениями, содержащими EXISTS, как уже было сказано выше.
Предостережение: К сожалению, не гарантируется, что описанные выше
трансформации (с использованием MAX и MIN) будут работать, если аргументом MAX или MIN окажется пус­тое множес­тво. Причина в том,
что в SQL MAX и MIN для пус­того множес­тва определены – неправиль1

Так что здесь мы имеем два важных исключения из общей рекомендации
избегать сравнений с модификаторами ALL или ANY; то есть допус­тимо
на равных правах использовать =ANY и IN, а также ALL и NOT IN.
(Лично мне кажется, что IN и NOT IN гораздо понятнее альтернатив, но
выбор за вами.)

302

Глава 11. Использование формальной логики для SQL-выражений

но – как null.1 Приведу еще раз формулировку запроса «Получить названия деталей, вес которых больше веса каждой синей детали»:
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE PX.WEIGHT >ALL ( SELECT PY.WEIGHT
FROM P AS PY
WHERE PY.COLOR = ‘Blue’ )

А вот ее трансформированный «эквивалент»:
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE PX.WEIGHT > ( SELECT MAX ( PY.WEIGHT )
FROM P AS PY
WHERE PY.COLOR = ‘Blue’ )

Теперь предположим, что синих деталей нет. Тогда первое выражение
вернет названия всех деталей в таб­лице P, а второе – пус­той результат.2
Как бы то ни было, чтобы описанная в этом примере трансформация
была корректной, воспользуйтесь оператором COALESCE, например,
следующим образом:
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE PX.WEIGHT > ( SELECT COALESCE ( MAX ( PY.WEIGHT ) , 0.0 )
FROM P AS PY
WHERE PY.COLOR = ‘Blue’ )

В качестве другого примера рассмотрим запрос «Получить названия
деталей, вес которых меньше веса какой-нибудь детали, находящейся
в Париже». Вот формулировка в терминах логики:
{ PX.PNAME } WHERE EXISTS PY ( PY.CITY = ‘Paris’ AND
PX.WEIGHT < PY.WEIGHT )

А вот соответствующая формулировка на SQL:
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE EXISTS ( SELECT *
FROM P AS PY
WHERE PY.CITY = ‘Paris’
AND
PX.WEIGHT < PY.WEIGHT )

1

Для справки: MAX для пус­того множес­тва должен быть равен минимально
возможному значению, а MIN для пус­того множес­тва – максимально воз­
можному значению (соответствующего типа).

2

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

Пример 12. GROUP BY и HAVING

303

Но этот запрос можно было бы выразить также в терминах сравнения
с модификатором ANY:
SELECT DISTINCT PX.PNAME
FROM P AS PX
WHERE PX.WEIGHT 7000.0

Для интересующихся перепишу этот пример, сделав все неявные квалификаторы явными:
SELECT TEMP.PNO , TEMP.GMWT
FROM ( SELECT P.PNO , P.WEIGHT * 454 AS GMWT
FROM P ) AS TEMP
WHERE TEMP.GMWT > 7000.0

Примечание
Определение пе­ре­мен­ной кортежа в SQL может включать необязательный список имен столбцов (определяющий имена столбцов таб­лицы, которую пробегает данная пе­ре­мен­ная кортежа), как в примере ниже:
SELECT *
FROM ( S JOIN P ON S.CITY > P.CITY ) AS TEMP
( A , B , C , D , E , F , G , H , I )

Здесь A, B, ..., I являются другими именами столбцов SNO, SNAME, STATUS,
S.CITY, PNO, PNAME, COLOR, WEIGHT и P.CITY соответственно (объяснение конструкции JOIN ... ON см. в главе 6). Однако, если вы будете следовать рекомендациям, приведенным в этой книге, то часто прибегать к такой возможности не придется.

Рекомендация: Отдавайте предпочтение явному использованию пе­ре­
мен­ных кортежа, особенно в «сложных» выражениях – это проясняет
намерения, а иногда даже уменьшает количество ударов по клавишам.
Однако помните, что правила областей видимости таких пе­ре­мен­ных
в SQL понять непросто (впрочем, это в равной мере относится и к явным,
и к неявным пе­ре­мен­ным).
Предостережение: Во многих учебниках по SQL имена пе­ре­мен­ных кортежа (или корреляционные имена) называются псевдонимами и описываются так, будто это просто альтернативные имена для таб­лиц, ко-

Подзапросы

311

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

Подзапросы
Подзапросом в SQL называется таб­личное выражение, назовем его tx,
заключенное в скобки; если таб­лицу, обозначенную выражением tx, назвать t, то таб­лица, обозначенная подзапросом, тоже будет t. Выражение tx не может быть явным выражением JOIN, поэтому «(SELECT *
FROM A NATURAL JOIN B)» – допус­тимый подзапрос, а «(A NATURAL
JOIN B)» – недопус­тимый.
Подзапросы можно разбить на три категории (хотя синтаксис во всех
случаях одинаков):
•• Таб­личный подзапрос – это любой подзапрос, не являющийся однострочным или скалярным.
•• Однострочный подзапрос – это подзапрос, встречающийся в том месте, где ожидается строковое выражение. Пусть rsq – такой подзапрос; тогда rsq должен обозначать таб­лицу, содержащую всего одну
строку. Назовем эту таб­лицу t, а ее единственную строку – r; тогда
rsq ведет себя так, будто обозначает саму строку r (иными словами,
t приводится к типу r).
•• Скалярный подзапрос – это подзапрос, встречающийся в том месте,
где ожидается скалярное выражение. Пусть ssq – такой подзапрос;
тогда ssq должен обозначать таб­лицу, содержащую всего одну строку
и всего один столбец. Назовем эту таб­лицу t, ее единственную строку – r, а единственное значение в этой строке – v; тогда ssq ведет себя
так, будто обозначает это значение v (иными словами, t приводится
к типу r, а затем r приводится к типу v).
В следующих примерах показаны таб­личный подзапрос, однострочный
подзапрос и скалярный подзапрос (в этом порядке):
SELECT SNO
FROM S
WHERE CITY IN ( SELECT CITY
FROM P
WHERE COLOR = ‘Red’ )
UPDATE S
SET ( STATUS , CITY ) = ( SELECT DISTINCT STATUS , CITY
FROM S
WHERE SNO = ‘S1’ )
WHERE CITY = ‘Paris’ ;
SELECT SNO
FROM S

312

Глава 12. Различные вопросы, связанные с SQL
WHERE CITY = ( SELECT CITY
FROM P
WHERE PNO = ‘P1’ )

Далее, коррелированный подзапрос – это особый вид подзапроса (таб­
личного, однострочного или скалярного), а именно такой, в котором
есть ссылка на некоторую «внешнюю» таб­лицу. В следующем примере
заключенное в скобки выражение после ключевого слова IN и есть коррелированный подзапрос, потому что включает ссылку на внешнюю
таб­лицу S (запрос звучит так: «Получить названия поставщиков, которые поставляют деталь P1»).
SELECT DISTINCT S.SNAME
FROM S
WHERE ‘P1’ IN ( SELECT PNO
FROM SP
WHERE SP.SNO = S.SNO )

В главе 11 отмечалось, что коррелированные подзапросы часто противопоказаны с точки зрения производительности (во всяком случае, концептуально), потому что их приходится вычислять для каждой строки
внешней таб­лицы, а не раз и навсегда. (В примере выше, если выражение в целом вычисляется так, как сформулировано в запросе, то подзапрос будет вычисляться N раз, где N – количество строк в таб­лице S.)
Поэтому лучше избегать коррелированных подзапросов, если удастся.
В рассматриваемом случае этого легко добиться, переформулировав запрос следующим образом:
SELECT DISTINCT S.SNAME
FROM S
WHERE SNO IN ( SELECT SNO
FROM SP
WHERE PNO = ‘P1’ )

Наконец, «латеральный» подзапрос – это частный случай коррелированного подзапроса, а именно такой, который (а) находится внутри фразы FROM и (б) включает ссылку на «внешнюю» таб­лицу, которая определяется ссылкой на таб­лицу, встречающуюся ранее в той же фразе
FROM. Рассмотрим, к примеру, запрос «Для каждого поставщика получить его номер и количество поставляемых им деталей». Вот одна из его
возможных формулировок на SQL:
SELECT S.SNO , TEMP.PCT
FROM S , LATERAL ( SELECT COUNT ( ALL PNO ) AS PCT
FROM SP
WHERE SP.SNO = S.SNO ) AS TEMP

Назначение ключевого слова LATERAL состоит в том, чтобы сообщить
сис­теме, что следующий за ним подзапрос коррелирован с чем-то, ранее упоминавшимся в той же фразе FROM (в данном примере подзапрос

313

Подзапросы

возвращает ровно одно значение – счетчик деталей – для каждого значения SNO в таб­лице S). На наших тестовых данных получается такой
результат:
SNO
S1
S2
S3
S4
S5

PCT
6
2
1
3
0

Кстати, здесь есть один тонкий момент, который может привести к путанице. Элементы в списке FROM – ссылки на таб­лицы, поэтому они
обозначают таб­лицы. Но в примере выше ссылка на таб­лицу, начинающаяся с ключевого слова LATERAL, – точнее, то, что останется, если
слово LATERAL убрать, – больше напоминает ссылку на скаляр или,
точнее, на скалярный подзапрос; разумеется, ее можно было бы использовать в этом качестве, если бы контекст требовал такой интерпретации. Однако на самом деле это таб­личный подзапрос, пус­ть даже результатом его вычисления (для данного номера поставщика, посредством двойного приведения типа) является одно скалярное значение,
которое затем вносит вклад в строку результата.
Вообще непонятно, зачем в данном случае нужен «латеральный» подзапрос. Конечно, его легко переписать в таком виде, чтобы избежать «необходимости» (?) в подобных изысках:
SELECT S.SNO , ( SELECT COUNT ( ALL PNO )
FROM SP
WHERE SP.SNO = S.SNO ) AS PCT
FROM S

Подзапрос переместился из фразы FROM во фразу SELECT; он попрежнему ссылается еще на что-то в той же фразе (S.SNO, если быть
точным), но ключевое слово LATERAL уже не нужно. Однако обратите
внимание, что произошло со спецификацией AS PCT, которая раньше
находилась внутри латерального подзапроса, а теперь вышла наружу.
И напоследок: я определил термин подзапрос; быть может, самое время определить заодно и термин запрос! – пус­ть даже он неоднократно
использовался в предыдущих главах. Что ж, вот определение: запросом (query) называется команда выборки (retrieval request); иными словами, это таб­личное выражение – хотя такие выражения могут встречаться и в контекстах, не связанных с запросами, как таковыми, – или
предложение, например, предложение SELECT в «прямом (то есть интерактивном) SQL», в котором требуется вычислить такое выражение.
Иногда, хотя и не в этой книге, запросами называют также и команды
обновления.

314

Глава 12. Различные вопросы, связанные с SQL

«Потенциально недетерминированные»
выражения
Напомню (см. главу 2), что «потенциально недетерминированным» выражением в SQL называется таб­личное выражение, вычисление которого в разные моменты времени может давать разные значения, даже
если в промежутке база данных не изменялась. Отдельно напомню, что
такие выражения не разрешены в ограничениях целостности в SQL.
Опишу стандартные правила пометки таб­личного выражения как «потенциально недетерминированного». Пусть tx – таб­личное выражение.
Тогда tx считается «потенциально недетерминированным», если выполнено хотя бы одно из следующих условий:
•• tx – объединение (без ALL), пересечение или разность, и таб­лицыоперанды содержат столбец типа строки символов.
•• tx – выражение SELECT, список элементов SELECT в этом выражении содержит элемент (назовем его C) типа строки символов, и справедливо хотя бы одно из следующих утверждений:
–– списку элементов SELECT предшествует ключевое слово DIS­
TINCT.
–– C включает вызов MAX или MIN.
–– tx непосредственно включает фразу GROUP BY или C является
одним из столбцов группировки.
•• tx – выражение SELECT, которое непосредственно включает фразу
HAVING, и булево выражение в этой фразе HAVING включает либо
ссылку на столбец группировки типа строки символов, либо вызов
MAX или MIN с аргументом типа строки символов.
Однако эти правила представляются неполными и недостаточно точными. Например:
•• Объединение с ALL, безусловно, является «потенциально недетерминированным», если один из его операндов – «потенциально недетерминированное» выражение SELECT.
•• Явное соединение NATURAL JOIN или JOIN USING имеет те же
шансы оказаться потенциально недетерминированным, как, например, пересечение.
•• Разность с ALL не может быть «недетерминированной», если таковым не является первый операнд.
Кроме того, в некоторых случаях эти правила строже, чем необходимо, например, когда в дейст­вующей схеме упорядочения задана спецификация NO PAD и в этой схеме нет символов, которые «равны, но различимы».

Пустые множес­тва

315

Пустые множес­тва
Пустым называется множес­тво, не содержащее ни одного элемента.
Это понятие встречается в реляционной теории повсеместно и является крайне важным, но в SQL с ним связан целый ряд ошибок. К сожалению, поделать с ними вы ничего не можете, но хотя бы знать об этом
нужно. Вот перечень таких ошибок (возможно, неполный):
•• В выражении VALUES не допускается пус­той список строковых выражений.
•• Все «функции множес­тва» в SQL возвращают null, если аргумент –
пус­тое множес­тво (за исключением функций COUNT(*) и COUNT,
которые возвращают нуль).
•• Если результатом вычисления скалярного подзапроса является пус­
тая таб­лица, то она приводится к null.
•• Если результатом вычисления однострочного подзапроса является
пус­тая таб­лица, то она приводится к строке, состоящей из одних nullзначений.
•• Если множес­тво столбцов группировки и группируемая таб­лица
пус­ты, то GROUP BY порождает результат, содержащий всего одну,
по необходимости пус­тую, группу, тогда как должен был бы порождаться результат, не содержащий групп вовсе.
•• Пус­тое множес­тво столбцов не может быть ключом (равно как и внешним ключом).
•• Таб­лица не может иметь пус­той заголовок.
•• Список элементов SELECT не может быть пус­тым.
•• Список элементов FROM не может быть пус­тым.
•• Множес­тво общих столбцов в операторах UNION, INTERSECT и EX­
CEPT не может быть пус­тым.
•• Строка не может иметь пус­тое множес­тво компонентов.

БНФ-грамматика таб­личных выражений SQL
Для справки представляется уместным завершить эту главу и основную
часть книги БНФ-грамматикой таб­личных выражений SQL. По причинам, которые здесь не так уж важны, некоторые используемые мною
термины отличаются от встречающихся в стандарте SQL; кроме того,
конструкции, которые я ранее советовал не употреблять, сознательно
опущены, равно как и некоторые экзотичес­кие особенности (например,
рекурсия).1 Используются следующие упрощающие сокращения:
1

То есть грамматика несколько консервативна в том смысле, что некоторые
допус­тимые согласно стандарту выражения не определены. Но я не думаю,
что определены какие-то выражения, не допускаемые стандартом.

316

Глава 12. Различные вопросы, связанные с SQL

exp – expression (выражение)
ref

– reference (ссылка)

spec – specification (спецификация)
Также предполагается, что следующие символы грамматики являются
идентификаторами () и нигде далее не определяются:
(имя таб­лицы)
(имя столбца)
(имя пе­ре­мен­ной кортежа)
Неопределенными оставлены следующие символы (хотя стоит помнить
о том, что одной из форм скалярного выражения является скалярный
подзапрос):



Примечание
Вы можете заметить, что грамматика начинается с продукции , хотя этот терм не упоминался в тексте книги. Я ввел эту синтаксическую категорию, чтобы отразить тот факт, что выражения соединения не могут встречаться иначе, как вложенными в какое-то другое таб­личное выражение1, но это не означает, что терм , определенный в грамматике, не
на 100 процентов соответствует тому, что в книге называется таб­личным выражением! (Если быть точным, – таб­личное выражение, но не
.) Приношу извинения, если такое положение дел кому-то покажется путаным, но это неизбежно, когда пытаешься определить грамматику языка,
который по природе своей неортогонален.

::= [ ]

::= WITH

::= AS

::= [ ]

::= |
1

Это ограничение (которое упоминалось в разных местах книги без специальных пояснений) появилось в версии стандарта 2003 года; в версии 1999 года
его не было.

БНФ-грамматика таб­личных выражений SQL

::=
| UNION
[ DISTINCT ] [ CORRESPONDING ]
| EXCEPT
[ DISTINCT ] [ CORRESPONDING ]

::=
|

::= INTERSECT
[ DISTINCT ] [ CORRESPONDING ]
|

::=
|

( )
TABLE




::= VALUES

::=
|
|

::= ( )

::=

::= ( )

::= SELECT [ DISTINCT ] [ * | ]
FROM
[ WHERE ]
[ GROUP BY ]
[ HAVING ]

317

318

Глава 12. Различные вопросы, связанные с SQL

::= [ AS ]
| .*

::= [ AS ]
| [ LATERAL ] AS
|

::=

::= CROSS JOIN
| NATURAL JOIN
| JOIN
USING ( )
| ( )

Упражнения
Упражнение 12.1. Какие из приведенных ниже SQL-выражений являются допус­тимыми термами , а какие не являются, согласно определенной выше грамматике? (A и B – имена таб­
лиц.)
A NATURAL JOIN B
A INTERSECT B
TABLE A NATURAL JOIN TABLE B
TABLE A INTERSECT TABLE B
SELECT * FROM A NATURAL JOIN SELECT * FROM B
SELECT * FROM A INTERSECT SELECT * FROM B
( SELECT * FROM A ) NATURAL JOIN ( SELECT * FROM B )
( SELECT * FROM A ) INTERSECT ( SELECT * FROM B )
( TABLE A ) NATURAL JOIN ( TABLE B )
( TABLE A ) INTERSECT ( TABLE B )
( TABLE A ) AS AA NATURAL JOIN ( TABLE B ) AS BB
( TABLE A ) AS AA INTERSECT ( TABLE B ) AS BB
( ( TABLE A ) AS AA ) NATURAL JOIN ( ( TABLE B ) AS BB )
( ( TABLE A ) AS AA ) INTERSECT ( ( TABLE B ) AS BB )

Быть может, следует напомнить, что с точки зрения реляционной
теории, пересечение – это частный случай естественного соединения.
Какие выводы вы можете сделать из этого упражнения?
Упражнение 12.2. Исследуйте какую-нибудь доступную вам SQL-сис­
тему. Поддерживаются ли в ней (а) выражения UNIQUE, (б) явные

Упражнения

319

таб­лицы, (в) латеральные подзапросы, (г)«потенциально недетерминированные» выражения?
Упражнение 12.3. На протяжении всей книги я понимал под термином
SQL официальную версию стандарта этого языка (хотя сознательно не рассматривал стандарт во всей полноте). Однако любой представленный на рынке продукт в той или иной мере отклоняется от
стандарта, как опуская некоторые определенные в нем средства, так
и добавляя свои собственные расширения. Исследуйте любой доступный вам продукт на основе SQL. Найдите столько отклонений
от стандарта, сколько сможете. (Предыдущее упражнение могло
дать кое-какие идеи в этом направлении.)

A

Приложени

.

Реляционная модель
Я глубоко убежден, что если задуматься над этим вопросом на надлежащем уровне абстракции, то неизбежно придешь к выводу, что базы
данных должны быть реляционными. Позвольте мне сразу же попытаться обосновать это весьма сильное утверждение! Я рассуждаю следующим образом.
•• В главе 5 я объяснил, что база данных, несмотря на название, по существу является не совокупностью данных, а совокупностью фактов,
или, иначе говоря, истинных высказываний, например: «зарплата
Джо составляет 50K долларов в год».
•• Высказывания типа «зарплата Джо составляет 50K долларов в год»
легко кодируются в виде упорядоченных пар – в данном случае (Джо,
50K), где «Джо» – значение, скажем, типа NAME, а «50K» – значение типа MONEY (например).
•• Но мы хотим записывать не все вообще высказывания, а только те,
которые являются порождениями некоторых предикатов, принимающими значение «истина». Так, в случае высказывания «зарплата Джо составляет 50K долларов в год» предикатом является «зарплата x составляет y долларов в год», где x – значение типа NAME,
а y – значение типа MONEY.
•• Иными словами, мы хотим записывать экстенцию предиката «зарплата x составляет y долларов в год» и можем это сделать в виде
множес­тва упорядоченных пар.
•• Но множес­тво упорядоченных пар – это и есть бинарное от­но­ше­ние
в математичес­ком смысле слова. Вот его определение: бинарным от­
но­ше­нием над двумя множес­твами A и B называется подмножес­тво
декартова произведения A и B; иначе говоря, множес­тво упорядоченных пар (a,b) таких, что первый элемент a взят из A, а второй элемент
b взят из B.

321
•• Бинарное от­но­ше­ние в указанном выше смысле можно изобразить
в виде таб­лицы, например:
значения типа NAME
значения типа MONEY
Joe
Am y
Sue
...
Ron

50K
60K
45K
...
60K

(Кстати говоря, в этом конкретном примере мы имеем не просто от­
но­ше­ние, а функцию, поскольку у каждого человека есть только
одна зарплата. Функция – частный случай бинарного от­но­ше­ния.)
Таким образом, мы можем считать, что на этом рисунке изображено
подмножес­тво декартова произведения множес­тва всех имен («тип
NAME») и множес­тва всех денежных величин («тип MONEY»), именно в таком порядке.
Уже это рассуждение наводит на мысль, что у нас есть пока довольно
скромные (но весьма основательные) зачатки чего-то. Однако в 1969 году
Кодд осознал, что:
•• Нам следует рассматривать n-местные, а не только двуместные предикаты и высказывания (например, «Джо имеет зарплату 50K, работает в отделе D4 и был принят на работу в 1993 году»). Следовательно, нужно работать с n-арными, а не только бинарными, от­но­
ше­ниями и n-кортежами (для краткости просто кортежами), а не
только с упорядоченными парами.
•• Упорядочение слева направо, возможно, и приемлемо для пар, но при
n > 2 очень быст­ро становится неудобным; поэтому мы заменяем понятие упорядоченности понятием атрибутов (идентифицируемых по
имени) и соответственно переопределяем понятие от­но­ше­ния. Наш
пример теперь выглядит следующим образом:
атрибут типа NAME
атрибут типа MONEY
PERSON

SALARY

Joe
Am y
Sue
...
Ron

50K
60K
45K
...
60K

Не существует такого понятия,
как «первый» или «второй» атрибут
Обратите внимание на логическое различие
между атрибутом и его типом

Начиная с этого места, можете считать, что термин от­но­ше­ние обозначает от­но­ше­ние в описанном выше смысле, если явно не оговорено противное.

322

Приложение A. Реляционная модель

•• Но на представлении данных история не заканчивается – нам нужны операторы для порождения новых от­но­ше­ний из имеющихся
(«базовых»), чтобы можно было предъявлять запросы и т. п. (например, «Получить всех людей с зарплатой больше 60K»). Но так как
от­но­ше­ние одновременно является и логичес­кой конструкцией (экстенция предиката), и математичес­кой (частный случай множес­тва),
то мы может применять к нему как логичес­кие, так и математические операторы. Именно поэтому Кодд и сумел определить как реляционное исчисление (основанное на формальной логике), так и реляционную алгебру (основанную на теории множес­тв). Так и родилась
реляционная модель.

Реляционная и другие модели
Теперь вы, наверное, понимаете, почему я считаю (повторяя мысль, высказанную в главе 5), что реляционная модель покоится на твердых
основаниях, «правильна» и выдержит испытание временем. Я твердо
уверен, что и через сто лет сис­темы баз данных будут основаны на реляционной модели Кодда. Почему? Потому что основания этой модели,
а именно теория множес­тв и логика предикатов, сами прочны, как камень. В частности, элементы логики предикатов восходят еще к Аристотелю, который жил больше 2000 лет назад (384–322 годы до нашей эры).
Ну а что сказать о других моделях – к примеру, «объектно-ориентирован­
ной модели», или «иерархичес­кой модели», или «сетевой модели» CODA­
SYL, или «полуструктурированной модели»? На мой взгляд, они просто
находятся в другой весовой категории. Да, я серьезно задаю вопрос, а заслуживают ли они вообще названия модели.1 Иерархичес­кая и сетевая
модели в особенности вообще никогда не существовали! – как абстрактные модели, я хочу сказать, предшествующие конкретным реализациям. Они были изобретены уже постфактум, то есть сначала были созданы иерархичес­кие и сетевые сис­темы, а уже потом определены соответствующие модели – по индукции, а это лишь вежливое слово для
обозначения догадки. Что касается объектно-ориентированной и полуструктурированной модели, то не исключено, что и к ним приложима та
же критика; подозреваю, что так и есть, хотя полной уверенности у меня
нет. Одна из проблем заключается в том, что отсутствует единое мнение
о составе таких моделей. Например, безусловно, нельзя утверждать, что
существует единая, четко определенная и всеми признанная объектноориентированная модель, и то же самое относится к полуструктурированной модели. (Впрочем, кто-то может сказать, что единой реляционной модели тоже нет, но к этому аргументу я вернусь чуть позже.)
1

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

Реляционная и другие модели

323

Есть и еще одна важная причина, почему я не верю в то, что другие модели заслуживают названия моделей. Во-первых, полагаю, никто не
станет отрицать, что реляционная модель – это дейст­вительно модель и,
следовательно, по определению, не связана с деталями реализации. Напротив, все остальные модели в большинстве случаев не могут провести четкое различие между аспектами, относящимися собственно к модели, и вопросами реализации; в лучшем случае они высказываются
об этом различии очень путано (все они, так сказать, «гораздо ближе
к железу»). Как следствие, они труднее для понимания и использования и дают разработчикам гораздо меньше свободы (меньше, чем реляционная модель) в придумывании неординарных, творчес­ких подходов
к вопросам реализации.
А как быть с заявлениями о существовании каких-то других реляционных моделей? Например, Джо Селко (Joe Celko) автор книги «Data and
Databases: Concepts in Practice» (Morgan Kaufmann, 1999) пишет:
Говорить о существовании единой реляционной модели баз данных не больше оснований [sic], чем о существовании только одной
геометрии.

А в обоснование этого утверждения он далее описывает шесть «различных реляционных моделей».
Я отозвался на эти заявления сразу, как только их увидел. Вот отредактированный вариант того, что я тогда написал:
Да, дейст­вительно существует несколько различных геометрий
(евклидова, эллиптичес­кая, гиперболичес­кая и т. д.). Но можно ли эту аналогию назвать корректной? То есть различаются ли
«различные реляционные модели» так же, как различаются различные геометрии? Мне кажется, что ответ на этот вопрос отрицателен. Эллиптичес­кую и гиперболичес­кую геометрии часто явно
называют неэвклидовыми; поэтому, чтобы аналогия была полной,
по крайней мере, пять из «шести различных реляционных моделей» должны были бы быть нереляционными, следовательно, по
определению, не «реляционными моделями». (Фактичес­ки, я бы
согласился с тем, что некоторые из «шести различных реляционных моделей» дейст­вительно не реляционные. Но тогда трудно
утверждать – по крайней мере, не впадая в противоречие, – что
это различные реляционные модели.)

И далее (опять-таки текст немного отредактирован):
Но я должен признать, что Кодд дейст­вительно пересматривал
собственные определения реляционной модели в 1970–1980-е
годы. Это дало основание критикам обвинить Кодда в частности
и сторонников реляционной теории в целом в том, что они меняют правила по ходу игры. Например, Майк Стоунбрейкер (Mike
Stonebraker) писал (во введении к книге «Readings in Database
Systems», второе издание, Morgan Kaufmann, 1994), что «может
представить себе четыре разные версии» модели:

324

Приложение A. Реляционная модель
•• Версия 1: определена в статье, опубликованной в журнале CACM
в 1970 году
•• Версия 2: определена в статье 1981 года, представленной на присуждение премии Тьюринга
•• Версия 3: определена в 12 правилах и оценочной сис­теме Кодда
•• Версия 4: определена в книге Кодда

Тут я хотел бы прерваться и объяснить, на что ссылается Стоунбрейкер.
Все это работы Кодда. В 1970 году в журнале CACM – CACM 13, No. 6
(June 1970) – была опубликована статья «Реляционная модель данных для больших разделяемых банков данных» (A Relational Model of
Data for Large Shared Data Banks). На присуждение премии Тьюринга
была представлена статья «Relational Database: A Practical Foundation
for Productivity», CACM 25, No. 2 (February 1982). 12 правил и соответствующая им оценочная сис­тема описаны в статьях Кодда для журнала
Computerworld: «Is Your DBMS Really Relational?» и «Does Your DBMS
Run By The Rules?» (14 и 21 октября 1985). И, наконец, под книгой Кодда разумеется книга «The Relational Model For Database Management
Version 2» (Addison-Wesley, 1990). А теперь я вернусь к своему ответу.
Вероятно, потому, что нас немного задела такая критика, мы
с Хью Дарвеном постарались привести в нашей совместной книге
«Databases, Types, and the Relational Model: The Third Manifesto»1
свою собственную аккуратную формулировку того, что такое реляционная модель (или чем она должна быть!). Дейст­вительно,
мы хотели, чтобы «Манифест» отчасти рассматривался как такая
авторитетная формулировка. За подробностями отсылаю к самой
книге; здесь же хотелось бы только сказать, что мы видим свой
вклад в этой области, прежде всего, в расстановке всех точек над i,
которые не были расставлены должным образом в работах самого
Кодда. Мы нисколько не отклонились от видения Кодда в какихлибо существенных аспектах; весь «Манифест» составлен в духе
идей Кодда и следует по проторенной им дороге.

Ко всему сказанному выше я хотел бы добавить еще одно соображение, которое, как мне кажется, убедительно опровергает доводы Селко. Я согласен, что существует несколько разных геометрий. Но почему они различны? Потому что в основу положены разные аксиомы. Напротив, аксиоматика реляционной модели никогда не изменялась. Да,
на протяжении нескольких лет мы внесли в саму модель ряд модификаций – например, добавили реляционные сравнения, – но аксиомы (по
существу, те же, что в классичес­кой логике предикатов) оставались неизменными со времен первых работ Кодда. Добавлю, что те изменения,
которые вносились, имели, на мой взгляд, эволюционный, а не революционный характер. Поэтому я с уверенностью заявляю, что существует
только одна реляционная модель, хотя она и развивалась со временем и,
1

См. приложение D к настоящей книге.

Определение реляционной модели

325

вероятно, будет развиваться и дальше. В главе 1 я отмечал, что реляционную модель можно считать небольшой ветвью математики, и, следовательно, она расширяется по мере доказательства новых теорем и появления новых результатов.
Так каковы же эти эволюционные изменения? Вот некоторые из них:
•• Как уже отмечалось, мы добавили реляционные сравнения.
•• Мы уточнили логичес­кие различия между от­но­ше­ниями и пе­ре­мен­
ными-от­но­ше­ниями.
•• Мы уточнили понятие первой нормальной формы и попутно ввели
понятие атрибутов с от­но­ше­ниями в качестве значений (RVA-атри­
бутов).
•• Мы стали лучше понимать природу реляционной алгебры, в том
числе сравнительную значимость различных операторов и важность
от­но­ше­ний степени 0, а также идентифицировали несколько новых
операторов (например, расширения и полусоединения).
•• Мы добавили понятие от­но­ше­ний-образов.
•• Мы стали лучше понимать, что такое обновление, в частности, обновление представлений.
•• Мы стали лучше понимать фундаментальную значимость ограничений целостности вообще и получили немало хороших теоретичес­ких
результатов, касающихся некоторых важных частных случаев.
•• Мы прояснили природу взаимосвязей между моделью и логикой
предикатов.
•• Наконец, мы стали лучше понимать взаимосвязи между реляционной моделью и теорией типов (точнее, мы прояснили природу доменов).

Определение реляционной модели
Теперь я хотел бы дать точное определение того, что входит в состав реляционной модели. Беда в том, что мое определение дейст­вительно точное; настолько точное, что его трудно было бы понять, приведи я его
в главе 1. (Как однажды афористично заметил Бертран Рассел: «Книга должна быть либо ясной, либо строгой, совместить эти два требования невозможно»). Вообще-то, некое определение я все же в первой главе дал – определение того, что назвал «оригинальной моделью», – но,
честно говоря, не думаю, что его хотя бы отдаленно можно назвать хорошим, в том числе по следующим причинам:
•• Для начала оно слишком длинное и бессвязное. (Что, впрочем, было
неплохо, учитывая назначение этой вводной главы, но теперь я хочу
иметь лаконичное и точное определение.)

326

Приложение A. Реляционная модель

•• Мне совершенно не по душе идея, что модель следует представлять
себе как «структуру плюс целостность плюс средства манипулирования»; я даже думаю, что в некоторых от­но­ше­ниях рассуждать
о ней в таких терминах – только запутывать себя.
•• В «оригинальной модели» было несколько вещей, которые мне не
нравятся, например: операция деления, null-значения, правило целостности сущностей, мысль о том, что какой-то ключ нужно обязательно делать первичным, и представление о том, что домены и типы
в чем-то различаются. Кстати, о null-значениях: замечу, что Кодд
определил реляционную модель в 1969 году, а null-значения добавил
только в 1979. Иными словами, в течение десяти лет модель отлично
себя чувствовала – на мой взгляд, даже лучше – безо всяких null. Более того, ранние реализации тоже обходились без них.
•• В оригинальной модели было упущено несколько существенных, на
мой взгляд, вещей. Например, не было упоминаний, по крайней мере
явных, о предикатах, ограничениях (помимо ограничений ключа
и внешнего ключа), пе­ре­мен­ных-от­но­ше­ниях, реляционных сравнениях, выводе типа от­но­ше­ния и связанных с этим механизмом средствах, от­но­ше­ниях-образах, некоторых алгебраичес­ких операторах
(особенно переименования, расширения, обобщения, полусоединения и полуразности) и важных от­но­ше­ниях TABLE_DUM и TABLE_
DEE. (С другой стороны, будет справедливо добавить, что все эти
средства и не запрещались оригинальной моделью, можно даже сказать, что некоторые из них даже были включены, в зачаточном виде.
Например, безусловно, с самого начала предполагалось, что реализация должна поддерживать не только ограничения ключа и внешнего
ключа, но и другие. Реляционные сравнения тоже, по меньшей мере,
неявно входили в состав требований.)
А теперь позвольте мне, не мудрствуя лукаво, дать собственное определение.
Определение: Реляционная модель состоит из пяти компонентов:
•• Расширяемый набор скалярных типов, включающий,
в частности, тип BOOLEAN;
•• Генератор типов от­но­ше­ний и подразумеваемая интерпретация от­но­ше­ний сгенерированных им типов;
•• Средства для определения пе­ре­мен­ных-от­но­ше­ний, принадлежащих сгенерированным типам от­но­ше­ний;
•• Оператор реляционного присваивания для присваивания от­но­ше­ний-значений пе­ре­мен­ным-от­но­ше­ниям;
•• Расширяемый набор обобщенных реляционных операторов для порождения новых от­но­ше­ний-значений из существующих.

Определение реляционной модели

327

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

Скалярные типы
В общем случае скалярные типы могут быть сис­темными или определенными пользователем; следовательно, должны быть предоставлены
средства для определения новых типов пользователями (это требование отчасти вытекает из того, что множес­тво скалярных типов расширяемо). Но тогда должны существовать доступные пользователям средства для определения скалярных операторов, поскольку типы без операторов бесполезны. Множес­тво сис­темных скалярных типов должно включать тип BOOLEAN – самый фундаментальный из всех, – но
реальная сис­тема наверняка будет поддерживать и другие (INTEGER,
CHAR и так далее).1
Из поддержки типа BOOLEAN вытекает поддержка обычных логических операторов (NOT, AND, OR и так далее), а также других операторов, сис­темных или определенных пользователями, которые возвращают булевы значения. В частности, для каждого типа, скалярного или
нескалярного, должен быть определен оператор сравнения на равенство
«=», поскольку без него мы даже не могли бы сказать, каковы значения,
составляющие данный тип. Более того, модель предписывает семантику такого оператора. Точнее, если v1 и v2 – значения одного и того же
типа, то v1 = v2 возвращает TRUE, если v1 и v2 – одно и то же значение,
и FALSE в противном случае.

Типы от­но­ше­ний
Генератор типов от­но­ше­ний позволяет пользователям определять типы
отдельных от­но­ше­ний по своему желанию: в частности, как тип некоторой пе­ре­мен­ной-от­но­ше­ния или некоторого RVA-атрибута. Предполагаемая интерпретация данного от­но­ше­ния данного типа в данном
контексте – множес­тво высказываний; каждое такое высказывание
(а) составляет порождение некоторого предиката, который соответствует заголовку от­но­ше­ния, (б) представлено кортежем в теле от­но­ше­ния
и (в) предполагается истинным. Если контекстом служит некоторая
пе­ре­мен­ная-от­но­ше­ние, то есть если мы говорим об от­но­ше­нии, которое является текущим значением какой-то пе­ре­мен­ной-от­но­ше­ния, то
предикатом будет предикат этой пе­ре­мен­ной-от­но­ше­ния. Пе­ре­мен­ныеот­но­ше­ния согласуются с допущением замкнутости мира (см. ниже
в этом приложении).
1

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

328

Приложение A. Реляционная модель

Пусть RT – тип от­но­ше­ния. Тогда с RT ассоциированы оператор-се­
лек­тор от­но­ше­ния со следующими свойствами: (а) каждый вызов этого оператора возвращает от­но­ше­ние типа RT и (б) любое от­но­ше­ние
типа RT возвращается некоторым вызовом этого оператора. Кроме
того, так как для любого типа определен оператор сравнения на равенство «=», то он определен и для типа RT. Также определен оператор реляционного включения («⊆»); если от­но­ше­ния r1 и r2 одного и того же
типа, то r1 включается в r2 тогда и только тогда, когда тело r1 является подмножес­твом r2.

Пе­ре­мен­ные-от­но­ше­ния
Как отмечалось в предыдущем подразделе, наиболее важным применением генератора типов от­но­ше­ний является задание типа пе­ре­мен­нойот­но­ше­ния в момент ее определения. Только такие пе­ре­мен­ные и разрешены в реляционной базе данных; в частности, скалярные пе­ре­мен­ные
и пе­ре­мен­ные-кортежи запрещены. (Напротив, в программах, обращающихся к такой базе данных, они не запрещены, а, скорее всего, даже
необходимы.)
Утверждение о том, что база данных не содержит ничего, кроме пе­
ре­мен­ных-от­но­ше­ний, – одна из возможных формулировок того, что
Кодд называл принципом информации, хотя я не думаю, что сам он такой формулировкой пользовался. Обычно он формулировал этот принцип следующим образом:
Все информационное содержимое базы данных в любой момент
времени представлено одним и только одним способом, а именно в виде явных значений в позициях атрибутов кортежей от­
но­ше­ний.
Я не раз слышал, как Кодд называл этот принцип фундаментальным
принципом реляционной модели. Стало быть, любое его нарушение
следует считать криминалом. Таб­лицы базы данных, подразумевающие упорядочение строк сверху вниз или упорядочение столбцов слева направо, содержащие строки-дубликаты, или указатели, или nullзначения, или имеющие безымянные столбцы либо столбцы с повторяющимися именами, – все это нарушения принципа информации. Но почему этот принцип так важен? Ответ вытекает из сделанных в главе 5
замечаний о том, что от­но­ше­ния (наряду с типами) необходимы и достаточны для представления любых данных на логичес­ком уровне. Иными словами, реляционная модель дает все, что необходимо в этом плане,
и не дает ничего лишнего.
Я хотел бы еще немного задержаться на этой мысли. Вообще говоря,
можно считать аксиомой, что если у нас есть n разных способов представления данных, то необходимо n разных наборов операторов. Например, если бы помимо от­но­ше­ний были еще и массивы, то потребовался бы полный набор операторов с массивами и полный набор операторов с от­но­ше­ниями. Если n больше единицы, то возникает больше

Определение реляционной модели

329

операторов, которые нужно реализовывать, документировать, преподавать, изучать, запоминать и использовать (да еще и выбирать нужный).
Однако дополнительные операторы лишь увеличивают сложность, но
не добавляют выразительных возможностей! Нет ничего полезного, что
можно было бы сделать лишь при n > 1 и нельзя при n = 1 (а в реляционной модели n равно 1).
Более того, реляционная модель не только дает нам всего одну конструкцию, само от­но­ше­ние, для представления данных, но и эта конструкция – цитируя Кодда (см. следующий раздел) – отличается спартанской простотой: в ней нет упорядочения столбцов, нет упорядочения атрибутов, нет кортежей-дубликатов, нет указателей и (по крайней
мере, с моей точки зрения) нет null-значений. Любое отклонение от этих
свойств равносильно введению еще одного способа представления данных, а, значит, и добавочных операторов. И SQL – живое доказательство этого замечания; так, в SQL аж восемь разных операторов объединения1, тогда как в реляционной модели – всего один.
Как видите, принцип информации дейст­вительно важен, но следует сказать, что такое название ему только вредит. Были предложены и другие
названия, в основном Хью Дарвеном, мной или нами совместно, в том
числе принцип однородного представления или принцип однородности
представления. (Признаю, что последнее звучит коряво, но оно все-таки
более точно.)
Остался еще один момент, который я хотел бы упомянуть в разделе «Пе­
ре­мен­ные-от­но­ше­ния». Дарвен и я продемонстрировали в Третьем манифесте, что база данных – не просто «контейнер для пе­ре­мен­ных-от­
но­ше­ний», несмотря на то, что именно так мы ее воспринимали на протяжении всей книги. Логичес­ки база данных сама является пе­ре­мен­ной
(пус­ть и довольно большой), которую можно было бы назвать db-пе­ре­
мен­ной. Таким образом, «пе­ре­мен­ные-от­но­ше­ния» на самом деле пе­ре­
мен­ными не являются; скорее, их следовало бы назвать, как в Третьем
манифесте, псевдопе­ре­мен­ными. Их назначение – создать у пользователя иллюзию, будто он может обновлять базу данных – точнее, db-пе­ре­
мен­ную – по частям, а не целиком (точно так же, как мы говорим об обновлении отдельного кортежа пе­ре­мен­ной-от­но­ше­ния, а не об обновлении пе­ре­мен­ной-от­но­ше­ния целиком).
1

Более того, по чести их должно было бы быть двенадцать, а не восемь – имеющийся в SQL оператор «объединения мультимножес­тв», применяемый
к «мультимножес­твам строк», которые допус­тимы в качестве значений
столбцов таб­лицы, поддерживает лишь два из шести режимов, поддерживаемых оператором объединения обычных таб­лиц. И при этом SQL все равно не поддерживает настоящий оператор объединения мультимножес­тв;
то, что в SQL называется «объединением мультимножес­тв», соответствует
встречающемуся в литературе понятию «объединение+». Дополнительную
информацию см. в статье «The Theory of Bags: An Investigative Tutorial»
(упомянутой в приложении D).

330

Приложение A. Реляционная модель

Реляционное присваивание
Как и оператор сравнения на равенство «=», оператор присваивания
«:=» должен быть определен для любого типа, поскольку без него мы
не смогли бы присваивать значения пе­ре­мен­ным этого типа. И, повторю еще раз, типы от­но­ше­ний – не исключение. Сокращения INSERT,
DELETE и UPDATE разрешены и полезны, но, строго говоря, являются лишь сокращениями. Более того, поддержка реляционного присваивания должна (а) включать и поддержку множес­твенного реляционного присваивания и (б) соответствовать принципу присваивания и золо­
тому правилу.

Реляционные операторы
«Обобщенные реляционные операторы» как раз и составляют реляционную алгебру, поэтому встроены (хотя нет принципиальных причин,
по которым пользователю нельзя определять дополнительные операторы, если он того хочет). Какие именно операторы включать, не указано,
но требуется, чтобы совместно они обладали не меньшей выразительной мощностью, чем реляционное исчисление; другими словами, набор
операторов должен быть реляционно полным (см. обсуждение ниже).
Похоже, существует широко распространенное заблуждение относительно целей алгебры. Точнее, многие полагают, что она предназначена
лишь для написания запросов. Но это не так, задача алгебры – написание реляционных выражений. Эти выражения служат многим разным
целям, в том числе и написанию запросов, но одним лишь запросами
дело не ограничивается. Вот еще несколько важных целей:
•• Определение представлений и снимков
•• Определение множес­тва кортежей, которые нужно вставить в некоторую пе­ре­мен­ную-от­но­ше­ние, удалить или обновить (или, более
общо, определение множес­тва кортежей, которое нужно присвоить
какой-то пе­ре­мен­ной-от­но­ше­нию)
•• Определение ограничений (хотя в этом случае реляционное выражение является лишь подвыражением некоторого булевого выражения; часто, но не всегда, вызова IS_EMPTY)
•• Основа для исследований в других областях, например, оптимизации и проектирования баз данных
И так далее (список неполный).
Алгебра также служит эталоном, с которым сравнивается выразительная мощность языков баз данных. Говорят, что язык реляционно полный, если он не менее выразителен, чем алгебра (или исчисление – что
одно и то же), то есть с помощью его выражений можно определить любое от­но­ше­ние, которое может быть определено с помощью выражений
реляционной алгебры. Реляционная полнота – основная мера вырази-

Цели реляционной модели

331

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

Цели реляционной модели
Мне кажется, что хотя бы для справки в этом приложении уместно перечислить те цели, которые сам Кодд сформулировал во введении к реляционной модели. Следующий список основан на том, что он привел
в работе «Recent Investigations into Relational Data Base Systems» (доклад, который его пригласили прочесть на конгрессе IFIP в 1974 году),
но я его слегка подредактировал:
1. Обеспечить высокую степень независимости от данных.
2. Обеспечить единый взгляд на данные, отличающийся спартанской
простотой, чтобы широкие круги пользователей в организации – от
ничего не понимающих в компьютерах до самых квалифицированных – могли иметь дело с общей моделью (не запрещая накладывать
поверх нее пользовательские представления для специальных целей).
3. Упростить потенциально очень трудную работу администратора базы
данных.
4. Заложить теоретичес­кие основы, пус­ть скромные, управления базами данных (область, которой, увы, недостает твердых принципов,
которыми можно было бы руководствоваться).
5. Объединить дисциплины извлечения фактов и управления файлами, готовясь в будущем добавить службы вывода знаний для использования в коммерчес­ких приложениях.
6. Поднять программирование баз данных на новый уровень – где мно­
жес­тва (а точнее, от­но­ше­ния) рассматриваются как операнды, а не
обрабатываются поэлементно.
Оставляю вам судить, до какой степени, на ваш взгляд, реляционная модель отвечает поставленным целям. Мне кажется, что отвечает – и неплохо.

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

332

Приложение A. Реляционная модель

ссылаться. Примечание: Перечень не претендует на полноту. В частности, я опус­тил принципы нормализации и другие принципы проектирования баз данных, потому что в тексте книги они не рассматривались
(см., однако, приложение B).
Принцип информации (известный также под названием принципа однородного представления, или принципа однородности представления)
База данных не содержит ничего, кроме от­но­ше­ний. Эквивалентно,
все информационное содержимое базы данных в любой момент времени представлено одним и только одним способом, а именно в виде
явных значений в позициях атрибутов кортежей от­но­ше­ний.
Допущение замкнутости мира
Если кортеж t в какой-то момент времени оказывается в пе­ре­мен­
ной-от­но­ше­нии R, то предполагается, что в этот момент истинно высказывание p, соответствующее t. Обратно, если кортеж t мог бы оказаться в пе­ре­мен­ной-от­но­ше­нии R в какой-то момент времени, но не
оказался, то предполагается, что в этот момент высказывание p, соответствующее t, ложно.
Принцип взаимозаменяемости
Не должно существовать произвольных, ненужных различий между базовыми и виртуальными пе­ре­мен­ными-от­но­ше­ниями.
Принцип присваивания
После присваивания значения v пе­ре­мен­ной V результатом сравнения V = v должно быть TRUE.
Золотое правило
Никакая операция обновления ни при каких обстоятельствах не
должна приводить к тому, что хотя бы одно ограничение базы данных примет значение FALSE.
Принцип тождества неразличимых
Пусть a и b – любые два предмета (или, если вам так больше нравится, «сущности»). Тогда если не существует никакого способа различить a и b, то это не два предмета, а всего один.1 Примечание: Раньше я не упоминал этот принцип, но молчаливо обращался к нему во
многих случаях. Его можно сформулировать и по-другому: у каждого предмета есть уникальная характеристика, определяющая
его индивидуальность. В реляционной модели такие характеристики представляются так же, как все остальное: с помощью значений
атрибутов (см. принцип информации выше), и из этого простого факта вытекают многочисленные полезные следствия.

1

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

Что осталось сделать?

333

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

Реализация
В каком-то смысле идею этой книги можно свести к очень простому
призыву:
Давайте реализуем реляционную модель!
Полагаю, из текста книги стало ясно, что называть SQL реляционным
языком значит произносить в его адрес ничем не заслуженный комплимент. Отсюда следует, что все продукты на основе SQL можно считать реляционными только в первом приближении. В дейст­вительности
реляционная модель так и не была надлежащим образом реализована в коммерчес­ком виде, и у пользователей не было возможности насладиться всеми благами, которые несет с собой по-настоящему реляционная сис­тема. Собственно, это одна из главных причин, по которым я взялся писать эту книгу, а также одна из причин, по которым мы
с Хью Дарвеном так долго работали над Третьим манифестом. Третий
манифест – для краткости просто Манифест – это формальное предложение по созданию солидного основания будущих СУБД. Без слов понятно, что его назначение – определить реляционную модель настолько
аккуратно и точно, насколько удалось авторам, и сформулировать некоторые следствия такого определения. (Там же подробно рассматривается влияние теории типов на модель и, в частности, предлагается полная
модель наследования типов, логичес­ки вытекающая из теории типов.)
Нам бы очень хотелось увидеть, что идеи Манифеста корректно реализованы в коммерчес­кой форме (говоря «мы», я имею в виду Дарвена и себя).1 Мы полагаем, что такая реализация могла бы стать прочным фундаментом для возведения многих других построек, например:
«объектно-реляционных» СУБД, пространственно-временных СУБД,
сис­тем, необходимых для Всемирной Паутины, и «машин правил» (известных также под названием «серверы бизнес-логики»), которые некоторым представляются следующим поколением СУБД общего назначения. Мы также полагаем, что в этом случае у нас появилась бы необходимая инфраструктура для поддержки других вещей, которые ниже
1

В этой связи добавлю, что нам хотелось увидеть реализацию, которая в некоторых от­но­ше­ниях была бы более техничес­ки изощренной, чем современные реализации SQL. Конкретно, хотелось бы увидеть реализацию,
основанную на так называемой Трансреляционной ™ Модели (см. раздел
«Некоторые замечания о физичес­ком проектировании» в приложении B).

334

Приложение A. Реляционная модель

названы желательными. Лично я пошел бы и дальше; рискну предположить, что попытка реализовать все это на базе любой другой инфраструктуры окажется более трудной, чем если делать правильно. Процитирую известного математика Григория Чудновского: «Если сделать
по-дурному, потом придется переделывать» (из статьи в газете New York
Times от 24 декабря 1997 года).

Основания
В области теоретичес­ких основ есть еще много интересной работы (иными словами, ни в коем случае нельзя сказать, что все фундаментальные
задачи решены). Вот три примера.
•• Пусть rx – реляционное выражение. По определению, от­но­ше­ние r,
обозначаемое rx, удовлетворяет ограничению rc, производному от
ограничений, которым удовлетворяют от­но­ше­ния, в терминах которых выражено rx. В какой мере ограничение rc можно вычислить?
•• Можно ли сделать процесс проектирования баз данных в большей
степени наукой, чем он сейчас является? В частности, можно ли дать
точную характеристику понятию избыточности?
•• Можно ли придумать хороший способ – то есть надежный, логически обоснованный и эргономичес­ки приемлемый – решения проблемы «отсутствующей информации»?

Высокоуровневые абстракции
Один из способов добиться прогресса в компьютерных языках и приложениях состоит в том, чтобы повысить уровень абстракции. Например,
в главе 5 я отметил, что всем известные спецификации KEY и FOREIGN
KEY – в дейст­вительности лишь сокращенная запись ограничений, которые можно выразить более пространно с помощью общих средств обеспечения целостности, имеющихся в любом реляционно полном языке, например Tutorial D. Но эти сокращения полезны: не говоря уже
том, что они уменьшают объем ввода, они позволяют поднять уровень
абстракции и рассуждать в терминах некоей группы естественно взаимосвязанных понятий. В каком-то смысле они позволяют увидеть лес
за деревьями.
В качестве другого примера рассмотрим реляционную алгебру. В главах 6 и 7 я показал, что многие операторы реляционной алгебры – в том
числе и те, которыми мы постоянно пользуемся, даже не подозревая об
этом, например полусоединение, – на самом деле являются сокращенной записью некоторых комбинаций других операторов.1 Кстати, есть
1

Фактичес­ки Дарвен и я в нашем Манифесте показываем, что любой ал­
гебраичес­кий оператор, рассмотренный в настоящей книге, можно выразить в терминах двух примитивов: remove (по существу, проекция) и либо
nand, либо nor.

335

Что осталось сделать?

и другие полезные операторы, которые я здесь вообще не рассматривал из-за нехватки места, и для них эти замечания в каком-то смысле «даже более справедливы». И в этом случае имеет место повышение
уровня абстракции (похоже на то, как макросы повышают уровень абстракции в традиционном языке программирования).
Повышение уровня абстракции в реляционном мире можно считать
возведением новых этажей на фундаменте реляционной модели; сама
модель не изменяется, но становится лучше приспособленной к конкретным задачам. Особенно плодотворным такой подход, похоже, окажется в применении к темпоральным базам данных. В нашей совместной книге «Temporal Data and the Relational Model» (см. приложение D)
Хью Дарвен, Никос Лоренцос (Nikos Lorentzos) и я, основываясь на оригинальной работе Лоренцоса, вводим интервальные типы как основу
для поддержки темпоральных данных в реляционной инфраструктуре. Взгляните, например, на «темпоральное от­но­ше­ние» на рис. A.1, где
показано, что некоторые поставщики поставляют некоторые детали на
протяжении определенных промежутков времени (можно интерпретировать d04 как «день 4» d06 как «день 6» и т. д.; аналогично [d04:d06]
можно интерпретировать как «интервал между днем 4 и днем 6 включительно»). Значениями атрибута DURING в этом от­но­ше­нии являются интервалы.
SNO

PNO

DURING

S1
S1
S1
S2
S2
S2
S2

P1
P1
P3
P1
P1
P2
P2

[d04:d06]
[d09:d10]
[d05:d10]
[d02:d04]
[d08:d10]
[d03:d03]
[d09:d10]

Рис. A.1. От­но­ше­ние с атрибутом интервального типа

Поддержка интервальных атрибутов, а, следовательно, и темпоральных баз данных, подразумевает, среди прочего, поддержку обобщенных версий обычных алгебраичес­ких операторов. По причинам, которые здесь не так важны, мы называем эти обобщенные операторы «U_
операторами», то есть появляются операторы U_ограничения, U_соединения, U_объединения и т. д. Но – и это самое главное – все U_операторы при внимательном рассмотрении оказываются ни чем иным, как сокращенной записью некоторых комбинаций обычных алгебраичес­ких
операторов. И здесь мы наблюдаем повышение уровня абстракции.
Еще два слова на эту тему. Во-первых, наш реляционный подход к темпоральным данным подразумевает не только «U_» версии алгебраических операторов, но также (а) «U_» ключи и «U_» внешние ключи, (б) «U_» операторы сравнения и (в) «U_» версии операторов INSERT,
DELETE и UPDATE; и все эти конструкции тоже оказываются просто

336

Приложение A. Реляционная модель

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

Высокоуровневые интерфейсы
Есть и другой способ надстраивать реляционную модель – создавая различные приложения, основанные на реляционном интерфейсе и предоставляющие специализированные службы. Одним из примеров может
служить поддержка принятия решений, другим – добыча данных, еще
одним – интерфейс на основе естественных языков. Для пользователей таких приложений реляционная модель скрыта, по крайней мере,
в какой-то степени. (Но даже если это так и даже если большинство
пользователей взаимодейст­вуют с базой данных только посредством
интерфейса, я полагаю, что проектирование самой базы данных должно основываться на твердых реляционных принципах.)
Кстати, допус­тим, что ваша работа – как раз реализация такого рода
интерфейсных приложений. Что бы вы предпочли: реляционную СУБД
или что-то другое (скажем, объектно-ориентированную СУБД)? И если
вы предпочитаете первое – а я, разумеется, считаю, что так и должно
быть, – то какую СУБД вы предпочли бы: ту, что поддерживает реляционную модель, или ту, что поддерживает SQL?
Если это еще неясно, изложу свою точку зрения. Мы далеко ушли от
того времени, когда SQL рекламировался как язык, с которым пользователи могут работать самостоятельно, и я знаю немало людей, которые
именно по этой причине отвергают мои многочисленные критичес­кие
замечания в адрес SQL, считая их придирками. Конечные-то пользователи все равно с ним работать не будут, верно? Только программисты.
Да и в любом случае большая часть исполняемого SQL-кода все равно
пишется не человеком, а генерируется тем или иным приложением. Но
мне кажется, что в качестве конечного языка SQL плох по тем же причинам, что и в качестве исходного. И потому я полагаю, что моя критика все-таки уместна.

Так что насчет SQL?
SQL не способен предоставить те прочные основания, которые нам необходимы для роста и развития. Такие основания должна заложить
именно реляционная модель. Поэтому в Третьем манифесте мы с Дарвеном отвергаем SQL как таковой и предлагаем вместо него как можно
скорее реализовать по-настоящему реляционный язык типа Tutorial D.
Разумеется, мы не настолько наивны, чтобы думать, будто SQL когдато исчезнет. Но мы надеемся, что Tutorial D или еще какой-то истинно
реляционный язык окажется настолько превосходящим его в техническом от­но­ше­нии, что станет предпочтительным языком баз данных (путем естественногоотбора), а языку SQL останется роль «языка на край-

Что осталось сделать?

337

ний случай». Фактичес­ки тут прослеживается параллель с миром языков программирования, из которого COBOL не исчез (и никогда не исчезнет), но приложения на нем пишут, когда сов­сем уж некуда деваться, поскольку существуют более удачные альтернативы. Нам SQL видится как COBOL для баз данных, и мы хотели бы, чтобы ему на смену
пришел лучший язык.
Повторю, мы прекрасно осознаем, что базы данных и приложения на
основе SQL будут сопровождать нас еще долго – думать иначе было бы
утопией – и что мы должны уделить некоторое внимание тому, что делать с сегодняшним наследием SQL. Поэтому Манифест содержит несколько предложений по этому поводу. В частности, предлагается реализовать SQL поверх какого-нибудь истинно реляционного языка, так
чтобы существующие SQL-приложения продолжали работать. Детальное обсуждение этих предложений здесь вряд ли уместно, достаточно
сказать, что, на наш взгляд, возможно эмулировать такие нереляционные свойства SQL, как дубликаты и null-значения, не поддерживая их
непосредственно в базовом реляционном языке.

B

Приложени

.

Теория проектирования баз данных
Из принципа физичес­кой независимости от данных, обсуждавшегося в главе 1, напрямую следует, что логичес­кое и физичес­кое проектирование баз данных – разные дисциплины. Логичес­кое проектирование связано с тем, как базу данных видит пользователь, а физичес­кое –
с тем, как логичес­кая структура отображается на физичес­кое хранение.
Однако термин теория проектирования баз данных почти всегда применяется к логичес­кому, а не к физичес­кому проектированию (в предположении, что физичес­кая структура обязательно зависит от деталей
реализации конкретной СУБД, тогда как логичес­кая структура является или должна являться независимой от СУБД). Поэтому в настоящем приложении я буду употреблять просто термин проектирование
(или структура), понимая под ним именно логичес­кое проектирование, если явно не оговорено противное.
Одно замечание я хотел бы сделать прямо сейчас. Напомню, что полное
ограничение пе­ре­мен­ной-от­но­ше­ния R можно рассматривать как сис­
темную аппроксимацию предиката этой пе­ре­мен­ной-от­но­ше­ния; напомню также, что предикатом R называется предполагаемая интерпретация, или смысл, R. Отсюда следует, что ограничения и предикаты тесно связаны с задачей логичес­кого проектирования; в дейст­вительности
вся суть логичес­кого проектирования и состоит в максимально тщательном определении предикатов и последующем отображении их на
пе­ре­мен­ные-от­но­ше­ния и ограничения. Конечно, предикаты по необходимости не вполне формальны (в главе 8 отмечалось, что иногда их называют бизнес-правилами), тогда как пе­ре­мен­ные-от­но­ше­ния и ограничения обязательно носят формальный характер.
Кстати, описанное выше положение вещей объясняет, почему я не являюсь горячим сторонником моделей «сущность-связь» (E/R – entity/
relationship) и других подобных графичес­ких методик. Проблема в том,

Место теории проектирования

339

что диаграммы «сущность-связь» и аналогичные им картинки в состоянии представить не все ограничения, а только небольшую их часть,
пус­ть и важную. Поэтому, хотя их использование и допус­тимо для того,
чтобы объяснить проект на высоком уровне абстракции, думать, что на
такой диаграмме структура представлена во всей полноте – заблуждение, иногда весьма опасное. Напротив: структура базы данных состоит
из пе­ре­мен­ных-от­но­ше­ний, которые на диаграммах отражены, и ограничений, которые не отражены.
И еще одна мысль, которую я хотел бы высказать с самого начала. Напомню (см. главу 9), что представления должны выглядеть и вести себя
в точности, как базовые пе­ре­мен­ные-от­но­ше­ния (я имею в виду не те
представления, которые определены просто как сокращенная запись,
а те, что призваны как-то изолировать пользователя от «реальной»
базы данных). В общем случае пользователь взаимодейст­вует с базой
данных, которая содержит не только базовые пе­ре­мен­ные-от­но­ше­ния
(«реальной» базой данных), но также и представления (такую базу можно было бы назвать пользовательской). Но такая пользовательская база
данных должна выглядеть и вести себя, как реальная, поэтому все обсуждаемые в настоящем приложении принципы проектирования применимы к пользовательским базам данных в той же мере, что и к реальным.
Я чувствую необходимость еще в одном вступительном замечании. Некоторые рецензенты рукописи этого приложения, похоже, подумали,
что я собираюсь научить читателя элементам проектирования баз данных. Но такой цели я не ставил. Вы профессионально занимаетесь базами данных и с основами проектирования, надо полагать, уже знакомы. Поэтому я не собираюсь объяснять, как процесс проектирования
происходит на практике; моя задача – закрепить некоторые известные
вам аспекты проектирования, взглянув на них под, возможно, неожиданным для вас углом зрения, а также рассмотреть те аспекты, которые
вам, быть может, не знакомы. Я не хочу тратить много времени на повторение всем известного. Например, я сознательно не буду углубляться в детали второй и третьей нормальной форм, поскольку они уже стали частью традиционных знаний по проектированию и не нуждаются
в рассмотрении в такой книге, как эта (да и в любом случае они важны
не столько сами по себе, сколько в качестве перехода к нормальной форме Бойса/Кодда, о которой я расскажу).

Место теории проектирования
Теория проектирования как таковая не является частью реляционной
модели; это отдельная полноценная теория, построенная поверх этой
модели. (Можно воспринимать ее как часть реляционной теории вообще,
но, повторюсь, не как часть реляционной модели.) Однако она опирает-

340

Приложение B. Теория проектирования баз данных

ся на некоторые фундаментальные понятия – например, операторы проекции и соединения, – которые являются частью реляционной модели.
И еще одно: теория проектирования, о которой я буду рассказывать, не
дает конкретных наставлений, как проектировать. Она лишь говорит,
что плохого может случиться, если вы не станете проектировать базу
данных очевидным образом. Рассмотрим пример базы данных о поставщиках и деталях. Очевидной является структура, которая предполагалась всюду в этой книге; я хочу сказать, что «очевидна» необходимость трех пе­ре­мен­ных-от­но­ше­ний, что атрибут STATUS принадлежит
пе­ре­мен­ной-от­но­ше­нию S, атрибут COLOR – пе­ре­мен­ной-от­но­ше­нию P,
атрибут QTY – пе­ре­мен­ной-от­но­ше­нию SP и так далее. Но почему все
эти вещи так уж очевидны? Что ж, попробуем спроектировать базу данных по-другому, например, переместим атрибут STATUS из S в SP (интуитивно понятно, что это неподходящее место для него, поскольку статус – свойство поставщика, а не поставки). На рис. B.1 показаны значения атрибутов для модифицированной таким образом пе­ре­мен­ной-от­
но­ше­ния «поставки» (я назвал ее STP во избежание недоразумений).
STP SNO
S1
S1
S1
S1
S1
S1
S2
S2
S3
S4
S4
S4

STATUS
20
20
20
20
20
20
10
10
30
20
20
20

PNO

QTY

P1
P2
P3
P4
P5
P6
P1
P2
P2
P2
P4
P5

300
200
400
200
100
100
300
400
200
200
300
400

Рис. B.1. Пе­ре­мен­ная-от­но­ше­ние STP - тестовые значения

Одного взгляда на этот рисунок достаточно, чтобы понять, в чем недостаток такой структуры: она избыточна в том смысле, что каждый кортеж для поставщика S1 говорит нам, что S1 имеет статус 20, каждый
кортеж для поставщика S2 – что S2 имеет статус 10 и так далее. А теория проектирования говорит, что проектирование базы данных способом, отличным от очевидного, всегда приводит к такой избыточности, и кроме того, сообщает, каковы будут последствия этой избыточности. Другими словами, вся теория проектирования вращается вокруг
уменьшения избыточности, в чем мы вскоре и убедимся. Поэтому теорию проектирования иногда называют – пожалуй, слегка недоброжелательно – хорошим источником плохих примеров. Ее даже критиковали, на том основании, что все ее содержание – не более чем здравый
смысл. К этой критике я еще вернусь в следующем разделе.

Место теории проектирования

341

Чтобы придать обсуждению позитивную нотку, замечу, что теория проектирования может быть полезна для проверки того, что проекты, созданные путем применения какой-то другой методологии, не нарушают
формальных принципов проектирования. Но… мы снова сталкиваемся с печальным фактом – хотя формальные принципы проектирования
и составляют научную часть дисциплины проектирования, есть немало аспектов, которые в них вообще не рассматриваются. Проектирование базы данных остается по природе своей субъективным делом; формальные принципы, которые я собираюсь описать в настоящем приложении, – это то немногое, что можно отнести к науке в той отрасли, которую в основном следует относить, скорее, к искусству.
Итак, я хочу рассмотреть научную сторону проектирования. Точнее,
я собираюсь исследовать две обширные темы: нормализацию и ортогональность. Полагаю, что хотя бы первая из них вам хорошо знакома.
В частности, я считаю известными следующие факты:
•• Cуществует несколько нормальных форм (первая, вторая, третья
и т. д.).
•• Проще говоря, если пе­ре­мен­ная-от­но­ше­ние R находится в (n+1)-ой
нормальной форме, то она обязательно находится и в n-ой нормальной форме.
•• Пе­ре­мен­ная-от­но­ше­ние может находиться в n-ой, но не находиться
в (n+1)-ой нормальной форме.
•• Чем выше нормальная форма, тем лучше с точки зрения проектирования.
•• Все эти идеи опираются на некоторые зависимости (в данном контексте это просто другое название ограничений целостности).
Я хотел бы чуть подробнее остановиться на последних двух пунктах.
Я сказал, что ограничения, вообще говоря, крайне важны для процесса проектирования. Однако оказывается, что конкретные ограничения,
о которых идет речь здесь – так называемые зависимости, – обладают
некоторыми формальными свойствами, отсутствующими (насколько
нам известно) у других ограничений. Очень глубоко вдаваться в этот вопрос я не хочу, скажу лишь, что для таких зависимостей можно определить некоторые правила вывода, и именно существование правил вывода и позволяет разработать теорию проектирования, которую я собираюсь описать.
Еще раз повторю, что предполагаю наличие у вас определенных знаний в этой области. Но, как уже отмечалось, я хочу сосредоточить внимание на тех сторонах предмета, которые вам, возможно, незнакомы;
я попытаюсь высветить более важные части и умалить остальные, а, более общо, я хочу взглянуть на предмет в целом под несколько непривычным для вас углом зрения.

342

Приложение B. Теория проектирования баз данных

Функциональные зависимости
и нормальная форма Бойса/Кодда
Хорошо известно, что понятия второй нормальной формы (2НФ), третьей нормальной формы (3НФ) и нормальной формы Бойса/Кодда
(НФБК) опираются на понятие функциональной зависимости.1 Приведу точное определение этого понятия:
Определение: Пусть A и B – подмножес­тва заголовка пе­ре­
мен­ной-от­но­ше­ния R. Тогда говорят, что пе­ре­мен­ная-от­но­
ше­ние R удовлетворяет функциональной зависимости (ФЗ)
A → B, если для любого от­но­ше­ния, являющегося допус­
тимым значением R, в любых двух кортежах, где одинаковы значения A, одинаковы также значения B.
ФЗ A → B произносится «В функционально зависит от A», или «A функционально определяет B», или просто «A стрелка B».
В качестве примера предположим, что существует ограничение целостности, состоящее в том, что если два поставщика находятся в одном городе, то они должны иметь одинаковый статус (см. рис. B.2, на котором я изменил статус поставщика S2 с 10 на 30, чтобы не нарушать это
гипотетичес­кое новое ограничение). Тогда ФЗ
{ CITY } → { STATUS }

удовлетворяется этой модифицированной версией – назовем ее RS – пе­
ре­мен­ной-от­но­ше­ния S. Кстати, обратите внимание на фигурные скобки, я использую их, чтобы подчеркнуть тот факт, что обе части ФЗ –
множес­тва атрибутов, даже в том случае, когда (как в этом примере)
они состоят всего из одного атрибута.
RS SNO
S1
S2
S3
S4
S5

SNAME STATUS
Smith
Jones
Blake
Clark
Adams

20
30
30
20
30

CITY
London
Paris
Paris
London
Athens

обратите внимание на изменения

Рис. B.2. Модифицированная пе­ре­мен­ная-от­но­ше­ние RS – тестовые
значения

Как видно из примера, тот факт, что некоторая пе­ре­мен­ная-от­но­ше­
ние R удовлетворяет ФЗ, составляет ограничение базы данных в смысле главы 8; точнее, ограничение (с одной пе­ре­мен­ной-от­но­ше­нием) этой
пе­ре­мен­ной-от­но­ше­ния R. Например, ФЗ в рассмотренном выше примере эквивалентно следующему ограничению на языке Tutorial D:
1

Термины зависимость и функциональная зависимость в литературе и в этой
книге считаются синонимами.

Функциональные зависимости и нормальная форма Бойса/Кодда

343

CONSTRAINT RSC COUNT ( RS { CITY } ) =
COUNT ( RS { CITY , STATUS } ) ;

Кстати, полезно запомнить: если пе­ре­мен­ная-от­но­ше­ние R удовлетворяет ФЗ A → B, то она удовлетворяет и ФЗ A′ → B′ для любого надмножес­
тва A′ ⊃ A и любого подмножес­тва B′ ⊂ B. Иными словами, всегда можно
добавить атрибуты в левую часть или удалить атрибуты из правой части, и то, что получится, по-прежнему будет ФЗ, которой удовлетворяет рассматриваемая пе­ре­мен­ная-от­но­ше­ние.
Сейчас я хочу напомнить вам один термин и ввести новый. Первый термин – суперключ. Напомню (см. главу 5), что суперключ – это, по существу, надмножес­тво ключа (не обязательно собственное);1 эквивалентно,
подмножес­тво SK заголовка пе­ре­мен­ной-от­но­ше­ния R является суперключом R тогда и только тогда, когда обладает свойством уникальности, но не обязательно свойством неприводимости. Таким образом, любой ключ является суперключом, но большинство суперключей не являются ключами. Например, {SNO,CITY} – суперключ, но не ключ
пе­ре­мен­ной-от­но­ше­ния S. Замечу, в частности, что сам заголовок пе­
ре­мен­ной-от­но­ше­ния R является суперключом R. Также напомню, что
если SK – суперключ R и A – произвольное подмножес­тво заголовка R,
то R обязательно удовлетворяет ФЗ SK → A, потому что если в двух кортежах R одинаковы значения SK, то, по определению, это один и тот же
кортеж, а, значит, значения A тоже одинаковы.
Новый термин, который я хочу ввести, – это тривиальная ФЗ. ФЗ называется тривиальной, если ее невозможно нарушить. Например, все
показанные ниже ФЗ тривиально удовлетворяются любой пе­ре­мен­нойот­но­ше­нием, у которой есть атрибуты STATUS and CITY:
{ CITY , STATUS } → { CITY }
{ CITY , STATUS } → { STATUS }
{ CITY }
→ { CITY }

Например, в первом случае, если в двух кортежах одинаковы значения CITY и STATUS, то понятно, что значения CITY одинаковы. На самом деле, ФЗ тривиальна тогда и только тогда, когда левая часть есть
надмножес­тво правой части (не обязательно собственное). Обычно мы
не думаем о тривиальных ФЗ при проектировании базы данных просто потому, что они тривиальны. Но когда требуется формально и точно
рассуждать о таких вещах, надо принимать во внимание все ФЗ – тривиальные они или нет.
Дав точное определение функциональной зависимости, я теперь могу
сказать, что нормальная форма Бойса/Кодда (НФБК) нормальна относительно ФЗ, и точно определить, что это значит:

1

Напомню также, что термин ключ я зарезервировал для потенциального
ключа.

344

Приложение B. Теория проектирования баз данных

Определение: Говорят, что пе­ре­мен­ная-от­но­ше­ние R находится в НФБК, если для любой нетривиальной ФЗ A → B,
которой удовлетворяет R, A является суперключом R.
Другими словами, для пе­ре­мен­ной-от­но­ше­ния, находящейся в НФБК,
возможны только два вида ФЗ: тривиальные (от них, очевидно, избавиться невозможно) и «стрелки, исходящие из суперключей» (от них
тоже нельзя избавиться). Или, как иногда говорят: каждый факт представляет собой факт о ключе, всем ключе и ни о чем, кроме ключа, –
хотя я должен сразу же добавить, что эта неформальная характеристика, как бы она ни была приятна на слух, в дейст­вительности не точна,
потому, в частности, что предполагает наличие только одного ключа.
Я чувствую необходимость развернуть мысль, изложенную в предыдущем абзаце. Боюсь, что слова об «избавлении» от некоторой ФЗ тоже
несколько небрежны… Например, модифицированная пе­ре­мен­ная-от­
но­ше­ние «поставщики» – RS на рис. B.2 – удовлетворяет ФЗ {SNO} →
{STATUS}; но если разложить ее (что я и порекомендую сделать чуть
ниже) на пе­ре­мен­ные-от­но­ше­ния SNC и CS (где SNC имеет атрибуты SNO, SNAME и CITY, а CS – атрибуты CITY и STATUS), то эта ФЗ
в каком-то смысле «исчезает» и, следовательно, мы от нее «избавились».
Но что мы имеем в виду, когда говорим, что ФЗ исчезла? Ответ таков:
она превратилась в ограничение с несколькими пе­ре­мен­ными-от­но­ше­
ниями (то есть в ограничение, в котором участвуют две или более пе­
ре­мен­ные-от­но­ше­ния). Так что ограничение, конечно, никуда не делось – просто оно перестало быть ФЗ. Аналогичные замечания применимы и ко всем другим употреблениям слова «избавиться» в этом приложении.
Наконец, вы наверняка знаете, что теория нормализации гласит: если
пе­ре­мен­ная-от­но­ше­ние R не находится в НФБК, то следует разложить
ее на меньшие, которые уже будут находиться в такой форме (где под
«меньшими» понимается «имеющие меньше атрибутов»). Например:
•• Пе­ре­мен­ная-от­но­ше­ние STP (см. рис. B.1) удовлетворяет ФЗ {SNO} →
{STATUS}, которая не является ни тривиальной, ни «стрелкой, исходящей из суперключа», – {SNO} не есть суперключ для STP, – следовательно, эта пе­ре­мен­ная-от­но­ше­ние не находится в НФБК (и, как
вы видели выше, страдает избыточностью). Поэтому разложим ее на
пе­ре­мен­ные-от­но­ше­ния, которые назовем SP и SS, где SP имеет атрибуты SNO, PNO и QTY (как обычно), а SS – атрибут SNO и STATUS.
Упражнение: Выпишите значения пе­ре­мен­ных-от­но­ше­ний SP и SS,
соответствующие значению STP, показанному на рис. B.1; убедитесь,
что SP и SS находятся в НФБК, и, значит, декомпозиция устранила
избыточность.
•• Аналогично пе­ре­мен­ная-от­но­ше­ние RS (см. рис. B.2) удовлетворяет
ФЗ {CITY} → {STATUS} и потому должна быть разложена на пе­ре­
мен­ные-от­но­ше­ния SNC (с атрибутами SNO, SNAME и CITY) и CS

Функциональные зависимости и нормальная форма Бойса/Кодда

345

(с атрибутами CITY и STATUS). Упражнение: Выпишите значения
пе­ре­мен­ных-от­но­ше­ний SТС и CS, соответствующие значению RS,
показанному на рис. B.2; убедитесь, что SNС и CS находятся в НФБК,
и, значит, декомпозиция устранила избыточность.

Декомпозиция без потери информации
Если некоторая пе­ре­мен­ная-от­но­ше­ние не находится в НФБК, она может и должна быть разложена в меньшие пе­ре­мен­ные-от­но­ше­ния. Разумеется, важно, чтобы при такой декомпозиции не терялась информация – мы должны иметь возможность вернуться туда, откуда начали.
Рассмотрим еще раз пе­ре­мен­ную-от­но­ше­ние RS (рис. B.2) с ФЗ {CITY} →
{STATUS}. Предположим, что мы решили разложить ее не на пе­ре­мен­
ные-от­но­ше­ния SNC и CS, а на пе­ре­мен­ные-от­но­ше­ния SNS и CS, как
показано на рис. B.3. (CS в обоих случаях одинакова, а SNS имеет атрибуты SNO, SNAME и STATUS вместо SNO, SNAME и CITY.) Тогда, надеюсь, понятно, что (а) SNS и CS находятся в НФБК, но (б) декомпозиция
приводит к потере информации – например, мы теперь не можем сказать, находится ли поставщик S2 в Париже или в Афинах.
SNS SNO
S1
S2
S3
S4
S5

SNAME STATUS
Smith
Jones
Blake
Clark
Adams

20
30
30
20
30

CS

CITY
London
Paris
Athens

STATUS
20
30
30

Рис. B.3. Пе­ре­мен­ные-от­но­ше­ния SNS и CS – тестовые значения

Почему одни декомпозиции приводят к потере информации, а другие
не приводят? Во-первых, отметим, что процесс декомпозиции – это, по
существу, взятие проекции; все «меньшие» пе­ре­мен­ные-от­но­ше­ния
в приведенных до сих пор примерах были проекциями исходной пе­ре­
мен­ной-от­но­ше­ния. Иными словами, оператор декомпозиции в точности совпадает с оператором проекции из реляционной алгебры (и именно в таком смысле я и буду использовать термин декомпозиция, если
явно не оговорено противное). Примечание: И снова я небрежен. Как
и все алгебраичес­кие операторы, проекция применяется к от­но­ше­ниям,
а не к пе­ре­мен­ным-от­но­ше­ниям. Но мы часто говорим, что пе­ре­мен­
ная-от­но­ше­ние CS является проекцией пе­ре­мен­ной-от­но­ше­ния RS, когда в дейст­вительности имеем в виду, что от­но­ше­ние, которое является значением пе­ре­мен­ной-от­но­ше­ния CS в произвольный момент времени, есть проекция от­но­ше­ния, которое является значением пе­ре­мен­
ной-от­но­ше­ния RS в тот же момент времени (надеюсь, я выразился не
слишком запутанно).
Идем далее. Когда мы говорим, что некоторая декомпозиция не приводит к потере информации, то в дейст­вительности имеем в виду следующее: если соединить обе получившиеся проекции, то получится исход­

346

Приложение B. Теория проектирования баз данных

ная пе­ре­мен­ная-от­но­ше­ние. Но, обратившись к рис. B.3, мы увидим,
что пе­ре­мен­ная-от­но­ше­ние RS не равна результату соединения своих
проекций SNS и CS, и именно поэтому декомпозиция приводит к потере информации. Напротив, если взглянуть на рис. B.2, то станет ясно,
что RS равна результату соединения проекций SNC и CS, так что такая
декомпозиция не приводит к потере информации.
Повторю еще раз, что оператор декомпозиции – это проекция, а оператор рекомпозиции – соединение. А формальный вопрос, составляющий
самую суть теории нормализации, звучит так:
Пусть R – пе­ре­мен­ная-от­но­ше­ние, и пус­ть R1, R2, ..., Rn – проекции R. Какие условия должны быть выполнены, чтобы R была
равна соединению этих проекций?

Важный, хотя и неполный, ответ на этот вопрос дал Ян Хит (Ian Heath)
в 1971 году, доказав следующую теорему:
Пусть A, B, C – подмножес­тва заголовка пе­ре­мен­ной-от­но­ше­ния R
такие, что теоретико-множес­твенное объединение A, B и C равно
этому заголовку. Пусть AB обозначает теоретико-множес­твенное
объединение A и B и аналогично для AC. Если R удовлетворяет ФЗ
A → B, то R равно соединению своих проекций на AB и AC.

В качестве примера еще раз рассмотрим пе­ре­мен­ную-от­но­ше­ние RS
(рис. B.2). Эта пе­ре­мен­ная-от­но­ше­ние удовлетворяет ФЗ {CITY} →
{STATUS}. Следовательно, если взять в качестве A {CITY}, в качестве B
{STATUS}, а в качестве C {SNO,SNAME}, то по теореме Хита RS может
быть без потери информации разложена на проекции на {CITY,STATUS}
и {CITY,SNO,SNAME}, что мы и так уже знаем.

Примечание
Если вам интересно, почему я назвал ответ Хита на исходный вопрос «неполным», то я поясню на рассмотренном выше примере. По существу, теорема говорит нам, что декомпозиция, показанная на рис. B.2, не приводит к потере информации, но она не говорит, что декомпозиция, показанная на рис. B.3, приводит к потере. То есть она дает достаточное условие, которое, однако, не является необходимым для того, чтобы декомпозиция не приводила к потере информации. (Более сильную форму теоремы Хита, которая дает необходимое
и достаточное условие, доказал в 1977 году Рон Фейджин (Ron Fagin), но детали выходят за рамки этого обсуждения. См. упражнение B.18 в конце приложения.)

Попутно отмечу, что в той же статье, где Хит доказал эту теорему, он
дал определение того, что назвал «третьей» нормальной формой, хотя
фактичес­ки это было определение НФБК. Так как это определение
было дано на три года раньше, чем его предложили Бойс и Кодд, то мне
кажется, что НФБК по праву должна называться нормальной формой
Хита. Но не называется.

Функциональные зависимости и нормальная форма Бойса/Кодда

347

И последнее замечание: из обсуждений в этом подразделе следует, что
показанное раньше ограничение для пе­ре­мен­ной-от­но­ше­ния RS
CONSTRAINT RSC COUNT ( RS { CITY } ) =
COUNT ( RS { CITY , STATUS } ) ;

можно было бы по-другому записать так:
CONSTRAINT RSC RS = JOIN { RS { SNO , SNAME , CITY } ,
RS { CITY , STATUS } } ;

(«В любой момент времени пе­ре­мен­ная-от­но­ше­ние RS равна соединению своих проекций на {SNO,SNAME,CITY} и {CITY,STATUS}»; здесь
я воспользовался префиксной, или n-местной версией JOIN.)

Но разве все это не простой здравый смысл?
Выше я уже отмечал, что теорию нормализации критикуют на том
основании, что все это простой здравый смысл. Рассмотрим, к примеру,
снова пе­ре­мен­ную-от­но­ше­ние STP (рис. B.1). Очевидно, что она спроектирована плохо; избыточности как на ладони, последствия тоже ясны,
и любой компетентный проектировщик «естественно» разложит ее на
проекции SP и SS, которые мы обсуждали выше, пус­ть даже он никогда не слыхал о НФБК. Но что здесь означает слово «естественно»? Какими принципами руководствовался проектировщик, выбирая это «естественное» решение?
Ответ таков: именно принципами нормализации. То есть эти принципы уже отпечатались в мозгу компетентного проектировщика, даже
если он никогда не изучал их формально и не может правильно назвать. Да, принципы продиктованы здравым смыслом – но формализованным здравым смыслом. (Здравый-то он здравый, но не всегда легко сказать, в чем смысл!) Теория нормализации как раз и формулирует,
в чем заключаются некоторые стороны здравого смысла. На мой взгляд,
реальное достижение теории нормализации состоит в следующем: она
формализует некоторые принципы, продиктованные здравым смыслом, открывая путь к механизации этих принципов (то есть включения их в автоматизированные инструменты проектирования). Критики нормализации часто упускают эту мысль из виду; они заявляют, совершенно справедливо, что ее идеи – не более чем обычный здравый
смысл, но, как правило, не осознают тот факт, что точное и формальное
описание того, что означает здравый смысл, – само по себе существенное достижение.

1НФ, 2НФ, 3НФ
Нормальные формы низших по сравнению с НФБК порядков представляют, в основном, историчес­кий интерес; я даже не буду приводить их
определения. Напомню только, что все пе­ре­мен­ные-от­но­ше­ния нахо-

348

Приложение B. Теория проектирования баз данных

дятся, по меньшей мере, в 1НФ, даже те, у которых есть RVA-атрибуты.1
Однако с точки зрения проектирования пе­ре­мен­ные-от­но­ше­ния с RVAатрибутами обычно, хотя и не всегда, противопоказаны. Это не означает, что RVA-атрибутами никогда не следует пользоваться (на самом
деле нет ничего плохого в том, что результаты запроса включают такие атрибуты); обычно не стоит лишь «забивать RVA-атрибуты в базу на
этапе проектирования». Я бы не хотел детально заниматься этим вопросом здесь, скажу лишь, что пе­ре­мен­ные-от­но­ше­ния с RVA-атрибутами
очень напоминают иерархичес­кие структуры, присутствующие в других, нереляционных сис­темах типа IMS, и вместе с ними возникают
все старые проблемы, присущие иерархиям. Вот краткий перечень таких проблем:
•• Фундаментальная проблема связана с тем, что иерархии асиммет­
ричны. Поэтому, хотя на первый взгляд кажется, что они «упрощают» решение некоторых задач, другие задачи определенно только
усложняются.
•• Поэтому запросы тоже становятся асимметричны, а также усложняются по сравнению с симметричными аналогами.
•• То же самое относится к ограничениям целостности.
•• То же самое относится к обновлениям, только в еще более отягченной форме.
•• Не существует рекомендаций по выбору «наилучшей» иерархии.
•• Даже «естественные» иерархии, например организационные диаграммы, обычно лучше представлять неиерархичес­ки.
В примерах 7.10, 7.12 и 7.13 в главе 7 приведены примеры, иллюстрирующие некоторые из этих моментов. Повторю, однако, что иногда RVAатрибуты могут оказаться полезными даже в базовых пе­ре­мен­ных-от­
но­ше­ниях. См. упражнение B.14 в конце приложения.

Зависимости соединения
и пятая нормальная форма
Пятая нормальная форма (5НФ) в некотором смысле, который я объясню ниже в этом разделе, – это «последняя нормальная форма». На самом деле, если НФБК – нормальная форма относительно функциональных зависимостей, то пятая нормальная форма нормальна относительно так называемых зависимостей соединения:

1

Строго говоря, в 1НФ всегда находятся от­но­ше­ния, а не пе­ре­мен­ные-от­но­
ше­ния (см. главы 1 и 2), то есть первая нормальная форма – это свойство
от­но­ше­ний, а не пе­ре­мен­ных-от­но­ше­ний. Но никакого вреда не будет, если
мы обобщим это понятие и на пе­ре­мен­ные-от­но­ше­ния тоже.

Зависимости соединения и пятая нормальная форма

349

Определение: Пусть A, B, ..., C – подмножес­тва заголовка
пе­ре­мен­ной-от­но­ше­ния R. Тогда R удовлетворяет зависимости соединения (ЗС)
* { A , B , ... , C }

(произносится «звездочка A, B, ..., C»), если любое от­но­ше­
ние, являющееся допус­тимым значением R, равно соединению своих проекций на A, B, ..., C.
Из этого определения вытекают следующие выводы:
•• Очевидно, что R можно без потери информации разложить на свои
проекции на A, B, ..., C тогда и только тогда, когда оно удовлетворяет ЗС * {A, B, ..., C}.
•• Также очевидно, что всякая ФЗ является также и ЗС, поскольку
(по теореме Хита) если R удовлетворяет некоторой ФЗ, то она может быть без потери информации разложена на некоторые проекции
(другими словами, удовлетворяет некоторой ЗС).
Для иллюстрации последнего замечания рассмотрим еще раз пе­ре­мен­
ную-от­но­ше­ние RS (см. рис. B.2). Эта пе­ре­мен­ная-от­но­ше­ние удов­
летворяет ФЗ {CITY} → {STATUS} и потому может быть без потерь
разложена на проекции SNC (на SNO, SNAME и CITY) и CS (на CITY
и STATUS). Отсюда следует, что пе­ре­мен­ная-от­но­ше­ние RS удовлетворяет ЗС *{SNC,CS} – если вы позволите мне использовать имена SNC
и CS для обозначения не только самих проекций, но и соответствующих подмножес­тв заголовка этой пе­ре­мен­ной-от­но­ше­ния.
В предыдущем разделе мы видели, что всегда существуют «стрелки,
исходящие из суперключей», то есть существуют некоторые функцио­
нальные зависимости, которые вытекают из наличия суперключей
и от которых невозможно избавиться. Более общо, наличие суперключей подразумевает некоторые зависимости соединения, от которых также невозможно избавиться. Точнее, если пе­ре­мен­ная-от­но­ше­ние R удов­
летворяет ЗС *{A,B,...,C}, то эта ЗС обусловлена суперключами тогда
и только тогда, когда каждое подмножес­тво A, B, ..., C является суперключом для R.1 Например, рассмотрим нашу привычную пе­ре­мен­нуюот­но­ше­ние «поставщики» S. Она может быть без потерь разложена на
свои проекции на SNO и SNAME, на SNO и STATUS и на SNO и CITY;
иными словами, она удовлетворяет ЗС
* { SN , SS , SC }

1

Это определение того, что «ЗС обусловлена суперключами», корректно
лишь, если существует ровно один ключ. Его можно обобщить на случай
двух и более ключей, но детали слишком запутаны, поэтому я их опущу.
Точно и полное определение можно найти в работе «The Relational Database
Dictionary, Extended Edition» (см. приложение D).

350

Приложение B. Теория проектирования баз данных

где SN – это {SNO,SNAME}, SS – {SNO,STATUS}, а SC – {SNO,CITY}. Поскольку каждая из этих проекций, очевидно, является суперключом
для S, то ЗС дейст­вительно обусловлена суперключами. (Хотим ли мы
в дейст­вительности выполнять такую декомпозицию – дело другое.
Сможем, если захотим, и этого достаточно.)
В предыдущем разделе мы также видели, что некоторые ФЗ тривиальны. Вы уже, наверное, понимаете, что есть и тривиальные ЗС. Точнее, ЗС
*{A,B,...,C} называется тривиальной, если хотя бы одно из подмножес­тв
A, B, ..., C совпадает со всем заголовком соответствующей пе­ре­мен­нойот­но­ше­ния R. Например, вот одна из многих тривиальных ЗС, которым
удовлетворяет пе­ре­мен­ная-от­но­ше­ние S:
* { S , SN , SS , SC }

Примечание
Здесь я использую имя S для обозначения множес­тва всех атрибутов – заголовка – пе­ре­мен­ной-от­но­ше­ния S (соответствует тождественной проекции S,
то есть проекции этой пе­ре­мен­ной-от­но­ше­ния на все свои атрибуты). Кстати, я надеюсь, вы понимаете, что любую пе­ре­мен­ную-от­но­ше­ние можно без
потерь разложить на заданное множес­тво проекций, если одна из проекций
в этом множес­тве является тождественной, хотя говорить в этом случае о «декомпозиции» было бы натяжкой, так как одна из проекций в такой «декомпозиции» совпадает с исходной пе­ре­мен­ной-от­но­ше­нием, то есть ни о каком
разложении говорить, в общем-то, не приходится.

Дав точное определение понятию ЗС, я могу теперь определить, что такое 5НФ.
Определение: Говорят, что пе­ре­мен­ная-от­но­ше­ние R находится в 5НФ, если любая нетривиальная ЗС, которой удовлетворяет R, обусловлена суперключами R.
Иными словами, единственные ЗС, которым удовлетворяет пе­ре­мен­
ная-от­но­ше­ние, находящаяся в 5НФ, – это те, от которых нельзя избавиться. Если пе­ре­мен­ная-от­но­ше­ние удовлетворяет еще каким-то ЗС,
то она не находится в 5НФ (и потому страдает избыточностью) и, возможно, должна быть подвергнута декомпозиции.

Значимость 5НФ
Уверен, вы заметили, что при обсуждении ЗС и 5НФ я не привел примера пе­ре­мен­ной-от­но­ше­ния, которая находилась бы в НФБК, но не в 5НФ
(и потому могла бы быть без потери информации на что-то с пользой разложена). А причина такова: хотя существуют ЗС, которые не являются простыми ФЗ, (а) такие ЗС редко встречаются на практике и (б) они
оказываются довольно сложными, просто по определению. Из-за этой

Зависимости соединения и пятая нормальная форма

351

сложности я решил не приводить пример сразу (но в следующем подразделе приведу), а, поскольку они редко встречаются, то с практичес­кой
точки зрения они не слишком важны. На этом я хотел бы остановиться чуть подробнее.
Прежде всего, если вы проектируете базы данных, то, конечно, должны
знать о ЗС и 5НФ, это часть вашего инструментария, и (при прочих равных условиях) следует стремиться к тому, чтобы все пе­ре­мен­ные-от­но­
ше­ния в базе данных находились в 5НФ. Но большинство пе­ре­мен­ныхот­но­ше­ний (не все), встречающихся на практике, таковы, что если находятся хотя бы в НФБК, то находятся и в 5НФ. Редко приходится сталкиваться с пе­ре­мен­ной-от­но­ше­нием, которая бы находилась в НФБК,
но не в 5НФ. Есть даже соответствующая теорема:
Пусть R – пе­ре­мен­ная-от­но­ше­ние, находящаяся в НФБК, и пус­ть
R не имеет составных ключей (то есть ключей, составленных из
двух или более атрибутов). Тогда R находится в 5НФ.

Это очень полезная теорема. Она говорит, что если вам удалось получить НФБК (что достаточно просто) и если ваша пе­ре­мен­ная-от­но­ше­ние,
находящаяся в НФБК, не имеет составных ключей (так бывает часто,
хотя и не всегда), то о сложностях, связанных с ЗС и 5НФ, можно не беспокоиться, – заведомо известно, что эта пе­ре­мен­ная-от­но­ше­ние уже находится в 5НФ.
Попутно в интересах точности замечу, что эта теорема на самом деле
относится к 3НФ, а не к НФБК, то есть она утверждает, что любая пе­ре­
мен­ная-от­но­ше­ние, находящаяся в 3НФ и не имеющая составных ключей, обязательно находится в 5НФ. Но всякая пе­ре­мен­ная-от­но­ше­ние,
находящаяся в НФБК, находится также и в 3НФ, а с прагматичес­кой
точки зрения НФБК гораздо важнее 3НФ.
Есть и еще одна простая теорема на ту же тему:
Пусть R – пе­ре­мен­ная-от­но­ше­ние, находящаяся в НФБК, и пус­ть
R имеет хотя бы один неключевой атрибут (то есть атрибут, не являющийся частью ни одного ключа R). Тогда R находится в 5НФ.

Эта теорема, как и предыдущая, описывает ситуацию, которая на практике встречается очень часто.
Поэтому понятие 5НФ, по-видимому, не так уж важно с практичес­кой
точки зрения. Однако с теоретичес­кой оно очень важно, потому что (как
я сказал в начале раздела) это «последняя нормальная форма», и – что
то же самое – эта форма нормальна относительно зависимостей соединения общего вида. Поскольку, если пе­ре­мен­ная-от­но­ше­ние R находится в 5НФ, то нетривиальные ЗС исчерпываются теми, что обусловлены
суперключами. Поэтому единственными декомпозициями без потери
информации являются те, в которых проекции производятся только на
атрибуты некоторого суперключа; иными словами, всякая такая про-

352

Приложение B. Теория проектирования баз данных

екция включает какой-то ключ R. Как следствие,1 соответствующие соединения «рекомпозиции» все взаимно однозначны, поэтому такая декомпозиция не устраняет и не может устранить никакую избыточность.
Сформулирую это по-другому. Сказать, что пе­ре­мен­ная-от­но­ше­ние R
находится в 5НФ, – это все равно, что сказать, что дальнейшее разложение R на проекции без потери информации, хотя и возможно, но не
устраняет никакую избыточность. Но заметьте и не забывайте, что
нахождение R в 5НФ еще не означает, что R свободна от избыточности. Есть много видов избыточности, которые проецирование устранить не может, и это иллюстрирует мысль, высказанную мной ранее
в разделе «Место теории проектирования», о том, что многие вопросы современная теория проектирования даже не затрагивает. Взгляните, к примеру, на рис. B.4, где показано тестовое значение пе­ре­мен­
ной-от­но­ше­ния SPJ, которая находится в 5НФ и тем не менее страдает избыточностью. Так, тот факт, что поставщик S2 поставляет деталь
P3, представлен несколько раз, как и факт, что деталь P3 поставляется
для проекта J4 (JNO обозначает номер проекта). И то же самое можно
сказать о факте, что проект J1 поставляется поставщиком S2. (Предикат звучит так Поставщик SNO поставляет деталь PNO для проекта
JNO в количестве QTY, и единственный ключ равен {SNO,PNO,JNO}.)
Единственная нетривиальная ЗС, которой удовлетворяет эта пе­ре­мен­
ная-от­но­ше­ние, – функциональная зависимость2
{ SNO , PNO , JNO } → { QTY }

то есть «стрелка, исходящая из суперключа». Иначе говоря, QTY зависит от всех трех атрибутов SNO, PNO и JNO, и потому не может встречаться ни в какой пе­ре­мен­ной-от­но­ше­нии иначе, как со всеми тремя;
таким образом, не существует декомпозиции без потерь, которая могла
бы устранить такую избыточность.
Я хотел бы сделать еще несколько небольших замечаний. Во-первых,
хотя раньше я об этом и не упоминал, вы, наверное, знаете, что 5НФ
всегда достижима, то есть всегда можно разложить пе­ре­мен­ную-от­но­
ше­ние, не находящуюся в 5НФ, на проекции, находящиеся в 5НФ (без
потери информации).

1

На самом деле то, что я здесь называю «следствием», может быть неверно, если R обладает более чем одним ключом. Рассуждение можно было бы
обобщить и на этот случай, но детали опять же слишком запутаны, поэтому
я их опускаю. Дополнительную информацию можно найти в работе «The
Relational Database Dictionary, Extended Edition» (см. приложение D).

2

Эквивалентно, если назвать SPJQ множес­тво всех атрибутов SPJ, то эта
пе­ре­мен­ная-от­но­ше­ние удовлетворяет ЗС *{SPJQ}, которая одновременно
тривиальна и обусловлена суперключами r. (Напомню (см. главу 6), что соединение единственного от­но­ше­ния r – это само r.)

Зависимости соединения и пятая нормальная форма
SPJ

SNO

PNO

JNO

QTY

S1
S1
S2
S2
S2
S2
S2
S2
S2
S2

P1
P3
P3
P3
P3
P3
P3
P3
P3
P5

J1
J4
J1
J2
J3
J4
J5
J6
J7
J1

200
700
400
200
200
500
600
400
800
100

353

Рис. B.4. Пе­ре­мен­ная-от­но­ше­ние SPJ, находящаяся в 5НФ, –
тестовое значение

Во-вторых, каждая пе­ре­мен­ная-от­но­ше­ние, находящаяся в 5НФ, находится также в НФБК, поэтому то, что R находится в НФБК, безусловно,
не исключает возможность, что R также находится и в 5НФ. Однако неформально фразу о том, что R находится в НФБК, часто интерпретируют так, будто R не находится в нормальной форме более высокого порядка. Я не придерживался такой практики в настоящем приложении (и
дальше не буду ей следовать).
В-третьих, поскольку 5НФ – «последняя нормальная форма», ее часто называют нормальной формой проекции-соединения (ПС/НФ), чтобы подчеркнуть тот факт, что эта форма является нормальной до тех
пор, пока мы ограничиваемся проекцией в качестве оператора декомпозиции и соединением в качестве оператора рекомпозиции. Но, конечно,
можно рассматривать и другие операторы, а потому и другие нормальные формы. В частности, возможно и желательно определить (а) обобщенные версии операторов проекции и соединения и, значит, (б) обобщенные версии зависимости соединения и, значит, (в) новую «шестую»
нормальную форму 6НФ. Эти идеи приобрели особое значение в связи с поддержкой темпоральных данных и подробно рассматриваются
в книге «Temporal Data and the Relational Model», написанной Хью Дарвеном, Никосом Лоренцосом и мной (см. приложение D). Однако здесь
я хочу лишь дать определение 6НФ, которое пригодно для «обычных»
(то есть не темпоральных) пе­ре­мен­ных-от­но­ше­ний.
Определение: Говорят, что пе­ре­мен­ная-от­но­ше­ние R находится в 6НФ, если она не удовлетворяет вообще никаким
нетривиальным ЗС.
Другими словами, пе­ре­мен­ную-от­но­ше­ние, находящуюся в 6НФ, никак нельзя разложить без потери информации, разве что тривиально.1
В частности, «обычная» пе­ре­мен­ная-от­но­ше­ние находится в 6НФ тог1

Декомпозиция называется тривиальной, если она основана на зависимостях, которые сами по себе тривиальны.

354

Приложение B. Теория проектирования баз данных

да и только тогда, когда она находится в 5НФ, имеет степень n и не имеет ключей степени, меньшей чем n – 1 (отметим, что всякая пе­ре­мен­
ная-от­но­ше­ние, находящаяся в 6НФ, обязательно находится и в 5НФ).
Наша привычная пе­ре­мен­ная-от­но­ше­ние «поставки» SP находится
в 6НФ, как и пе­ре­мен­ная-от­но­ше­ние SPJ (см. рис. B.4). А пе­ре­мен­ныеот­но­ше­ния S (поставщики) и P (детали) находятся в 5НФ, но не в 6НФ.
Примечание: Пе­ре­мен­ную-от­но­ше­ние в 6НФ иногда называют неприводимой, потому что ее нельзя без потерь разложить с помощью проецирования иначе, чем тривиально.
В заключение этого подраздела отметим, что ниоткуда не следует, что
пе­ре­мен­ная-от­но­ше­ние хорошо спроектирована просто потому, что находится в 5НФ или 6НФ. Например, если пе­ре­мен­ная-от­но­ше­ние RS
(рис. B.2) удовлетворяет ФЗ {CITY} → {STATUS}, то ее проекция на
{SNO,STATUS}, конечно, находится в 6НФ, но вряд ли кто станет спорить, что такой выбор базовой пе­ре­мен­ной-от­но­ше­ния неудачен. См. обсуждение «сохранения зависимостей» в разделе «Тост за здоровье нормализации» ниже.

Еще о 5НФ
Рассмотрим рис. B.5, на котором показано значение упрощенного варианта пе­ре­мен­ной-от­но­ше­ния SPJ из предыдущего подраздела. Предположим, что этот упрощенный вариант удовлетворяет зависимости соединения *{SP,PJ,SJ}, где SP, PJ и SJ обозначают соответственно
{SNO,PNO}, {PNO,JNO} и {SNO,JNO}. Что тогда означает эта ЗС с интуитивной точки зрения?
SPJ

SNO

PNO

JNO

S1
S1
S2
S1

P1
P2
P1
P1

J2
J1
J1
J1

Рис. B.5. Упрощенная пе­ре­мен­ная-от­но­ше­ние SPJ – тестовое значение

•• Прежде всего, ЗС по определению означает, что пе­ре­мен­ная-от­но­ше­
ние равнасоединению своих проекций SP, PJ и SJ и, стало быть, может быть без потерь разложена на эти проекции. (Я использую имена SP, PJ и SJ для обозначения самих проекций, а не соответствующих подмножес­тв заголовка пе­ре­мен­ной-от­но­ше­ния SPJ; надеюсь,
что такая вольность с моей стороны вас не смутит.)
•• Отсюда следует, что удовлетворяется такое ограничение
IF (s,p) ∈ SP AND (p,j) ∈ PJ AND (s,j) ∈ SJ THEN (s,p,j) ∈ SPJ

потому что если (s,p), (p,j) и (s,j) встречаются соответственно в SP, PJ
и SJ, то (s,p,j), конечно, встречается в соединении SP, PJ и SJ, и это

Зависимости соединения и пятая нормальная форма

355

соединение предположительно равно SPJ (именно в этом и состоит
смысл ЗС). Так, для тех данных, что изображены на рис. B.5, кортежи (S1,P1), (P1,J1) и (S1,J1) встречаются соответственно в SP, PJ
и SJ, а кортеж (S1,P1,J1) встречается в SPJ. Примечание: Надеюсь,
что использованная мной сокращенная нотация кортежей понятна
без объяснений, и напоминаю, что символ ∈ произносится как «элемент» или «встречается в».
•• Далее, кортеж (s,p) встречается в SP тогда и только тогда, когда кортеж (s,p,z) встречается в SPJ для некоторого z. Аналогично, кортеж
(p,j) встречается в PJ тогда и только тогда, когда кортеж (x,p,j) встречается в SPJ для некоторого x, а кортеж (s,j) встречается в SJ тогда
и только тогда, когда кортеж (s,y,j) встречается в SPJ для некоторого
y. Поэтому приведенное выше ограничение логичес­ки эквивалентно следующему:
IF для некоторых x, y, z (s,p,z)
(x,p,j)
(s,y,j)
THEN
(s,p,j)






SPJ AND
SPJ AND
SPJ
SPJ

Обращаясь к рис. B.5, мы видим, например, что каждый из кортежей (S1,P1,J2), (S2,P1,J1) и (S1,P2,J1) встречается в SPJ, поэтому
там встречается и кортеж (S1,P1,J1).
Итак, исходная ЗС эквивалентна показанному выше ограничению. Но
что означает это ограничение в терминах реального мира? Рассмотрим
конкретный пример. Предположим, что пе­ре­мен­ная-от­но­ше­ние SPJ содержит кортежи, говорящие о том, что все три приведенных ниже высказывания истинны:
1. Смит поставляет разводные гаечные ключи для какого-то проекта.
2. Кто-то поставляет разводные гаечные ключи для Манхэттенского
проекта.
3. Что-то поставляется Смитом для Манхэттенского проекта.
Тогда наличие ЗС говорит, что эта пе­ре­мен­ная-от­но­ше­ние должна содержать кортеж, подтверждающий истинность еще и следующего высказывания:
4. Смит поставляет разводные гаечные ключи для Манхэттенского проекта.
Но обычно из высказываний 1, 2 и 3, взятых вместе, высказывание 4 не
вытекает. Если мы знаем только то, что высказывания 1, 2 и 3 истинны,
то знаем, что Смит поставляет разводные ключи для какого-то проекта
(пус­ть z), что какой-то поставщик (пус­ть x) поставляет разводные ключи для Манхэттенского проекта и что Смит поставляет какую-то деталь
(пус­ть y) для Манхэттенского проекта, – но мы не можем сделать отсюда вывод, что x – это Смит, или что y – это разводной ключ, или что z –

356

Приложение B. Теория проектирования баз данных

это Манхэттенский проект. Подобные ложные выводы – пример того,
что иногда называют ловушками связи (connection trap). Однако в данном случае наличие ЗС говорит, что ловушки нет, то есть мы можем без
опаски выводить высказывание 4 из высказываний 1, 2 и 3.
Обратите внимание на цик­личес­кую природу ограничения («если s связано с p, а p связано с j, а j связано снова с s, то s и p и j должны быть связаны напрямую в том смысле, что должны встречаться в одном и том
же кортеже»). Если имеется такое цик­личес­кое ограничение, то, возможно, мы столкнулись с пе­ре­мен­ной-от­но­ше­нием, которая находится
в НФБК, но не в 5НФ.1 Впрочем, в моей практике такие ограничения
встречались крайне редко, поэтому я и сказал в предыдущем подразделе, что не думаю, что с практичес­кой точки зрения они так уж существенны.
Я хочу завершить этот раздел кратким замечанием о четвертой нормальной форме (4НФ). В подразделе «Значимость 5НФ» я сказал, что
всякий проектировщик баз данных должен знать о ЗС и 5НФ. На самом
деле, вы должны знать и о многозначных зависимостях (МЗЗ) и о четвертой нормальной форме. Однако эти понятия я упоминаю лишь для
полноты картины; как и 2НФ и 3НФ, они представляют, в основном,
историчес­кий интерес. Для справки:
•• МЗЗ – это такая ЗС, которая включает не более двух проекций (на
практике обычно ровно две);
•• пе­ре­мен­ная-от­но­ше­ние находится в 4НФ, если любая нетривиальная МЗЗ, которой она удовлетворяет, обусловлена некоторым суперключом.
Детали того, что означает тривиальность МЗЗ и обусловленность некоторым суперключом, выходят за рамки настоящего обсуждения (см.
упражнение B.19 в конце приложения); отмечу лишь, что из этих определений следует, что повторяющегося разложения без потери информации ровно на две проекции достаточно для достижения 4НФ. Но вы, конечно, заметили, что ЗС в предыдущем подразделе требовала трех проекций. На самом деле, мы можем сказать, что для достижения 5НФ
декомпозиция на n проекций (где n > 2) необходима только в том случае, когда рассматриваемая пе­ре­мен­ная-от­но­ше­ние удовлетворяет цик­
личес­кому ограничению с n ветвями, или, эквивалентно, когда она удовлетворяет ЗС, включающей n проекций, и не удовлетворяет никакой
ЗС с меньшим числом проекций.

1

Если ограничение принимает более простую форму «если s связано с p и j
связано с s, то s и p и j должны быть связаны напрямую», то, возможно, мы
столкнулись с пе­ре­мен­ной-от­но­ше­нием, которая находится в НФБК, но не
в 4НФ (и тем более не в 5НФ). См. упражнение B.16 в конце приложения.

Тост за здоровье нормализации

357

Тост за здоровье нормализации
Нормализацию ни в коем случае нельзя считать панацеей, в чем легко
убедиться, рассмотрев ее цели и то, насколько хорошо она им отвечает.
Итак, вот цели:
•• Прийти к структуре, которая бы «хорошо» представляла реальный
мир, то есть была бы интуитивно понятна и являлась хорошей основой для будущего расширения
•• Уменьшить избыточность
•• Тем самым избежать некоторых аномалий при обновлении
•• Упростить формулировку и проверку некоторых ограничений целостности
Рассмотрим их по порядку.
Хорошее представление реального мира. С этим нормализация справляется. У меня нет критичес­ких замечаний.
Уменьшение избыточности. Нормализация – неплохой первый шаг на
пути решения этой проблемы, но только первый. С одной стороны, она
сводится к взятию проекций, а, как мы видели, не всякую избыточность
удается устранить с помощью проецирования; есть и такие виды избыточности, пока еще не обсуждавшиеся, которые нормализация даже не
рассматривает. С другой стороны, цель уменьшения избыточности может вступать в конфликт с другой целью, также пока не обсуждавшейся, а именно с целью сохранения зависимостей. Объясню. Рассмотрим
следующую пе­ре­мен­ную-от­но­ше­ние (атрибут ZIP обозначает почтовый
индекс):
ADDR { STREET , CITY , STATE , ZIP }

Предположим, что эта пе­ре­мен­ная-от­но­ше­ние удовлетворяет таким ФЗ:
{ STREET , CITY , STATE } → { ZIP }
{ ZIP }
→ { CITY , STATE }

Первая ФЗ означает, что {STREET,CITY,STATE} – ключ, вторая – что
эта пе­ре­мен­ная-от­но­ше­ние не находится в НФБК. Однако, если применить теорему Хита и разложить ее без потери информации на HФБКпроекции следующим образом (в качестве A выступает {ZIP}, в качестве
B – {CITY,STATE}, а в качестве C – {STREET}):
ZCS { ZIP , CITY , STATE }
KEY { ZIP }
ZS { ZIP , STREET }
KEY { ZIP , STREET }

то ФЗ {STREET,CITY,STATE} → {ZIP}, которой исходная пе­ре­мен­ная-от­
но­ше­ние, конечно, удовлетворяла, «исчезает». (Она удовлетворяется со-

358

Приложение B. Теория проектирования баз данных

единением ZCS и ZS, но ни одной из этих проекций в отдельности.) Следовательно, пе­ре­мен­ные-от­но­ше­ния ZCS и ZS нельзя обновлять независимо. Предположим, например, что эти проекции в настоящий момент
имеют значения, показанные на рис. B.6; тогда любая попытка вставить
кортеж в ZS нарушит «отсутствующую» ФЗ. Однако
этот факт нельзя обнаружить, не принимая во внимание проекцию ZCS,
помимо ZS. Именно по таким причинам цель сохранения зависимостей
гласит: не расщепляйте зависимость на проекции. Однако приведенный пример показывает, что, как ни печально, эта цель и цель разложения на НФБК-проекции иногда могут конфликтовать.
ZCS

ZIP
10003
10111

CITY
New York
New York

STATE
NY
NY

ZS

ZIP
10003

STREET
Broadway

Рис. B.6. Проекции ZCS и ZS – тестовые значения

Отступление
Однако в главе 9 показано, что если мы все-таки выполним такую декомпозицию, то, по крайней мере, сможем определить соединение проекций ZCS и ZS
в виде представления, а затем определить {ZIP,CITY,STATE} в качестве ключа
этого представления; иными словами, можно хотя бы без особых трудов сформулировать необходимое ограничение.

Избежать аномалий при обновлении. Этот пункт по существу совпадает с предыдущим («уменьшение избыточности»). Хорошо известно, что
не до конца нормализованная структура может быть подвержена некоторым аномалиям при обновлении именно из-за остаточной избыточности. Например, в пе­ре­мен­ной-от­но­ше­нии STP (рис. B.1) может случиться так, что поставщик S1 в одном кортеже будет иметь статус 20, а в
другом 25. Конечно, такая «аномалия обновления» может возникать
только при недостаточно тщательной проверке ограничений целостности. Быть может, правильнее было бы охарактеризовать проблему аномалий при обновлении следующим образом: ограничения, необходимые для предотвращения подобных аномалий, проще формулировать
и, быть может, проверять, если структура базы данных полностью нормализована (см. следующий абзац). Или так: в случае полностью нормализованной базы данных увеличивается количество допус­тимых обновлений одного кортежа (поскольку отсутствие нормализации означает избыточность, а при наличии избыточности иногда требуется обновлять сразу несколько сущностей).
Упростить формулировку и проверку некоторых ограничений целостности. Из общих соображений очевидно, что одни ограничения влекут за собой другие. Вот тривиальный пример: если количество должно

Тост за здоровье нормализации

359

быть меньше или равно 5000, то очевидно, что оно должно быть меньше
или равно 6000. Если ограничение A влечет за собой ограничение B, то,
формулируя и проверяя A, мы «автоматичес­ки» формулируем и проверяем B (на самом деле, B вообще необязательно формулировать, разве
что для документирования). А нормализация до 5НФ дает очень простой способ сформулировать и проверить некоторые важные ограничения; фактичес­ки нам нужно лишь определить ключи и постулировать
их уникальность, – что мы все равно делаем, – и тогда автоматичес­ки
будут сформулированы и проверены все ЗС (а также все МЗЗ и ФЗ), так
как все они обусловлены этим ключами. Поэтому нормализация неплохо справляется и с этой задачей.
С другой стороны, вот несколько причин, помимо названных выше, по
которым нормализация не панацея:
•• Во-первых, ЗС – не единственный вид ограничений, а для других
нормализация бесполезна.
•• Во-вторых, для данного множес­тва пе­ре­мен­ных-от­но­ше­ний часто
возможно несколько вариантов декомпозиции без потерь на 5НФпроекции, и не существует никаких формальных рекомендаций, какой выбрать.
•• В-третьих, существует много вопросов, которые нормализация попросту игнорирует. Например, исходя из чего мы решаем, что должна быть только одна пе­ре­мен­ная-от­но­ше­ние «поставщики», а не по
одной для поставщиков в Лондоне, в Париже и так далее? Очевидно,
нормализация в классичес­ком понимании тут не причем.
Но я вовсе не хочу, чтобы сделанные мною в этом разделе замечания
воспринимались как какие-то нападки. Я твердо уверен, что любая не
до конца нормализованная база данных противопоказана. И хочу завершить этот раздел доводом – логичес­ким доводом, который вам, возможно, раньше не встречался, – в поддержку того, что прибегать к денормализации следует лишь в самом крайнем случае. Иначе говоря, отступать от полностью нормализованной структуры следует лишь тогда,
когда все остальные попытки повысить производительность не увенчались успехом. (Кстати, замечу, что я согласен с расхожим мнением
о том, что нормализация влияет на производительность. Для современных продуктов на основе SQL так оно и есть; однако это другая тема,
к которой я еще вернусь в разделе «Некоторые замечания о физичес­ком
проектировании»). Итак, вот этот довод.
Все мы знаем, что денормализация негативно отражается на обновлении (логичес­ки негативно; некоторые обновления становится сложнее
формулировать, а целостность базы данных может оказаться под угрозой). Но не так хорошо известен тот факт, что денормализация плоха
и с точки зрения выборки, то есть становится сложнее формулировать
некоторые запросы (или, что то же самое, становится проще сформулировать их неправильно, а это означает, что при выполнении таких

360

Приложение B. Теория проектирования баз данных

запросов вы получаете «правильные» ответы на неверно заданные вопросы). Приведу пример. Рассмотрим еще раз пе­ре­мен­ную-от­но­ше­ние
RS (см. рис. B.2) с ФЗ {CITY} → {STATUS}. Ее можно рассматривать как
результат денормализации пе­ре­мен­ных-от­но­ше­ний SNC (с атрибутами
SNO, SNAME и CITY) и CS (с атрибутами CITY и STATUS). Теперь возьмем запрос «Получить средний статус города». Для данных на рис. B.2
значения статус для Афин, Лондона и Парижа равны соответственно 30,
20 и 30, поэтому с точностью до трех десятичных знаков после запятой
средний статус равен 26,667.
Вот несколько попыток сформулировать этот запрос на SQL:
1. SELECT AVG ( STATUS ) AS RESULT
FROM
RS
Результат (неправильный): 26. Проблема в том, что статусы Лондона и Парижа учитываются дважды. Быть может, нужно включить
DISTINCT в вызове AVG? Попробуем:
2. SELECT AVG ( DISTINCT STATUS ) AS RESULT
FROM RS
Результат (неправильный): 25. Нет, нам нужно рассматривать разные города, а не разные значения статуса. Попробуем воспользоваться группировкой:
3. SELECT CITY, AVG ( STATUS ) AS RESULT
FROM
RS
GROUP BY CITY
Результат (неправильный): (Athens,30), (London,20), (Paris,30). При
такой формулировке мы получаем средний статус по городу, а не общее среднее. Быть может, надо усреднить средние?
4. SELECT CITY, AVG ( AVG ( STATUS ) ) AS RESULT
FROM RS
GROUP BY CITY
Результат: синтаксичес­кая ошибка. Стандарт SQL совершенно правильно запрещает такие вложенные вызовы «функций множес­тва».1
Еще одна попытка:
1

Я говорю «совершенно правильно» только потому, что речь идет об SQL;
более ортодоксальный язык типа Tutorial D, безусловно, разрешил бы та­
кие вложенные вызовы. Объясню. Рассмотрим SQL-выражение SELECT
SUM(SP.QTY) AS SQ FROM SP WHERE SP.QTY > 100 (я сознательно взял
другой пример). Аргументом оператора SUM здесь на самом деле является
SP.QTY FROM SP WHERE SP.QTY > 100, и более ортодоксальный язык
потребовал бы заключить все выражение в скобки. Но не SQL. Как следствие,
выражение вида AVG(SUM(QTY)) обязано быть некорректным, потому
что SQL не может понять, какие компоненты объемлющего выражения
являются частью аргумента AVG, а какие – частью аргумента SUM.

361

Ортогональность

5. SELECT AVG ( TEMP.STATUS ) AS RESULT
FROM ( SELECT DISTINCT RS.CITY, RS.STATUS
FROM RS ) AS TEMP
Результат (наконец-то правильный): 26,667. Но обратите внимание,
насколько сложнее это выражение по сравнению с его аналогом для
полностью нормализованной структуры, представленной на рис. B.3:
SELECT AVG ( STATUS ) AS RESULT
FROM CS

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

Ортогональность
На рис. B.7 показаны тестовые значения возможной, но откровенно неудачной структуры поставщиков: пе­ре­мен­ная-от­но­ше­ние SA представляет поставщиков в Париже, а пе­ре­мен­ная SB – поставщиков, которые
либо не находятся в Париже, либо имеют статус 30. Как видите, такая
схема приводит к избыточности – конкретно, кортеж для поставщика
S3 встречается в обеих пе­ре­мен­ных-от­но­ше­ниях, – и, как обычно, наличие избыточности может приводить к аномалиям при обновлении.
(Любая избыточность всегда является причиной таких аномалий.)
SA

SNO
S2
S3

SNAME STATUS
10
Jones
Blake
30

CITY
Paris
Paris

SB

SNO

SNAME STATUS

CITY

S1
S3
S4
S5

Smith
Blake
Clark
Adams

London
Paris
London
Athens

20
30
20
30

???
???

избыточность

/*поставщики в Париже*/

/*поставщики не в Париже или имеющие статус 30*/

Рис. B.7. Пе­ре­мен­ные-от­но­ше­ния SA и SB – тестовые значения

Кстати, отметим, что поставщик S3 обязательно должен присутствовать в обеих пе­ре­мен­ных-от­но­ше­ниях. Допус­тим, что он встречается
в SB, но не встречается в SA. Тогда из допущения замкнутости мира,
примененного к SA, следовало бы, что поставщик S3 не находится в Париже. Но SB говорит, что S3 находится в Париже. Следовательно, мы
имеем противоречие, и база данных противоречива (а из главы 8 мы
знаем, к каким бедам это может привести).

362

Приложение B. Теория проектирования баз данных

Проблема, присущая структуре на рис. B.7, очевидна. Она как раз и заключается в том, что один и тот же кортеж должен присутствовать
в двух разных пе­ре­мен­ных-от­но­ше­ниях, или, иначе говоря, в том, что
семантика или предикаты этих пе­ре­мен­ных-от­но­ше­ний «перекрываются». Дейст­вительно, мы можем и, наверное, должны были бы написать соответствующее ограничение:
CONSTRAINT SA_AND_SB_OVERLAP
( SA WHERE STATUS = 30 ) =
( SB WHERE STATUS = 30 AND CITY = ‘Paris’ ) ;

Итак, очевидное правило гласит:
Принцип ортогонального проектирования (первая версия):
не должно быть двух различных пе­ре­мен­ных-от­но­ше­ний
таких, что если некоторый кортеж t присутствует в одной
из них, то ограничения этих пе­ре­мен­ных-от­но­ше­ний требуют, чтобы тот же кортеж присутствовал и в другой.
Термин ортогональный призван подчеркнуть тот факт, что этот принцип по существу говорит, что пе­ре­мен­ные-от­но­ше­ния должны быть независимы друг от друга, а это не так, если их семантики перекрываются в указанном выше смысле.
Должно быть понятно, что две пе­ре­мен­ные-от­но­ше­ния не смогут нарушить этот принцип, если их типы различны, поэтому может сложиться
впечатление, будто принцип ортогональности немногого стоит, – ведь
очень редко бывает, что база данных содержит две или более пе­ре­мен­
ные-от­но­ше­ния одного и того же типа. Однако взгляните на рис. B.8,
где показана еще одна возможная, хотя и столь же неудачная, структура. В этом случае один и тот же кортеж никак не может встретиться
в обеих пе­ре­мен­ных-от­но­ше­ниях, зато вполне может статься, что у кортежей из SX и из SY будут одинаковые проекции на {SNO,SNAME}1,
и это снова приводит к избыточности и аномалиям при обновлении. Поэтому нам следует обобщить сформулированный ранее принцип проектирования:
SX SNO
S1
S2
S3
S4
S5

SNAME STATUS
Smith
Jones
Blake
Clark
Adams

20
10
30
20
30

SY SNO
S1
S2
S3
S4
S5

SNAME CITY
Smith
Jones
Blake
Clark
Adams

London
Paris
Paris
London
Athens

Рис. B.8. Пе­ре­мен­ные-от­но­ше­ния SX и SY – тестовые значения
1

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

Ортогональность

363

Принцип ортогонального проектирования (вторая версия): пус­ть A и B – различные пе­ре­мен­ные-от­но­ше­ния. Тогда не должно существовать декомпозиций A и B без потери
информации на A1, A2, ..., Am и B1, B2, ..., Bn соответственно таких, что для некоторой проекции Ai из множес­тва A1,
A2, ..., Am и некоторой проекции Bj из множес­тва B1, B2, ...,
Bn имеются ограничения пе­ре­мен­ных-от­но­ше­ний, которые говорят, что если некоторый кортеж t присутствует
в Ai, то этот же кортеж t должен присутствовать и в Bj.
Первая версия принципа является частным случаем второй, потому
что искомая «декомпозиция без потери информации», имеющаяся для
любой пе­ре­мен­ной-от­но­ше­ния R, – это просто тождественная проекция
R. Но и эта версия еще не является безупречной. Для справки я приведу формулировку, которую считаю безупречной, но не буду комментировать ее подробно.
Принцип ортогонального проектирования (окончательная
версия): пус­ть A и B – различные пе­ре­мен­ные-от­но­ше­ния.
Заменим A и B декомпозициями без потери информации на
проекции A1, A2, ..., Am и B1, B2, ..., Bn соответственно такие,
что любая Ai (i = 1, ..., m) и любая Bj (j = 1, ..., n) находятся
в 6НФ. Пусть i и j таковы, что существует последовательность из нуля или более переименований атрибутов со следующим свойством: (а) применение ее к Ai дает Ak и (б) Ak
и Bj имеют одинаковый тип. Тогда не должно существовать
ограничения, утверждающего, что в любой момент времени (Ak WHERE ax) = (Bj WHERE bx), где ax и bx – условия,
ни одно из которых не обращается в FALSE тождественно.
Из сказанного выше следует несколько выводов.
•• Как и принципы нормализации, принцип ортогонального проектирования, по существу, продиктован здравым смыслом, но (опять же, как
и в случае нормализации) – формализованным здравым смыслом.
•• Цель ортогонального проектирования состоит в том, чтобы уменьшить избыточность и тем самым избежать аномалий при обновлении (как и в случае нормализации). На самом деле, ортогональность
дополняет нормализацию в том смысле, что – говоря неформально –
нормализация уменьшает избыточность внутри одной пе­ре­мен­нойот­но­ше­ния, а ортогональность – между разными пе­ре­мен­ными-от­
но­ше­ниями.
•• В дейст­вительности ортогональность дополняет нормализацию и еще
одним способом. Снова рассмотрим декомпозицию пе­ре­мен­ной-от­но­
ше­ния S на ее проекции SX и SY (рис. B.8). Замечу, что эта декомпозиция удовлетворяет всем обычным принципам нормализации: обе
проекции находятся в 5НФ, потери информации нет, зависимости
сохранены и для реконструкции исходной пе­ре­мен­ной-от­но­ше­ния S

364

Приложение B. Теория проектирования баз данных

необходимы обе проекции.1 Именно нарушение ортогональности, а не
принципов нормализации – причина неудачности такой структуры.
•• Предположим, мы по какой-то причине решили разложить пе­ре­мен­
ную-от­но­ше­ние «поставщики» на множес­тво ограничений (restric­
tion). Тогда принцип ортогональности говорит, что эти ограничения
должны попарно не пересекаться в том смысле, что никакой кортеж не
должен присутствовать более, чем в одном ограничении. (Кроме того,
объединение ограничений, которое фактичес­ки будет дизъюнктным
объединением, должно давать исходную пе­ре­мен­ную-от­но­ше­ние.) Такую декомпозицию называют ортогональной декомпозицией.

Некоторые замечания
о физичес­ком проектировании
Реляционная модель ничего не говорит о физичес­ком проектировании.
Тем не менее есть ряд полезных вещей, которые можно сказать на эту
тему в реляционном контексте. Как минимум, они вытекают из духа
модели, хотя явно не сформулированы (и несмотря на то, что детали
физичес­кой реализации по необходимости зависят от СУБД и в разных
сис­темах различны).
Первое: физичес­кое проектирование должно следовать за логичес­ким.
То есть «правильный» подход состоит в том, чтобы сначала создать не
имеющий изъянов логичес­кий проект, а уже потом отобразить его на
те физичес­кие структуры, которые поддерживает целевая СУБД. Подругому эту мысль можно выразить, сказав, что физичес­кий проект
должен вытекать из логичес­кого, а не наоборот. В идеале сис­тема должна самостоятельно вывести оптимальный физичес­кий проект без вмешательства человека. (Эта задача не так уж амбициозна, как может показаться. Чуть я ниже я скажу еще несколько слов на эту тему.)
Второе: в главе 1 я говорил, что одна из причин исключения любых
физичес­ких аспектов из реляционной модели состояла в том, чтобы
дать разработчикам возможность реализовать ее, как они сочтут нужным, – и тут-то недостаточное понимание модели широкими массами
разработчиков нам здорово аукнулось. Без сомнения, большинство продуктов на основе SQL не сумели раскрыть весь заложенный в модели по1

Ранее я говорил, что декомпозиция не приводит к потере информации, если
соединение проекций дает исходную пе­ре­мен­ную-от­но­ше­ние. Это правда, но не вся правда. На практике мы хотим, чтобы выполнялось дополнительное требование: для соединения необходимы все проекции. Например,
вряд ли мы сочли бы разложение пе­ре­мен­ной-от­но­ше­ния S на проекции на
{SNO,STATUS}, {SNO,SNAME} и {SNO,STATUS,CITY} разумной структурой, несмотря на то, что S, безусловно, равно соединению этих трех проекций. А все потому, что первая проекция не принимает участия в процедуре
реконструкции.

Некоторые замечания о физичес­ком проектировании

365

тенциал; в этих продуктах видимое пользователю и физичес­ки хранящееся – по существу, одно и то же. Другими словами, то, что физичес­
ки хранится, – прямое отображение того, что пользователь логичес­ки
видит (рис. B.9). (Я понимаю, что высказанные замечания чрезмерно
упрощены, но для настоящего обсуждения этого достаточно.)
базовая переменнаяотношение

физический файл

. . . . . атрибуты . . . .

. . . . . . поля . . . . . . .

кортеж
кортеж
кортеж
кортеж
кортеж

запись
запись
запись
запись
запись

Рис. B.9. Реализация с прямым отображением (достойна порицания)

В реализации с прямым отображением многое не так, слишком многое, чтобы детально обсуждать недостатки здесь. Но самое главное –
почти полное отсутствие независимости от данных: если нам понадобится изменить физичес­кую структуру (как правило, из соображений производительности), то придется менять и логичес­кую структуру тоже. В частности, речь идет о так часто раздающемся предложении
«денормализовать ради повышения производительности». В принципе,
логичес­кая структура не имеет ничего общего с производительностью,
но если она один к одному отображается на физичес­кую… В общем,
сами понимаете. Очевидно, можно найти более удачное решение. Защитники реляционной теории много лет убеждали, что реляционную
модель необязательно реализовывать таким способом. И дейст­вительно,
если все пойдет хорошо, то скоро появится совершенно новая технология, которая решит все проблемы, свойственные схеме с прямым отображением. Эта технология называется The TransRelational™ Model.
Поскольку это технология реализации, она выходит за рамки настоящей книги, но ее предварительное описание можно найти в моей книге
«An Introduction to Database Systems» (см. приложение D). Здесь я хочу
лишь рассказать о нескольких желательных последствиях реализации,
в которой физичес­кий и логичес­кий уровни строго разделены.
Во-первых, нам никогда не придется «денормализовывать ради повышения производительности» (на логичес­ком уровне); все пе­ре­мен­ныеот­но­ше­ния могут находиться в 5НФ, а то и в 6НФ, и это не повредит производительности. Логичес­кая структура вообще никак не будет отражаться на производительности.
Во-вторых, 6НФ создает основу для по-настоящему реляционного подхода к проблеме отсутствующей информации (без использования nullзначений и трехзначной логики). Используя null-значения, вы по существу заставляете базу данных явно сообщать о том, что есть нечто,
чего вы не знаете. Но если вы чего-то не знаете, то гораздо лучше об

366

Приложение B. Теория проектирования баз данных

этом вообще не говорить. Цитируя Виттгенштейна: «Wovon man nicht
reden kann, darüber muss man schweigen» («О чем не можешь ничего сказать, о том должен молчать»). Например, предположим для простоты,
что есть всего два поставщика S1 и S2, и статус S1 нам известен, а статус S2 – нет. В этой ситуации 6НФ могла бы выглядеть, как показано
на рис. B.10.
SN SNO
S1
S2

SNAME
Smith
Jones

SS SNO
S1

STATUS
20

SC SNO
S1
S2

CITY
London
Paris

Рис. B.10. Статус поставщика S2 неизвестен

Разумеется, о таком подходе к отсутствующей информации можно было
бы сказать гораздо больше, но это приложение – неподходящее место
для подобного разговора. (В статье «The Closed World Assumption», упомянутой в приложении D, имеется дополнительная информация.) Здесь
я только хочу подчеркнуть, что при такой структуре отсутствует «кортеж», в котором статус поставщика S2 содержал бы null, – у нас вообще
нет кортежа, показывающего статус S2.
И последнее: в той сис­теме, которую я бегло обрисовал, будет возможность автоматичес­ки вывести оптимальную физичес­кую структуру
из логичес­кой без участия или почти без участия проектировщикачеловека. Из-за нехватки места и ряда других причин я не могу привести свидетельства в пользу этой позиции, но твердо стою на ней.

Заключительные замечания
Основной упор в данном приложении был сделан на теории проектирования баз данных, под которой я понимаю, прежде всего, нормализацию и ортогональность (научные составляющие дисциплины проектирования). Идея в том, что логичес­кая структура, в отличие от физичес­
кой, является – или должна являться – независимой от конкретной
СУБД, и, как мы видели, существуют солидные теоретичес­кие принципы, которые можно с пользой применить к этой проблеме.
Еще одна мысль, которую я не высказывал явно, заключается в том, что
логичес­кая структура, вообще говоря, должна в идеале не зависеть и от
приложения, а не только от СУБД.1 Задача заключается в том, чтобы составить проект, который описывает, что означают данные, а не как они
будут использоваться, и в этой связи я подчеркивал важность ограниче1

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

Заключительные замечания

367

ний и предикатов («бизнес-правил»). База данных должна быть верным
представлением семантики ситуации, а семантику представляют именно ограничения. Таким образом, абстрактно процесс логического проектирования выглядит следующим образом:
1. Максимально тщательно выписать предикаты пе­ре­мен­ных-от­но­ше­
ний.
2. Отобразить результаты шага 1 на пе­ре­мен­ные-от­но­ше­ния и ограничения. (В роли некоторых ограничений будут выступать ФЗ, МЗЗ
и ЗС).
Значительная часть теории проектирования связана с уменьшением
избыточности. Нормализация уменьшает избыточность внутри пе­ре­
мен­ных-от­но­ше­ний, ортогональность – между несколькими пе­ре­мен­
ными-от­но­ше­ниями. При обсуждении нормализации я уделил основное внимание НФБК и 5НФ, которые представляют собой нормальные
формы относительно ФЗ и ЗС соответственно. (Однако вкратце я упомянул и другие нормальные формы, в том числе 6НФ.) Я подчеркнул, что
нормализация позволяет проще формулировать (и, быть может, проверять) некоторые ограничения; эквивалентно можно сказать, что ее наличие допускает большее количество обновлений единственного кортежа, чем было бы возможно в противном случае. Я объяснил, что нормализация – это, по существу, формализованный здравый смысл. Я также привел логичес­кий и, возможно, не слишком широко известный довод в пользу отказа от денормализации, рассматривая проблему с точки зрения не столько обновления, сколько выборки. Я бы хотел также
добавить, что хотя чаще всего к денормализации призывают во имя повышения производительности, на самом деле, как вы, наверное, знаете,
это может привести к снижению производительности (и в плане обновления, и в плане выборки). Фактичес­ки «денормализация ради производительности» обычно означает повышение производительности одного приложения за счет остальных.
Я также описал принцип ортогонального проектирования (еще один
пример формализации здравого смысла) и поделился некоторыми соображениями относительно физичес­кого проектирования. Во-первых,
физичес­кая структура должна вытекать из логичес­кой, а не наоборот.
Во-вторых, нам остро необходимо уйти от повсеместно распространившегося способа реализации путем прямого отображения. В-третьих,
было бы хорошо добиться полной автоматизации физичес­кого проектирования, и у меня есть некоторые надежды в этом от­но­ше­нии.
И последнее: я хочу подчеркнуть, что принципы нормализации и ортогональность в некотором смысле необязательны. Их нельзя назвать непререкаемыми правилами, которые следует заучить и никогда не нарушать. Как мы знаем, бывают основательные причины не доводить нормализацию «до конца» (я имею в виду основательные логичес­кие причины, а не «денормализацию ради производительности»). То же самое

368

Приложение B. Теория проектирования баз данных

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

Упражнения
Упражнение B.1. Дайте по возможности точные определения функциональной зависимости и зависимости соединения.
Упражнение B.2. Перечислите все ФЗ, тривиальные и нетривиальные,
которым удовлетворяет пе­ре­мен­ная-от­но­ше­ние «поставки» SP.
Упражнение B.3. Понятие ФЗ опирается на понятие равенства кортежей. Истинно или ложно это утверждение?
Упражнение B.4. Декомпозиция без потери информации означает, что
пе­ре­мен­ную-от­но­ше­ние можно разложить на проекции таким образом, что соединение этих проекций восстанавливает исходную пе­ре­
мен­ную-от­но­ше­ние. Фактичес­ки, если проекции r1 и r2 от­но­ше­ния r
таковы, что каждый атрибут r встречается хотя бы в одной из проекций r1 и r2, то соединение r1 и r2 всегда порождает каждый кортеж
r. Докажите это утверждение. (Из сказанного выше следует, что проблема декомпозиции с потерей информации связана с тем, что соединение порождает «лишние» кортежи. Так как в общем случае мы не
знаем, какие кортежи соединения лишние, а какие настоящие, то
имеет место потеря информации.)
Упражнение B.5. Докажите теорему Хита. Докажите, что обратное
этой теореме утверждение неверно.
Упражнение B.6. Что такое суперключ? Что означает фраза «ФЗ обусловлена суперключом»? Что означает фраза «ЗС обусловлена суперключом»?
Упражнение B.7. Предполагается, что ключи уникальны и неприводимы. Нет сомнений, что сис­тема способна проверить уникальность.
А как насчет неприводимости?
Упражнение B.8. Что такое (а) тривиальная ФЗ, (б) тривиальная ЗС?
Является ли первая частным случаем второй?
Упражнение B.9. Пусть R – пе­ре­мен­ная-от­но­ше­ние степени n. Каково максимальное количество ФЗ, которым может удовлетворять R
(тривиальных и нетривиальных)?
Упражнение B.10. Принимая во внимание, что A и B в ФЗ A → B –
множес­тва атрибутов, что произойдет, если хотя бы одно из этих
множес­тв пус­то?

Упражнения

369

Упражнение B.11. Рассмотрим предикат: в день d на протяжении периода p студент посещает занятие l, которое проводит преподаватель t в аудитории c (где d – рабочий день, от понедельника до пятницы, а p – протяженность периода, от 1 до 8 часов в течение этого
дня). Каждое занятие занимает один период и имеет название, уникальное относительно всех занятий, проводимых в течение недели.
Спроектируйте множес­тво пе­ре­мен­ных-от­но­ше­ний для этой базы
данных. Находятся ли ваши от­но­ше­ния в 5НФ? А в 6НФ? Каковы
ключи?
Упражнение B.12. В большинстве примеров декомпозиции без потерь,
приведенных в тексте этого приложения, показано разложение пе­
ре­мен­ной-от­но­ше­ния ровно на две проекции. Бывает ли необходимо
разлагать на три или более проекций?
Упражнение B.13. Многие проектировщики баз данных рекомендуют
использовать искусственные или суррогатные ключи вместо так называемых естественных ключей. Например, мы могли бы добавить
атрибут (назовем его SPNO) в пе­ре­мен­ную-от­но­ше­ние «поставки»
(конечно, позаботившись о свойстве уникальности), а затем объявить {SPNO} суррогатным ключом этой пе­ре­мен­ной-от­но­ше­ния. (Отметим, однако, что {SNO,PNO} от этого не перестанет быть ключом,
просто теперь этот ключ уже не единственный.) Таким образом, суррогатные ключи – это ключи в обычном реляционном смысле, но
(а) они всегда состоят из единственного атрибута и (б) их значения
служат просто суррогатами обозначаемых сущностей (то есть лишь
представляют тот факт, что сущность дейст­вительно существует, никакой дополнительные семантики такие ключи не несут). В идеале
суррогатные ключи должна генерировать сис­тема, но вопрос о том,
кто их генерирует – сис­тема или пользователь, – к самой идее суррогатного ключа от­но­ше­ния не имеет. Два вопроса: суррогатные ключи и идентификаторы кортежей – это одно и то же? Как вы думаете,
сама идея здравая?
Упражнение B.14. (С благодарностью Хью Дарвену.) Я решил организовать вечеринку, поэтому составил список тех, кого хотел бы пригласить, и предварительно поговорил с ними. Общая реакция была положительной, но некоторые сказали, что придут, только если придут некоторые другие приглашенные. Например, Боб и Кэл сказали, что придут, если придет Эми; Хэл сказал, что придет, если одновременно придут Дон и Ева либо придет Фэй; Гай сказал, что придет
в любом случае; Джо сказал, что придет, если одновременно придут
Боб и Эми и так далее. Спроектируйте базу данных, из которой было
бы ясно, чье согласие от чьего зависит.
Упражнение B.15. Спроектируйте базу данных для следующей задачи.
Сущностями являются служащие и программисты. Каждый программист – служащий, но не все служащие – программисты. У слу-

370

Приложение B. Теория проектирования баз данных

жащего есть номер, имя и зарплата. У программиста есть навык – владение (одним) языком программирования. Что изменилось бы, если
бы у программиста могло быть произвольное количество навыков?
Упражнение B.16. Пусть A, B и C – подмножес­тва заголовка пе­ре­мен­
ной-от­но­ше­ния R такие, что теоретико-множес­твенное объединение
A, B и C равно всему заголовку. Обозначим AB теоретико-множес­
твенное объединение A и B и аналогично для AC. Тогда говорят, что
R удовлетворяет многозначным зависимостям (МЗЗ)
A →→ B
A →→ C

(где A →→ B произносится «A двойная стрелка B», или «A многозначно определяет B», или «B многозначно зависит от A»), если R
удовлетворяет ЗС *{AB,AC}. Покажите, что пе­ре­мен­ная-от­но­ше­ние
R, удовлетворяющая МЗЗ A →→ B и A →→ C, обладает следующим
свойством: если она включает пару кортежей (a,b1,c1) и (a,b2,c2), то
включает также пару кортежей (a,b1,c2) и (a,b2,c1).
Упражнение B.17. Покажите, что если R удовлетворяет ФЗ A → B, то
она удовлетворяет также МЗЗ A →→ B.
Упражнение B.18. (Теорема Фейджина) Пусть R определена, как в упражнении B.16. Покажите, что R можно без потери информации разложить на проекции на AB и AC тогда и только тогда, когда она удовлетворяет МЗЗ A →→ B и A →→ C.
Упражнение B.19. Покажите, что если K – ключ R, то K →→ A удовлетворяется для всех атрибутов A пе­ре­мен­ной-от­но­ше­ния R. Примечание: Здесь удобное место для того, чтобы ввести еще несколько определений. Напомним, что R находится в 4НФ, если любая нетривиальная МЗЗ, которой R удовлетворяет, обусловлена каким-то суперключом. МЗЗ A →→ B называется тривиальной, если AB равно заголовку R или A – надмножес­тво B; эта МЗЗ называется обусловленной
суперключом, если A – суперключ.
Упражнение B.20. Приведите пример пе­ре­мен­ной-от­но­ше­ния, которая
находится в НФБК, но не находится в 4НФ.
Упражнение B.21. Спроектируйте базу данных для следующей задачи.
Сущностями являются торговые представители, регионы продаж
и изделия. Каждый представитель отвечает за один или несколько
регионов; в каждом регионе есть один или несколько представителей.
Каждый представитель отвечает за продажу одного или нескольких
изделий; каждое изделие продает один или несколько представителей. Каждое изделие продается в каждом регионе, однако никакие
два представителя не продают одно и то же изделие в одном и том же
регионе. Каждый представитель продает один и тот же набор изделий в каждом регионе, за который отвечает.

Упражнения

371

Упражнение B.22. Напишите на языке Tutorial D предложение CON­
STRAINT, выражающее ЗС, которой удовлетворяет пе­ре­мен­ная-от­
но­ше­ние SPJ на рис. B.5.
Упражнение B.23. (Модифицированный вариант упражнения B.21.)
Спроектируйте базу данных для следующей задачи. Сущностями являются торговые представители, регионы продаж и изделия.
Каждый представитель отвечает за один или несколько регионов;
в каждом регионе есть один или несколько представителей. Каждый
представитель отвечает за продажу одного или нескольких изделий;
каждое изделие продает один или несколько представителей. Каждое изделие продается в одном или нескольких регионах, и в каждом регионе продается одно или несколько изделий. Наконец, если
представитель r отвечает за регион a, и изделие p продается в регионе a, и представитель r продает изделие p, то r продает p в a.
Упражнение B.24. Какие из следующих утверждений верны?
a. Любая пе­ре­мен­ная-от­но­ше­ние, ключом которой является множест­
во всех атрибутов, находится в НФБК.
b. Любая пе­ре­мен­ная-от­но­ше­ние, ключом которой является множест­
во всех атрибутов, находится в 5НФ.
c. Любая бинарная пе­ре­мен­ная-от­но­ше­ние находится в НФБК.
Упражнение B.25. В настоящее время в индустрии ведутся оживленные дискуссии по поводу возможности баз данных XML. Но XMLдо­кументы по природе своей иерархичес­кие. Как вы думаете, относятся ли приведенные в этом приложении критичес­кие замечания
в адрес иерархий к базам данных XML? Обоснуйте свой ответ.
Упражнение B.26. Нарисуйте диаграммы «сущность-связь» для баз
данных из упражнений B.11, B.14, B.21 и B.23. Какие выводы вы можете сделать из этого упражнения?

С

Приложени

.

Ответы к упражнениям
Глава 1
Упражнение 1.1. Вот несколько примеров предложений из начала главы, в которых термин от­но­ше­ние следует заменить на пе­ре­мен­наяот­но­ше­ние:
•• «В каждом от­но­ше­нии есть по меньшей мере один потенциальный
ключ»;
•• «Внешним ключом называется множес­тво атрибутов одного от­но­
ше­ния, значения которых должны совпадать со значениями некоторого потенциального ключа в каком-то другом (или в том же
самом) от­но­ше­нии»;
•• «Оператор реляционного присваивания … позволяет присвоить
значение реляционного выражения … какому-то от­но­ше­нию»;
•• «Представлением (или виртуальным от­но­ше­нием) называется именованное от­но­ше­ние, значением которого в каждый момент времени t является результат вычисления некоторого реляционного выражения в этот момент».
И так далее.
Упражнение 1.2. Э. Ф. Кодд (1923–2003)первым придумал реляционную модель, среди многих других работ. В декабре 2003 года я опубликовал небольшую статью, в которой отдал дань уважения ему
и его достижениям, ее текст можно найти на сайте ACM SIGMOD
по адресу http://www.acm.org/sigmod и в других местах. Расширенное описание заслуг Кодда включено в мою книгу «Date on Database:
Writings 2000-2006» (Apress, 2006).

Глава 1

373

Упражнение 1.3. Домен можно представлять себе как концептуальный пул значений, из которого выбираются значения фактичес­ких
атрибутов фактичес­ких от­но­ше­ний. Другими словами, домен – это
тип, а термины домен и тип можно считать синонимами, хотя лично я предпочитаю слово тип, как имеющее более длинную родословную (по крайней мере, в компьютерном мире). Однако термин домен
употреблялся в большинстве старых работ по базам данных. Примечание: не путайте понятие домена в реляционной теории с таким
же термином в SQL, где его в лучшем случае можно рассматривать
как очень слабую разновидность типа. См. главу 2 (в частности, ответ к упражнению 2.1).
Упражнение 1.4. База данных удовлетворяет условию ссылочной целостности, если для каждого кортежа, содержащего ссылку (другими словами, значение внешнего ключа), существует объект ссылки (то есть кортеж в той пе­ре­мен­ной-от­но­ше­нии, на которую ведет
ссылка, с таким же значением соответствующего потенциального
ключа). Неформально говоря, если B ссылается на A, то A должно
существовать. Более подробное обсуждение см. в главах 5 и 8.
Упражнение 1.5. Пусть R – пе­ре­мен­ная-от­но­ше­ние, и пус­ть r – от­но­ше­
ние, являющееся значением R в некоторый момент времени. Тогда
по определению заголовок, атрибуты и степень R совпадают с заголовком, атрибутами и степенью r. Аналогично тело, кортежи и кардинальность R по определению совпадают с телом, кортежами и кардинальностью r. Отметим, однако, что тело, кортежи и кардинальность R изменяются со временем, тогда как заголовок, атрибуты
и степень не изменяются.
Кстати, из этих определений следует, что если мы используем SQLпредложение для добавления или удаления столбца из некоторой
базовой таб­лицы T, то, по сути дела, заменяем эту таб­лицу T другой, логичес­ки отличной от нее таб­лицей T′ (в таких контекстах термин таб­лица употребляется как SQL-аналог реляционного термина
пе­ре­мен­ная-от­но­ше­ние). Иначе говоря, со строгой логичес­кой точки
зрения T′ – не «та же таб­лица, что и раньше». Однако в неформальных контекстах на эту тонкость удобно не обращать внимания.
Упражнение 1.6. См. раздел «Модель и реализация» в тексте главы.
Упражнение 1.7. Физичес­кая независимость от данных – это независимость пользователей и приложений от способа физичес­кого хранения данных и доступа к ним. Это логичес­кое следствие строгого разделения между моделью и ее реализацией. В той мере, в какой такое разделение соблюдается и, стало быть, достигается физичес­кая
независимость от данных, мы свободны вносить изменения в методы физичес­кого хранения и доступа к данным (возможно, из соображений производительности), не внося соответствующих изменений в запросы и приложения. Такая независимость желательна, по-

374

Приложение С. Ответы к упражнениям

скольку позволяет уменьшить капиталовложения в обучение и разработку приложений.
Модель – это абстрактная сис­тема, с которой взаимодейст­вуют пользователи, а реализация – конкретное воплощение этой абстрактной
сис­темы в конкретной физичес­кой вычислительной сис­теме. Пользователи должны понимать модель, так как именно она определяет интерфейс, с которым они работают, но не обязаны понимать реализацию, потому что она скрыта (по крайней мере, так должно
быть). Полезна следующая аналогия: чтобы водить автомобиль, вам
не нужно понимать, что происходит под капотом, достаточно знать,
как рулить, как переключать передачи и т. д. Следовательно, правила руления, переключения передач и т. д. – это модель, а происходящее под капотом – реализация. (Да, понимая, что творится под капотом, вы, возможно, стали бы водить лучше, но это знание необязательно. Аналогично может статься, что вы смогли бы лучше использовать модель, зная что-то о том, как она реализована, но в идеале
это не должно быть необходимо.) Примечание: иногда употребляется термин архитектура, очень близкий по смыслу к тому, что выше
названо моделью.
Упражнение 1.8. Строки в таб­лицах упорядочены сверху вниз, а кортежи в от­но­ше­ниях – нет; столбцы в таб­лицах упорядочены слева направо, а атрибуты от­но­ше­ний – нет; в таб­лицах могут быть строкидубликаты, а в от­но­ше­ниях никогда не бывает кортежей-дубликатов.
Кроме того, от­но­ше­ния содержат значения, а изображения таб­лиц –
нет (они не содержат даже «вхождений» таких значений), вместо
этого они содержат символы, обозначающие такие значения, – например, символ (или числительное) 5, который обозначает значение
пять. Другие различия названы в ответе к упражнению 3.5 в главе 3.
Упражнение 1.9. Без ответа.
Упражнение 1.10. В этой книге я под термином реляционная модель понимаю абстрактную сис­тему, предложенную самим Коддом (хотя
с тех пор эта абстрактная сис­тема улучшалась, уточнялась и дополнялась). Я не называю этим словом реляционный проект какой-то конкретной базы данных. Реляционных моделей во втором смысле существует множес­тво, а в первом – только одна. (Как отмечалось в тексте
главы, эта тема значительно подробнее освещена в приложении A.)
Упражнение 1.11. Вот несколько примеров.
•• Реляционная модель не имеет ничего общего с «хранимыми от­
но­ше­ниями»; в частности, она абсолютно ничего не говорит о том,
какие от­но­ше­ния хранятся, а какие – нет. На самом деле, она
даже не говорит, что от­но­ше­ния вообще должны храниться, – может существовать и более удачный способ работы с ними (и такой
способ есть, хотя подробности выходят за рамки этой книги).

375

Глава 1

•• Даже если согласиться с тем, что термин «хранимое от­но­ше­ние»
имеет какой-то смысл – и означает видимое пользователю от­но­
ше­ние, которое представлено в памяти некоторым прямым и эффективным способом, не уточняя, что такое прямой и эффективный, – какие именно от­но­ше­ния «хранятся», не должно иметь
никакого значения на реляционном (пользовательском) уровне
сис­темы. В частности, реляционная модель не говорит, что «таб­
лицы» (или «базовые таб­лицы», или «базовые от­но­ше­ния») должны храниться, а представления – нет.
•• В приведенной цитате не упомянуто важнейшее логичес­кое различие между от­но­ше­ниями и пе­ре­мен­ными-от­но­ше­ниями.
•• В приведенной цитате, похоже, предполагается, что таб­лица и базовая таб­лица – взаимозаменяемые термины и понятия (на мой
взгляд, очень серьезная ошибка).
•• В приведенной цитате, похоже, проводится различие между таб­
лицами и от­но­ше­ниями (и/или пе­ре­мен­ными-от­но­ше­ниями). Если
под «таб­лицей» понимается таб­лица SQL, то я, конечно, согласен
с тем, что между ними имеются некоторые важные отличия, но,
кажется, не они интересуют автора отрывка.
•• «Важно различать хранимые от­но­ше­ния … и виртуальные от­но­
ше­ния». На самом деле с точки зрения пользователя (и реляционной модели, если на то пошло) крайне важно вообще не различать их!
Упражнение 1.12. Вот несколько ошибок:
•• Сама реляционная модель вообще не «определяет таб­лицы» в том
смысле, который подразумевается в цитированном отрывке. Она
даже не «определяет» от­но­ше­ния (точнее, пе­ре­мен­ные-от­но­ше­ния).
Такие определения предоставляются пользователем. И в любом
случае, что такое «простая» таб­лица? А бывают еще и сложные?
•• Что означает фраза «для каждого от­но­ше­ния и для связей многиеко-многим»? Что означает «определить таб­лицы» для таких предметов?
•• Насколько мне известно, следующие понятия не являются частью
модели: сущности, связи между сущностями, связывание таб­лиц,
«перекрестные ключи». (Да, дейст­вительно в оригинальной модели Кодда было правило, названное «целостность сущностей», но
название так и осталось всего лишь названием, и в любом случае
я это правило отвергаю.) Справедливо и то, что всем этим терминам можно дать некоторые интерпретации, но утверждения, проистекающие из таких интерпретаций, обычно оказываются неверными. Например, от­но­ше­ния не всегда представляют «сущности» (какая «сущность» представлена от­но­ше­нием, которое является проекцией поставщиков на атрибуты STATUS и CITY?).

376

Приложение С. Ответы к упражнениям

•• Первичные и вторичные индексы, равно как и быст­рый доступ
к данным, – понятия уровня реализации, они не имеют ничего
общего с моделью. В частности, первичные или потенциальные
ключи не следует приравнивать к «первичным индексам».
•• «На основе заданных параметров»? Нельзя ли как-нибудь поточнее? Очень раздражает, и особенно в реляционном контексте (где
точность мысли и выражения всегда было одной из основных целей), встретить такой ужасающе небрежный стиль. Э-э, ну-у, как
бы это сформулировать, от­но­ше­ние – это что-то вроде таб­лицы,
некий вид таб­лицы… в общем, вы понимаете, что я хочу сказать.
•• Наконец, где же операторы? Весьма распространенная ошибка –
полагать, что реляционная модель имеет дело только со структурой, напрочь забывая об операторах. Но без операторов никуда!
Как заметил сам Кодд, «структура без операторов – все равно, что
анатомия без физиологии».
Упражнение 1.13. Вот несколько возможных предложений CREATE
TABLE. Относительно типов данных столбцов см. главу 2. Примечание: Эти предложения CREATE TABLE, вместе со своими эквивалентами на языке Tutorial D, повторены в главе 5, где обсуждаются более подробно.
CREATE TABLE S
( SNO
VARCHAR(5)
SNAME VARCHAR(25)
STATUS INTEGER
CITY VARCHAR(20)
UNIQUE ( SNO ) ) ;

NOT
NOT
NOT
NOT

NULL
NULL
NULL
NULL

,
,
,
,

CREATE TABLE P
( PNO
VARCHAR(6)
PNAME VARCHAR(25)
COLOR CHAR(10)
WEIGHT NUMERIC(5,1)
CITY VARCHAR(20)
UNIQUE ( PNO ) ) ;

NOT
NOT
NOT
NOT
NOT

NULL
NULL
NULL
NULL
NULL

,
,
,
,
,

CREATE TABLE SP
( SNO
VARCHAR(5) NOT NULL ,
PNO
VARCHAR(6) NOT NULL ,
QTY
INTEGER
NOT NULL ,
UNIQUE ( SNO , PNO ) ,
FOREIGN KEY ( SNO ) REFERENCES S ( SNO ) ,
FOREIGN KEY ( PNO ) REFERENCES P ( PNO ) ) ;

Обратите внимание, что в SQL определения столбцов, а также специ­
фикации ключей и внешних ключей находятся внутри одних и тех
же скобок (сравните с тем, как это делается в Tutorial D, см. главу 2). Заметьте также, что по умолчанию столбцы SQL допускают

377

Глава 1

null-значения; если мы хотим их запретить (лично я хочу), то необходимо задавать явное ограничение. Задать такое ограничение можно разными способами, включение NOT NULL в состав определения
столбца, пожалуй, самый простой.
Упражнение 1.14. На языке Tutorial D (на SQL показать не могу, потому что SQL не поддерживает реляционное присваивание):
SP := SP UNION
RELATION { TUPLE { SNO ‘S5’ , PNO ‘P6’ , QTY 250 } } ;

Текст между ключевым словом UNION и завершающей точкой с запятой – это вызов селектора от­но­ше­ния (см. главу 3), он обозначает
от­но­ше­ние, содержащее только вставляемый кортеж. Примечание:
На практике лучше бы вместо UNION использовать D_UNION. См.
главу 5.
Упражнение 1.15. Для полноты я дам ответ, но подробные объяснения
отложу до главы 7:
S := WITH ( S WHERE CITY = ‘Paris’ ) AS R1 ,
( EXTEND R1 ADD ( 25 AS NEW_STATUS ) ) AS R2 ,
R2 { ALL BUT STATUS } AS R3 ,
R3 RENAME ( NEW_STATUS AS STATUS ) AS R4 :
( S MINUS R1 ) UNION R4 ;

Упражнение 1.16. Сначала рассмотрим присваивание общего вида:
R := rx ;

Здесь R – имя пе­ре­мен­ной-от­но­ше­ния, а rx – реляционное выражение, обозначающее от­но­ше­ние, которое следует присвоить R. Аналог
на SQL мог бы выглядеть так:
DELETE FROM T ;
INSERT INTO T tx ;

Здесь T – SQL-таб­лица, соответствующая пе­ре­мен­ной-от­но­ше­нию
R, а tx – SQL-таб­лица, соответствующая реляционному выражению
rx. Отметим необходимость предварительного DELETE; также отметим, что (допуская вольность речи) между DELETE и последующим
INSERT может произойти все что угодно, тогда как в реляционной
формулировке не существует никакого «между DELETE и INSERT»
(присваивание – семантичес­ки атомарная операция).
Итак, я ответил на вопрос: «Все ли реляционные присваивания можно выразить в терминах предложений INSERT / DELETE / UPDATE?»
Однако обращаю внимание, что реляционная присваивание – это
одна операция, тогда как приведенная выше комбинация DELETE/
INSERT – последовательность двух разных операций. Отсюда, в частности, следует, что между ними может произойти ошибка, которая не
случилась бы, если бы выполнялось настоящее присваивание.

378

Приложение С. Ответы к упражнениям

Я бы хотел прояснить еще один вопрос. В тексте главы я сказал, что
SQL не поддерживает реляционное присваивание напрямую, и это
правда. Однако один из рецензентов возразил, что, например, следующее SQL-выражение «можно было бы рассматривать как реляционное присваивание» (я несколько упростил приведенный рецензентом пример):
SELECT LS.*
FROM ( SELECT SNO, SNAME, STATUS
FROM S
WHERE CITY = ‘London’ ) AS LS

По существу, рецензент считает, что это выражение есть присваивание некоторого таб­личного значения таб­личной пе­ре­мен­ной с именем LS. Но это не так. В частности, невозможно сделать следующий
шаг – предъявить запрос к LS или как-то обновить ее. LS не является независимой полноправной таб­лицей, это лишь временная таб­
лица, которая концептуально материализована в ходе процедуры
вычисления заданного выражения. Такое выражение не является
реляционным присваиванием. (Да и в любом случае присваивание
любого вида – это предложение, а не выражение.)
И еще один момент. Стандарт SQL поддерживает вариант предложения CREATE TABLE – «CREATE TABLE AS», – который позволяет
создать базовую таб­лицу, инициализировав ее результатом выполнения некоторого запроса, то есть не только создать, но и присвоить
начальное значение. Однако после инициализации такая таб­лица
ведет себя, как любая другая базовая таб­лица, то есть и CREATE
TABLE AS тоже нельзя считать поддержкой реляционного присваивания.
Упражнение 1.17. Следующие далее рассуждения основаны на более
развернутых, которые можно найти в моей книге «An Introduction
to Database Systems» (см. приложение D).
Кортежи-дубликаты. По существу, это понятие не имеет смысла.
Предположим для простоты, что от­но­ше­ние «поставщики» имеет
всего два атрибута: SNO и CITY, и предположим, что оно содержит
кортеж, говорящий о том, что «поставщик S1 находится в Лондоне» –
истинный факт. Тогда если бы оно содержало дубликат такого кортежа (если бы такое было возможно), мы просто получили бы повторное
подтверждение истинности этого факта. Но (как замечено в главе 4),
если некое утверждение является истинным, то оно не станет более
истинным, если повторить его дважды! Более подробное обсуждение
см. в главе 4 или в статье «Double Trouble, Double Trouble», упомянутой в приложении D.
Упорядочение кортежей. Отсутствие упорядочения кортежей означает, что не существует такой вещи, как «первый кортеж», «пятый
кортеж» или «97-й кортеж» от­но­ше­ния, как не существует и поня-

379

Глава 2

тия «следующий кортеж». Иными словами, не существует понятия
позиционной адресации или «очередности». Если бы такие понятия
существовали, то понадобились бы и дополнительные операторы,
например: «получить n-й кортеж», «вставить кортеж в это место»,
«переместить кортеж отсюда туда» и так далее. На самом деле (цитируя приложение A), можно считать аксиомой, что если у нас есть
n разных способов представления информации, то необходимо n разных наборов операторов. Если n > 1, то возникает больше операторов, которые нужно реализовывать, документировать, преподавать,
изучать, запоминать и использовать. Однако дополнительные операторы лишь увеличивают сложность, но не добавляют выразительных возможностей! Нет ничего полезного, что можно было бы сделать лишь при n > 1 и нельзя при n = 1.
Кстати, еще один аргумент против упорядочения (любого) состоит
в том, что позиционная адресация неустойчива – по мере вставок
и удалений адреса изменяются. А кроме того, семантика конкретного элемента данных (например, кортежа) может зависеть от его позиции.
Упорядочение атрибутов. Отсутствие упорядочения атрибутов означает, что не существует такой вещи, как «первый атрибут» или «второй атрибут» (и так далее), как не существует и понятия «следующий атрибут» (нет понятия «очередности»), – на атрибуты можно
ссылаться только по имени, но не по позиции. В результате сокращается простор для ошибок и запутанного программирования. Например, невозможно подорвать сис­тему, каким-то образом «перепрыгнув» от одного атрибута к другому. Эта ситуация резко контрастирует с тем, что мы видим во многих языках программирования, где часто есть возможность, намеренно или случайно, воспользоваться физическим соседством логичес­ки различных элементов для совершения разного рода подрывных дейст­вий. Примечание: Многие другие
негативные последствия упорядочения атрибутов (или упорядочения столбцов в контексте SQL) обсуждаются в последующих главах.
См. также статью «A Sweet Disorder», упомянутую в приложении D.
Точности ради я должен добавить, что по причинам, которые нас
здесь не интересуют, в математике (в противоположность реляционной модели) атрибуты от­но­ше­ний считаются упорядоченными слева
направо. Аналогичное замечание касается и кортежей.

Глава 2
Упражнение 2.1. Тип – это конечное именованное множес­тво значений –
всех значений определенного вида, например: всех возможных целых чисел, всех возможных строк символов, всех возможных номеров поставщиков, всех возможных XML-документов, всех возможных от­но­ше­ний с некоторым заголовком и т. д. и т. п. Между типом

380

Приложение С. Ответы к упражнениям

и доменом нет никакой разницы. Примечание: однако в SQL проводится различие между доменами и типами. В частности, поддерживаются предложения CREATE TYPE и CREATE DOMAIN. В первом
приближении CREATE TYPE можно считать SQL-аналогом предложения TYPE языка Tutorial D, которое я буду обсуждать в главе 8
(хотя между тем и другим существует очень много различий, и не
все из них по природе своей тривиальны). Предложение CREATE
DOMAIN можно с большим снисхождением считать попыткой предоставить маленькую часть полной функциональности CREATE
TYPE (оно появилось в языке в 1992 году, а CREATE TYPE только
в 1999); теперь, когда существует CREATE TYPE, не осталось причин использовать и даже поддерживать CREATE DOMAIN.
Упражнение 2.2. С каждым типом ассоциирован по меньшей мере один
селектор; селектором называется оператор, который позволяет выбрать или задать произвольное значение рассматриваемого типа.
Пусть T – некоторый тип, а S – селектор для T; тогда каждое значение типа T должно возвращаться некоторым вызовом S, и каждый вызов S должен возвращать некоторое значение типа T. Дополнительные сведения см. в главе 8. Примечание: В языке Tutorial D
селекторы предоставляются «автоматичес­ки» (так как реляционная
модель, по крайней мере неявно, требует их наличия), но в SQL (в общем случае) это не так. На самом деле, хотя понятие селектора по
необходимости существует, в SQL нет для него термина, и уж точно
само слово селектор термином SQL не является. Подробности выходят за рамки этой книги.
Литерал – это «самоопределенный символ»; он обозначает значение, которое можно определить на этапе компиляции. Точнее, литерал – это символ, обозначающий значение, которое фиксировано
и определено этим символом (а тип значения поэтому тоже фиксирован и определен типом символа). Вот примеры литералов в языке
Tutorial D:
4
‘XYZ’
FALSE
5.0
POINT(5.0,2.5)

/* литерал типа INTEGER
/* литерал типа CHAR
/* литерал типа BOOLEAN
/* литерал типа FIXED
/* литерал типа POINT

*/
*/
*/
*/
*/

Для любого значения любого типа, включая типы кортежа и от­но­
ше­ния, должен быть какой-то способ обозначения с помощью литерала. Литерал – это частный случай вызова селектора; точнее, это
вызов селектора, все аргументы которого сами заданы литералами (откуда, в частности, следует, что вызов селектора без аргументов, например вызов селектора типа INTEGER, 95, является литералом по определению). Отметим, что существует логичес­кое различие
между литералом как таковым и константой; константа – это значение, а литерал – символ, обозначающий значение.

Глава 2

381

Упражнение 2.3. Оператор THE_ предоставляет доступ к некоторому компоненту некоторого «допус­тимого представления» какого-то
значения заданного типа. Подробнее см. главу 8. Примечание: В первом приближении можно считать, что операторы THE_ предоставляются в языках Tutorial D и SQL «автоматичес­ки». Однако, хотя
понятие оператора THE_ по необходимости существует, в SQL нет
для него термина, и уж точно само слова оператор THE_ термином
SQL не является. Подробности выходят за рамки этой книги.
Упражнение 2.4. В принципе, верно; но на практике не всегда (впрочем,
неверно оно может быть только из-за путаницы между моделью и реализацией).
Упражнение 2.5. Параметр – это формальный операнд, участвующий
в определении некоторого оператора. Аргумент – это фактичес­кий
операнд, подставляемый вместо параметра при вызове этого оператора. (Часто эти термины употребляются как синонимы, из-за чего возникает много недоразумений, так что будьте начеку.) Примечание:
существует логичес­кое различие между аргументом как таковым
и выражением, которое применяется для его задания. Например,
рассмотрим выражение (2+3)–1, являющееся вызовом арифметичес­
кого оператора «–». Первый аргумент этого вызова – значение, равное пяти, но задан он выражением 2+3 (которое является вызовом
арифметичес­кого оператора «+»; на самом деле, любое выражение
представляет собой вызов какого-то оператора).
База данных – это репозиторий данных. (Примечание: Можно дать
гораздо более точные определения, одно из них приведено в главе 5
этой книги.) СУБД – это программная сис­тема для управления базами данных; она обеспечивает восстановление, конкурентный доступ,
запросы и обновление, а также другие службы.
Внешний ключ – это подмножес­тво заголовка некоторой пе­ре­мен­
ной-от­но­ше­ния, значения которого должны быть равны значениям некоторого ключа какой-то другой (или той же самой) пе­ре­мен­
ной-от­но­ше­ния. Указатель – это значение (по существу, адрес), для
которого должны быть определены операторы весьма специального вида и, прежде всего, операторы ссылки и разыменования.1 Примечание: краткие определения операторов ссылки и разыменования
приведены в сноске в тексте главы.
Сгенерированный тип – это тип, полученный в результате выполнения некоторого генератора типа, например ARRAY или RELATION;
таким образом, конкретные типы массива и от­но­ше­ния являются
сгенерированными типами. Несгенерированный тип – это тип, не
являющийся сгенерированным.
1

Гораздо более полное обсуждение логичес­кого различия между внешними
ключами и указателями можно найти в статье «Inclusion Dependencies and
Foreign Keys» (см. приложение D).

382

Приложение С. Ответы к упражнениям

От­но­ше­ние – это значение; у него есть тип, но само оно типом не является. Тип – это именованное множес­тво значений (всех возможных значений определенного вида).
Скалярный тип – это тип, у которого нет видимых пользователю
компонентов; нескалярный тип – это тип, не являющийся скалярным. Значения, пе­ре­мен­ные, операторы и т. д. называются скалярными или нескалярными в зависимости от того, принадлежат ли
они скалярному или нескалярному типу. Однако имейте в виду, что
эти термины при тщательном анализе не являются ни формальными, ни точными. В частности, в главе 3 мы столкнемся с двумя важными от­но­ше­ниями TABLE_DUM и TABLE_DEE, которые согласно
данному определению являются скалярными (по крайней мере, такая точка зрения возможна)!
Тип – это понятие, относящееся к модели; у типов есть семантика,
которая должна быть понятна пользователю. Представление типа –
понятие, относящееся к реализации; предполагается, что представления скрыты от пользователя. В частности (и это отмечалось в тексте главы), если X – значение или пе­ре­мен­ная типа T, то операторы,
применимые к X, – это операторы, определенные для T, а не операторы, определенные для представления T. Так, из того, что представление типа ENO («номера служащих») – CHAR (к примеру), не следует,
что мы можем конкатенировать два номера служащих; это возможно только, если оператор конкатенации («||») определен для типа ENO.
Системный (или встроенный) тип – это тип, который становится
доступен сразу после установки сис­темы («лежит в одной коробке
с сис­темой»). Определенный пользователем, или пользовательский,
тип – это тип, определение и реализация которого предоставляются
квалифицированным пользователем уже после установки сис­темы.
(Однако для пользователя – в отличие от проектировщика и разработчика – такой тип должен выглядеть и вести себя в точности так
же, как сис­темный.)
Системный (или встроенный) оператор – это оператор, который становится доступен сразу после установки сис­темы («лежит в одной
коробке с сис­темой»). Определенный пользователем, или пользовательский, оператор – это оператор, определение и реализация которого предоставляются квалифицированным пользователем уже после установки сис­темы. (Однако для пользователя – в отличие от проектировщика и разработчика – такой оператор должен выглядеть
и вести себя в точности так же, как сис­темный.) Определенные пользователем операторы могут принимать значения как сис­темных, так
и пользовательских типов (в любом сочетании), но сис­темные операторы могут принимать значения только сис­темных типов.
Упражнение 2.6. Приведением типа называется неявное преобразование типа. Оно не поощряется, так как чревато ошибками (однако от-

383

Глава 2

метим, что этот вопрос носит в основном прагматичес­кий характер;
разрешены приведения или нет, не имеет от­но­ше­ния к реляционной
модели как таковой).
Упражнение 2.7. Потому что при этом происходит смешение понятий
типа и его представления.
Упражнение 2.8. Генератором типа называется оператор, который возвращает не значение, а тип (и вызывается во время компиляции,
а не выполнения). В реляционной модели поддерживается два таких
оператора: TUPLE и RELATION. Примечание: Эти конкретные генераторы генерируют нескалярные типы, но нет никаких причин, по
которым только нескалярные типы и можно генерировать. Так, оператор REF в SQL являет собой пример генератора скалярного типа.
Упражнение 2.9. Говорят, что от­но­ше­ние находится в первой нормальной форме (1НФ), если каждый кортеж содержит единственное значение некоторого типа в позиции каждого атрибута; иными словами,
каждое от­но­ше­ние находится в первой нормальной форме. С учетом
этого факта простительным выглядит вопрос – а зачем вообще говорить об этом понятии (и, в частности, называть эту нормальную форму «первой»). Уверен, вы знаете, что причина в том, что (а) мы можем
обобщить его на пе­ре­мен­ные-от­но­ше­ния (а не только на от­но­ше­ния)
и (б) мы можем определить для пе­ре­мен­ных-от­но­ше­ний нормальные
формы «более высокого порядка», которые оказываются важны при
проектировании баз данных. Другими словами, 1НФ – это основа,
на которой строятся нормальные формы более высокого порядка. Но
само по себе это понятие не так уж важно.

Примечание
Должен добавить, что 1НФ – одно из тех понятий, определение которых со
временем эволюционировало. Раньше оно означало, что любой кортеж должен содержать единственное «атомарное» значение в позиции каждого атрибута. Но впоследствии мы осознали (и я пытался показать это в тексте главы), что понятие атомарности значения данных не имеет объективного смысла.
Развернутое обсуждение 1НФ можно найти в статье «What First Normal Form
Really Means» (см. приложение D).

Упражнение 2.10. Типом X является тип, назовем его T, определенный
в качестве типа результата последнего выполненного при вычислении X оператора – «самого внешнего оператора». Этот тип важен, потому что благодаря ему X можно использовать точно в тех (то есть
в тех и только тех) местах, где допус­тимо появление литерала типа T.
Упражнение 2.11.
OPERATOR CUBE ( I INTEGER ) RETURNS INTEGER ;
RETURN I * I * I ;
END OPERATOR ;

384

Приложение С. Ответы к упражнениям

Упражнение 2.12.
OPERATOR FGP ( P POINT ) RETURNS POINT ;
RETURN POINT ( F ( THE_X ( P ) ) , G ( THE_Y ( P ) ) ) ;
END OPERATOR ;

Упражнение 2.13. Следующий тип от­но­ше­ния является типом пе­ре­
мен­ной-от­но­ше­ния «поставщики» S:
RELATION { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }

Сама пе­ре­мен­ная-от­но­ше­ние S является пе­ре­мен­ной этого типа. И любое допус­тимое значение этой пе­ре­мен­ной – например, показанное на
рис. 1.3 в главе 1, – является значением этого типа.
Упражнение 2.14. Определения на SQL даны в ответе к упражнению
1.13 в главе 1. Вот определения на Tutorial D:
VAR P BASE RELATION
{ PNO CHAR , PNAME CHAR , COLOR CHAR , WEIGHT FIXED , CITY CHAR }
KEY { PNO } ;
VAR SP BASE RELATION
{ SNO CHAR , PNO CHAR , QTY INTEGER }
KEY { SNO }
FOREIGN KEY { SNO } REFERENCES S
FOREIGN KEY { PNO } REFERENCES P ;

Перечислим некоторые различия между определениями на SQL и Tu­
torial D:
•• Как отмечалось в ответе к упражнению 1.13 в главе 1, в SQL ключи и внешние ключи определяются вместе со столбцами таб­лицы1
внутри одной и той же пары скобок. Из-за этого трудно определить, из чего состоит собственно тип. (На самом деле SQL вообще
не поддерживает понятие типа от­но­ше­ния, как мы увидим в главе 3.)
•• Порядок перечисления столбцов слева направо в SQL имеет значение. Дальнейшее обсуждение см. в главе 5.
•• Таб­лицы в SQL не обязательно должны иметь ключи.
Тот факт, что пе­ре­мен­ная-от­но­ше­ние (скажем, P) принадлежит определенному типу, важен по следующим причинам:
•• Пе­ре­мен­ной-от­но­ше­нию P можно присвоить в качестве значения
только от­но­ше­ние соответствующего типа.
•• Ссылка на пе­ре­мен­ную-от­но­ше­ние P может встречаться всюду, где
допус­тим литерал соответствующего типа (как, например, в выражении P JOIN SP), и в этом случае обозначает от­но­ше­ние, которое
1

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

Глава 2

385
является текущим значением пе­ре­мен­ной-от­но­ше­ния в некоторый
момент времени. (Другими словами, ссылка на пе­ре­мен­ную-от­но­
ше­ние – допус­тимое реляционное выражение языка Tutorial D; отметим, однако, что аналогичное замечание неприменимо к SQL, по
крайней мере, не на 100 процентов.) Дальнейшее обсуждение см.
в главе 6.

Упражнение 2.15. a. Недопус­тимо; LOCATION = CITY(‘London’). b. Допус­
тимо; BOOLEAN. c. Предположительно допус­тимо; MONEY (я предполагаю, что умножение значения типа MONEY на целое число дает
значение типа MONEY). d. Недопус­тимо; BUDGET+MONEY(50000).
e. Недопус­тимо; ENO > ENO(‘E2’). f. Недопус­тимо; NAME(THE_C(EN
AME)||THE_C(DNAME)) (я предполагаю, что допус­тимое представление типа NAME состоит из единственного компонента C типа CHAR).
g. Недопус­тимо; CITY(THE_C(LOCATION)||‘burg’) (я предполагаю, что
допус­тимое представление типа CITY состоит из единственного компонента C типа CHAR). Примечание: В ответах к этому упражнению
я также предполагаю, что у типа T имеется селектор с таким же именем. Дальнейшее обсуждение см. в главе 8.
Упражнение 2.16. Такая операция логичес­ки означает замену одного
типа другим, а не «изменение типа» (типы – не пе­ре­мен­ные). Рассмотрим следующие соображения. Прежде всего, операция определения типа не создает соответствующее множес­тво значений; концептуально эти значения уже существуют и всегда будут существовать (возьмите, к примеру, тип INTEGER). На самом деле операция
«определить тип» (предложение TYPE в Tutorial D – см. главу 8)
лишь вводит имя, по которому на это множес­тво значений можно будет ссылаться. Аналогично удаление типа не уничтожает соответствующие значения, а лишь удаляет имя, созданное в ходе операции
«определить тип». Отсюда следует, что «изменение типа» в дейст­
вительности означает удаление имени типа с последующим созданием такого же имени, которое теперь ссылается на другое множес­
тво значений. Конечно, ничто не мешает ради упрощения поддержать некую сокращенную нотацию «изменить тип» – и в SQL такой
оператор есть, – но использование сокращения не означает, что мы
в дейст­вительности «изменяем тип».
Упражнение 2.17. Пустой тип, конечно, допус­тим; однако определять
пе­ре­мен­ную такого типа особого смысла не имеет, потому что ей нельзя присвоить никакого значения! Однако, несмотря на это, пус­той
тип оказывается крайне важен в связи с наследованием типов, но эта
тема (к сожалению) выходит за рамки настоящей книги. Дополнительную информацию см. в написанной Хью Дарвеном и мной книге «Databases, Types, and the Relational Model: The Third Manifesto»
(см. приложение D).
Упражнение 2.18. Пусть T – тип SQL, для которого оператор «=» не
определен, и пус­ть C – столбец типа T. Тогда C не может быть частью

386

Приложение С. Ответы к упражнениям

потенциального или внешнего ключа, не может быть частью аргумента DISTINCT, GROUP BY или ORDER BY и в терминах C не могут быть определены ограничения, соединения, объединения, пересечения и разности. А как насчет таких конструкций уровня реализации, как индексы? Вероятно, имеются и другие последствия.
Далее, пус­ть T – тип SQL, для которого семантика оператора «=»
определена пользователем, и пус­ть C – столбец типа T. Тогда результат включения C в состав потенциального или внешнего ключа, а также применения к нему DISTINCT или GROUP BY (и т. д.
и т. п.) тоже должен быть определен пользователем – в лучшем случае, а в худшем случае окажется непредсказуемым.
Упражнение 2.19. Вот тривиальный пример такого нарушения. Пусть
X – строка символов ‘AB ’ (обратите внимание на пробел в конце),
а Y – строка символов ‘AB’, и пус­ть для соответствующей схемы упорядочения задана опция PAD SPACE. Тогда сравнение X = Y возвращает TRUE, но при этом вызовы операторов CHAR_LENGTH(X)
и CHAR_LENGTH(Y) возвращают 3 и 2 соответственно. Предлагаю
вам детально рассмотреть последствия такого положения вещей,
но должно быть ясно, что это приведет к проблемам при выполнении операций DISTINCT, GROUP BY и ORDER BY наряду со многими другими (а также в таких конструкциях уровня реализации, как
индексы).
Упражнение 2.20. Потому что (а) они логичес­ки излишни, (б) чреваты ошибками, (в) их не может использовать конечный пользователь,
(в) неудобны для работы – в частности, с указателем ассоциировано направление, отсутствующее для не-указательных типов, и (г)
подрывают идею наследования типов. (Детали последнего замечания выходят за рамки этой книги.) Есть и другие причины. Дополнительную информацию см. в цитированной ранее работе «Inclusion
Dependencies and Foreign Keys».
Упражнение 2.21. Один ответ связан с null-значениями; если мы «устанавливаем X в null» (это не означает присваивания X некоторого значения, потому что null – не значение, но неважно), то сравнение X =
NULL заведомо не дает TRUE. Есть и много других примеров, не относящихся к null-значениям. Предлагаю вам самостоятельно поразмыслить над последствиями.
Упражнение 2.22. Нет! (Какой базе данных принадлежит, к примеру,
тип INTEGER?) В некотором важном смысле весь механизм типов
и управления типами ортогонален тематике баз данных и управления базами данных. Мы можем даже представить себе «администратора типов», в задачу которого входит следить за типами аналогично тому, как администратор базы данных следит за базами данных.
Упражнение 2.23. Выражение обозначает значение; его можно трактовать как правило вычисления или определения значения. Предложение не обозначает значение; оно приводит к выполнению некото-

387

Глава 3

рого дейст­вия, например, присваивания значения пе­ре­мен­ной или
изменения потока управления. Например, в SQL
X + Y

является выражением, а
SET Z = X + Y ;

является предложением.
Упражнение 2.24. RVA-атрибут – это атрибут, типом которого является
тип некоторого от­но­ше­ния, а значениями, следовательно, – от­но­ше­
ния этого типа. Повторяющаяся группа – это «атрибут» некоторого типа T, значениями которого являются не значения типа T, – обратите внимание на противоречивую терминологию! – а мультимножества, или множес­тва, или последовательности (и т. д.) значений
типа T. Примечание: В данном случае «атрибут» сам часто принадлежит некоторому типу кортежа (или чему-то, аппроксимирующему тип кортежа). Например, файл в сис­теме, допускающей повторяющиеся группы, может быть составлен из записей, состоящих из
поля ENO (номер служащего), поля ENAME (имя служащего) и повторяющейся группы JOBHIST, каждый элемент которой состоит из
поля JOB (должность), поля FROM и поля TO (где FROM и TO – даты).
Упражнение 2.25. В SQL подзапрос – это, неформально говоря, таб­
личное выражение, заключенное в скобки. В последующих главах
(особенно в главе 12) этот вопрос рассматривается подробнее.
Упражнение 2.26. См. главу 3.

Глава 3
Упражнение 3.1. См. текст главы.
Упражнение 3.2. Два значения любого вида равны тогда и только тогда,
когда имеют одно и то же значение! В частности, (а) два кортежа tx
и ty равны тогда и только тогда, когда имеют одинаковые атрибуты
A1, A2, ..., An и для любого i (i = 1, 2, ..., n) значение vx атрибута Ai кортежа tx равно значению vy атрибута Ai кортежа ty; (б) два от­но­ше­ния
rx и ry равны тогда и только тогда, когда имеют одинаковые заголовки и одинаковые тела.
Упражнение 3.3. Вызовы селекторов кортежей на языке Tutorial D:
TUPLE { PNO ‘P1’ , PNAME ‘Nut’ ,
COLOR ‘Red’ , WEIGHT 12.0 , CITY ‘London’ }
TUPLE { SNO ‘S1’ , PNO ‘P1’ , QTY 300 }

Аналоги на SQL (вызовы «конструкторов значений строк»):
ROW ( ‘P1’ , ‘Nut’ , ‘Red’ , 12.0 , ‘London’ )

388

Приложение С. Ответы к упражнениям
ROW ( ‘S1’ , ‘P1’ , 300 )

Обратите внимание на отсутствие имен столбцов (или имен «полей»,
если использовать терминологию SQL) и зависимость от упорядочения столбцов слева направо в SQL-выражениях. Ключевое слово
ROW можно опус­тить без потери смысла.
Упражнение 3.4. Следующий вызов селектора обозначает от­но­ше­ние,
состоящее из двух кортежей:
RELATION { TUPLE { SNO ‘S1’ , PNO ‘P1’ , QTY 300 } ,
TUPLE { SNO ‘S1’ , PNO ‘P2’ , QTY 200 } }

Аналог на SQL (вызов «конструктора таб­личного значения»):
VALUES ROW ( ‘S1’ , ‘P1’ , 300 ) ,
ROW ( ‘S1’ , ‘P2’ , 200 )

В обоих вызовах «конструкторов значений строк» можно при желании опус­тить ключевое слово ROW. Кстати, отсутствие скобок вокруг вызовов этих конструкторов значений строк – не ошибка. На
самом деле, SQL-выражение
VALUES ( ROW ( ‘S1’ , ‘P1’ , 300 ) ,
ROW ( ‘S1’ , ‘P2’ , 200 ) )

(синтаксичес­ки вполне допус­тимое) обозначает нечто совершенно
иное! См. упражнение 3.10.
Упражнение 3.5. Следующий перечень основан на приведенном в моей
книге «An Introduction to Database Systems» (см. приложение D).
•• С любым атрибутом в заголовке от­но­ше­ния связано имя типа,
однако эти имена типов опускаются в таб­лицах (я имею в виду
графичес­кое изображение от­но­ше­ний в виде таб­лиц).
•• С любым компонентом любого кортежа в теле от­но­ше­ния связаны
имя типа и имя атрибута, но эти имена типов и атрибутов обычно
опускаются на рисунках в виде таб­лиц.
•• Значение любого атрибута любого кортежа в теле от­но­ше­ния является значением соответствующего типа, но эти значения (или,
точнее, обозначающие их литералы) обычно изображаются на рисунках в сокращенной форме – например, S1 вместо ‘S1’.
•• Столбцы таб­лицы упорядочены слева направо, а атрибуты от­но­
ше­ния – нет. Одним из следствий такого положения вещей является тот факт, что столбцы могут иметь повторяющиеся имена
или вообще не иметь имени. Рассмотрим, к примеру такое SQLвыражение:
SELECT DISTINCT S.CITY, S.STATUS * 2, P.CITY
FROM S, P

Какие имена имеют столбцы результата вычисления этого выражения?

389

Глава 3

•• Строки таб­лицы упорядочены сверху вниз, а кортежи от­но­ше­
ния – нет.
•• Таб­лица может содержать строки-дубликаты, а от­но­ше­ние никогда не содержит кортежей-дубликатов.
•• Обычно считается, что таб­лица имеет хотя бы один столбец, тогда как от­но­ше­ние может вообще не иметь атрибутов (см. раздел
«TABLE_DUM и TABLE_DEE» в тексте главы).
•• Таб­лицы (по крайней мере, в SQL) могут включать null-значения,
а от­но­ше­ния – нет.
•• Таб­лицы являются «плоскими», или двумерными, а от­но­ше­ния
n-мерны.
Упражнение 3.6. Вот пример исключения: так как никакое от­но­ше­ние
в базе данных не может иметь атрибутов указательного типа, то никакой кортеж от­но­ше­ния также не может иметь атрибутов указательного типа. Другое исключение сформулировать труднее, но сводится оно к тому, что если кортеж t имеет заголовок {H}, то никакой
атрибут t нельзя выразить в терминах типа кортежа или от­но­ше­ния
с таким же заголовком {H} на любом уровне вложенности.
Ниже приведены примеры (а) кортежа, атрибутом которого является кортеж, и (б) кортежа, атрибутом которого является от­но­ше­ние:
TUPLE { NAME ‘Superman’ ,
ADDR TUPLE { STREET
CITY
STATE
ZIP

‘1600 Pennsylvania Ave.’ ,
‘Washington’ ,
‘DC’ ,
‘20500’ } }

TUPLE { SNO ‘S2’ ,
PNO_REL RELATION { TUPLE { PNO ‘P1’ } ,
TUPLE { PNO ‘P2’ } } }

Упражнение 3.7. Примером от­но­ше­ния с одним RVA-атрибутом может
служить от­но­ше­ние R4 на рис. 2.2 в главе 2; эквивалентное ему от­
но­ше­ние без RVA-атрибутов – от­но­ше­ние R1 на рис. 2.1 в главе 2. Вот
примеры от­но­ше­ний с двумя RVA-атрибутами:
CNO
C1

C2

TEACHER

TEXT

TNO

XNO

T2
T4
T5

X1
X2

TNO

XNO

T4

X2
X4
X5

390

Приложение С. Ответы к упражнениям

Предполагаемая семантика такова: курс CNO может быть прочитан любым преподавателем TNO в TEACHER по любому учебнику
XNO в TEXT. Вот как выглядит от­но­ше­ние без RVA-атрибутов, несущее ту же самую информацию:
CNO

TNO

XNO

C1
C1
C1
C1
C1
C1
C2
C2
C2

T2
T2
T4
T4
T5
T5
T4
T4
T4

X1
X2
X1
X2
X1
X2
X2
X4
X5

Что касается от­но­ше­ния с RVA-атрибутом такого, что не существует
от­но­ше­ния без RVA-атрибута, представляющего точно такую же информацию, то простой пример можно получить из рис. 2.2 в главе 2,
заменив, к примеру, значение PNO_REL для поставщика S2 пус­тым
от­но­ше­нием:
SNO

PNO_REL

S2

PNO

S3

PNO
P2

S4

PNO
P2
P4
P5

Однако необязательно привлекать понятие пус­того от­но­ше­ния, чтобы получить пример от­но­ше­ния с RVA-атрибутом такого, что не существует от­но­ше­ния без RVA-атрибута, представляющего точно такую же информацию. (Дополнительное упражнение: Обоснуйте это
замечание! Если сдаетесь, ознакомьтесь с ответом к упражнению
B.14 в конце этого приложения.)
Быть может, мне следует пояснить, что я имею в виду, когда говорю,
что два от­но­ше­ния представляют одну и ту же информацию. По существу, от­но­ше­ния r1 и r2 представляют одну и ту же информацию,
если можно отобразить r1 в r2 и наоборот средствами операций реляционной алгебры, не внося дополнительной информации ни в том,
ни в другом направлении. Например, если взять от­но­ше­ния R4 на
рис. 2.2 в главе 2 и R1 на рис. 2.1 в главе 2, то будем иметь:

391

Глава 3
R4 = R1 GROUP ( { PNO } AS PNO_REL )
R1 = R4 UNGROUP ( PNO_REL )

Таким образом, каждое от­но­ше­ние можно выразить через другое,
поэтому оба представляют одну и ту жеинформацию.1 Дополнительное обсуждение операторов GROUP и UNGROUP см. в главе 7.
Упражнение 3.8. TABLE_DEE и TABLE_DUM (для краткости DEE
и DUM) – единственные от­но­ше­ния без атрибутов; DEE содержит
всего один кортеж (0-кортеж), а DUM вообще не содержит кортежей. В SQL они не поддерживаются, так как таб­лицы в SQL обязаны
иметь по крайней мере один столбец. (Другими словами, SQL-версия
реляционной алгебры подобна арифметике без нуля.)
Упражнение 3.9. (Примечание: Рекомендую вернуться к этому ответу
после прочтения главы 10.) Прежде чем формулировать понятие от­
но­ше­ния степени 0, нам необходимо иметь понятие от­но­ше­ния вообще. Общее понятие от­но­ше­ния основано на логике предикатов. Логика предикатов основана на логике высказываний. Логика высказываний основана на значениях истинности TRUE и FALSE. Поэтому если заменить TRUE и FALSE на DEE и DUM, то мы будем ходить
по кругу!
Кроме того, было бы несколько странно (мягко говоря), если бы все
булевы выражения внезапно превратились в реляционные выраже­
ния, и, следовательно, от включающего языка потребовалась бы поддержка типов от­но­ше­ний в качестве типов данных.
Имеет ли вообще смысл определять пе­ре­мен­ную-от­но­ше­ние степени 0? Трудно, хотя и возможно, представить себе ситуацию, в которой такая пе­ре­мен­ная-от­но­ше­ние могла бы оказаться полезной, но
дело не в этом. Система все равно не должна запрещать определение
такой пе­ре­мен­ной-от­но­ше­ния. Иначе был бы нарушен принцип ортогональности, а такие нарушения рано или поздно обязательно выходят боком.
Упражнение 3.10. Первое обозначает SQL-таб­лицу из четырех строк
(три разных и одна – дубликат). Второе обозначает SQL-таб­лицу из
одной строки, состоящей из четырех значений «полей», каждое из
которых, в свою очередь, является строкой. Обратите внимание, что
все поля (в обоих случаях) не именованы.
Упражнение 3.11. Данное выражение семантичес­ки эквивалентно такому:

1

Еще одна полезная неформальная характеристика формулируется так: от­
но­ше­ния r1 и r2 представляют одну и ту же информацию, если для любого
запроса q1, который можно предъявить к r1, существует запрос q2 к r2, дающий тот же самый результат (и наоборот).

392

Приложение С. Ответы к упражнениям
SELECT
FROM
WHERE
OR (
OR
OR

SNO
S
STATUS
STATUS
STATUS
SNO IS

> 20
= 20 AND SNO > ‘S4’ )
IS NULL
NULL

Упражнение 3.12. См. текст главы.
Упражнение 3.13. См. текст главы.
Упражнение 3.14. EXISTS (tx), где tx – SQL-аналог реляционного выражения rx (следовательно, tx – таб­личное выражение SQL).
Упражнение 3.15. См. текст главы.
Упражнение 3.16. AS используется во фразах SELECT (для назначения
имен столбцам); во фразах CREATE VIEW и FROM (для назначения
имен пе­ре­мен­ным кортежа – причем синтаксис назначения имен
столбцам в этом контексте не требует использования AS); во фразах WITH и в других контекстах, не рассматриваемых в этой книге.
Были также заданы вопросы, (а) в каких случаях это ключевое
слово необязательно; (б) в каких случаях фраза AS принимает вид
« AS имя»; (в) в каких случаях она принимает вид «имя AS
». Эти вопросы оставлены без ответа.

Глава 4
Упражнение 4.1. Чтобы должным образом возразить на этот довод, потребовалось бы больше места, чем мы располагаем, но смысл сводится к так называемому принципу тождества неразличимых (см. приложение A). Пусть a и b – произвольные сущности, например две копейки. Если не существует никакого способа отличить a от b, то мы
имеем не две сущности, а одну! Справедливо, что в некоторых случаях мы можем использовать одну сущность вместо другой, но этого еще недостаточно, чтобы сделать их неразличимыми (фактичес­ки
существует логичес­кое различие между взаимозаменяемостью и неразличимостью, а аргументы типа «дубликаты естественным образом встречаются в реальном мире» игноруют это различие). Детальный анализ этого вопроса можно найти в статье «Double Trouble,
Double Trouble» (см. приложение D).
Упражнение 4.2. Строго говоря, перед тем как отвечать на этот вопрос,
мы должны уточнить, что означают WHERE и UNION при наличии
дубликатов. Подробно этот вопрос освещается в работе «The Theory
of Bags: An Investigative Tutorial» (см. приложение D); здесь я лишь
скажу, что если принять определения SQL, то это правило, безусловно, неприменимо. На самом деле, оно неприменимо даже к UNION
ALL или UNION DISTINCT! В качестве примера предположим, что
r – SQL-таб­лица всего с одним столбцом – назовем его C, – содер-

393

Глава 4

жащая две строки, каждая из которых содержит одно значение v.
Ниже показан ряд выражений вместе с их результатами:
SELECT
FROM
WHERE
OR

C
r
TRUE
TRUE

Результат: v * 2.
SELECT
FROM
WHERE
UNION
SELECT
FROM
WHERE

C
r
TRUE
DISTINCT
C
r
TRUE

Результат: v * 1.
SELECT
FROM
WHERE
UNION
SELECT
FROM
WHERE

C
r
TRUE
ALL
C
r
TRUE

Результат: v * 4.

Примечание
Но если все ALL (явные и неявные) в приведенных выше выражениях заменить
на DISTINCT, то результаты будут совершенно другими. Какой вывод вы отсюда сделаете?

Упражнение 4.3. Замечания, аналогичные высказанным в ответе на
предыдущее упражнение, сохраняют силу и в этом случае. Детали
я снова опускаю, но смысл состоит в том, это правило почти наверняка неприменимо. Предлагаю вам придумать контрпример.
Упражнение 4.4. Насколько я могу судить, единственный способ разрешить эту неоднозначность состоит в том, чтобы определить отображение каждой из (мультимножес­тва) таб­лиц-аргументов на настоящее множес­тво и аналогично определить отображение таб­лицырезультата (мультимножес­тва), то есть требуемого декартова произведения, на настоящее множес­тво. (Для определения такого отображения необходимо сопоставить каждой строке уникальный идентификатор.) На самом деле, мне кажется, что неудачная попытка стандарта дать определение – лишнее доказательство того, что самые
базовые концепции в языке SQL (в частности, идея о допус­тимости

394

Приложение С. Ответы к упражнениям

строк-дубликатов в таб­лицах) имеют фундаментальные дефекты,
которые нельзя устранить, не отказавшись от концепции целиком.
Упражнение 4.5. Не думаю, что эту проблему можно решить.
Упражнение 4.6. Без ответа!
Упражнение 4.7. Вопрос ставился так: на ваш взгляд, встречаются ли
null-значения в реальном мире естественным образом? Только вы можете ответить на этот вопрос – но, если вы отвечаете «да», то полагаю,
что ваши доводы следует подвергнуть тщательному анализу. Например, рассмотрим утверждение «зарплата Джо составляет 50 000 долларов». Это утверждение либо истинно, либо ложно. Конечно, вы
можете не знать, истинно оно или ложно, но это не имеет никакого
от­но­ше­ния к его реальной истинности. В частности, не знать величину зарплаты Джо и сказать, что она равна null, – совершенно разные вещи! «Зарплата Джо составляет 50 000 долларов» – это утверждение о реальном мире. «Зарплата Джо составляет null» – утверждение о вашей осведомленности (точнее, об отсутствии таковой). Мы
ни в коем случае не должны смешивать эти два очень разных вида
утверждений в одном от­но­ше­нии или в одной пе­ре­мен­ной-от­но­ше­
нии! Примечание: Обсуждение предикатов пе­ре­мен­ных-от­но­ше­ний
в следующей главе должно дать вам новую пищу для размышлений
в этом направлении.
Предположим, что вам необходимо представить свое незнание зарплаты Джо в бумажной анкете. Вы напишете в соответствующей графе null? Не думаю! Скорее, вы оставите ее незаполненной или впишете вопросительный знак или слово «неизвестно» или еще что-то
в этом роде. И это что-то, будь то незаполненная графа, вопросительный знак или слово «неизвестно», есть значение, а не null (напомню,
что о null твердо можно сказать только одно – это не значение). Поэтому лично я не считаю, что у null «есть естественное место в реальном мире».
Упражнение 4.8. Истинно (хотя и не в SQL!). Null – это маркер, обозначающий отсутствие информации. UNKNOWN – это значение, точно
такое же, как TRUE и FALSE. Между тем и другим есть логичес­кое
различие, и смешивание их в SQL – логичес­кая ошибка (Я бы сказал,
серьезная логичес­кая ошибка, но все логичес­кие ошибки серьезны
по определению).
Упражнение 4.9. Да, поддерживает; аналог MAYBE p в SQL – p IS
UNKNOWN.
Упражнение 4.10. В 2VL существует 4 одноместных связки и 16 двуместных, им соответствуют 4 возможных одноместных таб­лицы истинности и 16 двуместных. Вот эти таб­лицы истинности (я указал
общепринятые имена, например NOT, AND и OR, для тех, у которых
такие имена существуют):

395

Глава 4

NOT
T
F

T
T

T
F

T
F

T
F

F
T

T F

IF

T F

NAND

T F

T
F

T T
T T

T
F

T F
T T

T
F

F T
T T

OR

T F

T
F

T
F

T
F

T
F

F
F
T F

T
F

F F
T T

T F

XOR

T F

T T
T F

T
F

T F
T F

T
F

F T
T F

T
F

F F
T F

T F

IFF

T F

T F

NOR

T F

T T
F T

T
F

T F
F T

F T
F T

T
F

F F
F T

T F

AND

T F

T T
F F

T
F

T F
F F

T
F

T F

T F
T
F

F T
F F

T F
T
F

F F
F F

В 3VL существует 27 (3 в третьей степени) одноместных связок
и 19683 (3 в степени 32) двуместных. (В общем случае в n-значной
логике (nVL) существует n в степени n одноместных и n в степени n2
двуместных связок). Из этого факта можно вывести много следствий,
и одно из самых очевидных состоит в том, что 3VL гораздо сложнее,
чем 2VL (намного сложнее, чем думает или, по крайней мере, готово
признать большинство людей, полагающих, что null – это хорошо).
Упражнение 4.11. 2VL функционально полна, если она поддерживает
NOT и либо AND, либо OR (дополнительное обсуждение см. в ответе к упражнению 10.2). И как выясняется, 3VL в смысле SQL – при
некоторой весьма снисходительной трактовке этого термина! – тоже
функционально полна. Более детальное исследование этого вопроса см. в статье «Is SQL’s Three-Valued Logic Truth Functionally Com­
plete?» (см. приложение D).
Упражнение 4.12. В 3VL это не тавтология, потому что, если bx принимает значение UNKNOWN, то и все выражение принимает значение UNKNOWN. Однако в 3VL существует аналогичная тавтология, а именно: bx OR NOT bx OR MAYBE bx. Примечание: Это объясняет, почему в SQL результаты запросов «Получить всех поставщиков в Лондоне» и «Получить всех поставщиков не в Лондоне» в совокупности не обязательно покрывают множес­тво всех поставщиков;
не исключено, что придется еще выполнить запрос «Получить всех
поставщиков, которые, возможно, находятся в Лондоне». Подумайте о том, что отсюда следует с точки зрения переписывания запро-

396

Приложение С. Ответы к упражнениям

сов… не говоря уже о возможности серьезных ошибок (со стороны
как пользователей, так и сис­темы). Подчеркну еще раз: очень естественно предполагать, что тавтологии в 2VL являются также тавтологиями и в 3VL, но в общем случае это не так.
Упражнение 4.13. Это не противоречие в 3VL, потому что, если bx принимает значение UNKNOWN, то и все выражение принимает значение UNKNOWN. Однако в 3VL существует аналогичное (довольно
хитрое!) противоречие, а именно: bx AND NOT bx AND NOT MAYBE
bx. Примечание: Как и следовало ожидать, такое положение вещей
ведет к последствиям, аналогичным упомянутым в ответе к предыдущему упражнению.
Упражнение 4.14. В 3VL (а) r JOIN r не обязательно равно r и (б) INTER­
SECT не является частным случаем JOIN. Причина состоит в том,
что (а) для соединения два null-значения не считаются «равными»,
но (б) для пересечения они равны (еще одна из очень многих – бесконечно многих? – нелепостей, к которым неизбежно ведут nullзначения). Однако операция TIMES все-таки является частным случаем JOIN, как и в 2VL.
Упражнение 4.15. Вот как формулируются правила. Пусть x – SQLстрока. Предположим для простоты, что x имеет всего два компонента: x1 и x2 (разумеется, в этом порядке слева направо!). Тогда x
IS NULL по определению эквивалентно x1 IS NULL AND x2 IS NULL,
а x IS NOT NULL по определению эквивалентно x1 IS NOT NULL
AND x2 IS NOT NULL. Отсюда следует, что данная строка не является ни null, ни не‑null… Какой вывод вы отсюда сделаете?
Кстати, один из рецензентов заметил, что он никогда не думал, что
о строках можно говорить, как о null-значениях. Но строки – это значения (так же, как значениями являются кортежи и от­но­ше­ния), поэтому идея о том, что строка может быть неизвестной, имеет такое
же право на существование, как идея о неизвестной величине зарплаты. Поэтому, если концепция представления неизвестного значения с помощью null вообще имеет смысл – а лично я считаю, что не
имеет, – то она, безусловно, применима и к строкам (и к таб­лицам,
и вообще к любым мыслимым значениям) точно так же, как к скалярам. И, как следует из этого упражнения, SQL пытается поддержать эту позицию (по крайней мере, для строк, хотя и не для таб­лиц),
правда, безуспешно.1
Упражнение 4.16. Нет. Вот их таб­лицы истинности в 3VL:
NOT
T
U
F

1

IS NOT TRUE
F
U
T

T
U
F

F
T
T

Логично было бы поддержать ее и для таб­лиц, но это не сделано.

397

Глава 4

Упражнение 4.17. Нет. Для определенности рассмотрим случай, когда
x – SQL-строка. Предположим для простоты (как в ответе к упражнению 4.15), что x имеет всего два компонента: x1 и x2. Тогда x IS
NOT NULL по определению эквивалентно x1 IS NOT NULL AND x2
IS NOT NULL, а NOT (x IS NULL) по определению эквивалентно x1
IS NOT NULL OR x2 IS NOT NULL. Какой вывод вы отсюда сделаете?
Упражнение 4.18. Такая трансформация некорректна, и, чтобы убедиться в этом, достаточно рассмотреть, что произойдет, когда EMP.
DNO содержит null (вы удивлены?). Вывод все тот же: и пользователи, и сис­тема могут допус­тить ошибки (и такие ошибки дейст­
вительно случались).
Упражнение 4.19. Этот запрос означает «Получить поставщиков, относительно которых известно, что они не поставляют деталь P2» (обратите внимание на тонкое различие между формулировками «известно, что не поставляют» и «неизвестно, что поставляют»). Это не то же
самое, что ««Получить поставщиков, которые не поставляют деталь
P2». Эти две формулировки не эквивалентны (рассмотрите, например, случай, когда единственное «значение» номера поставщика, соответствующее детали P2 в таб­лице SP, – это null).
Упражнение 4.20. Никакие два из этих трех утверждений не эквивалентны. Утверждение (a) следует из правил трехзначной логики в SQL;
утверждение (b) следует из определения оператора UNIQUE в SQL;
утверждение (c) следует из определения дубликатов в SQL. В частности, если k1 и k2 одновременно равны null, то (a) дает UNKNOWN, (b)
дает FALSE, а (c) дает TRUE. Примечание: для справки приведу правила, на которые ссылаюсь:
•• В трехзначной логике в интерпретации SQL сравнение k1 = k2
дает TRUE, если k1 и k2 отличны от null и равны; FALSE, если k1
и k2 отличны от null и не равны, и UNKNOWN в остальных случаях.
•• В определении SQL-оператора UNIQUE сравнение k1 = k2 дает
TRUE тогда и только тогда, когда k1 и k2 отличны от null и равны,
и FALSE в противном случае.
•• В SQL k1 и k2 называются дубликатами, если (а) они отличны от
null и равны или (б) оба совпадают с null.

Примечание
Выше слово «равны» относится к определению оператора «=» в SQL (несколько своеобразному) (см. главу 2). Дополнительное упражнение: как вы думаете,
эти правила разумны? Обоснуйте свой ответ.

Упражнение 4.21. Результаты выполнения операторов INTERSECT
ALL и EXCEPT ALL могут содержать дубликаты, но только если они
уже присутствовали в исходных операндах.

398

Приложение С. Ответы к упражнениям

Упражнение 4.22. Да! (Мы не хотим дубликатов в базе данных, но это
не означает, что мы не хотим дубликатов и в других местах.)
Упражнение 4.23. Очень хороший вопрос.

Глава 5
Упражнение 5.1. В некоторых от­но­ше­ниях кортеж дейст­вительно напоминает запись, а атрибут – поле, но это сходство очень приблизительное. Пе­ре­мен­ную-от­но­ше­ние следует рассматривать не просто как файл, а как «файл с определенной дисциплиной». Эта дисциплина приводит к существенному упрощению структуры данных,
видимой пользователю, а, значит, и к соответственному упрощению
операторов, необходимых для работы с этими данными, и пользовательского интерфейса в целом. Так в чем же состоит эта дисциплина? В том, что записи не упорядочены сверху вниз; что поля не упорядочены слева направо; что отсутствуют null-значения; что нет повторяющихся групп; что нет указателей; что нет анонимных полей
(и т. д. и т. п.). Отчасти поэтому гораздо лучше трактовать пе­ре­мен­
ную-от­но­ше­ние следующим образом: заголовок представляет некоторый предикат, а тело в конкретный момент времени представляет
экстенцию этого предиката в этот момент.
Упражнение 5.2. Говоря неформально, эта фраза означает «Обновить
атрибут STATUS в кортежах для всех поставщиков в Лондоне». Но
кортежи (а тем более значения атрибутов кортежей) – это значения,
и их просто невозможно обновить по определению. Поэтому вот более точная формулировка:
•• Пусть от­но­ше­ние s – текущее значение пе­ре­мен­ной-от­но­ше­ния S.
•• Пусть ls – ограничение s, для которого значением CITY является Лондон.
•• Пусть ls′ – от­но­ше­ние, идентичное ls с тем отличием, что значение STATUS в каждом кортеже такое, как указано в операции
UPDATE.
•• Пусть s′ – от­но­ше­ние, обозначенное выражением (s MINUS ls)
UNION ls′.
•• Тогда s′ присваивается S.
Упражнение 5.3. Потому что реляционные операции принципиально определены над множес­твами, а операции «позиционного обновления» в SQL – над кортежами (точнее, строками). Хотя операции
над множес­твами кардинальности 1 иногда – быть может, даже часто – допус­тимы, они могут и не сработать. Например, операции обновления кортежа могут некоторое время работать нормально, а потом – после изменения ограничения целостности – перестать.

399

Глава 5

Упражнение 5.4. Эти предложения не эквивалентны. Для первого исходной является таб­лица t1, обозначенная заданным таб­личным подзапросом; для второго исходной является таб­лица t2, содержащая
только строку, обозначенную заданным однострочным подзапросом
(то есть аргументом VALUES). Если таб­лица S включает строку для
поставщика S6, то t1 и t2 идентичны. Но если S не включает такую
строку, то t1 пус­та, а t2 содержит строку, состоящую только из null.
Упражнение 5.5. Принцип присваивания утверждает, что после присваивания значения v пе­ре­мен­ной V сравнение V = v должно возвращать TRUE. SQL нарушает этот принцип в случае, если «v есть
null», он также нарушает его для некоторых присваиваний символьных строк и, безусловно, нарушает для любого типа, для которого
не определен оператор «=», в том числе для типа XML, а также (вообще говоря) для некоторых пользовательских типов. Негативные последствия: их слишком много, чтобы перечислить здесь.
Упражнение 5.6. Как и в тексте главы, в следующих определениях
я предполагаю, что определены некоторые пользовательские типы:
CREATE TABLE TAX_BRACKET
( LOW
MONEY NOT NULL ,
HIGH
MONEY NOT NULL ,
PERCENTAGE INTEGER NOT NULL ,
UNIQUE ( LOW ) ,
UNIQUE ( HIGH ) ,
UNIQUE ( PERCENTAGE ) ) ;
CREATE TABLE ROSTER
( DAY DAY_OF_WEEK NOT NULL ,
HOUR TIME_OF_DAY NOT NULL ,
GATE GATE
NOT NULL ,
PILOT NAME
NOT NULL ,
UNIQUE ( DAY, HOUR, GATE ) ,
UNIQUE ( DAY, HOUR, PILOT ) ) ;
CREATE TABLE MARRIAGE
( SPOUSE_A
NAME NOT NULL ,
SPOUSE_B
NAME NOT NULL ,
DATE_OF_MARRIAGE DATE NOT NULL ,
UNIQUE ( SPOUSE_A, DATE_OF_MARRIAGE ) ,
UNIQUE ( DATE_OF_MARRIAGE, SPOUSE_B ) ,
UNIQUE ( SPOUSE_B, SPOUSE_A ) ) ;

Упражнение 5.7. Поскольку ключи представляют собой ограничения,
а ограничения применяются к пе­ре­мен­ным, а не к значениям. (Но
при этом, безусловно, допус­тимо, а иногда и полезно, рассматривать подмножес­тво k заголовка от­но­ше­ния r так, будто оно является «ключом для r», если это подмножес­тво уникально и неприводимо относительно кортежей r. Однако, строго говоря, такой взгляд

400

Приложение С. Ответы к упражнениям

некорректен, может стать причиной путаницы и, конечно, гораздо
менее полезен, чем взгляд на ключи, как на принадлежность пе­ре­
мен­ных-от­но­ше­ний, а не от­но­ше­ний.)
Упражнение 5.8. Вот еще одна. Предположим, что пе­ре­мен­ная-от­но­ше­
ние A обладает «неприводимым ключом», который состоит из дизъюнктного объединения K и X, где K и X – подмножес­тва заголовка A,
и K – настоящий ключ. Тогда пе­ре­мен­ная-от­но­ше­ние A удовлетворяет функциональной зависимости K → X. Предположим далее, что
пе­ре­мен­ная-от­но­ше­ние B имеет внешний ключ, ссылающийся на
этот «неприводимый ключ» A. Тогда B тоже удовлетворяет функциональной зависимости K → X; в результате B, вероятно, обладает некоторой избыточностью (фактичес­ки, она, скорее всего, не находится в нормальной форме Бойса-Кодда).
Упражнение 5.9. Ключи – это множес­тва атрибутов (фактичес­ки каждый ключ представляет собой некоторое подмножес­тво заголовка),
а значениями ключей по определению являются кортежи, даже если
эти кортежи состоят из одного атрибута. Так, например, ключом пе­
ре­мен­ной-от­но­ше­ния «детали» P является {PNO}, а не просто PNO,
и значением этого ключа для детали P1 является кортеж TUPLE
{PNO ‘P1’}, а не просто ‘P1’.
Упражнение 5.10. Пусть m – наименьшее целое число, большее или
равное n/2. R будет будет иметь максимально возможное количество
ключей, если либо (а) каждое различающееся множес­тво m атрибутов есть ключ, либо (б) n нечетно и каждое различающееся множес­
тво m-1 атрибутов есть ключ. В любом случае отсюда следует, что
максимальное поличество ключей R равно n!/(m!*(n-m)!). Примечание: выражение n! произносится либо как «n факториал», либо как
«факториал n» и по определению равно произведению n * (n-1) * ...
* 2 * 1. Обе пе­ре­мен­ные-от­но­ше­ния TAX_BRACKET и MARRIAGE
(см. упражнение 5.6) дают примеры пе­ре­мен­ных-от­но­ше­ний с максимально возможным количеством ключей, как и любая пе­ре­мен­
ная-от­но­ше­ние степени 0. (Если n = 0, то эта формула принимает вид
0!/(0!*0!), а 0! равно 1.)
Упражнение 5.11. Суперключом называется подмножес­тво заголовка,
обладающее свойством уникальности. Ключом называется суперключ, обладающий свойством неприводимости. Все ключи являются суперключами, но «большинство» суперключей ключами не являются.
Понятие подключа может быть полезно при изучении нормализации. Вот его определение. Пусть X – подмножес­тво заголовка пе­ре­
мен­ной-от­но­ше­ния R; тогда X называется подключом R, если существует такой ключ K для R, что K является надмножес­твом X. Например, каждое из следующих подмножес­тв является подключом
пе­ре­мен­ной-от­но­ше­ния SP: {SNO,PNO}, {SNO}, {PNO} и {}. Отмечу,

401

Глава 5

что пус­тое множес­тво {} является подключом любой допус­тимой пе­
ре­мен­ной-от­но­ше­ния R.
Упражнение 5.12. Пример данных:
EMP

ENO

MNO

E4
E3
E2
E1

E2
E2
E1
E1

Здесь я допус­тил, что некоторый служащий (а именно E1) является собственным начальником, – это один из способов избежать nullзначений в подобной ситуации. Другой и, пожалуй, более удачный
подход – выделить такие связи в отдельную пе­ре­мен­ную-от­но­ше­ние,
исключив из нее тех служащих, у которых нет начальников.
EMP ENO
E4
E3
E2
E1

...
...
...
...
...

EM

ENO

MNO

E4
E3
E2

E2
E2
E1

Дополнительное упражнение: Каковы предикаты пе­ре­мен­ной-от­но­
ше­ния EM и обоих вариантов пе­ре­мен­ной-от­но­ше­ния EMP? (Хорошенько подумав над этим упражнением, вы обнаружите добавочные
доводы в пользу второй структуры.)
Упражнение 5.13. Потому что в ней нет необходимости, учитывая, что
соответствие между столбцами устанавливается исходя из номера
позиции, а не имени. См. обсуждение в тексте главы.
Упражнение 5.14. Отметим, что такая ситуация по определению должна представлять взаимно однозначное соответствие. Один очевидный случай возникает, когда мы расщепляем некоторую пе­ре­мен­
ную-от­но­ше­ние «по вертикали», как в следующем примере:
VAR SNT BASE RELATION
{ SNO SNO, SNAME NAME, STATUS INTEGER }
KEY { SNO }
FOREIGN KEY { SNO } REFERENCES SC ;
VAR SC BASE RELATION
{ SNO SNO, CITY CHAR }
KEY { SNO }
FOREIGN KEY { SNO } REFERENCES SNT ;

Одним из последствий такого решения является то, что нам, скорее
всего, понадобится механизм для одновременного обновления двух
или более пе­ре­мен­ных-от­но­ше­ний (и, возможно, для одновременно-

402

Приложение С. Ответы к упражнениям

го определения двух или более пе­ре­мен­ных-от­но­ше­ний). См. обсуждение множес­твенного присваивания в главе 8.
Упражнение 5.15. В Tutorial D определения выглядят следующим образом (для этого упражнения я предполагаю, что Tutorial D поддерживает ссылочные дейст­вия CASCADE и NO CASCADE):
VAR P BASE RELATION { PNO ... , ... } KEY { PNO } ;
VAR PP BASE RELATION { MAJOR_PNO ... , MINOR_PNO ... , QTY ... }
KEY { MAJOR_PNO , MINOR_PNO }
FOREIGN KEY { MAJOR_PNO } REFERENCES P
RENAME ( PNO AS MAJOR_PNO ) ON DELETE CASCADE
FOREIGN KEY { MINOR_PNO } REFERENCES P
RENAME ( PNO AS MINOR_PNO ) ON DELETE NO CASCADE ;

При таких определениях удаление детали каскадно распространяется на те кортежи, в которых номер этой детали встречается в качестве значения атрибута MAJOR_PNO (то есть данная деталь включает другие детали в качестве компонентов), но не на кортежи, в которых номер этой детали встречается в качестве значения атрибута
MINOR_PNO (то есть данная деталь выступает только в роли компонентов других деталей).
Определения на SQL:
CREATE TABLE P ( PNO ... , ... , UNIQUE ( PNO ) ) ;
CREATE TABLE PP ( MAJOR_PNO ... , MINOR_P# ... , QTY ... ,
UNIQUE ( MAJOR_PNO , MINOR_PNO ) ,
FOREIGN KEY ( MAJOR_PNO ) REFERENCES P ( PNO )
ON DELETE CASCADE ,
FOREIGN KEY ( MINOR_PNO ) REFERENCES P
ON DELETE RESTRICT ) ;

Упражнение 5.16. Очевидно, что исчерпывающий ответ на этот вопрос
дать невозможно. Упомяну лишь ссылочные дейст­вия, поддерживаемые в стандарте: NO ACTION (по умолчанию), CASCADE, RESTRICT,
SET DEFAULT и SET NULL. Дополнительное упражнение: в чем разница между NO ACTION и RESTRICT?
Упражнение 5.17. Предикат – это функция, возвращающая значение
истинности; высказывание – это предикат без параметров. Некоторые примеры имеются в тексте главы, а в главе 10 приведены дополнительные примеры и общее обсуждение этих понятий.
Упражнение 5.18. Пе­ре­мен­ная-от­но­ше­ние P: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес
WEIGHT и хранится на складе в городе CITY. Пе­ре­мен­ная-от­но­ше­
ние SP: поставщик SNO поставляет деталь PNO в количестве QTY.
Упражнение 5.19. Интенцией пе­ре­мен­ной-от­но­ше­ния R называется ее
предполагаемая интерпретация. Экстенцией пе­ре­мен­ной-от­но­ше­ния

403

Глава 5

R в некоторый момент времени называется множес­тво кортежей, составляющих R в этот момент.
Упражнение 5.20. Без ответа.
Упражнение 5.21. Допущение замкнутости мира говорит (неформально), что все утверждаемое или вытекающее из содержимого базы
данных истинно, а все остальное ложно. А допущение открытости
мира (да, есть и такое) говорит, все утверждаемое или вытекающее
из содержимого базы данных истинно, а все остальное неизвестно.
Как легко видеть, допущение открытости мира прямо приводит
к требованию разрешить null-значения и к трехзначной логике, поэтому всячес­ки осуждается. Дополнительную информацию можно
найти в статье «The Closed World Assumption» (см. приложение D).
Упражнение 5.22. Сказать, что пе­ре­мен­ная-от­но­ше­ние R имеет пус­той
ключ, – все равно, что сказать, что R не может содержать более одного кортежа (поскольку у всех кортежей одинаковы значения пус­того
множес­тва атрибутов – пус­тое множес­тво; поэтому если бы R содержала два или более кортежей, то мы имели бы нарушение уникальности ключа). А ограничение, состоящее в недопус­тимости более одного кортежа, безусловно, может оказаться полезным. Придумывание такой ситуации оставляю вам в качестве дополнительного
упражнения.
Упражнение 5.23. См. ответ к упражнению 5.17.
Упражнение 5.24. Вопрос, несомненно, имеет смысл, учитывая, что
с каждой пе­ре­мен­ной-от­но­ше­нием ассоциирован некоторый предикат. Однако, какой именно предикат соответствует конкретной пе­
ре­мен­ной-от­но­ше­нию, известно лишь тому, кто ее определил (и, хочется надеяться, пользователю). Например, если я определю пе­ре­
мен­ную-от­но­ше­ние C следующим образом:
VAR C BASE RELATION { CITY CHAR } KEY { CITY } ;

то ей может соответствовать чуть ли не любой предикат! Например:
CITY – город в Калифорнии; CITY – город, в котором находится
хотя бы один поставщик; CITY – столица некоторого государства
и так далее1. Точно так же предикатом пе­ре­мен­ной-от­но­ше­ния степени 0
VAR Z BASE RELATION { } KEY { } ;

может быть «почти все, что угодно», с единственным условием (поскольку эта пе­ре­мен­ная-от­но­ше­ние не имеет атрибутов и, стало быть,
соответствующий ей предикат не имеет параметров), что предикат
1

Или даже CITY – прозвище чьего-то любимого плюшевого медвежонка.
В определении пе­ре­мен­ной-от­но­ше­ния CITY нет ничего, свидетельствующего о том, что это город.

404

Приложение С. Ответы к упражнениям

должен быть вырожденным, то есть высказыванием. Это высказывание должно принимать значение TRUE, если Z равно TABLE_DEE,
и FALSE, если Z равно TABLE_DUM.
Кстати, отмечу, что пе­ре­мен­ная-от­но­ше­ние Z имеет пус­той ключ; однако не следует делать вывод, будто пус­тые ключи могут быть только
у пе­ре­мен­ных-от­но­ше­ний степени 0 (см. ответ к упражнению 5.22).
Упражнение 5.25. Конечно, нет. На самом деле, «большинство» от­но­
ше­ний не являются значениями некоторой пе­ре­мен­ной-от­но­ше­ния.
В качестве тривиального примера отметим, что от­но­ше­ние, обозначенное {CITY}, то есть проекция S на CITY, не является значением
никакой пе­ре­мен­ной-от­но­ше­ния в базе данных о поставщиках и деталях. Поэтому в этой книге, говоря об от­но­ше­ниях, я, конечно, не
имею в виду только от­но­ше­ния, являющиеся значениями некоторой
пе­ре­мен­ной-от­но­ше­ния.

Глава 6
Прежде всего, приведу ответы на два упражнения, которые были включены в текст главы. В первом спрашивалось, в чем разница (при наших
обычных тестовых данных) между выражениями S JOIN (P{PNO}) и (S
JOIN P){PNO}. Ответ: Так как S и P{PNO} не имеют общих атрибутов,
то первое выражение представляет собой декартово произведение; для
нашего примера результат содержит все возможные комбинации кортежа поставщика с кортежем номера детали (говоря неформально), и его
кардинальность равна 30. Второе выражение дает номера тех деталей,
который находятся в одном городе с некоторым поставщиком (опять же,
говоря неформально); для нашего примера результат содержит все номера деталей, кроме P3.
Во втором упражнении спрашивалось, в чем разница между эквисоединением и естественным соединением. Ответ: Пус­ть соединяемые от­
но­ше­ния называются r1 и r2, и предположим для простоты, что r1 и r2
имеют ровно один общий атрибут A. Прежде чем выполнить эквисоединение, необходимо произвести некоторое переименование. Для определенности применим переименование к r2: r3 = r2 RENAME (A AS B).
Тогда эквисоединение определяется как (r1 TIMES r3) WHERE A = B.
Отметим, в частности, что и A, и B – атрибуты результата, и что в каждом кортеже результата значения этих атрибутов одинаковы. Если
спроецировать результат на множес­тво всех атрибутов, кроме B, то получится естественное соединение r1 JOIN r2.
Упражнение 6.1. a. Результат содержит столбцы с повторяющимися
именами (а также столбцы упорядочены слева направо). b. Столбцы
результата упорядочены слева направо. c. Результат содержит неименованный столбец (а также столбцы упорядочены слева направо).
d. Результат содержит строки-дубликаты. e. Синтаксичес­кая ошиб-

405

Глава 6

ка SQL: в S NATURAL JOIN P нет столбца с именем S.CITY.1 f. Все
правильно. g. Результат содержит строки-дубликаты, и столбцы
упорядочены слева направо; кроме того, отсутствует столбец с именем SNO, что может вызвать недоумение.
Упражнение 6.2. Нет! В частности, некоторые операции реляционного
деления, которые могли бы предположительно завершиться ошибкой, на самом деле выполняются успешно. Вот несколько примеров
(для понимания которых необходимо сначала прочитать соответствующий раздел в главе 7):
a. Пусть от­но­ше­ние PZ имеет тип RELATION {PNO PNO}, и пус­ть его
тело пус­то. Тогда выражение
SP { SNO , PNO } DIVIDEBY PZ { PNO }

сводится к проекции SP{SNO} от­но­ше­ния SP на SNO.
b. Пусть z – либо TABLE_DEE, либо TABLE_DUM. Тогда выражение
r DIVIDEBY z

сводится к r JOIN z.
c. Пусть от­но­ше­ния r и s одного типа. Тогда выражение
r DIVIDEBY s

дает TABLE_DEE, если r непус­то и каждый кортеж s встречается
в r, и TABLE_DUM – в противном случае.
d. Наконец, r DIVIDEBY r дает TABLE_DUM, если r пус­то, и TABLE_
DEE – в противном случае.
Упражнение 6.3. Соединение производится по атрибутам SNO, PNO
и CITY. Результат выглядит следующим образом:
SNO

SNAME STATUS

CITY

PNO

QTY

PNAME COLOR

S1
S1
S1
S2
S3
S4

Smith
Smith
Smith
Jones
Blake
Clark

London
London
London
Paris
Paris
London

P1
P4
P6
P2
P2
P4

300
200
100
400
200
200

Nut
Screw
Cog
Bolt
Bolt
Screw

20
20
20
10
30
20

Red
Red
Red
Green
Green
Red

WEIGHT
12.0
14.0
19.0
17.0
17.0
14.0

Предикат звучит так: поставщик SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY;
деталь PNO используется на предприятии, называется PNAME,
1

Нет также и столбца с именем S.SNO (есть столбец с неквалифицированным именем SNO); однако существует странное синтаксичес­кое правило,
позволяющее обращаться к этому столбцу без уточнения имени. (Я говорю странное, потому что его очень трудно точно сформулировать, а, кроме
того, оно противоречит интуиции и логичес­ки некорректно.)

406

Приложение С. Ответы к упражнениям

имеет цвет COLOR и вес WEIGHT и хранится на складе в городе
CITY; поставщик SNO поставляет деталь PNO в количестве QTY.
Отметим, что оба вхождения SNO в этот предикат относятся к одному и тому же параметру, равно как оба вхождения PNO и оба вхождения CITY.
Простейшая формулировка на SQL выглядит так:
S NATURAL JOIN SP NATURAL JOIN P

(хотя в зависимости от контекста может оказаться необходимым поставить впереди «SELECT * FROM»).
Упражнение 6.4. В декартовых координатах на двумерной плоскости
точки (x,0) и (0,y) являются проекциями точки (x,y) на оси X и Y соответственно; эквивалентно, (x) и (y) – проекции точки (x,y) двумерного пространства на некоторые одномерные пространства. Эти понятия легко обобщаются на n измерений (напомню (см. главу 3), что
от­но­ше­ния в дейст­вительности n-мерны).
Упражнение 6.5.
a. SQL-аналог:
SELECT DISTINCT CITY
FROM S NATURAL JOIN SP
WHERE PNO = ‘P2’

Примечание
Здесь и далее приводимые мной SQL-выражения не обязательно являются прямыми транслитерациями соответствующих алгебраичес­ких аналогов, а чаще
«более естественными» формулировками запроса в терминах SQL.

Предикат: город CITY таков, что в нем находится некоторый
поставщик, поставляющий деталь P2.
CITY
London
Paris

b. SQL-аналог:
SELECT
FROM
WHERE
(

*
P
PNO NOT IN
SELECT PNO
FROM SP
WHERE SNO = ‘S2’ )

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес WEIGHT, хранится на
складе в городе CITY и не поставляется поставщиком S2.

407

Глава 6
PNO

PNAME COLOR

P3
P4
P5
P6

Screw
Screw
Cam
Cog

Blue
Red
Blue
Red

WEIGHT
17.0
14.0
12.0
19.0

CITY
Oslo
London
Paris
London

c. SQL-аналог:
SELECT
FROM
EXCEPT
SELECT
FROM

CITY
S
CORRESPONDING
CITY
P

Предикат: город CITY таков, что в нем находится некоторый
поставщик, но не хранится ни одной детали.
CITY
Athens

d. SQL-аналог:
SELECT SNO , PNO
FROM S NATURAL JOIN P

Примечание
В версии на Tutorial D тоже нет необходимости проецировать S и P. Как вы думаете, может ли оптимизатор игнорировать их?

Предикат: поставщик SNO и деталь PNO находятся в одном городе.
SNO

PNO

S1
S1
S1
S2
S2
S3
S3
S4
S4
S4

P1
P4
P6
P2
P5
P2
P5
P1
P4
P6

e. SQL-аналог:
SELECT S.CITY AS SC , P.CITY AS PC
FROM S , P

Предикат: некоторый поставщик находится в городе SC, и некоторая деталь находится в городе PC.

408

Приложение С. Ответы к упражнениям
SC

PC

London
London
London
Paris
Paris
Paris
Athens
Athens
Athens

London
Paris
Oslo
London
Paris
Oslo
London
Paris
Oslo

Упражнение 6.6. Пересечение и декартово произведение – частные
случаи соединения, поэтому здесь их можно игнорировать. Коммутативность объединения и соединения следует непосредственно из
того, что оба от­но­ше­ния встречаются в определении симметричным
образом. Далее я докажу, что объединение ассоциативно. Пусть t –
кортеж. Считая, что символ «≡» обозначает «тогда и только тогда»,
а символ «∈» – «входит в», имеем:
t ∈ r UNION (s UNION u) ≡





t ∈ r OR t ∈ (s UNION u)
t ∈ r OR (t ∈ s OR t ∈ u)
(t ∈ r OR t ∈ s) OR t ∈ u
t ∈ (r UNION s) OR t ∈ u
t ∈ (r UNION s) UNION u

Обратите внимание, что в третьей строчке используется ассоциативность операции OR. Доказательство ассоциативности соединения
аналогично.
Что касается SQL, то, прежде всего, забудем о существовании null
и строк-дубликатов (а что случится, если не забудем?). Тогда:
•• SELECT A, B FROM T1 UNION CORRESPONDING SELECT B, A
FROM T2 и SELECT B, A FROM T2 UNION CORRESPONDING
SELECT A, B FROM T1 не эквивалентны, поскольку дают разные
результаты с точки зрения упорядочения столбцов слева направо.
Таким образом, объединение в SQL, вообще говоря, не коммутативно (и то же самое относится к пересечению).
•• T1 JOIN T2 и T2 JOIN T1 не эквивалентны (в общем случае), поскольку дают разные результаты с точки зрения упорядочения
столбцов слева направо. Таким, образом соединение в SQL, вообще говоря, не коммутативно (и то же самое относится к произведению).
Однако все операторы ассоциативны.
Упражнение 6.7. Только RENAME.
Упражнение 6.8. Декартово произведение одной таб­лицы t по определению совпадает с t. Однако вопрос о том, что такое произведение t1
и t2 в случае, когда t1 и t2 содержат строки-дубликаты, не прост! Дополнительное обсуждение см. в ответе к упражнению 4.4.

409

Глава 6

Упражнение 6.9. Как обычно, Tutorial D слева, SQL справа:1
a. SP

| SELECT * FROM SP
| or
| TABLE SP /* см. главу 12 */

b. ( SP WHERE PNO = ‘P1’ )
{ SNO }

c. S WHERE STATUS ≥ 15

AND STATUS ≤ 25

d. ( ( S JOIN SP )

| SELECT SNO
| FROM SP
| WHERE PNO = ‘P1’
| SELECT *
| FROM S
| WHERE S.STATUS BETWEEN
|
15 AND 25

| SELECT
WHERE CITY = ‘London’ ) | FROM
{ PNO } | WHERE
| AND

DISTINCT PNO
SP, S
SP.SNO = S.SNO
S.CITY = ‘London’

e. P { PNO } MINUS

| SELECT PNO
( ( S JOIN SP )
| FROM P
WHERE CITY = ‘London’ ) | EXCEPT CORRESPONDING
{ PNO } | SELECT PNO
| FROM SP , S
| WHERE SP.SNO = S.SNO
| AND
S.CITY = ‘London’

f. WITH SP { SNO , PNO } AS Z :

| SELECT DISTINCT
( ( Z RENAME ( PNO AS X ) )
|
JOIN
| FROM SP AS XX
( Z RENAME ( PNO AS Y ) ) ) | WHERE XX.SNO =
{ X , Y }
|

g. ( S WHERE STATUS <

| SELECT
STATUS FROM ( TUPLE FROM ( S | FROM
WHERE SNO = ‘S1’ ) ) ) { SNO } | WHERE
|
(
|
|

SNO
S
STATUS
SELECT
FROM
WHERE

XX.PNO AS X ,
YY.PNO AS Y
, SP AS YY
YY.SNO

<
STATUS
S
SNO = ‘S1’ )

Напомню (см. главу 3), что в Tutorial D выражение STATUS FROM
(TUPLE FROM r) извлекает значение STATUS из единственного кортежа от­но­ше­ния r (это от­но­ше­ние r должно иметь кардинальность 1). А в SQL-версии этого запроса производится двойное
приведение типа: сначала таб­лица из одной строки приводится

1

Вообще говоря, эти решения не единственные. В частности, решение на Tu­
torial D часто можно улучшить с помощью операторов, описанных в главе 7.

410

Приложение С. Ответы к упражнениям

к типу этой строки, а затем строка приводится к типу того единственного скалярного значения, которое содержит.
h. WITH ( S WHERE CITY =
‘London’ ) AS RX ,
( SP RENAME ( PNO AS Y ) )
AS RY :
( P WHERE RX { SNO } ⊆
( RY WHERE Y = PNO )
{ SNO } ) { PNO }

|
|
|
|
|
|
|
|
|
|
|

SELECT
FROM
WHERE
SELECT
WHERE
AND
SELECT
FROM
WHERE
AND

DISTINCT SPX.PNO
SP AS SPX
NOT EXISTS (
S.SNO FROM S
S.CITY = ‘London’
NOT EXISTS (
SPY.*
SP AS SPY
SPY.SNO = S.SNO
SPY.PNO =
SPX.PNO ) )

Обратите внимание на реляционное сравнение в выражении Tu­
torial D. В SQL-версии используется оператор EXISTS (см. главу 10).
i. ( S { SNO } JOIN P { PNO } ) | SELECT SNO , PNO
MINUS SP { SNO , PNO }

|
|
|
|

j. WITH ( SP WHERE SNO = ‘S2’ )
AS RA ,
( SP RENAME ( SNO AS X ) )
AS RB :
S WHERE ( RB WHERE X = SNO )
{ PNO } ⊇ RA { PNO }

FROM
EXCEPT
SELECT
FROM
|
|
|
|
|
|
|
|
|
|
|
|

S, P
CORRESPONDING
SNO , PNO
SP

SELECT
WHERE
SELECT
WHERE
SELECT
WHERE
AND
OR
SELECT
FROM
WHERE
AND

S.SNO FROM S
NOT EXISTS (
P.* FROM P
NOT EXISTS (
SP.* FROM SP
SP.PNO = P.PNO
SP.SNO = ‘S2’ )
EXISTS (
SP.*
SP
SP.PNO = P.PNO
SP.SNO = S.SNO ) )

Упражнение 6.10. Интуитивно очевидно, что все три утверждения верны. Более подробного ответа не будет.
Упражнение 6.11. Объединение не является идемпотентной операцией
в SQL, потому что выражение SELECT R.* FROM R UNION CORRES­
PONDING SELECT R.* FROM R не является тождественно равным
SELECT R.* FROM R. Дело в том, что если R содержит дубликаты,
то из объединения они удаляются. (А что, если R содержит nullзначения? Хороший вопрос!)
Соединение идемпотентно, а потому идемпотентны также пересечение и декартово произведение – во всех случаях. Но только в реляционной модели, а не в SQL, опять же благодаря дубликатам и nullзначениям.

411

Глава 6

Упражнение 6.12. Выражение r{} обозначает проекцию r на пус­тое
множес­тво атрибутов; оно возвращает TABLE_DUM, если r пус­
то и TABLE_DEE в противном случае. Ответ на вопрос «Каков соответствующий предикат?» зависит от предиката r. Например, предикат SP{} (не вполне формально) звучит так: существует поставщик SNO, существует деталь PNO и существует количество QTY
такие, что поставщик с номером SNO поставляет деталь PNO
в количестве QTY. Отметим, что этот предикат, по существу, является высказыванием; если SP пус­то (и в этом случае SP{} совпадает с TABLE_DUM), то он принимает значение FALSE, в противном
случае (и тогда SP{} совпадает с TABLE_DEE) он принимает значение TRUE.
Выражение r{ALL BUT} обозначает проекцию r на все свои атрибуты
(другими словами, тождественную проекцию r); оно возвращает r.
Упражнение 6.13. Полагаю, что DB2 и Ingres выполняют такую оптимизацию. Возможно, другие продукты тоже.
Упражнение 6.14. Это выражение означает «Получить поставщиков,
которые поставляют все фиолетовые детали». Конечно, смысл в том,
что фиолетовых деталей нет вовсе (в наших тестовых данных). Выражение правильно возвращает всех пятерых поставщиков. Дальнейшее объяснение (в частности, того, почему этот ответ правилен) см.
в главе 11.
Упражнение 6.15. Приближенный эквивалент на SQL мог бы выглядеть так:
SELECT CITY
FROM ( SELECT CITY FROM S
UNION CORRESPONDING
SELECT CITY FROM P ) AS TEMP
WHERE NOT EXISTS
( SELECT
CITY FROM S
INTERSECT CORRESPONDING
SELECT
CITY FROM P )

Однако это SQL-выражение не является точным эквивалентом реляционной версии. Точнее, если множес­тва городов поставщиков и городов деталей пересекаются, то выражение будет вычислено успешно, но просто вернет пус­той результат. Примечание: В этом примере
специ­фикации CORRESPONDING можно было бы опус­тить без ущерба для смысла (почему?), но проще всегда задавать CORRESPONDING;
даже если это логичес­ки излишне, – вреда не будет.
Упражнение 6.16. Два от­но­ше­ния r1 и r2 можно соединить тогда и только тогда, когда они соединяемые, то есть в случае, когда одноименные атрибуты имеют одинаковые типы (эквивалентно, тогда и только тогда, когда теоретико-множес­твенное объединение их заголов-

412

Приложение С. Ответы к упражнениям

ков является корректным заголовком). Это двуместный случай.
Обобщить на n-местный случай тоже несложно: от­но­ше­ния r1, r2, ...,
rn (n > 0) называются соединяемыми тогда и только тогда, когда для
любых i, j (1 ≤ i ≤ n, 1 ≤ j ≤ n) от­но­ше­ния ri и rj соединяемые.
Упражнение 6.17. Определение n-местных вариантов возможно благодаря коммутативности и ассоциативности операций JOIN, UNION
и D_UNION.
SQL поддерживает n-местные варианты соединения и объединения,
правда, не для случая n < 2. Синтаксис выглядит так:
t1 NATURAL JOIN t2
NATURAL JOIN t3
..........
NATURAL JOIN tn
SELECT * FROM t1 UNION CORRESPONDING SELECT * FROM t2
UNION CORRESPONDING SELECT * FROM t3
...............................
UNION CORRESPONDING SELECT * FROM tn

n-местный вариант MINUS не имеет смысла, так как операция
MINUS не является ни коммутативной, ниассоциативной.
Упражнение 6.18. Краткое обоснование см. в ответе к упражнению 6.12.
Развернутое таково. Рассмотрим проекцию пе­ре­мен­ной-от­но­ше­ния
«поставщики» S на SNO – S{SNO}. Назовем результат этой проекции r; для наших тестовых данных r состоит из пяти кортежей. Теперь рассмотрим проекцию от­но­ше­ния r на пус­тое множес­тво атрибутов – r{}. Ясно, что проецирование любого кортежа «ни на что»
дает пус­тое множес­тво; следовательно, любой кортеж r порождает
пус­той кортеж при проецировании r на пус­тое множес­тво атрибутов.
Но все пус­тые кортежи – дубликаты друг друга, поэтому проецирование от­но­ше­ния r, содержащего 5 кортежей, на пус­тое множес­тво
атрибутов дает от­но­ше­ние без атрибутов с одним (пус­тым) кортежем,
иначе говоря, TABLE_DEE.
Теперь вспомним, что с любой пе­ре­мен­ной-от­но­ше­нием ассоциирован предикат. Для пе­ре­мен­ной-от­но­ше­ния S этот предикат звучит
так:
Поставщик SNO связан контрактом, называется SNAME,
имеет статус STATUS и находится в городе CITY.
Для проекции r = S{SNO} предикат такой:
Существует некоторое название SNAME и некоторый статус STATUS и некоторый город CITY такие, что поставщик
SNO связан контрактом, называется SNAME, имеет статус STATUS и находится в городе CITY.
А для проекции r{} – такой:

413

Глава 7

Существует некоторый номер поставщика SNO, некоторое
название SNAME и некоторый статус STATUS и некоторый
город CITY такие, что поставщик SNO связан контрактом,
называется SNAME, имеет статус STATUS и находится
в городе CITY.
Обратите внимание, что последний предикат фактичес­ки является высказыванием: он безусловно принимает значение TRUE или
FALSE. В рассматриваемом случае r{} совпадает с TABLE_DEE,
и предикат (высказывание) обращается в TRUE. Но предположим,
что в некоторый момент времени в базе данных вообще нет поставщиков. Тогда S{SNO} даст пус­тое от­но­ше­ние r, r{} будет равно TABLE_
DUM, и предикат (высказывание) обратится в FALSE.

Глава 7
Упражнение 7.1.
a. SQL-аналог:
SELECT
FROM
WHERE
(

*
S
SNO IN
SELECT SNO
FROM SP
WHERE PNO = ‘P2’ )

Примечание
Здесь и далее приводимые мной SQL-выражения не обязательно являются прямыми транслитерациями соответствующих алгебраичес­ких аналогов, а чаще
«более естественными» формулировками запроса в терминах SQL.

Предикат: поставщик SNO связан контрактом, называется
SNAME, имеет статус STATUS, находится в городе CITY и поставляет деталь P2.
SNO

SNAME STATUS

CITY

S1
S2
S3
S4

Smith
Jones
Blake
Clark

London
Paris
Paris
London

20
10
30
20

b. SQL-аналог:
SELECT
FROM
WHERE
(

*
S
SNO NOT IN
SELECT SNO
FROM SP
WHERE PNO = ‘P2’ )

414

Приложение С. Ответы к упражнениям

Предикат: поставщик SNO связан контрактом, называется
SNAME, имеет статус STATUS, находится в городе CITY и не
поставляет деталь P2.
SNO

SNAME STATUS

CITY

S5

Adams

Athens

30

c. SQL-аналог:
SELECT
FROM
WHERE
(

*
P AS PX
NOT EXISTS
SELECT *
FROM S AS SX
WHERE NOT EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO ) )

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес WEIGHT, хранится на
складе в городе CITY и поставляется всеми поставщиками.
PNO

PNAME COLOR

WEIGHT CITY

d. SQL-аналог:
SELECT *
FROM P
WHERE ( SELECT COALESCE ( SUM ( QTY ) , 0 )
FROM SP
WHERE SP.PNO = P.PNO ) < 500

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес WEIGHT, хранится на
складе в городе CITY и ее суммарное количество, поставляемое
всеми поставщиками, меньше 500.
PNO

PNAME COLOR

P3
P6

Screw
Co g

WEIGHT

Blue
Red

e. SQL-аналог:
SELECT
FROM
WHERE
(

*
P
CITY IN
SELECT CITY
FROM S )

17.0
19.0

CITY
Oslo
London

415

Глава 7

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес WEIGHT, хранится на
складе в городе CITY и находится в том же городе, что некоторый поставщик.
PNO

PNAME COLOR

P1
P2
P4
P5
P6

Nut
Bolt
Screw
Cam
Cog

Red
Green
Red
Blue
Red

WEIGHT
12.0
17.0
14.0
12.0
19.0

CITY
London
Paris
London
Paris
London

f. SQL-аналог:
SELECT S.* , ‘Supplier’ AS TAG
FROM S

Предикат: поставщик SNO связан контрактом, называется
SNAME, имеет статус STATUS, находится в городе CITY, поставляет деталь P2 и имеет метку TAG «Supplier».
SNO

SNAME STATUS CITY

S1
S2
S3
S4
S5

Smith
Jones
Blake
Clark
Adams

20
10
30
20
30

London
Paris
Paris
London
Athens

TAG
Supplier
Supplier
Supplier
Supplier
Supplier

g. SQL-аналог:
SELECT SNO , 3 * STATUS AS TRIPLE_STATUS
FROM S

Предикат: поставщик SNO связан контрактом, называется
SNAME, имеет статус STATUS, находится в городе CITY, поставляет деталь P2 и имеет атрибут TRIPLE_STATUS, значение которого в три раза больше значения STATUS.
SNO

SNAME STATUS CITY

S1
S2
S3
S4
S5

Smith
Jones
Blak e
Clark
Adams

20
10
30
20
30

London
Paris
Paris
London
Athens

TRIPLE_STATUS
60
30
90
60
90

h. SQL-аналог:
SELECT PNO , PNAME, COLOR , WEIGHT , CITY , SNO , QTY
WEIGHT * QTY AS SHIPWT
FROM P NATURAL JOIN SP

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR и вес WEIGHT, хранится на
складе в городе CITY, поставляется поставщиком SNO в количе-

416

Приложение С. Ответы к упражнениям

стве QTY, общий вес этой поставки (детали PNO поставщиком
SNO) SHIPWT равен произведению WEIGHT на QTY.
SNO

PNO

QTY

PNAME COLOR

S1
S1
S1
S1
S1
S1
S2
S2
S3
S4
S4
S4

P1
P2
P3
P4
P5
P6
P1
P2
P2
P2
P4
P5

300
200
400
200
100
100
300
400
200
200
300
400

Nut
Bolt
Screw
Screw
Cam
Cog
Nut
Bolt
Bolt
Bolt
Screw
Cam

WEIGHT
12.0
17.0
17.0
14.0
12.0
19.0
12.0
17.0
17.0
17.0
14.0
12.0

Red
Green
Blue
Red
Blue
Red
Red
Green
Green
Green
Red
Blue

CITY

SHIPWT

London
Paris
Oslo
London
Paris
London
London
Paris
Paris
Paris
London
Paris

3600. 0
3400. 0
6800. 0
2800. 0
1200. 0
1900. 0
3600. 0
6800. 0
3400. 0
3400. 0
4200. 0
4800. 0

i. SQL-аналог:
SELECT P.* , WEIGHT * 454 AS GMWT , WEIGHT * 16 AS OZWT
FROM P

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR, вес WEIGHT, вес в граммах
GMWT (= 454, умноженное на WEIGHT) и вес в унциях OZWT
(= 16, умноженное на WEIGHT).
PNO

PNAME COLOR

P1
P2
P3
P4
P5
P6

Nut
Bolt
Screw
Screw
Cam
Cog

Red
Green
Blue
Red
Blue
Red

WEIGHT
12.0
17.0
17.0
14.0
12.0
19.0

CITY

GMWT

OZWT

London
Paris
Oslo
London
Paris
London

5448.0
7718.0
7718.0
6356.0
5448.0
8626.0

192.0
204.0
204.0
168.0
192.0
228.0

j. SQL-аналог:
SELECT P.* , ( SELECT COUNT ( SNO )
FROM SP
WHERE SP.PNO = P.PNO ) AS SCT
FROM P

Предикат: деталь PNO используется на предприятии, называется PNAME, имеет цвет COLOR, вес WEIGHT, находится на
складе в городе CITY и поставляется SCT поставщиками.
PNO

PNAME COLOR

P1
P2
P3
P4
P5
P6

Nut
Bolt
Screw
Screw
Cam
Cog

Red
Green
Blue
Red
Blue
Red

WEIGHT
12.0
17.0
17.0
14.0
12.0
19.0

CITY
London
Paris
Oslo
London
Paris
London

SCT
2
4
1
2
2
1

417

Глава 7

k. SQL-аналог:
SELECT S.* , ( SELECT COUNT ( PNO )
FROM SP
WHERE SP.SNO = S.SNO ) AS NP
FROM S

Предикат: поставщик SNO связан контрактом, называется
SNAME, имеет статус STATUS, находится в городе CITY и поставляет деталь NP деталей.
SNO

SNAME STATUS CITY

S1
S2
S3
S4
S5

Smith
Jones
Blake
Clark
Adams

20
10
30
20
30

London
Paris
Paris
London
Athens

NP
6
2
1
3
0

l. SQL-аналог:
SELECT CITY , AVG ( STATUS ) AS AVG_STATUS
FROM S
GROUP BY CITY

Предикат: средний статус поставщиков в городе CITY равен
AVG_STATUS.
CITY

AVG_STATUS

London
Paris
Athens

20
20
30

m. SQL-аналог:
SELECT COUNT ( SNO ) AS N
FROM S
WHERE CITY = ‘London’

Предикат: в Лондоне находится N поставщиков.
N
2

Отсутствие здесь двойного подчеркивания – не ошибка.
n. SQL-аналог:
( SELECT *
FROM SP
EXCEPT CORRESPONDING
SELECT *
FROM SP
WHERE SNO = ‘S1’ )
UNION CORRESPONDING

418

Приложение С. Ответы к упражнениям
SELECT ‘S7’ AS SNO , PNO , QTY * 0.5 AS QTY
FROM SP
WHERE SNO = ‘S1’

Предикат: либо (а) поставщик SNO поставляет деталь PNO
в количестве QTY (и SNO не равно S1), либо (б) SNO равно S7
и поставщик S1 поставляет деталь PNO в количестве, в два
раза превышающем QTY. Кстати: что произойдет, если SP уже
включает какие-нибудь кортежи для поставщика S7?
SNO

PNO

QTY

S7
S7
S7
S7
S7
S7
S2
S2
S3
S4
S4
S4

P1
P2
P3
P4
P5
P6
P1
P2
P2
P2
P4
P5

150
100
200
100
50
50
300
400
200
200
300
400

Упражнение 7.2. Выражения r1 MATCHING r2 и r2 MATCHING r1 эквивалентны тогда и только тогда, когда r1 и r2 имеют один и тот же
тип, при этом оба выражения сводятся просто к JOIN{r1,r2}, а оно,
в свою очередь, сводится к INTERSECT{r1,r2}.
Упражнение 7.3. Оператор переименования не является примитивным, например потому, что выражения
S RENAME ( CITY AS SCITY )

и
( EXTEND S ADD ( CITY AS SCITY ) ) { ALL BUT CITY }

эквивалентны.
Упражнение 7.4. EXTEND S { SNO } ADD ( COUNT ( !! SP ) AS NP )
Упражнение 7.5. Определить, какие выражения эквивалентны, можно,
посмотрев на результаты их вычисления. Отмечу, что SUM(1), вычисленное по n кортежам, равно n.
a.
r пусто: CT

r состоит из n кортежей (n > 0): CT
n

b.
r пусто: CT

r состоит из n кортежей (n > 0): CT

0

n

419

Глава 7

c.
r пусто: CT

r состоит из n кортежей (n > 0): CT
n

d.
r пусто:

CT

r состоит из n кортежей (n > 0):

0

CT
n

Иными словами, результатом в любом случае является от­но­ше­ние
степени 1. Если r не пус­то, то все четыре выражения эквивалентны,
в противном случае (a) эквивалентно (c), а (b) эквивалентно (d). SQLаналоги:
a. SELECT COUNT ( * ) AS CT
FROM
EXCEPT
SELECT
FROM

r
CORRESPONDING
0 AS CT
r

b. SELECT COUNT ( * ) AS CT
FROM

r

Примечание
При желании можно было бы без ущерба для смысла добавить к этому выражению INTERSECT CORRESPONDING SELECT 0 AS CT FROM r.

c. То же, что (a).
d. То же, что (b).
Упражнение 7.6. SQL возвращает null во всех случаях, кроме COUNT,
где правильно возвращается 0. О причинах можно только догадываться.
Упражнение 7.7. Существует достаточно прямолинейная формулировка: поставщик SNO поставляет деталь PNO тогда и только тогда, когда деталь PNO встречается в от­но­ше­нии PNO_REL. Кстати
говоря, фраза «тогда и только тогда» важна (верно?).
Упражнение 7.8. От­но­ше­ние r имеет такую же кардинальность, как
SP, и такой же заголовок, если не считать одного дополнительного RVA-атрибута X. От­но­ше­ния, являющиеся значениями X, имеют степень 0, причем каждое представляет собой TABLE_DEE, а не
TABLE_DUM, потому что каждый кортеж sp в SP включает 0-кортеж в качестве значения того подкортежа sp, который соответствует пус­тому множес­тву атрибутов. Таким образом, каждый кортеж
от­но­ше­ния r по сути дела состоит из соответствующего кортежа SP,

420

Приложение С. Ответы к упражнениям

расширенного значением TABLE_DEE атрибута X, и исходное выражение GROUP логичес­ки эквивалентно следующему:
EXTEND SP ADD ( TABLE_DEE AS X )

Выражение r UNGROUP (X) дает исходное от­но­ше­ние SP.
Упражнение 7.9.
a. N := COUNT ( SP

| SET N = ( SELECT COUNT ( * )
WHERE SNO = ‘S1’ ) ; |
FROM S
|
WHERE SNO = ‘S1’ ) ;
b. ( S WHERE CITY =
| SELECT *
MIN ( S , CITY ) ) { SNO } | FROM S
| WHERE CITY =
|
( SELECT MIN ( CITY )
|
FROM S )
c. S { CITY }
| SELECT DISTINCT CITY
WHERE COUNT ( !!S ) > 1 | FROM S AS SX
| WHERE ( SELECT COUNT ( * )
|
FROM S AS SY
|
WHERE SY.CITY = SX.CITY )
|
> 1
d. RESULT :=
| SET RESULT = CASE WHEN
IF AND ( S , SNAME < CITY ) |
( SELECT COALESCE ( EVERY
THEN ‘Y’ ELSE ‘N’ END IF ; |
( SNAME < CITY ) , TRUE ) )
|
FROM S ) THEN ‘Y’
|
ELSE ‘N’
|
END ;

Примечание
EVERY – это SQL-аналог агрегатного оператора AND в Tutorial D, но он возвращает null, а не TRUE, если аргумент пус­т.

Упражнение 7.10. Номера поставщиков, поставляющих деталь P2, и номера деталей, поставляемых поставщиком S2, соответственно. Обратите внимание, что формулировки на естественном языке симметричны, тогда как формальные – нет; это объясняется асимметричностью
самого от­но­ше­ния R4 в том смысле, что номера поставщиков и номера деталей рассматриваются в нем совершенно по-разному. Примечание: Именно потому, что от­но­ше­ния с RVA-атрибутами (обычно) несимметричны, они и противопоказаны (обычно).
Упражнение 7.11. Оно обозначает от­но­ше­ние, которое выглядит примерно так, как показано ниже.
Здесь PNO_REL – RVA-атрибут. Отметим, в частности, что пус­тое
множес­тво деталей, поставляемых поставщиком S5, представлено
пус­тым множес­твом, а не (как то имело бы место, если бы мы образовали внешнее соединение S и SP) null-значением. Представление
пус­того множес­тва пус­тым множес­твом, очевидно, выглядит здравой

421

Глава 7

идеей; на самом деле, во внешнем соединении вообще не было бы нужды, если бы RVA-атрибуты были надлежащим образом поддержаны!
SNO

SNAME STATUS CITY

S1

Smith

20

London

PNO_REL
PNO
P1
P2
...
P6

S2

Jones

10

Paris

PNO
P1
P2

...

.....

..

......

.....

S5

Adams

30

Athens

PNO

Попутно отметим, что если обозначить показанное выше от­но­ше­ние
r, то выражение
( r UNGROUP ( PNO_REL ) ) { ALL BUT PNO }

не вернет наше привычное от­но­ше­ние «поставщики». Точнее, оно
вернет от­но­ше­ние, идентичное от­но­ше­нию «поставщики» с тем отличием, что в нем не будет кортежа для поставщика S5.
Упражнение 7.12. С первым все ясно: оно вставляет новый кортеж,
в котором SNO – S6, SNAME – Lopez, STATUS – 30, CITY – Madrid,
а значением PNO_REL является от­но­ше­ние, содержащее единственный кортеж, в котором есть только один атрибут PNO со значением
P5. Что касается второго, то думаю, было бы полезно показать соответствующую часть (хотя бы упрощенную) грамматики Tutorial D,
описывающую реляционное присваивание (предполагается, что названия символов грамматики не требуют пояснения):

:=





::= INSERT

::= DELETE [ WHERE ]

::= UPDATE [ WHERE ] :

422

Приложение С. Ответы к упражнениям
{ }

А символ в случае, когда значениями рассматриваемого атрибута являются от­но­ше­ния, – это, по существу, просто
, что нам и требуется. Таким образом, второе обновление в данном упражнении заменяет кортеж для поставщика S2
другим, в котором значение PNO_REL дополнительно включает кортеж для поставщика S5.
Отметим поэтому, что оба обновления суть формальные представления следующих фраз на естественном языке:
a. Добавить в базу данных тот факт, что поставщик S6 поставляет деталь P5.
b. Добавить в базу данных тот факт, что поставщик S2 поставляет деталь P5.
В случае принятой нами структуры базы о поставщиках и деталях
(без RVA-атрибутов) между двумя обновлениями нет качественной
разницы – оба сводятся к вставке одного кортежа в пе­ре­мен­ную-от­
но­ше­ние SP следующим образом (для простоты игнорируем атрибут
QTY):
a. INSERT SP RELATION { TUPLE { SNO ‘S6’ , PNO ‘P5’ } } ;
b. INSERT SP RELATION { TUPLE { SNO ‘S2’ , PNO ‘P5’ } } ;
Однако в случае SSP эти симметричные обновления обрабатываются несимметрично. Как было отмечено в ответе к упражнению 7.10,
именно такое отсутствие симметрии и составляет (обычно, но не
всегда) проблему при работе с пе­ре­мен­ными-от­но­ше­ниями, включающими RVA-атрибуты.
Упражнение 7.13. Запрос (a) простой:
WITH ( SSP RENAME ( SNO AS XNO ) ) { XNO, PNO_REL } AS X ,
( SSP RENAME ( SNO AS YNO ) ) { YNO, PNO_REL } AS Y :
( X JOIN Y ) { XNO, YNO }

Отметим, что здесь соединение производится по RVA-атрибуту (поэтому неявно выполняются реляционные сравнения).
Однако запрос (b) не столь очевиден. С запросом (a) мы так легко
справились, потому что в SSP «детали вложены в поставщиков»,
а для запроса (b) хотелось бы, чтобы поставщики были вложены
в детали. Так сделаем это:1
1

Этот пример подчеркивает важное различие между RVA-атрибутами в реляционной сис­теме и иерархиями в сис­темах типа IMS (или XML?). В IMS
иерархии «зашиты» в базу данных, иными словами, нам ничего не остается, как работать с иерархиями, которые придумал проектировщик базы
данных. Напротив, в реляционной сис­теме мы можем динамичес­ки строить
любые удобные нам иерархии с помощью подходящих операторов реляционной алгебры.

423

Глава 7
WITH ( SSP UNGROUP ( PNO_REL ) ) GROUP ( { SNO } AS
AS
( PPS RENAME ( PNO AS XNO ) ) { XNO, SNO_REL }
( PPS RENAME ( PNO AS YNO ) ) { YNO, SNO_REL }
( X JOIN Y ) { XNO, YNO }

SNO_REL )
PPS ,
AS X ,
AS Y :

Упражнение 7.14.
WITH ( P RENAME ( WEIGHT AS WT ) ) AS R1 ,
( EXTEND P ADD ( COUNT ( R1 WHERE WT > WEIGHT )
AS N_HEAVIER ) AS R2 :
( R2 WHERE N_HEAVIER < 2 ) { ALL BUT N_HEAVIER }
SELECT *
FROM P AS PX
WHERE ( SELECT COUNT ( * )
FROM P AS PY
WHERE PX.WEIGHT > PY.WEIGHT ) < 2

Этот запрос возвращает кортежи для деталей P2, P3 и P6 (то есть
от­но­ше­ние кардинальности 3, хотя была задана квота 2). Квотированные запросы могут возвращать и меньше кортежей, чем заказано; рассмотрим, к примеру, запрос «Получить десять самых тяжелых деталей».

Примечание
Квотированные запросы часто встречаются на практике. Поэтому в книге
«Databases, Types, and the Relational Model: The Third Manifesto» (см. приложение D) мы с Хью Дарвеном предлагаем для них «дружес­твенную» сокращенную нотацию, в которой показанный выше запрос выглядел бы так:
( ( RANK P BY ( DESC WEIGHT AS W ) ) WHERE W Ј 2 ) { ALL BUT W }

В SQL есть нечто похожее.

Упражнение 7.15. Этот запрос демонстрирует одно слабое место оператора SUMMARIZE. Вот требуемая нам формулировка:
SUMMARIZE SP PER ( S { SNO } ) ADD ( SUMD ( QTY ) AS SDQ )

Вся хитрость в операторе «SUMD» – здесь буква «D» происходит от
distinct и означает «устранить избыточные дубликаты перед тем,
как вычислять сумму». Но это не более чем трюк (ситуативный, на
мой взгляд), поэтому (наряду с многими другими причинами) лучше прибегнуть к следующему выражению, в котором используются
оператор EXTEND и от­но­ше­ния-образы:
EXTEND S { SNO } ADD ( SUM ( !!SP , QTY ) AS SDQ )

Упражнение 7.16.
EXTEND S ADD ( COUNT ( !!SP ) AS NP , COUNT ( !!SJ ) AS NJ )

424

Приложение С. Ответы к упражнениям
( SUMMARIZE SP PER ( S { SNO } ) ADD ( COUNT ( PNO ) AS NP ) )
JOIN
( SUMMARIZE SJ PER ( S { SNO } ) ADD ( COUNT ( JNO ) AS NJ ) )
SELECT SNO , ( SELECT
FROM
WHERE
( SELECT
FROM
WHERE
FROM S

COUNT ( PNO )
SP
SP.SNO = S.SNO ) AS NP ,
COUNT ( JNO )
SJ
SJ.SNO = S.SNO ) AS NJ

Упражнение 7.17. Легко видеть, что операция «!!» идемпотентна, следовательно, !!(!!SP) совпадает с !!SP, и выражение семантичес­ки эквивалентно такому:
S WHERE (!!SP) { PNO } = P { PNO }

(«Получить поставщиков, которые поставляют все детали»).
Упражнение 7.18. Нет никакого логичес­кого различия.
Упражнение 7.19. S JOIN SP не является полусоединением; S MATCH­
ING SP не является соединением (это проекция соединения). Выражения r1 JOIN r2 и r1 MATCHING r2 эквивалентны тогда и только
тогда, когда от­но­ше­ния r1 и r2 одного типа (тогда конечная проекция становится тождественной и все выражение вырождается в r1
INTERSECTION r2).
Упражнение 7.20. Если r1 и r2 одного типа и t1 – кортеж r1, то выражение !!r2 (для t1) обозначает от­но­ше­ние степени 0: TABLE_DEE, если
t1 встречается в r2, и TABLE_DUM в противном случае. А если r1
и r2 – одно и то же от­но­ше­ние, то !!r2 обозначает TABLE_DEE для
любого кортежа r1.
Упражнение 7.21. Они одинаковы, если таб­лица S не пус­та, а в этом
случае первое дает таб­лицу из одной строки и одного столбца, содержащую нуль, а второе – таб­лицу из одной строки и одного столбца,
«содержащую null».

Глава 8
Упражнение 8.1. Ограничение типа – это определение множес­тва значений, составляющих данный тип. Ограничение типа для типа T проверяется в момент вызова селектора типа T; если проверка не проходит, то вызов селектора завершается ошибкой нарушения ограничения типа.
Ограничение базы данных – это ограничение на значения, которые
могут встречаться в этой базе. Ограничения базы данных проверяются в «месте появления точки с запятой», точнее, в конце любого
предложения, которое присваивает значение любой из относящихся

Глава 8

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

Упражнение 8.2. Золотое правило утверждает, что никакая операция
обновления не должна приводить к обращению в FALSE ни одного ограничения базы данных, и тем более операция обновления не
должна приводить к обращению в FALSE никакого ограничения пе­
ре­мен­ной-от­но­ше­ния. Однако полное ограничение пе­ре­мен­ной-от­но­
ше­ния может обращаться в FALSE – не потому, что нарушено какоето ограничение с одной пе­ре­мен­ной-от­но­ше­нием, а из-за нарушения
ограничения с несколькими пе­ре­мен­ными-от­но­ше­ниями. Впрочем,
этот момент носит академичес­кий характер, учитывая, что разделение на ограничения с одной и с несколькими пе­ре­мен­ными-от­но­ше­
ниями все равно произвольно.
Упражнение 8.3. «Утверждением» в SQL называется ограничение, созданное с помощью предложения CREATE ASSERTION. Под ограничением атрибута понимается тот факт, что атрибут имеет определенный тип. Ограничением базовой таб­лицы в SQL называется ограничение, которое задается в составе определения базовой таб­лицы
(а не в составе определения столбца внутри определения базовой таб­
лицы). Ограничением столбца в SQL называется ограничение, которое задается в составе определения столбца. Ограничение с несколькими пе­ре­мен­ными-от­но­ше­ниями – это ограничение базы данных,
в котором упоминаются две или более различных пе­ре­мен­ных-от­но­
ше­ния. Ссылочное ограничение сводится к тому, что если B ссылается на A, то A должно существовать. Ограничением пе­ре­мен­ной-от­но­
ше­ния для пе­ре­мен­ной-от­но­ше­ния R называется ограничение базы
данных, в котором упоминается R. Ограничением строки в SQL называется ограничение, обладающее тем свойством, что его можно
проверить для любой строки, исследуя только эту строку. Ограничение с одной пе­ре­мен­ной-от­но­ше­нием – это ограничение базы данных,
в котором упоминается только одна пе­ре­мен­ная-от­но­ше­ние. Ограничение состояния – это ограничение базы данных, не являющееся
ограничением перехода. Полное ограничение базы данных DB – это
логичес­кое AND всех полных ограничений пе­ре­мен­ных-от­но­ше­ние
в базе данных DB и значения TRUE. Полное ограничение пе­ре­мен­
ной-от­но­ше­ния R – это логичес­кое AND всех ограничений базы данных, в которых упоминается R, и значения TRUE. Ограничением перехода называется ограничение на допус­тимые переходы из одного
«состояния» (то есть значения) базы данных в другое. Ограничением
кортежа называется ограничение пе­ре­мен­ной-от­но­ше­ния, обладающее тем свойством, что его можно проверить для данного кортежа,
исследуя только сам этот кортеж. В какие из этих категорий попада-

426

Приложение С. Ответы к упражнениям

ют (а) ограничения ключа, (б) ограничения внешнего ключа? Вопрос
оставлен без ответа.
Упражнение 8.4. См. текст главы.
Упражнение 8.5. a. 345. b. QTY.
Упражнение 8.6. См. текст главы.
Упражнение 8.7.
TYPE CITY POSSREP { C CHAR CONSTRAINT
OR
OR
OR
OR
OR
OR
OR

C
C
C
C
C
C
C
C

=
=
=
=
=
=
=
=

‘London’
‘Paris’
‘Rome’
‘Athens’
‘Oslo’
‘Stockholm’
‘Madrid’
‘Amsterdam’ } ;

Теперь для атрибута CITY в пе­ре­мен­ных-от­но­ше­ниях S и P можно
задать тип CITY, а не просто CHAR.
Упражнение 8.8. По определению, не существует способа наложить
ограничение, эквивалентное приведенному в ответе на предыдущее
упражнение, не определяя явно тип. Но можно потребовать, чтобы
города поставщиков могли принимать только перечисленные выше
восемь значений, воспользовавшись подходящим ограничением
базы данных. И точно так же ограничить города деталей. Например,
можно было бы определить базовую таб­лицу следующим образом:
CREATE TABLE C ( CITY CHAR , UNIQUE ( CITY ) ) ;

Затем заполнить эту таб­лицу, поместив в нее восемь значений городов:
INSERT INTO C VALUES ‘London’
‘Paris’
‘Rome’
‘Athens’
‘Oslo’
‘Stockholm’
‘Madrid’
‘Amsterdam’

,
,
,
,
,
,
,
;

После чего определить внешние ключи:
CREATE TABLE S ( ... ,
FOREIGN KEY ( CITY ) REFERENCES C ( CITY ) ) ;
CREATE TABLE P ( ... ,
FOREIGN KEY ( CITY ) REFERENCES C ( CITY ) ) ;

Преимущество такого подхода в том, что множес­тво допус­тимых городов при необходимости легко можно изменить.

427

Глава 8

Другой подход – задать ограничения базовой таб­лицы (или столбца) в составе определений базовых таб­лиц S и P. Третий – воспользоваться предложениями CREATE ASSERTION. И еще один – определить триггерные процедуры.
Все эти подходы требуют определенной работы, причем первый, пожалуй, – самой неприятной.
Упражнение 8.9.
TYPE SNO POSSREP
{ C CHAR CONSTRAINT
CHAR_LENGTH ( C ) ≥ 2 AND CHAR_LENGTH ( C ) ≤ 5
AND SUBSTR ( C, 1, 1 ) = ‘S’
AND CAST_AS_INTEGER ( SUBSTR ( C, 2 ) ) ≥ 0
AND CAST_AS_INTEGER ( SUBSTR ( C, 2 ) ) ≤ 9999 } ;

Я предполагаю, что операторы CHAR_LENGTH, SUBSTR и CAST_
AS_INTEGER имеются и наделены очевидной семантикой.
Упражнение 8.10.
TYPE LINESEG POSSREP { BEGIN POINT, END POINT } ;

Я предполагаю, что пользовательский тип POINT определен, как показано в тексте главы.
Упражнение 8.11. Один из примеров – тип POINT, но есть и много других. Так, можете подумать о типе PARALLELOGRAM, который
можно было бы представить многими разными способами (сколько
вы сможете предложить?). Что касается ограничений типа: концептуально спецификация каждого допус­тимого представления должна включать ограничение типа, и все эти ограничения должны быть
логичес­ки эквивалентны. Например:
TYPE POINT
POSSREP CARTESIAN { X FIXED , Y FIXED
CONSTRAINT SQRT ( X ** 2 + Y ** 2 ) ≤ 100.0 }
POSSREP POLAR { R FIXED , THETA FIXED
CONSTRAINT R ≤ 100.0 } ;

Вопрос о том, можно ли предложить некую сокращенную нотацию,
которая позволила бы задавать ограничение только один раз, выходит за рамки настоящей книги.
Упражнение 8.12. Отрезок прямой можно, например, представить начальной и конечной точками или средней точкой, длиной и углом
наклона.
Упражнение 8.13. Я дам ответы в терминах сокращений INSERT,
DELETE и UPDATE, а не самих реляционных присваиваний:
CX1: INSERT в S, UPDATE атрибута STATUS в S
CX2: INSERT в S, UPDATE атрибута CITY или STATUS в S

428

Приложение С. Ответы к упражнениям

CX3: INSERT в S, UPDATE атрибута SNO в S
CX4: INSERT в S, UPDATE атрибута SNO или CITY в S
CX5: UPDATE атрибута STATUS в S, INSERT в SP, UPDATE атрибута
SNO или PNO в SP (здесь я предполагаю, что дейст­вует ограничение
CX6 – ограничение внешнего ключа в направлении SP к S)
CX6: DELETE из S, UPDATE атрибута SNO в S, INSERT в SP, UPDATE
атрибута SNO в SP
CX7: INSERT в LS или NLS, UPDATE атрибута SNO в LS или NLS
CX8: INSERT в S или P, UPDATE атрибута SNO или CITY в S,
UPDATE атрибута PNO или CITY в P
CX9: UPDATE атрибута SNO или STATUS в S
Упражнение 8.14. Нет, хотя не видно причин, по которым его нельзя
было бы при желании добавить.
Упражнение 8.15. (Ниже приводится несколько упрощенный ответ,
но смысл происходящего в нем отражен.) Пусть c – ограничение
базовой таб­лицы T; тогда эквивалентное c предложение CREATE
ASSERTION логичес­ки должно было бы иметь вид FORALL r (c) –
или, чуть ближе к реальному синтаксису SQL, NOT EXISTS r
(NOT c), – где r обозначает строку T. Иными словами, логичес­ки необходимый квантор всеобщности неявно подразумевается в ограничении базовой таб­лицы, но должен быть явно задан в утверждении.
Упражнение 8.16. Формальная причина связана с определением квантора FORALL в случае, когда областью применимости является пус­
тое множес­тво; дополнительные пояснения см. в главе 10. В языке
Tutorial D нет прямого аналога ограничениям базовых таб­лиц, поэтому и такое поведение в нем не наблюдается.
Упражнение 8.17.
CREATE TABLE S
( ... ,
CONSTRAINT CX5 CHECK
( STATUS >= 20 OR SNO NOT IN ( SELECT SNO
FROM SP
WHERE PNO = ‘P6’ ) ) ;
CREATE TABLE P
( ... ,
CONSTRAINT CX5 CHECK
( NOT EXISTS ( SELECT
FROM
WHERE
AND

*
S NATURAL JOIN SP
STATUS < 20
PNO = ‘P6’ ) ) ;

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

429

Глава 8

торой входит. Следовательно, точно такая же спецификация могла
бы входить в состав определения абсолютно любой базовой таб­лицы.
(По существу, она идентична версии с CREATE ASSERTION.)
Упражнение 8.18. Булево выражение в ограничении CX1 – это простое
условие выборки (restriction); выражение в ограничении CX5 сложнее. Одним из следствий является тот факт, что для кортежа, вставляемого в S, ограничение CX1 можно проверить, даже не рассматривая уже имеющиеся в базе данных значения, тогда как для ограничения CX5 это не верно.
Упражнение 8.19. Да, конечно, это возможно; пример дает ограничение CX3. Однако отметим, что в общем случае ни ограничение типа
CX3, ни явная спецификация KEY не могут гарантировать, что заданная комбинация атрибутов удовлетворяет требованию неприводимости. (Хотя можно было бы хотя бы ввести синтаксичес­кое правило о том, что если для одной и той же пе­ре­мен­ной-от­но­ше­ния заданы два разных ключа, то ни один из них не должен быть собственным подмножес­твом другого. Такое правило было бы полезно, но
полностью проблему все равно не решило бы.)
Упражнение 8.20.
CREATE ASSERTION CX8 CHECK
( ( SELECT COUNT ( * )
FROM ( SELECT CITY
FROM S
WHERE SNO = ‘S1’
UNION CORRESPONDING
SELECT CITY
FROM P
WHERE PNO = ‘P1’ ) AS POINTLESS ) < 2 ) ;

Упражнение 8.21. Из-за длины строк слишком трудно привести формулировки на Tutorial D и SQL рядом, поэтому в каждом случае сначала я привожу первую, а вторую – вслед за ней. Подробное описание того, какие операции могут привести к нарушению этих ограничений, я опускаю.
a. CONSTRAINT CXA IS_EMPTY

( P WHERE COLOR = ‘Red’ AND WEIGHT ≥ 50.0 ) ;

CREATE ASSERTION CXA CHECK ( NOT EXISTS (
SELECT *
FROM P
WHERE COLOR = ‘Red’
AND
WEIGHT >= 50.0 ) ) ;

b. CONSTRAINT CXB IS_EMPTY (
( S WHERE CITY = ‘London’ )
WHERE TUPLE { PNO ‘P2’ } ∉ (!!SP) { PNO } ) ;

430

Приложение С. Ответы к упражнениям
CREATE ASSERTION CXB CHECK (
NOT EXISTS ( SELECT * FROM S
WHERE CITY = 'London'
AND
NOT EXISTS
( SELECT * FROM SP
WHERE SP.SNO = S.SNO
AND
SP.PNO = 'P2' ) ) ) ;

c. CONSTRAINT CXC COUNT ( S ) = COUNT ( S { CITY } ) ;
CREATE ASSERTION CXC CHECK ( UNIQUE ( SELECT CITY FROM S ) ) ;

d. CONSTRAINT CXD COUNT ( S WHERE CITY = 'Athens' ) < 2 ;
CREATE ASSERTION CXD CHECK
( UNIQUE ( SELECT CITY FROM S WHERE CITY = 'Athens' ) ) ;

e. CONSTRAINT CXE COUNT ( S WHERE CITY = 'London' ) > 0 ;
CREATE ASSERTION CXE CHECK
( EXISTS ( SELECT * FROM S WHERE CITY = 'London' ) ) ;

f. CONSTRAINT CXF COUNT ( P WHERE COLOR = 'Red'
AND WEIGHT < 50.0 ) > 0 ;
CREATE ASSERTION CXF CHECK
( EXISTS ( SELECT * FROM P
WHERE COLOR = 'Red'
AND
WEIGHT < 50.0 ) ) ;

g. CONSTRAINT CXG CASE
WHEN IS_EMPTY ( S ) THEN TRUE
ELSE AVG ( S , STATUS ) > 10
END CASE ;
CREATE ASSERTION CXG CHECK
( CASE
WHEN NOT EXISTS ( SELECT * FROM S ) THEN TRUE
ELSE ( SELECT AVG ( STATUS ) FROM S ) > 10
END ) ;

h. CONSTRAINT CXH
CASE
WHEN IS_EMPTY ( SP ) THEN TRUE
ELSE IS_EMPTY ( SP WHERE QTY > 2 * AVG ( SP , QTY ) )
END CASE ;
CREATE ASSERTION CXH CHECK
( CASE
WHEN NOT EXISTS ( SELECT * FROM SP ) THEN TRUE
ELSE NOT EXISTS ( SELECT * FROM SP
WHERE QTY > 2 *

431

Глава 8
( SELECT AVG ( QTY )
FROM SP ) )
END ) ;

i. CONSTRAINT CXI CASE
WHEN COUNT ( S ) < 2 THEN TRUE
ELSE IS_EMPTY ( JOIN
{ ( S WHERE STATUS = MAX ( S { STATUS } ) ) { CITY } ,
( S WHERE STATUS = MIN ( S { STATUS } ) ) { CITY } } )
END CASE ;
CREATE ASSERTION CXI CHECK ( CASE
WHEN ( SELECT COUNT ( * ) FROM S ) < 2 THEN TRUE
ELSE NOT EXISTS
( SELECT * FROM S AS X , S AS Y
WHERE X.STATUS = ( SELECT MAX ( STATUS ) FROM S )
AND
Y.STATUS = ( SELECT MIN ( STATUS ) FROM S )
AND
X.CITY = Y.CITY )
END ) ;

j. CONSTRAINT CXJ P { CITY } ⊆ S { CITY } ;
CREATE ASSERTION CXJ CHECK ( NOT EXISTS
( SELECT * FROM P
WHERE NOT EXISTS
( SELECT * FROM S
WHERE S.CITY = P.CITY ) ) ) ;

k. CONSTRAINT CXK IS_EMPTY (
( ( EXTEND P ADD ( (!!SP) JOIN S ) { CITY } AS SC )
WHERE TUPLE { CITY CITY } ∉ SC ) ) ;
CREATE ASSERTION CXK CHECK ( NOT EXISTS
( SELECT * FROM P
WHERE NOT EXISTS
( SELECT * FROM S
WHERE S.CITY = P.CITY
AND
EXISTS
( SELECT * FROM SP
WHERE S.SNO = SP.SNO
AND
P.PNO = SP.PNO ) ) ) ) ;

l. CONSTRAINT CXL
COUNT ( ( ( S WHERE CITY = 'London' ) JOIN SP ) { PNO } ) >
COUNT ( ( ( S WHERE CITY = 'Paris' ) JOIN SP ) { PNO } ) ;
CREATE ASSERTION CXL CHECK (
( SELECT COUNT ( DISTINCT PNO ) FROM S , SP
WHERE S.SNO = SP.SNO
AND
S.CITY = 'London' ) >
( SELECT COUNT ( DISTINCT PNO ) FROM S , SP

432

Приложение С. Ответы к упражнениям
WHERE S.SNO = SP.SNO
AND
S.CITY = 'Paris' ) ) ;

m. CONSTRAINT CXM
SUM ( ( ( S WHERE CITY = 'London' ) JOIN SP ) , QTY ) >
SUM ( ( ( S WHERE CITY = 'Paris' ) JOIN SP ) , QTY ) ;
CREATE ASSERTION CXM CHECK (
( SELECT COALESCE ( SUM ( QTY ) , 1 ) FROM S , SP
WHERE S.SNO = SP.SNO
AND
S.CITY = 'London' ) >
( SELECT COALESCE ( SUM ( QTY ) , 0 ) FROM S , SP
WHERE S.SNO = SP.SNO
AND
S.CITY = 'Paris' ) ) ;

n. CONSTRAINT CXN IS_EMPTY
( ( SP JOIN P ) WHERE QTY * WEIGHT > 20000.0 ) ;
CREATE ASSERTION CXN CHECK
( NOT EXISTS ( SELECT * FROM SP NATURAL JOIN P
WHERE QTY * WEIGHT > 20000.0 ) ) ;

Упражнение 8.22. Оно гарантирует, что ограничение удовлетворяется
для пус­той базы данных (то есть не содержащей никаких пе­ре­мен­
ных-от­но­ше­ний).
Упражнение 8.23. Предположим, что требуется определить пе­ре­мен­
ную-от­но­ше­ние SC с атрибутами SNO и CITY и предикатом Поставщик SNO не имеет офиса в городе CITY. Предположим далее, что поставщик S1 имеет офисы всего в десяти городах. Тогда из допущения
замкнутости мира следовало бы, что в пе­ре­мен­ной-от­но­ше­нии SC
должно быть n-10 кортежей для поставщика S1, где n – общее количество возможных городов (быть может, вообще всех городов в мире)!
Упражнение 8.24. Нам понадобится множес­твенное присваивание
(если требуется выполнить удаление в одном предложении):
DELETE S WHERE SNO = x , DELETE SP WHERE SNO = x ;

Упражнение 8.25. Эти ограничения нельзя выразить декларативно (ни
SQL, ни Tutorial D в настоящее время не поддерживают ограничения перехода; придется воспользоваться триггерными процедурами, но детали выходят за рамки настоящей книги). Однако ниже
приведены формулировки, в которых используется соглашение об
«именах пе­ре­мен­ных-от­но­ше­ний со штрихом», обсуждаемое в разделе «Разное»:
a. CONSTRAINT CXA

IS_EMPTY ( ( ( S′ WHERE CITY = ‘Athens’ ) { SNO } ) JOIN S )
WHERE CITY ≠ ‘Athens’
AND CITY ≠ ‘London’
AND CITY ≠ ‘Paris’ )

433

Глава 9
AND IS_EMPTY ( ( ( S′ WHERE CITY = ‘London’ ) { SNO } ) JOIN S )
WHERE CITY ≠ ‘London’
AND CITY ≠ ‘Paris’ ) ;

b. CONSTRAINT CXB IS_EMPTY
( P WHERE SUM ( !!SP , QTY ) > SUM ( !!SP’ , QTY ) ) ;

c. CONSTRAINT CXC IS_EMPTY

( S WHERE SUM ( !!SP′ , QTY ) < 0.5 * SUM ( !!SP , QTY ) ) ;

Условие «за одну операцию обновления» важно, чтобы мы не пытались уменьшить общее количество деталей в поставке сначала, скажем на треть, а потом еще на треть.
Упражнение 8.26. См. текст главы.
Упражнение 8.27. Без ответа.
Упражнение 8.28. В SQL ограничения типа не поддерживаются по причине, связанной с наследованием типов. Детали выходят за рамки
этой книги; если вам интересно, можете найти подробное обсуждение в книге «Databases, Types, and the Relational Model: The Third
Manifesto», написанной Хью Дарвеном и мной (см. приложение D).
Что касается последствий, то одно из них состоит в том, что при определении типа в SQL вы даже не можете указать, какие значения составляют данный тип! – разве что в виде априорного ограничения,
налагаемого представлением. А потому – в отсутствие каких-либо
иных средств контроля – в базе данных могут оказаться неправильные данные (даже бессмысленные, например, размер обуви 1000).
Упражнение 8.29. В принципе, все применимы, хотя в языке Tutori­
al D намеренно не предоставляется способ определить для нескалярных или сис­темных типов какие-либо ограничения типа кроме
априорных.
Упражнение 8.30. См. обсуждение потенциально недетерминированных типов в главе 12. Другого ответа не будет.

Глава 9
Упражнение 9.1.
VAR NON_COLOCATED VIRTUAL
( S { SNO } JOIN P { PNO } ) MINUS ( S JOIN P ) { SNO , PNO }
KEY { SNO , PNO } ;
CREATE VIEW NON_COLOCATED AS
SELECT SNO , PNO
FROM S , P
WHERE S.CITY P.CITY
/* UNIQUE ( SNO , PNO ) */ ;

434

Приложение С. Ответы к упражнениям

Упражнение 9.2. Подставляя определение представления вместо ссылки на представление во внешней фразе FRON (и явно показывая все
квалификаторы имен), получаем:
SELECT DISTINCT LSSP.STATUS , LSSP.QTY
FROM ( SELECT S.SNO , S.SNAME , S.STATUS , SP.PNO , SP.QTY
FROM S NATURAL JOIN SP
WHERE S.CITY = ‘London’ ) AS LSSP
WHERE LSSP.PNO IN
( SELECT P.PNO
FROM P
WHERE P.CITY ‘London’ )

Это выражение можно упростить:
SELECT
FROM
WHERE
AND
(

DISTINCT STATUS , QTY
S NATURAL JOIN SP
CITY = ‘London’
PNO IN
SELECT PNO
FROM P
WHERE CITY ‘London’ )

Упражнение 9.3. Единственный ключ – {SNO,PNO}. Предикат: Поставщик SNO связан контрактом, называется SNAME, имеет статус
STATUS, находится в некотором городе и поставляет деталь PNO
в количестве QTY.
Упражнение 9.4.
a. ( ( P WHERE WEIGHT > 14.0 ) { PNO , WEIGHT , COLOR } )
WHERE COLOR = ‘Green’

b. ( EXTEND ( ( P WHERE WEIGHT > 14.0 ) { PNO , WEIGHT , COLOR } )
ADD ( WEIGHT + 5.3 AS WTP ) ) { PNO , WTP }

c. INSERT ( ( P WHERE WEIGHT > 14.0 ) { PNO , WEIGHT , COLOR } )
RELATION { TUPLE { PNO ‘P99’, WEIGHT 12.0, COLOR ‘Purple’ } } ;

Отметим, что этот оператор INSERT логичес­ки эквивалентен реляционному присваиванию, в котором целевое выражение определено иначе, чем просто ссылка на пе­ре­мен­ную-от­но­ше­ние. Из
возможности обновлять представления следует, что такие присваивания должны быть допус­тимы как синтаксичес­ки, так и се­
мантичес­ки. Аналогичные замечания относятся также к пунк­
там (d) и (e) ниже.
d. DELETE ( ( P WHERE WEIGHT > 14.0 ) { PNO , WEIGHT , COLOR } )
WHERE WEIGHT < 9.0 ;

e. UPDATE ( ( P WHERE WEIGHT > 14.0 ) { PNO , WEIGHT , COLOR } )
WHERE WEIGHT = 18.0 : { COLOR := ‘White’ } ;

435

Глава 9

Дополнительное упражнение: Какие еще упрощения можно применить к рассмотренным выше подстановкам?
Упражнение 9.5.
a. ( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) ) WHERE WT > 6356.0 )
{ PNO , WT , COLOR } ) WHERE COLOR = ‘Green’

b. ( EXTEND
( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) ) WHERE WT > 6356.0 )
{ PNO , WT , COLOR } ) ADD ( WT + 5.3 AS WTP ) ) { PNO , WTP }

c. INSERT
( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) ) WHERE WT > 6356.0 )
{ PNO , WT , COLOR } )
RELATION { TUPLE { PNO ‘P99’ , WT 12.0 , COLOR ‘Purple’ } } ;

d. DELETE
( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) ) WHERE WT > 6356.0 )
{ PNO , WT , COLOR } ) WHERE WT < 9.0 ;

e. UPDATE
( ( ( EXTEND P ADD ( WEIGHT * 454 AS WT ) ) WHERE WT > 6356.0 )
{ PNO , WT , COLOR } ) WHERE WT = 18.0 : { COL := ‘White’ } ;

Упражнение 9.6. Сначала SQL-версия определения представления из
упражнения 9.4:
CREATE VIEW HEAVYWEIGHT AS
SELECT PNO , WEIGHT AS WT , COLOR AS COL
FROM P
WHERE WEIGHT > 14.0 ;

Для пунктов a–e я сначала привожу SQL-аналог формулировки на
Tutorial D, а затем результат, получающийся после подстановки:
a. SELECT *
FROM HEAVYWEIGHT
WHERE COL = ‘Green’
SELECT *
FROM ( SELECT PNO , WEIGHT AS WT , COLOR AS COL
FROM P
WHERE WEIGHT > 14.0 ) AS POINTLESS
WHERE COL = ‘Green’

Здесь и далее оставляю вам дальнейшее упрощение в качестве дополнительного упражнения (если явно не оговорено противное).
b. SELECT PNO , WT + 5.3 AS WTP
FROM

HEAVYWEIGHT

SELECT PNO , WT + 5.3 AS WTP

436

Приложение С. Ответы к упражнениям
FROM ( SELECT PNO , WEIGHT AS WT , COLOR AS COL
FROM P
WHERE WEIGHT > 14.0 ) AS POINTLESS

c. INSERT INTO HEAVYWEIGHT ( PNO , WT , COL )
VALUES ( ‘P99’ , 12.0 , ‘Purple’ ) ;
INSERT INTO ( SELECT
FROM
WHERE
VALUES

PNO , WEIGHT AS WT , COLOR AS COL
Р
WEIGHT > 14.0 ) ( PNO , WT , COL )
( ‘P99’ , 12.0 , ‘Purple’ ) ;

Интересно отметить, что это предложение INSERT нарушает син­так­сичес­кие правила SQL (потому что целевое выражение
не является простой ссылкой на таб­лицу); таким образом, в данном случае обработка представлений в SQL в терминах одной
лишь подстановки выражена быть не может. Поэтому приведенное выше предложение INSERT должно быть трансформировано
в нечто иное, но во что именно, сказать трудно.
d. DELETE FROM ( SELECT PNO , WEIGHT AS WT , COLOR AS COL
FROM Р
WHERE WEIGHT > 14.0 ) WHERE WT < 9.0 ;

И снова в трансформированной версии применен недопус­тимый
синтаксис SQL, но на этот раз отыскать допус­тимый эквивалент
немного проще:
DELETE FROM P WHERE WEIGHT > 14.0 AND WEIGHT < 9.0 ;

(Это, как легко видеть, «пус­тая операция». Как вы думаете, сумеет оптимизатор распознать этот факт?)
e. UPDATE ( SELECT PNO , WEIGHT AS WT , COLOR AS COL
FROM P
WHERE WEIGHT > 14.0 )
SET
COL = ‘White’
WHERE WT = 18.0 ;

Синтаксичес­ки допус­тимый эквивалент:
UPDATE P
SET
COLOR = ‘White’
WHERE WEIGHT = 18.0 AND WEIGHT > 14.0 ;

SQL-версии ответов к упражнению 9.5 не приводятся.
Упражнение 9.7. Пожалуй, только для HEAVYWEIGHT (первый вариант), потому что только его и можно рассматривать как обновляемое.
Упражнение 9.8. В обоих случаях единственный ключ {PNO}. Предикат представления из упражнения 9.4: Деталь PNO используется
на предприятии, имеет некоторое название и хранится на складе

437

Глава 9

в некотором городе, имеет цвет COL и вес в фунтах WT (и WT больше 14). Предикат представления из упражнения 9.5: Деталь PNO используется на предприятии, имеет некоторое название и хранится на складе в некотором городе, имеет цвет COL и вес в граммах
WT (и WT больше 6356). Что касается полных ограничений пе­ре­мен­
ных-от­но­ше­ний: для представления из упражнения 9.4 полное ограничение в основном такое же, как для пе­ре­мен­ной-от­но­ше­ния P, но
модифицировано с учетом переименования и проекции И ограничения, что вес должен быть больше 14. Полное ограничение для представления из упражнения 9.5 аналогично.
Упражнение 9.9. Вот несколько причин:
•• Если пользователю разрешено оперировать представлениями вместо базовых пе­ре­мен­ных-от­но­ше­ний, то понятно, что представления должны выглядеть по возможности так же, как базовые пе­
ре­мен­ные-от­но­ше­ния. На самом деле, в соответствии с принципом взаимозаменяемости пользователь вообще не должен знать,
что работает с представлениями, и обращаться с ними может точно так же, как с базовыми пе­ре­мен­ными-от­но­ше­ниями. И если
пользователь базовой пе­ре­мен­ной-от­но­ше­ния должен знать, какие для нее определены ключи (в общем случае), то и пользователь представления тоже должен знать ключи этого представления (опять же в общем случае).
•• СУБД не всегда может вывести ключи (так обстоит дело почти
во всех SQL-сис­темах, представленных сегодня на рынке). Поэтому явные объявления, по-видимому, остаются единственным
средством (для администратора базы данных – АБД) сообщить
СУБД – и пользователям тоже – о существовании таких ключей.
•• Даже если бы СУБД умела выводить ключи самостоятельно, явные объявления, по крайней мере, помогли бы сис­теме проверить,
что ее выводы и явные спецификации, заданные АБД, согласуются между собой.
•• АБД может быть известна некоторая информация, которой СУБД
не располагает, поэтому он может уточнить выводы СУБД.
•• В тексте главы отмечалось, что с помощью такого средства можно
было бы легко и удобно задавать некоторые важные ограничения,
которые иначе пришлось бы формулировать очень многословно.
Дополнительное упражнение: Как вы думаете, какие из вышеперечисленных причин относятся не только к ограничениям ключей, но
и к ограничениям целостности вообще?
Упражнение 9.10. Вот один такой пример: пе­ре­мен­ная-от­но­ше­ние «поставщики» равна соединению своих проекций на {SNO,SNAME},
{SNO,STATUS} и {SNO,CITY} – при условии, конечно, что дейст­вуют
подходящие ограничения (верно?). Поэтому мы могли бы сделать эти

438

Приложение С. Ответы к упражнениям

проекции базовыми пе­ре­мен­ными-от­но­ше­ниями, а их соединение –
представлением.
Упражнение 9.11. Вот несколько замечаний на эту тему. Во-первых,
сам процесс замены состоит из нескольких шагов, которые можно
вкратце охарактеризовать следующим образом:
/* определить новые базовые пе­ре­мен­ные-от­но­ше­ния */
VAR LS BASE RELATION
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;
VAR NLS BASE RELATION
{ SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
KEY { SNO } ;
/* скопировать данные в новые базовые пе­ре­мен­ные-от­но­ше­ния */
INSERT LS ( S WHERE CITY = ‘London’ ) ;
INSERT NLS ( S WHERE CITY ≠ ‘London’ ) ;
/* удалить старую пе­ре­мен­ную-от­но­ше­ние */
DROP VAR S ;
/* создать требуемое представление */
VAR S VIRTUAL ( LS D_UNION NLS ) ;

Далее нужно как-то поступить с внешним ключом пе­ре­мен­ной-от­
но­ше­ния SP, который ссылался на старую пе­ре­мен­ную-от­но­ше­ние S.
Очевидно, лучше всего было бы сделать так, чтобы этот внешний
ключ теперь ссылался на представление S;1 если это невозможно
(а так оно и есть в большинстве современных продуктов), то можно
было бы определить еще одну базовую пе­ре­мен­ную-от­но­ше­ние следующим образом:
VAR SS BASE RELATION { SNO CHAR } KEY { SNO } ;

И скопировать данные (понятно, что это следует делать до удаления
пе­ре­мен­ной-от­но­ше­ния S):
INSERT SS S { SNO } ;

1

На самом деле, логичес­кая независимость от данных – сильный аргумент
в пользу того, чтобы разрешить определять любые ограничения не только
для базовых пе­ре­мен­ных-от­но­ше­ний, но и для представлений. См. дополнительное упражнение в конце ответа к упражнению 9.9.

439

Глава 9

Теперь следует добавить такую спецификацию внешнего ключа
в определения пе­ре­мен­ных-от­но­ше­ний LS и NLS:
FOREIGN KEY { SNO } REFERENCES SS

И наконец, необходимо так изменить спецификацию внешнего ключа {SNO} в пе­ре­мен­ной-от­но­ше­нии SP, чтобы он ссылался на SS вмес­
то S.
Упражнение 9.12. a. Без ответа – отмечу лишь, что если дать ответ на
этот вопрос для конкретного продукта затруднительно, то сам этот
факт и составляет смысл упражнения. b. То же, что для (a). c. Без
ответа.
Упражнение 9.13. Описание различий см. в тексте главы. В настоящее
время SQL не поддерживает снимки. (Поддерживается предложение
CREATE TABLE AS – см. ответ к упражнению 1.16 в главе 1, – которое позволяет инициализировать базовую таб­лицу в момент создания, но у этого предложения нет опции REFRESH.)
Упражнение 9.14. Термин «материализованное представление» – не рекомендованный синоним термина снимок. Он не рекомендуется, потому что смешивает понятия, которые логичес­ки различны и таковыми должны оставаться. По определению представления с точки зрения модели не материализуются,так что мы оказываемся
в ситуа­ции, когда отсутствует четкий термин для понятия, которое исходно было определено вполне однозначно. Поэтому термина
«материализованное представление» следует всячес­ки избегать.1 На
самом деле меня так и подмывает сделать следующий напрашивающийся вывод: мне кажется, что поддержка термина «материализованное представление» выдает непонимание реляционной модели
в частности и различия между моделью и реализацией вообще.
Упражнение 9.15. Сначала приведу определение варианта (b) в терминах варианта (a):
VAR SSP VIRTUAL ( S JOIN SP )
KEY { SNO , PNO } ;
VAR XSS VIRTUAL ( S NOT MATCHING SP )
KEY { SNO } ;

А вот как выглядит определение варианта (a) в терминах варианта (b):
VAR S VIRTUAL ( XSS D_UNION SSP { ALL BUT PNO , QTY } )
KEY { SNO } ;
VAR SP VIRTUAL ( SSP { SNO , PNO , QTY } )
KEY { SNO , PNO } ;
1

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

440

Приложение С. Ответы к упражнениям

К обоим вариантам применимы следующие ограничения базы данных:
CONSTRAINT DESIGN_A IS_EMPTY ( SP { SNO } MINUS S { SNO } ) ;
CONSTRAINT DESIGN_B IS_EMPTY ( SSP { SNO } JOIN XSS { SNO } ) ;

При таких ограничениях эти варианты представления информации
эквивалентны. Но вариант (a) лучше, потому что обе пе­ре­мен­ные-от­
но­ше­ния в нем полностью нормализованы. В варианте же (b) пе­ре­
мен­ная-от­но­ше­ние SSP нормализована не до конца (она даже не находится во второй нормальной форме), а это значит, что она избыточна и потенциально подвержена различным «аномалиям при обновлении». Подумайте, что произойдет в варианте (b), если какой-нибудь
поставщик вообще прекратит поставку деталей или же поставщик,
который раньше ничего не поставлял, начнет поставлять какие-то
детали. Более пристальное обсуждение проблем, присущих варианту (b), выходит за рамки этой книги, но в некоторых публикациях,
упоминаемых в приложении D, они рассматриваются подробно (см.
также приложение B).
Замечу мимоходом, что, учитывая тот факт, что {SNO} – ключ для
пе­ре­мен­ной-от­но­ше­ния S, ограничение DESIGN_A дает пример еще
одной формулировки ссылочного ограничения.
Упражнение 9.16. (Советую еще раз прочитать раздел «Зависимость от
имен атрибутов» в главе 6 перед тем, как знакомиться с ответом.)
Да, представлений дейст­вительно должно быть достаточно для решения проблемы логичес­кой независимости от данных. Но беда
в том, что определение представления обычно воспринимают как
взгляд приложения на некоторую часть базы данных и одновременно соответствие между этим взглядом и «настоящей» базой данных.
Чтобы добиться такой независимости от данных, которую я здесь
имею в виду, эти две спецификации следует отделять друг от друга.

Глава 10
Упражнение 10.1. См. ответ к упражнению 4.10 в главе 4.
Упражнение 10.2. Прежде всего, легко видеть, что одновременно OR
и AND не нужны, так как
p AND q ≡ NOT ( NOT ( p ) OR NOT ( q ) )

(Эта эквиваленция легко устанавливается с помощью таб­лиц истинности.) Отсюда следует, что ниже мы можем свободно пользоваться
OR и AND.

441

Глава 10

Далее рассмотрим связки, в которых участвует единственное высказывание p. Пусть c(p) – рассматриваемая связка. Существуют следующие возможности:
c(p)
c(p)
c(p)
c(p)






p OR NOT ( p )
p AND NOT ( p )
p
NOT ( p )

/*
/*
/*
/*

всегда TRUE
всегда FALSE
тождество
NOT

*/
*/
*/
*/

Теперь рассмотрим связки с двумя высказываниями p и q. Пусть
c(p,q) – рассматриваемая связка. Существуют следующие возможности:
c
c
c
c
c
c
c
c
c
c
c
c
c
c
c
c

(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)
(p,q)


















p OR NOT ( p ) OR q OR NOT ( q )
p AND NOT ( p ) AND q AND NOT ( q )
p
NOT ( p )
q
NOT ( q )
p OR q
p AND q
p OR NOT ( q )
p AND NOT ( q )
NOT ( p ) OR q
NOT ( p ) AND q
NOT ( p ) OR NOT ( q )
NOT ( p ) AND NOT ( q )
( NOT ( p ) OR q ) AND ( NOT ( q ) OR p )
( NOT ( p ) AND q ) OR ( NOT ( q ) AND p )

В качестве дополнительного упражнения и чтобы убедиться, что
приведенные выше определения дейст­вительно покрывают все возможности, можете построить соответствующие таб­лицы истинности.
Теперь обратимся к части (b) упражнения. На самом деле существует два таких примитива: NOR и NAND, которые часто обозначаются,
соответственно, направленной вниз стрелкой, «↓» (стрелка Пирса),
и вертикальной черточкой, «|» (штрих Шеффера). Вот их таб­лицы
истинности:
NOR

T F

NAND

T F

T
F

F F
F T

T
F

F T
T T

Как следует из этих таб­лиц, p↓q («p NOR q») эквивалентно NOT
(p OR q), а p|q («p NAND q») эквивалентно NOT (p AND q). Далее
я бу­ду рассматривать только NOR (NAND оставляю вам). Замечу,
что эту связку можно назвать «ни-ни» («ни первый, ни второй опе-

442

Приложение С. Ответы к упражнениям

ранд не являются истинными»). Теперь я покажу, как в терминах
этого оператора определить NOT, OR и AND:
≡ p ↓ p
≡ ( p ↓ q ) ↓ ( p ↓ q )
≡ ( p ↓ p ) ↓ ( q ↓ q )

NOT ( p )
p OR q
p AND q

К примеру, рассмотрим более внимательно случай «p AND q»:
p

q

p p

q q

T
T
F
F

T
F
T
F

F
F
T
T

F
T
F
T

(p p )

(q q )
T
F
F
F

Эта таб­лица истинности доказывает, что выражение (p↓p)↓(q↓q) эквивалентно p AND q, поскольку первый, второй и последний столбец
как раз и образуют таб­лицу истинности для AND:
p

q

p AND q

T
T
F
F

T
F
T
F

T
F
F
F

Поскольку ранее мы видели, что все остальные связки можно выразить через NOT, OR и AND, то требуемое утверждение доказано.
Упражнение 10.3. «Солнце – звезда» и «Луна – звезда» – высказывания, хотя первое истинно, а второе ложно.
Упражнение 10.4. Солнце удовлетворяет этому предикату, а Луна нет.
Упражнение 10.5. Вместо параметра можно подставить любой аргумент, лишь бы он был подходящего типа. А вместо обозначения вообще ничего нельзя подставить; оно – как ссылка на пе­ре­мен­ную
в языке программирования – просто «обозначает» значение соответствующей пе­ре­мен­ной в определенный момент времени (в нашем
случае, при проверке ограничения).
Упражнение 10.6. «Получить названия таких поставщиков, что существует поставка – единственная поставка, – связывающая их со всеми деталями». Отметим, что этот запрос вернет либо (а) все названия
поставщиков, если кардинальность пе­ре­мен­ной-от­но­ше­ния P меньше двух, либо (б) пус­той результат в противном случае.
Упражнение 10.7. Следующие SQL-выражения являются наиболее
близкими аналогами соответствующих реляционных выражений:
Пример 1. Получить все пары номеров поставщиков такие, что оба
поставщика находятся в одном городе.

443

Глава 10
SELECT
FROM
WHERE
AND

SX.SNO AS SA , SY.SNO AS SB
S AS SX , S AS SY
SX.CITY = SY.CITY
SX.SNO < SY.SNO

Пример 2. Получить названия поставщиков, которые поставляют
хотя бы одну красную деталь.
SELECT
FROM
WHERE
(

DISTINCT SX.SNAME
S AS SX
EXISTS
SELECT *
FROM SP AS SPX
WHERE EXISTS
( SELECT *
FROM P AS PX
WHERE SX.SNO = SPX.SNO
AND
SPX.PNO = PX.PNO
AND
PX.COLOR = ‘Red’ ) )

Пример 3. Получить названия поставщиков, которые поставляют
хотя бы одну деталь, поставляемую поставщиком S2.
SELECT
FROM
WHERE
(

DISTINCT SX.SNAME
S AS SX
EXISTS
SELECT *
FROM SP AS SPX
WHERE EXISTS
( SELECT *
FROM SP AS SPY
WHERE SX.SNO = SPX.SNO
AND
SPX.PNO = SPY.PNO
AND
SPY.SNO = ‘S2’ ) )

Пример 4. Получить названия поставщиков, которые не поставляют деталь P2.
SELECT DISTINCT SX.SNAME
FROM S AS SX
WHERE NOT EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = ‘P2’ )

Пример 5. Для каждой поставки получить всю информацию о поставке, а также ее общий вес.
SELECT SPX.* , PX.WEIGHT * SPX.QTY AS SHIPWT
FROM P AS PX , SP AS SPX
WHERE PX.PNO = SPX.PNO

444

Приложение С. Ответы к упражнениям

Пример 6. Для каждой детали получить номер детали и общее поставленное количество.
SELECT PX.PNO ,
( SELECT COALESCE ( SUM ( ALL SPX.QTY ) , 0 )
FROM SP AS SPX
WHERE SPX.PNO = PX.PNO ) AS TOTQ
FROM P AS PX

Пример 7. Получить города, в которых хранится более пяти красных
деталей.
SELECT DISTINCT PX.CITY
FROM P AS PX
WHERE ( SELECT COUNT ( * )
FROM P AS PY
WHERE PY.CITY = PX.CITY
AND
PY.COLOR = ‘Red’ ) > 5

Упражнение 10.8. Следующая таб­лица истинности доказывает, что операция AND ассоциативна; доказательство для OR аналогично.
p

q

r

p AND q

T
T
T
T
F
F
F
F

T
T
F
F
T
T
F
F

T
F
T
F
T
F
T
F

T
T
F
F
F
F
F
F

(p AND q) AND r
T
F
F
F
F
F
F
F

(q AND r)
T
F
F
F
T
F
F
F

p AND (q AND r)
T
F
F
F
F
F
F
F

Упражнение 10.9. a. Неверно (предположим, что x пробегает пус­тое
множес­тво и q равно TRUE, тогда EXISTS x (q) равно FALSE). b. Неверно (предположим, что x пробегает пус­тое множес­тво и q равно FALSE, тогда FORALL x (q) равно TRUE). c. Верно. d. Верно.
e. Неверно (предположим, что x пробегает пус­тое множес­тво, тогда FORALL x (p(x)) равно TRUE, но EXISTS x (p(x)) равно FALSE,
а TRUE ⇒ FALSE равно FALSE). f. Неверно (предположим, что x
пробегает пус­тое множес­тво, тогда EXISTS x (TRUE) равно FALSE).
g. Неверно (предположим, что x пробегает пус­тое множес­тво, тогда FORALL x (FALSE) равно TRUE). h. Верно. i. Неверно (например,
сказать, что ровно одно целое число равно нулю, не то же самое, что
сказать, что все целые числа равны нулю). j. Неверно (например, из
того, что во всех сутках 24 часа, и из того, что существуют хотя бы
одни сутки, в которых 24 часа, не следует, что ровно в одних сутках
24 часа). k. Верно. Отметим, что (правильные!) экиваленции и импликации, подобные рассматриваемым, можно положить в основу
правил трансформации выражений исчисления, похожих на правила трансформации алгебраичес­ких выражений, которые обсуждались в главе 6.

445

Глава 10

Упражнение 10.10. a. Верно. b. Верно. c. Верно. d. Верно. e. Неверно (например, сказать, что для каждого целого числа y существует большее целое число x, не то же самое, что сказать, что существует целое
число x, большее всех целых чисел y). f. Верно.
Упражнение 10.11. a. Верно. b. Верно.
Упражнение 10.12. Я привожу лишь такие решения, для которых можно отметить что-то интересное. Упражнения из главы 6:
6.12. Следующие выражения реляционного исчисления обозначают
соответственно TABLE_DEE и TABLE_DUM:
{ } WHERE TRUE
{ } WHERE FALSE

А следующее выражение обозначает проекцию текущего значения
пе­ре­мен­ной-от­но­ше­ния S на пус­тое множес­тво атрибутов:
{ } WHERE EXISTS ( SX )

Обычно не считают, что в реляционном исчислении имеется прямой
аналог выражения r{ALL BUT ...} языка Tutorial D, но в принципе
нет причин, по которым его не могло бы быть.
6.15. Обычно не считают, что в реляционном исчислении имеется
прямой аналог выражения D_UNION языка Tutorial D, но в принципе нет причин, по которым его не могло бы быть.
Упражнение 10.12 (продолжение). Упражнения из главы 7:
7.1.
d. { PX } WHERE SUM ( SPX WHERE SPX.PNO = PX.PNO , QTY ) < 500
e. { PX } WHERE EXISTS ( SX WHERE SX.CITY = PX.CITY )
j. { PX , COUNT ( SPX WHERE SPX.PNO = PX.PNO ) AS SCT }
7.8. В реляционном исчислении аналогом выражения SP GROUP ({}
AS X) языка Tutorial D является выражение:
{ SPX , { } AS X }

7.10. В реляционном исчислении аналогом выражения (R4 WHERE
TUPLE {PNO ‘P2’} ∈ PNO_REL){SNO} языка Tutorial D является выражение:
RANGEVAR RX RANGES OVER R4 ,
RANGEVAR RY RANGES OVER RX.PNO_REL ;
RX.SNO WHERE EXISTS ( RY WHERE RY.PNO = ‘P2’ )

Отметим, что определение RY выше зависит от RX (определения разделяются запятой, а не точкой с запятой, и потому считаются одной
операцией).

446

Приложение С. Ответы к упражнениям

А вот аналог выражения ((R4 WHERE SNO = ‘S2’) UNGROUP (PNO_
REL)){PNO}:
RY.PNO WHERE RX.SNO = ‘S2’

7.11. { SX , { SPX.PNO WHERE SPX.SNO = SX.SNO } AS PNO_REL }
7.12. На практике нам необходимы аналоги традиционных операторов INSERT, DELETE и UPDATE (и реляционного присваивания),
которые соответствовали бы духу реляционного исчисления, а не алгебры (и это замечание остается справедливым вне зависимости от
того, говорим мы о пе­ре­мен­ных-от­но­ше­ниях с RVA-атрибутами, как
в данном контексте, или без них). Детали выходят за рамки этой
книги, но в любом случае не вызывают затруднений. Более подробного ответа не будет.
Упражнение 10.12 (продолжение). Упражнения из главы 8: без ответа.
Упражнение 10.12 (продолжение). Упражнения из главы 9: без ответа.
Упражнение 10.13. В тексте главы отмечалось, что множес­тво, которое пробегает пе­ре­мен­ная кортежа, всегда является телом некоторого от­но­ше­ния – обычно, но не всегда, от­но­ше­ния, которое является текущим значением некоторой пе­ре­мен­ной-от­но­ше­ния (обратите
внимание на курсив). В данном примере пе­ре­мен­ная кортежа пробегает множес­тво, которое, по существу, является объединением:
RANGEVAR CX RANGES OVER { SX.CITY } , { PX.CITY } ;
{ CX } WHERE TRUE

Отметим, что в определении пе­ре­мен­ной кортежа CX используются
пе­ре­мен­ные кортежа SX и PX, которые, как я предполагаю, были
определены ранее.
Упражнение 10.14. Для доказательства реляционной полноты SQL достаточно1 показать, что (а) существуют SQL-выражения для каждой
из алгебраичес­ких операций ограничения, проекции, произведения,
объединения и разности и что (б) операндами этих SQL-выражений
могут быть произвольные SQL-выражения.
Прежде всего, как мы знаем, SQL поддерживает оператор реляционной алгебры RENAME благодаря наличию необязательной спецификации AS для элементов фразы SELECT.2 Поэтому можно гаранти-

1

Достаточно, но необходимо ли?

2

Немного более точно: SQL-аналогом алгебраичес­кого выражения T RENAME (A AS B) служит (крайне неудобное!) SQL-выражение SELECT A AS
B, X, Y, . . . , Z FROM T (где X, Y, ..., Z – все столбцы T за исключением A,
и я закрываю глаза на то, что это SQL-выражение дает таб­лицу, столбцы которой упорядочены слева направо).

447

Глава 10

ровать, что столбцы во всех таб­лицах имеют надлежащие имена, а,
значит, операнды произведения, объединения и разности удовлетворяют требованиям алгебры, предъявляемым к именам столбцов. Далее – при условии, что требования к именам столбцов дейст­вительно
удовлетворяются, – правила наследования имен столбцов в SQL совпадают с правилами алгебры, которые были описаны в главе 6.
Вот SQL-выражения, приблизительно соответствующие пяти примитивным операторам:
Алгебра

SQL

R WHERE p

SELECT * FROM R WHERE p

R { A , B , ... , C }

SELECT DISTINCT A , B , ... , C FROM R

R1 TIMES R2

SELECT * FROM R1 , R2

R1 UNION R2

SELECT * FROM R1
UNION CORRESPONDING
SELECT * FROM R2

R1 MINUS R2

SELECT * FROM R1
EXCEPT CORRESPONDING
SELECT * FROM R2

Далее, (а) R1 и R2 в приведенных выше SQL-выражениях фактически являются ссылками на таб­лицы, и (б) если взять любое из
пяти показанных SQL-выражений и заключить его в скобки, то снова получится ссылка на таб­лицу.1 Отсюда следует, что SQL – дейст­
вительно реляционно полный язык.

Примечание
В предыдущем рассуждении есть один изъян – SQL не поддерживает проецирование на пус­тое множес­тво столбцов (поскольку не поддерживает пус­тые
фразы SELECT). Следовательно, не поддерживаются также TABLE_DEE и TABLE_
DUM, а, значит, SQL все-таки не является на 100 процентов реляционно полным.

Упражнение 10.15. Пусть TP и DC обозначают, соответственно, высказывания «база данных содержит только истинные высказывания»
и «база данных непротиворечива». Тогда первый пункт означает:
IF TP THEN DC

а второй:
IF NOT ( DC ) THEN NOT ( TP )
1

Я игнорирую тот факт, что SQL в таких ссылках на таб­лицы настаивает на
включении бессмысленного определения пе­ре­мен­ной кортежа.

448

Приложение С. Ответы к упражнениям

Если вспомнить определение логичес­кой импликации в терминах
NOT и OR, то легко видеть, что эти два высказывания на самом деле
эквивалентны. (В дейст­вительности, второе – контрапозиция первого. Дополнительные объяснения см. в главе 11.)
Упражнение 10.16. Нет, хотя в учебниках по формальной логике обычно утверждается противоположное, и на практике она «обычно» достижима. В статье «A Remark on Prenex Normal Form» (см. приложение D) содержится дополнительная информация.

Глава 11
Упражнение 11.1. Сначала приведу формулировку на SQL запроса
«Получить поставщиков SX таких, что для любых деталей PX и PY,
если PX.CITY ≠ PY.CITY, то SX не поставляет обе детали». (Насколько эта формулировка отличается от данной в тексте главы?)
SELECT
FROM
WHERE
(

SX.*
S AS SX
NOT EXISTS
SELECT *
FROM P AS PX
WHERE EXISTS
( SELECT *
FROM P AS PY
WHERE PX.CITY PY.CITY
AND
EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PX.PNO )
AND
EXISTS
( SELECT *
FROM SP AS SPX
WHERE SPX.SNO = SX.SNO
AND
SPX.PNO = PY.PNO ) ) )

Далее требовалось дать SQL-формулировки (а) в которых используются GROUP BY и HAVING, (b) в которых не используются GROUP
BY и HAVING, для следующих запросов:
•• Получить номера поставщиков, которые поставляют N различных деталей для некоторого N > 3.
•• Получить номера поставщиков, которые поставляют N различных деталей для некоторого N < 4.
Вот как выглядят формулировки с GROUP BY и HAVING:
SELECT SNO
FROM SP

449

Глава 11
GROUP BY SNO
HAVING COUNT ( * ) > 3
SELECT
FROM
GROUP
HAVING
UNION
SELECT
FROM
WHERE
(

SNO
SP
BY SNO
COUNT ( * ) < 4
CORRESPONDING
SNO
S
SNO NOT IN
SELECT SNO
FROM SP )

А вот без GROUP BY и HAVING:
SELECT SNO
FROM S
WHERE ( SELECT COUNT ( * )
FROM SP
WHERE SP.SNO = S.SNO ) > 3
SELECT SNO
FROM S
WHERE ( SELECT COUNT ( * )
FROM SP
WHERE SP.SNO = S.SNO ) < 4

Также требовалось ответить на вопрос, какие выводы можно сделать из этого упражнения. Лично я заключаю, что нужно проявлять большую осмотрительность при использовании GROUP BY
и HAVING. В частности, отметим, что формулировки на естественном языке симметричны, тогда как запросы с использованием
GROUP-BY/HAVING, очевидно, несимметричны. А вот запросы без
GROUP BY и HAVING, напротив, симметричны.
Упражнение 11.2. Без ответа.
Упражнение 11.3. Без ответа.
Упражнение 11.4. Разумеется, я не собираюсь давать сколько-нибудь
полный ответ к этому упражнению, но, по крайней мере, замечу,
что следующие эквиваленции позволяют преобразовать некоторые
алгебраичес­кие выражения в выражения реляционного исчисления,
и наоборот:
•• r WHERE bx1 AND bx2 ≡ ( r WHERE bx1 ) JOIN ( r WHERE bx2 )
•• r WHERE bx1 OR bx2

≡ ( r WHERE bx1 ) UNION ( r WHERE bx2 )

•• r WHERE NOT ( bx )

≡ r MINUS ( r WHERE bx )

В тексте книги (начиная с главы 6) упоминались и некоторые другие
трансформации.

450

Приложение С. Ответы к упражнениям

Глава 12
Упражнение 12.1.
A NATURAL JOIN B : Допус­тимо
A INTERSECT B : Допус­тимо
TABLE A NATURAL JOIN TABLE B : Допус­тимо
TABLE A INTERSECT TABLE B : Допус­тимо
SELECT * FROM A NATURAL JOIN SELECT * FROM B : Недопус­тимо
SELECT * FROM A INTERSECT SELECT * FROM B : Допус­тимо
( SELECT * FROM A ) NATURAL JOIN ( SELECT * FROM B ) : Недопус­тимо
( SELECT * FROM A ) INTERSECT ( SELECT * FROM B ) : Допус­тимо
( TABLE A ) NATURAL JOIN ( TABLE B ) : Недопус­тимо
( TABLE A ) INTERSECT ( TABLE B ) : Допус­тимо
( TABLE A ) AS AA NATURAL JOIN ( TABLE B ) AS BB : Допус­тимо
( TABLE A ) AS AA INTERSECT ( TABLE B ) AS BB : Недопус­тимо
( ( TABLE A ) AS AA ) NATURAL JOIN ( (TABLE B) AS BB ) : Недопус­тимо
( ( TABLE A ) AS AA ) INTERSECT ( ( TABLE B ) AS BB ) : Недопус­тимо

Из этого упражнения я делаю вывод, что эти правила очень трудно запомнить (это еще мягко сказано). В частности, SQL-выражения
с INTERSECT не всегда можно напрямую трансформировать в эквивалентные выражения с JOIN.
Упражнение 12.2. Без ответа.
Упражнение 12.3. Без ответа.

Приложение B
Упражнение B.1. Без ответа.
Упражнение B.2. Полное множес­тво ФЗ (формально оно называется замыканием, хотя не имеет ничего общего со свойством замкнутости
реляционной алгебры) для пе­ре­мен­ной-от­но­ше­ния SP таково:
{
{
{
{
{
{
{
{

SNO
SNO
SNO
SNO
SNO
SNO
SNO
SNO

,
,
,
,
,
,
,
,

PNO
PNO
PNO
PNO
PNO
PNO
PNO
PNO

,
,
,
,
,
,
,
,

{ SNO , PNO }
{ SNO , PNO }

QTY
QTY
QTY
QTY
QTY
QTY
QTY
QTY

}
}
}
}
}
}
}
}










{
{
{
{
{
{
{
{

SNO
SNO
PNO
SNO
SNO
PNO
QTY
}

,
,
,
,
}
}
}

PNO
PNO
QTY
QTY

, QTY }
}
}
}

→ { SNO , PNO , QTY }
→ { SNO , PNO }

451

Приложение B
{
{
{
{
{
{

SNO
SNO
SNO
SNO
SNO
SNO

,
,
,
,
,
,

PNO
PNO
PNO
PNO
PNO
PNO

}
}
}
}
}
}








{
{
{
{
{
{

PNO
SNO
SNO
PNO
QTY
}

{
{
{
{

PNO
PNO
PNO
PNO

,
,
,
,

QTY
QTY
QTY
QTY

}
}
}
}






{
{
{
{

PNO , QTY }
PNO }
QTY }
}

{
{
{
{

SNO
SNO
SNO
SNO

,
,
,
,

QTY
QTY
QTY
QTY

}
}
}
}






{
{
{
{

SNO , QTY }
SNO }
QTY }
}

, QTY }
, QTY }
}
}
}

{ SNO }
{ SNO }

→ { SNO }
→ { }

{ PNO }
{ PNO }

→ { PNO }
→ { }

{ QTY }
{ QTY }

→ { QTY }
→ { }

{ }

→ { }

Упражнение B.3. Истинно (из утверждения «если в двух кортежах одинаковы значения A, то одинаковы и значения B» следует, что проекции этих кортежей на {A,B} равны). Примечание: В тексте приложения я заметил, что понятие проекции кортежа столь же осмысленно,
как понятия проекции от­но­ше­ния, и аналогичное замечание справедливо для некоторых других реляционных операторов (например,
переименования и расширения). Техничес­ки мы говорим, что такие
операторы перегружены.
Упражнение B.4. Без ответа (легкое упражнение).
Упражнение B.5. Теорема Хита утверждает: если R{A,B,C} удовлетворяет ФЗ A → B, то R равно соединению своих проекций R1 на {A,B} и R2
на {A,C}. В следующем простом доказательстве этой теоремы я пользуюсь неформальной сокращенной нотацией для кортежей, которая
была введена в тексте приложения.
Сначала я покажу, что в результате взятия проекций и последующего их обратного соединения ни один кортеж R не теряется. Пусть
(a,b,c) ∈ R. Тогда (a,b) ∈ R1 и (a,c) ∈ R2, поэтому (a,b,c) ∈ R1 JOIN R2.
Теперь я покажу, что любой кортеж соединения дейст­вительно является кортежем R (иначе говоря, соединение не порождает «лишних»
кортежей). Пусть (a,b,c) ∈ R1 JOIN R2. Чтобы породить такой кортеж

452

Приложение С. Ответы к упражнениям

в соединении, мы должны иметь (a,b) ∈ R1 и (a,c) ∈ R2. Следовательно, чтобы можно было породить кортеж (a,c) ∈ R2, должен существовать кортеж (a,b′,c) ∈ R для некоторого b′. Тогда должно быть (a,b′) ∈ R1.
Итак, мы имеем (a,b) ∈ R1 и (a,b′) ∈ R1; поэтому должно быть b = b′, поскольку A → B. Отсюда (a,b,c) ∈ R.
Обратная теорема Хита утверждала бы, что если R{A,B,C} равно соединению своих проекций на {A,B} и на {A,C}, то R удовлетворяет ФЗ
A → B. Но это неверно. Чтобы убедиться в этом, достаточно привести
контрпример; оставляю эту задачу вам. (Если сдаетесь, посмотрите ответ к упражнению B.20. Обратите также внимание на пе­ре­мен­
ную-от­но­ше­ние SPJ на рис. B.5 в тексте этого приложения.)
Упражнение B.6. См. текст приложения.
Упражнение B.7. См. главу 5.
Упражнение B.8. Определения см. в тексте главы. Первая является частным случаем второй. Например, пе­ре­мен­ная-от­но­ше­ние S удовлетворяет тривиальной ФЗ {CITY,STATUS} → {STATUS}. Применяя теорему Хита, мы видим, что S удовлетворяет тривиальной ЗС *{AB,AC},
где A – {CITY,STATUS}, B – {STATUS}, а C – {SNO,SNAME}.
Упражнение B.9. ФЗ по сути дела представляет собой суждение (на самом деле, высказывание) вида A → B, где A и B – подмножес­тва заголовка R. Так как у множес­тва из n элементов есть 2n различных
подмножес­тв, то A и B могут принимать 2n значений, следовательно, количество возможных ФЗ сверху ограничено величной 22n. Например, для пе­ре­мен­ной-от­но­ше­ния степени 5 верхняя граница составляет 1024.
Упражнение B.10. Пусть заданной ФЗ удовлетворяет пе­ре­мен­ная-от­но­
ше­ние R. Для любого кортежа t, принадлежащего R, все его подкортежи, соответствующие пус­тому множес­тву атрибутов, принимают одно и то же значение (а именно, 0-кортеж). Если B пус­то, то ФЗ
A → B тривиально удовлетворяется для всех возможных множес­тв A
атрибутов R; фактичес­ки это тривиальная ФЗ (и потому не очень интересная). С другой стороны, если A пус­то, то ФЗ A → B означает, что
все кортежи R принимают одно и то же значение для B (поскольку
значения для A, очевидно, одинаковы). А если B при этом состоит из
«всех атрибутов R» – иными словами, если R имеет пус­той ключ, – то
R не может иметь более одного кортежа.
Упражнение B.11. Предположим, что мы начали с пе­ре­мен­ной-от­но­
ше­ния R с атрибутами D, P, S, L, T и C (имена атрибутов очевидным
образом соответствуют параметрам предиката). Тогда R удовлетворяет следующим нетривиальным ФЗ:
{
{
{
{

L
D
D
D

}
→ {
, P , C } → {
, P , T } → {
, P , S } → {

D
L
L
L

,
,
,
,

P
T
C
C

, C , T }
}
}
}

453

Приложение B

Один их возможных наборов пе­ре­мен­ных-от­но­ше­ний, находящихся
в НФБК, выглядит так:
SCHEDULE { L
KEY
KEY
KEY

,
{
{
{

D
L
D
D

, P , C , T }
}
, P , C }
, P , T }

STUDYING { S , L }
KEY { S , L }

STUDYING находится в 6НФ; SCHEDULE – в 5НФ, но не в 6НФ. При
такой декомпозиции «потерялась» ФЗ {D,P,T} → {L,C}; более того,
если бы мы разложили SCHEDULE на 6НФ-проекции на {L,D}, {L,P},
{L,C} и {L,T} (а при желании мы, конечно, можем это сделать), то дополнительно «потеряли» бы ФЗ {D,P,C} → {L,T} и {D,P,T} → {L,C}. Поэтому продолжать декомпозицию, пожалуй, не стоит.
Упражнение B.12. Да, иногда, хотя и не очень часто; на самом деле,
именно эта возможность и лежит в основе различия между 5НФ
и 4НФ. В подразделе «Еще о 5НФ» в тексте этого приложения приводится пример пе­ре­мен­ной-от­но­ше­ния, которую можно без потерь разложить на три, но не на две проекции. Такую декомпозицию можно даже назвать желательной, потому что (повторюсь) она
уменьшает избыточность и, стало быть, позволяет избежать аномалий при обновлении.
Упражнение B.13. Суррогатные ключи – не то же самое, что идентификаторы кортежей. Во-первых (начнем с очевидного), суррогатный
ключ идентифицирует сущность, а идентификатор кортежа – кортежи, а между сущностями и кортежами, безусловно, не существует
взаимно однозначного соответствия. (Вспомните, например, о производных кортежах, которые возникают в результате выполнения
некоторого запроса. Собственно, даже неясно, а есть ли у производ­
ных кортежей идентификатор.) Далее, идентификаторы кортежей
имеют непосредственное касательство к производительности, а суррогатные ключи – нет (обычно предполагается, что доступ к кортежу по его идентификатору производится быст­ро, но о суррогатных
ключах этого не скажешь). Кроме того, идентификаторы кортежей
обычно скрыты от пользователя, а суррогатные ключи должны быть
ему видны (в силу принципа информации – см. приложение A); другими словами, идентификатор кортежа, скорее всего, невозможно
(и нежелательно!) хранить в пе­ре­мен­ной-от­но­ше­нии базы данных,
тогда как хранить там суррогатный ключ возможно и желательно.
Резюмируем: суррогатные ключи относятся к логичес­кой структуре
базы данных, а идентификаторы кортежей – к физичес­кой.
Можно ли назвать идею суррогатных ключей здравой? Для начала
отметим, что сама реляционная модель об этом ничего не говорит;
как и вообще все проектирование баз данных, вопрос о том, исполь-

454

Приложение С. Ответы к упражнениям

зовать суррогатные ключи или нет, относится к применению реляционной модели, а не к самой модели.
Однако я должен сказать, что этот вопрос далеко не прост. Существуют сильные доводы как за, так и против суррогатных ключей, причем их так много, что я не смогу разобрать все. Вместо этого я просто
отсылаю вас к собственной подробной статье на эту тему «Composite
Keys» (см. приложение D). Примечание: Эта статья называется «Составные ключи», потому что на практике суррогатные ключи чаще
всего бывают полезны, когда имеющиеся ключи (и соответствующие
им внешние ключи) являются составными.
И последнее замечание о суррогатах. Поскольку пе­ре­мен­ная-от­
но­ше­ние, находящаяся в НФБК и не имеющая составных ключей,
«автоматичес­ки» находится в 5НФ, многие, похоже, считают, что
стоит ввести суррогатный ключ в НФБК-пе­ре­мен­ную, как она тут же
окажется в 5НФ. Отнюдь нет. В частности, если пе­ре­мен­ная-от­но­ше­
ние имела составной ключ до введения суррогатного, то будет иметь
и после этого.
Упражнение B.14. В этом упражнении я буду использовать нотацию
X → Y для обозначения того, что если каждый человек, принадлежащий множес­тву X, придет на вечеринку, то и каждый человек,
принадлежащий множес­тву Y, тоже придет (такой выбор нотации
не сов­сем произволен). А выражения вида X → Y я буду называть
утверждениями.
Таким образом, начальный и самый очевидный проект состоит из
единственной пе­ре­мен­ной-от­но­ше­ния IXAYWA («if X attends, Y will
attend» – если X придет, то и Y придет), содержащий по одному кортежу для каждого из имеющихся утверждений (см. следующий рисунок, на котором INV означает «invitee» – приглашенный). Отметим, что X и Y в этой пе­ре­мен­ной-от­но­ше­нии – RVA-атрибуты.
IXAY WA

X

Y

INV

INV

INV

INV

Fay

Hal

Am y

Bob
Cal

INV

INV
Guy

INV

INV

Don
Eve

Hal

INV

INV
Joe

...

...

Bob
Amy

Но можно пойти дальше. Разумеется, я выбрал нотацию ФЗ не случайно. На самом деле, утверждение «если X придет, то и Y придет», очевидно, изоморфно утверждению «ФЗ X → Y удовлетворяет-

Приложение B

455

ся» (представьте себе пе­ре­мен­ную-от­но­ше­ние с булевыми атрибутами для Эми, Боба, Кэла и всех остальных; при этом каждый кортеж представляет битовую карту присутствия на вечеринке, совместимую с заданными условиями). Теперь мы можем воспользоваться
теорией ФЗ, чтобы уменьшить объем информации, явно хранимой
в этой пе­ре­мен­ной-от­но­ше­нии. С помощью теории мы сумеем найти
так называемый неприводимый эквивалент заданного множес­тва
утверждений, то есть (обычно небольшое) множес­тво утверждений,
которое не содержит избыточной информации и обладает тем свойством, что все исходные утверждения следуют из утверждений, вошедших в это множес­тво.
Подробное рассмотрение того, как искать неприводимый эквивалент, выходит за рамки настоящей книги, я лишь хочу описать набросок алгоритма. Во-первых, в приложении B я уже отмечал, что
если X → Y удовлетворяется, то и X′ → Y′ удовлетворяется для всех
надмножес­тв X′ ⊇ X и всех подмножес­тв Y′ ⊆ Y. Поэтому мы можем
сократить объем явно сохраняемой информации, проверяя для каждого записываемого утверждения X → Y, что множес­тво X – наименьшее из возможных, а множес­тво Y – наибольшее из возможных.
Точнее:
•• Если хотя бы одного человека удалить из X, то результирующее
утверждение перестанет быть верным.
•• Если хотя бы одного человека добавить в Y, то результирующее
утверждение перестанет быть верным.
Кроме того, мы знаем, что ФЗ X → Y обязательно удовлетворяется
для всех множес­тв Y таких, что Y – подмножес­тво X (в частности, зависимость вида X → X всегда удовлетворяется, так как означает, например, что «если Эми придет, то Эми придет»). Эти случаи тривиальны, поэтому их явно сохранять вообще не нужно.
Отметим также, что если пе­ре­мен­ная-от­но­ше­ние IXAYWA содержит только те ФЗ, которые составляют неприводимый эквивалент,
то {X} будет ее ключом.
Желающих почитать продолжение отсылаю к своей книге «An In­
tro­duction to Database Systems» (см. приложение D).
Упражнение B.15. Самый простой проект выглядит примерно так:
EMP { ENO , ENAME , SALARY }
KEY { ENO }
PGMR { ENO , LANG }
KEY { ENO }
FOREIGN KEY { ENO } REFERENCES EMP

Каждый служащий представлен кортежем в EMP (и никаких других
кортежей в EMP нет). Для служащих-программистов дополнитель-

456

Приложение С. Ответы к упражнениям

но имеется кортеж в PGMR (и никаких других кортежей в PGMR
нет). Отметим, что соединение EMP и PGMR дает всю информацию
(номер служащего, имя, зарплату и владение языком) о программистах (и только о них).
Если программист может владеть несколькими языками, то нужно будет внести всего одно изменение – сделать ключом пе­ре­мен­
ной-от­но­ше­ния совокупность всех атрибутов, то есть множес­тво
{ENO,LANG}.
Упражнение B.16. Назовем проекции R на {A,B} и на {A,C} R1 и R2 соответственно, и пус­ть (a,b1,c1) ∈ R и (a,b2,c2) ∈ R. Тогда (a,b1) ∈ R1
и (a,b2) ∈ R1, а (a,c1) ∈ R2 и (a,c2) ∈ R2; таким образом, (a,b1,c2) ∈ J
и (a,b2,c1) ∈ J, где J = R1 JOIN R2. Но R удовлетворяет ЗС *{AB,AC},
поэтому J = R; следовательно, (a,b1,c2) ∈ R и (a,b2,c1) ∈ R.
Кстати, отметим, что определение МЗЗ симметрично относительно
B и C; следовательно, R удовлетворяет МЗЗ A →→ B тогда и только
тогда, когда она удовлетворяет МЗЗ A →→ C. МЗЗ всегда встречаются такими парами, поэтому принято записывать их одним предложением:
A →→ B | C

Упражнение B.17. Пусть C – атрибуты R, не вошедшие ни в A, ни в B.
По теореме Хита, если R удовлетворяет ФЗ A → B, то она удовлетворяет и ЗС *{AB,AC}. Однако по определению (см. упражнение B.16),
если R удовлетворяет ЗС *{AB,AC}, то она удовлетворяет МЗЗ A →→ B
и A →→ C. Таким образом, из A → B следует A →→ B.
Упражнение B.18. Результат немедленно следует из определения многозначной зависимости (см. упражнение B.16).
Упражнение B.19. Упражнение B.17 показывает, что если K → A, то
K →→ A. Но если K – ключ, то K → A, откуда сразу же следует требуемое утверждение.
Упражнение B.20. Пусть C – некоторый клуб, и пус­ть пе­ре­мен­ная-от­но­
ше­ние R{A,B} такова, что кортеж (a,b) встречается в R тогда и только
тогда, когда и a, и b являются членами клуба C. Тогда R равно декартову произведению своих проекций R{A} и R{B}, поэтому она удовлетворяет ЗС K{A,B} и, что эквивалентно, следующим МЗЗ:
{ } →→ A | B

Эти МЗЗ не тривиальны, так как им, конечно, удовлетворяют не все
бинарные пе­ре­мен­ные-от­но­ше­ния, и они не обусловлены суперключом. Отсюда следует, что R не находится в 4НФ. Однако она находится в НФБК, потому что ключом является совокупность всех атрибутов (см. упражнение B.24).
Упражнение B.21. Сначала введем три пе­ре­мен­ные-от­но­ше­ния с очевидной интерпретацией:

Приложение B

457

REP
{ REPNO , ... } KEY { REPNO }
AREA
{ AREANO , ... } KEY { AREANO }
PRODUCT { PRODNO , ... } KEY { PRODNO }

Далее, связи между (а) торговыми представителями и регионами
продаж и (б) торговыми представителями и изделиями можно представить такими пе­ре­мен­ными-от­но­ше­ниями:
RA { REPNO , AREANO } KEY { REPNO , AREANO }
RP { REPNO , PRODNO } KEY { REPNO , PRODNO }

Каждое изделие продается в каждом регионе. Поэтому вводим пе­ре­
мен­ную-от­но­ше­ние
AP { AREANO , PRODNO } KEY { AREANO , PRODNO }

для представления связи между регионами и изделиями и следующее ограничение:
CONSTRAINT C1 AP = AREA { AREANO } JOIN PRODUCT { PRODNO } ;

Отметим, что из этого ограничения следует, что AP не находится в 4НФ. На самом деле, AP не несет информации, которую нельзя было бы получить из других пе­ре­мен­ных-от­но­ше­ний. Точнее, мы
имеем:
AP { AREANO } = AREA { AREANO }
AP { PRODNO } = PRODUCT { PRODNO }

Но предположим ненадолго, что мы все-таки включили пе­ре­мен­
ную-от­но­ше­ние AP в проект.
Никакие два представителя не продают одно и то же изделие в од­ном
и том же регионе. Иными словами, для данной комбинации {AREANO,
PRODNO} существует ровно один отвечающий за нее торговый представитель REPNO, поэтому можно ввести пе­ре­мен­ную-от­но­ше­ние
APR { AREANO , PRODNO , REPNO } KEY { AREANO , PRODNO }

в которой (чтобы сделать ФЗ явной)
{ AREANO , PRODNO } → { REPNO }

(Задания {AREANO,PRODNO} в качестве ключа достаточно для выражения этой ФЗ.) Теперь, однако, все пе­ре­мен­ные-от­но­ше­ния RA,
RP и AP стали избыточными, потому что являются проекциями
APR; следовательно, их можно удалить. Вместо ограничения C1 нам
необходимо ограничение C2:
CONSTRAINT C2 APR { AREANO , PRODNO } =
AREA { AREANO } JOIN PRODUCT { PRODNO } ;

Это ограничение должно быть сформулировано явно и независимо
(оно не «обусловлено ключами»).

458

Приложение С. Ответы к упражнениям

Также, поскольку каждый представитель продает все изделия, за
которые отвечает, во всех подведомственных ему регионах, то мы
имеем дополнительное ограничение C3 на пе­ре­мен­ную-от­но­ше­ние
APR:
{ REPNO } →→ { AREANO } | { PRODNO }

(Эти МЗЗ не тривиальны, и пе­ре­мен­ная-от­но­ше­ние APR не находится в 4НФ.) Это ограничение также должно быть сформулировано явно и независимо.
Таким образом, окончательно проект состоит из пе­ре­мен­ных-от­но­
ше­ний REP, AREA, PRODUCT и APR и ограничений C2 и C3:
CONSTRAINT C2 APR { AREANO , PRODNO } =
AREA { AREANO } JOIN PRODUCT { PRODNO } ;
CONSTRAINT C3 APR = APR { REPNO , AREANO } JOIN
APR { REPNO , PRODNO } ;

Это упражнение наглядно демонстрирует тот факт, что в общем случае нормализации может быть достаточно для представления некоторых семантичес­ких аспектов задачи (в основном, ФЗ, МЗЗ и ЗС,
обусловленных ключами), но для представления других аспектов
могут понадобиться дополнительные ограничения. Кроме того, из
него видно, что нормализация «до конца» не всегда желательна (пе­
ре­мен­ная-от­но­ше­ние APR находится в НФБК, но не в 4НФ).

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

Упражнение B.22.
CONSTRAINT SPJ_JD SPJ = JOIN { SPJ { SNO , PNO } ,
SPJ { PNO , JNO } ,
SPJ { JNO , SNO } ;

Упражнение B.23. Это пример «цик­личес­кого ограничения». Подойдет такая структура:
REP
{ REPNO , ... } KEY { REPNO }
AREA
{ AREANO , ... } KEY { AREANO }
PRODUCT { PRODNO , ... } KEY { PRODNO }
RA { REPNO , AREANO } KEY { REPNO , AREANO }
AP { AREANO , PRODNO } KEY { AREANO , PRODNO }
PR { PRODNO , REPNO } KEY { PRODNO , REPNO }

459

Приложение B

Кроме того, пользователя необходимо проинформировать о том, что
соединение RA, AP и PR не приводит к «ловушке связи»:
CONSTRAINT
(
(
(

NO_TRAP
RA JOIN AP JOIN PR ) { REPNO , AREANO } = RA AND
RA JOIN AP JOIN PR ) { AREANO , PRODNO } = AP AND
RA JOIN AP JOIN PR ) { PRODNO , REPNO } = PR ;

Примечание
Как и в упражнении B.21, можете подумать над тем, подходит ли для этой задачи структура с RVA-атрибутами

Упражнение B.24. a. Верно. b. Верно. c. Неверно, хотя «почти» верно. Вот
контрпример: пус­ть имеется пе­ре­мен­ная-от­но­ше­ние USA {COUNTRY,
STATE} («STATE (штат) является частью COUNTRY (страны)»), где во
всех кортежах COUNTRY содержит U.S. Эта пе­ре­мен­ная-от­но­ше­ние
удовлетворяет ФЗ {} → {COUNTRY}, которая не является ни тривиальной, ни обусловленной ключом, поэтому данная пе­ре­мен­ная-от­
но­ше­ние не находится в НФБК (она может быть без потерь разложена
на две унарных проекции).
Упражнение B.25. Да, относятся. Какой вывод вы из этого сделаете?
Упражнение B.26. Я не собираюсь здесь рисовать никакие диаграммы
«сущность-связь», а смысл упражнения в том, чтобы придать вес следующим замечаниям из приложения B:
Проблема в том, что диаграммы «сущность-связь» и аналогичные
им картинки в состоянии представить не все ограничения, а только небольшую часть, пус­ть и важную. Поэтому, хотя их использование и допус­тимо для того, чтобы объяснить проект на высоком уровне абстракции, думать, что на такой диаграмме структура представлена во всей полноте – заблуждение, иногда весьма
опасное. Напротив: структура базы данных состоит из пе­ре­мен­
ных-от­но­ше­ний, которые на диаграммах отражены, и ограничений, которые не отражены [курсив добавлен].

D

Приложени

.

Дополнительная литература
Как следует из названия, в этом приложении содержится список литературы для дополнительного чтения. Приношу извинения за то, что
в большинстве публикаций я сам являюсь автором или соавтором. Работы упорядочены по имени автора, а работы одного и того же автора – в хронологичес­ком порядке по возрастанию. Примечание: В этой
книге не рассматриваются конкретные продукты на основе SQL, поэтому и публикаций, относящихся к таким продуктам, вы здесь не найдете. Однако таких работ много, и, возможно, вы захотите познакомиться с некоторыми из них, чтобы применить обсуждавшиеся в этой книге
идеи к какому-то конкретному продукту.
1. Surajit Chaudhuri and Gerhard Weikum «Rethinking Database System Architecture: Towards a Self-tuning RISC-style Database System»
Proc. 26th Int. Conf. on Very Large Data Bases, Cairo, Egypt (September 2000).
Среди прочего в этой работе убедительно отстаивается одна из ключевых идей настоящей книги, а именно (как я писал в предисловии):
«SQL сложен, запутан, при написании SQL-команд легко допус­тить
ошибку – рискну предположить, что куда легче, чем можно судить
по уверениям апологетов этого языка.» Приведу пространную цитату из введения к статье:
SQL – источник мучений. Одна из самых неприятных проблем,
присущих сис­теме баз данных, – это язык SQL. Он представляет собой конгломерат самой разнообразной функциональности
(многие его средства используются редко или должны быть поставлены под запрет) и слишком сложен для типичного разработчика приложений. Его основа, отнесем к ней выборку-проекциюсоединение и агрегирование, чрезвычайно полезна, но мы сомневаемся, что у многочисленных «бантиков» есть широкое и разумное применение. Разбираться в семантике SQL (даже до SQL-92),
включающей в себя сочетание вложенных (и коррелированных)

Дополнительная литература

461

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

2. E. F. Codd «Derivability, Redundancy, and Consistency of Relations
Stored in Large Data Banks» IBM Research Report RJ599 (August
19th, 1969); «A Relational Model of Data for Large Shared Data Banks,»
CACM 13, No. 6 (June 1970).
Написанная в 1969 году статья Кодда была первой работой по реляционной модели и по существу явилась предтечей статьи 1970 года,
с рядом очень любопытных отличий (главное из них состоит в том,
что в 1969 году были разрешены атрибуты, значениями которых являются от­но­ше­ния, а в 1970 – уже нет). Эта статья 1970 года – перепечатанная в Milestones of Research, CACM 26, No. 1 (January
1982) и в других изданиях – стала первой широкодоступной работой на данную тему. Обычно ее называют первопроходчес­кой работой в этой области, хотя это, пожалуй, несправедливо по от­но­ше­нию
к предвосхитившей ее статье 1969 года. Я считаю, что каждый, кто
профессионально занимается базами данных, должен ежегодно перечитывать хотя бы одну, а то и обе эти статьи.
3. E. F. Codd «Relational Completeness of Data Base Sublanguages»
в сборнике Randall J. Rustin (ed.), Data Base Systems, Courant Com­
puter Science Symposia Series 6. Englewood Cliffs, N.J.: Prentice Hall
(1972).
В этой работе Кодд впервые формально определил реляционную алгебру и реляционное исчисление в их первозданном виде. Чтение нелегкое, но вдумчивое изучение окупится сторицей.
4. E. F. Codd and C. J. Date «Much Ado about Nothing» в сборнике
C. J. Date, Relational Database Writings 1991–1994. Reading, Mass.:
Addison-Wesley (1995).
Кодд был, пожалуй, самым яростным приверженцем null-значений
и трехзначной логики, как основы для обработки отсутствующей
информации (забавно, учитывая, что null-значения нарушают сформулированный тем же Коддом принцип информации). В этой статье

462

Приложение D

приводится спор между Коддом и мной на эту тему. Здесь же вы найдете восхитительное замечание: «Управление базами данных стало
бы куда проще, не будь этих отсутствующих значений» (Кодд). Примечание: Я включил именно эту ссылку (из огромного числа публикаций на эту тему), потому что здесь так или иначе затрагиваются
почти все доводы обеих сторон.
5. Hugh Darwen «The Role of Functional Dependence in Query Decomposition» в сборнике C. J. Date and Hugh Darwen, Relational Database
Writings 1989–1991. Reading, Mass.: Addison-Wesley (1992).
В этой работе приводятся правила вывода, применяя которые можно вывести функциональные зависимости (ФЗ), которым удовлетворяет от­но­ше­ние r, являющееся результатом вычисления произвольного реляционного выражения, зная, каким ФЗ удовлетворяют
пе­ре­мен­ные-от­но­ше­ния,участвующие в этом выражении. Проанализировав множес­тво выведенных таким образом ФЗ, можно определить, каким ограничениям ключа удовлетворяет r, что составляет основу правил вывода ключа, которые мимоходом упоминались
в главе 4.
6. Hugh Darwen «What a Database Really Is: Predicates and Propositions» в сборнике C. J. Date, Relational Database Writings 1994–1997.
Reading, Mass.: Addison-Wesley (1998).
Очень внятное пособие по предикатам пе­ре­мен­ных-от­но­ше­ний и смеж­
ным вопросам.
7. Hugh Darwen «How to Handle Missing Information Without Using
Nulls» (слайды к презентации), http://www.thethirdmanifesto.com
(May 9th, 2003; revised May 16th, 2005).
Один из возможных подходов к проблеме, обозначенной в заголовке («Как обрабатывать отсутствие информации, не используя null»).
8. C. J. Date «Fifty Ways to Quote Your Query», http://www.dbpd.com
(July 1998).
На эту статью есть ссылка в главе 6.
9. C. J. Date «Composite Keys» в сборнике C. J. Date and Hugh Darwen,
Relational Database Writings 1989-1991. Reading, Mass.: AddisonWesley (1992).
На эту статью есть ссылка в приложении B.
10. C. J. Date «An Introduction to Database Systems» (8th edition). Boston,
Mass.: Addison-Wesley (2004).
Учебник по всем аспектам управления базами данных для студентов
младших курсов. SQL обсуждается на уровне стандарта SQL:1999
с некоторыми замечаниями, относящимися к SQL:2003; в частности, включено рассмотрение «объектно-реляционных» механизмов
(типы REF, ссылочные значения и т. д.), и объясняется, почему они

Дополнительная литература

463

нарушают реляционные принципы. Примечание: Эта тематика рассматривается также в работах [32], [41] и [42].
11. C. J. Date «The Relational Database Dictionary», Extended Edition.
Ber­keley, Calif.: Apress (2008).
Многие определения в тексте книги основаны на приведенных в этой
работе.
12. C. J. Date «Double Trouble, Double Trouble», в сборнике Date on Data­
base: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
Всестороннее и подробное рассмотрение проблем, вызванных дубликатами. Обсуждение дубликатов в главе 4 основано на примере, взятом из этой работы.
13. C. J. Date «What First Normal Form Really Means», в сборнике Date
on Database: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
На протяжении многих лет первая нормальная форма была источником многочисленных заблуждений. В этой статье сделана попытка исправить ситуацию – и даже служить авторитетным источником, насколько это возможно. В основу аргументации положена
идея о том (см. главу 2), что понятие атомарности (в терминах которой и была первоначально определена первая нормальная форма)
лишено всякого смысла.
14. C. J. Date «A Sweet Disorder», в сборнике Date on Database: Writings
2000–2006. Berkeley, Calif.: Apress (2006).
Атрибуты от­но­ше­ний не упорядочены слева направо, но в SQLтаб­­лицах такое упорядочение столбцов подразумевается. В этой
статье исследуются последствия такого положения вещей, которые
оказываются куда менее тривиальными, чем могло бы показаться.
15. C. J. Date «On the Notion of Logical Difference», «On the Logical Difference Between Model and Implementation» и «On the Logical Differences Between Types, Values, and Variables», все в сборнике Date on
Database: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
Заголовками все сказано («О понятии логичес­кого различия»,
«О логичес­ком различии между моделью и реализацией», «О логичес­
ких различиях между типами, значениями и пе­ре­мен­ными»).
16. C. J. Date «Two Remarks on SQL’s UNION» в сборнике Date on Data­
base: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
В этой короткой статье описываются некоторые странности, возникающие в SQL-операторе UNION – и, как следствие, в операторах
INTERSECT и EXCEPT – из-за (а) приведения типов и (б) строкдубликатов.
17. C. J. Date «A Cure for Madness» в сборнике Date on Database: Writings
2000–2006. Berkeley, Calif.: Apress (2006).

464

Приложение D

Детальное исследование того факта, что – вопреки интуиции – SQLвыражения SELECT нечто FROM (SELECT * FROM t WHERE p)
WHERE q и SELECT нечто FROM t WHERE p AND q логичес­ки не эквивалентны, хотя должны бы быть эквивалентны, и по крайней мере
один современный продукт на основе SQL иногда трансформирует
первое во второе.
18. C. J. Date «Why Three- and Four-Valued Logic Don’t Work» в сборнике
Date on Database: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
В книге отмечалось, что поддержка null в SQL базируется на трехзначной логике. Реализация этой логики в SQL страдает серьезными дефектами, но даже если бы их не было, использовать трехзначную логику все равно не рекомендуется, и в этой статье объясняется, почему.
19. C. J. Date «The Logic of View Updating» в сборнике Logic and Data­
bases: The Roots of Relational Theory. Victoria, BC: Trafford Publishing (2007). См. http://www.trafford.com/07-0690.
В этой работе, а также в пространном приложении на ту же тему
в работе [28] приведены доводы в поддержку мысли о том, что представления логичес­ки всегда допускают обновления, а препятствием служат только нарушения принципа присваивания или золото­
го правила.
20. C. J. Date «The Closed World Assumption» в сборнике Logic and Data­
bases: The Roots of Relational Theory. Victoria, BC: Trafford Publishing (2007). См. http://www.trafford.com/07-0690.
На допущении замкнутости мира редко акцентируют внимание,
а ведь оно лежит в основе почти всего, что мы делаем при использовании базы данных. В этой работе указанное допущение рассматривается подробно; в частности, показано, почему его следует предпочесть допущению открытости мира (на котором, кстати, основана «семантичес­кая паутина», по крайней мере, так было заявлено).
Здесь же объясняется, почему мы все-таки можем получать ответы
«я не знаю» (если это необходимо) даже от базы данных, в которой
нет null-значений.
21. C. J. Date «The Theory of Bags: An Investigative Tutorial» в сборнике
Logic and Databases: The Roots of Relational Theory. Victoria, BC:
Traf­ford Publishing (2007). См. http://www.trafford.com/07-0690.
Среди прочего, в этой статье обсуждается, что происходит с операторами типа объединения, когда операндами являются не множес­тва,
а мультимножес­тва.
22. C. J. Date «Inclusion Dependencies and Foreign Keys» (готовится к печати).
Альтернативным названием этой статьи могло быть быть Rethinking
Foreign Keys (Переосмысливая внешние ключи); среди прочего здесь

Дополнительная литература

465

показано, что понятие внешнего ключа охватывает гораздо больше,
чем это обычно представляют. Также обсуждаются логичес­кие различия между внешними ключами и указателями. (В главе 2 отмечалось, что некоторые авторы заявляют, будто внешние ключи – не более чем традиционные указатели «в овечьей шкуре», но это сов­сем
не так.)
23. C. J. Date «Image Relations» (готовится к печати).
Подробное обсуждение семантики и полезности от­но­ше­ний-образов
(см. главу 7).
24. C. J. Date «Is SQL’s Three-Valued Logic Truth Functionally Complete?»
(готовится к печати).
Среди прочего статья содержит полное и точное описание поддержки null-значений и трехзначной логики в SQL.
25. C. J. Date «A Remark on Prenex Normal Form» (готовится к печати).
Обсуждение предваренной нормальной формы в контексте баз данных.
26. C. J. Date «Go Faster! The TransRelational™ Approach to DBMS Im­ple­
mentation» (готовится к печати).
Подробное описание The TransRelational™ Model – новаторской технологии реализации, упомянутой в приложениях A и B. Примечание: Краткое введение в эту технологию можно найти также в приложении A к работе [10].
27. C. J. Date and Hugh Darwen «A Guide to the SQL Standard» (4th edition). Reading, Mass.: Addison-Wesley (1997).
В этой книге полностью описан стандарт SQL по состоянию на 1997
год. С тех пор в стандарт было добавлено немало нового (в том числе
так называемые объектно-реляционные средства (см. работу [28])),
но к реляционному использованию SQL они имеют мало от­но­ше­ния.
Поэтому на мой, несколько предвзятый взгляд, эта книга остается
неплохим источником подробной информации практичес­ки обо
всех аспектах SQL (по крайней мере, в стандартном воплощении),
затронутых в настоящей книге.
28. C. J. Date and Hugh Darwen «Databases, Types, and the Relational
Model: The Third Manifesto» (3rd edition). Boston, Mass.: AddisonWesley (2006).
В этой книге формулируется и объясняется Третий Манифест – детально проработанное предложение о будущем сис­тем управлениями данными и базами данных. Включено также точное, хотя и несколько формальное, определение реляционной модели и подробное
предложение, касающееся необходимой поддержки теории типов (в
том числе и исчерпывающая модель наследования типов), а, кроме
того, полное описание языка Tutorial D.

466

Приложение D

29. C. J. Date and Hugh Darwen «Multiple Assignment» в сборнике Date
on Database: Writings 2000–2006. Berkeley, Calif.: Apress (2006).
См. главу 8.
30. C. J. Date, Hugh Darwen, and Nikos A. Lorentzos «Temporal Data and
the Relational Model». San Francisco, Calif.: Morgan Kaufmann (2003).
Некоторое представление о том, чему посвящена эта книга, можно
найти в приложении A.
31. C. J. Date and David McGoveran «Why Relational DBMS Logic Must
Not Be Many-Valued,» в сборнике C. J. Date, Logic and Databases: The
Roots of Relational Theory. Victoria, BC: Trafford Publishing (2007).
See http://www.trafford.com/07-0690.
В этой статье аргументируется положение о том, что языки баз данных должны быть основаны на двузначной логике (как реляционная
модель, а не как SQL).
32. Ramez Elmasri and Shamkant Navathe «Fundamentals of Database
Systems» (4th edition). Boston, Mass.: Addison-Wesley (2004).
33. Stéphane Faroult with Peter Robson «The Art of SQL». Sebastopol, Calif.: O’Reilly Media Inc. (2006).
Практичес­кое руководство на тему о том, как добиваться высокой
производительности от SQL в современных продуктах. Некоторое
представление о степени охвата материала можно получить из следующего, немного отредактированного, списка подзаголовков из двенадцати глав книги:
1. Проектирование баз данных с учетом производительности
2. Эффективный доступ к базе данных
3. Индексирование
4. Предложения SQL
5. Физичес­кая реализация
6. Классичес­кие способы применения SQL
7. Иерархичес­кие данные
8. Трудные случаи
9. Конкурентный доступ
10. Большие объемы данных
11. Время реакции
12. Мониторинг производительности
В своих советах и рекомендациях авторы не сильно отклоняются от
реляционных принципов и даже, по большей части, явно выступают в поддержку этих принципов. Но они также понимают, что со-

Дополнительная литература

467

временные оптимизаторы далеки от идеала, а потому подсказывают,
как из многих логичес­ки эквивалентных формулировок выбирать
для конкретной задачи ту, которая будет работать быст­рее (и объясняют почему). Описываются также некоторые приемы кодирования,
способствующие повышению производительности, например, использование MIN для того, чтобы убедиться, что в столбце, допускающем только значения да/нет, все значения равны да (вместо того,
чтобы явно проверять на нет). Говоря о советах1 (hint) оптимизатору
(многие продукты поддерживают такую возможность), авторы пишут следующие мудрые слова: «Проблема советов в том, что они более императивны, чем можно предположить по названию, и любой
совет – азартная игра с будущим. Нет никаких гарантий, что в результате изменения обстоятельств, объемов, алгоритмов СУБД, аппаратуры и т. д. навязанный план выполнения останется не то, что
абсолютно лучшим, но хотя бы приемлемым… Помните, что нужно
тщательно документировать все, что вы силой навязываете СУБД».
34. Patrick Hall, Peter Hitchcock, and Stephen Todd «An Algebra of Re­
la­tions for Machine Computation,» Conf. Record of the 2nd ACM Sym­
posium on Principles of Programming Languages, Palo Alto, Calif.
(January 1975).
Эта работа, пожалуй немного «сложновата», но я думаю, что она важна. И язык Tutorial D, и та версия реляционной алгебры, которую
я описал в настоящей книге, берут начало в этой статье.
35. Jim Gray and Andreas Reuter «Transaction Processing: Concepts and
Techniques». San Mateo, Calif.: Morgan Kaufmann (1993).
Стандартный учебник по управлению транзакциями.
36. Lex de Haan and Toon Koppelaars «Applied Mathematics for Database
Professionals». Berkeley, Calif.: Apress (2007).
Среди прочего вы найдете в этой книге обширный набор тождеств
(здесь они называются правилами перезаписи), который можно использовать, как это было сделано в главе 11, для формулирования
сложных SQL-выражений. Здесь же показано, как реализовывать
ограничения целостности с помощью процедурного кода (см. главу
8). Рекомендую.
37. Wilfrid Hodges «Logic». London, England: Penguin Books (1977).
Введение в формальную логику для начинающих.
38. International Organization for Standardization (ISO) Database Lang­
uage SQL, Document ISO/IEC 9075:2003 (2003).
1

Обычно optimizer hint переводят как указание оптимизатору (что, кстати,
точнее отражает их природу), но в данном случае контекст требует перевести более мягко и ближе к смыслу английского слова (намек). – Прим. перев.

468

Приложение D

Официальный стандарт SQL. Отмечу, что это дейст­вительно международный стандарт, а не просто (как многие, похоже, думают) американский или «ANSI».
39. Jim Melton and Alan R. Simon «SQL:1999 – Understanding Relational
Components»; Jim Melton «Advanced SQL:1999 – Understanding Ob­
ject-Relational and Other Advanced Features». San Francisco, Calif.:
Morgan Kaufmann (2002 and 2003, respectively).
Это единственные известные мне книги, в которых рассмотрены все
аспекты стандарта SQL:1999 (непосредственного предшественника
текущей версии стандарта SQL:2003). Мелтон в течение многих лет
был редактором стандарта SQL.
40. Fabian Pascal «Practical Issues in Database Management: A Reference
for the Thinking Practitioner». Boston, Mass.: Addison-Wesley (2000).
В некоторых от­но­ше­ниях эту книгу можно считать дополнением
к той, что вы держите в руках. Здесь рассматриваются многие типичные вопросы баз данных: нормализация, избыточность, целостность,
отсутствующая информация и другие, и обсуждаются теоретичес­ки
правильные подходы к их решению, с упором на практичес­кое применение теории.
41. Raghu Ramakrishnan and Johannes Gehrke «Database Management
Systems» (3rd edition). New York, N.Y.: McGraw-Hill (2003).
42. Avi Silberschatz, Henry F. Korth, and S. Sudarshan «Database System
Concepts» (5th edition). New York, N.Y.: McGraw-Hill (2005).
43. Robert R. Stoll «Sets, Logic, and Axiomatic Theories». San Francisco,
Calif.: W. H. Freeman and Company (1961).
Корни реляционной модели глубоко уходят в формальную логику
и теорию множес­тв. Эта книга является достаточно формальным, но
не слишком трудным введением в эти предметы. См. также книгу
Hodges [37].
44. Dave Voorhis Rel. http://db@builder.sourceforge.net/rel.html.
Отсюда можно скачать код эталонной реализации диалекта Tu­to­ri­
al D.
45. Moshé M. Zloof «Query-By-Example», Proc. NCC 44, Anaheim, Calif.
(May 1975). Montvale, N.J.: AFIPS Press (1977).
Query-By-Example (QBE) – отличная иллюстрация того факта, что
можно создать очень «дружес­твенный пользователю» язык, основанный на реляционном исчислении, а не на реляционной алгебре.
Злуф придумал и спроектировал язык QBE, и эта статья – одна из
многих его работ на эту тему.

Алфавитный указатель
Цифры и символы

E

0-кортеж, 85
2VL, двузначная логика, 109
3VL, трехзначная логика, 109
>, (ФЗ), 130, 208
>>, (МЗЗ), 370
⇒, влечет, 251
⊇, включает, 92
⊆, включается, 92, 145
∈, принадлежность множеству, 91
θ-соединение, 160
↓, стрелка Пирса, 441
⊃, строго включает, 92
⊂, строго включается, 92
|, штрих Шеффера, 441
≡, эквивалентность, 251

EVERY, 420

A

NO PAD, 73
NOT NULL, 113, 211
null-значения, 30, 84, 109
в реальном мире, 394
и UNKNOWN, 394
порождаемые в SQL, 113
n-арное отношение, 27

ACID-свойства, 212
ALL BUT, 150
ALL и ANY, сравнения с, 299
AND (агрегатный оператор), 180, 420

B
BOOLEAN, 52, 327
связанная переменная, 259

C
CASCADE, 133
SQL, 402
CHECK, опция, 241
COALESCE, 114
CORRESPONDING, 156
CREATE DOMAIN, 380
CREATE TABLE AS, 378, 439
CREATE TYPE, 380

D
DELETE как сокращенная запись, 125
DISTINCT, 107, 155

G
GROUP BY, 297, 303, 448
избыточность, 188

H
HAVING, 297, 303, 448
избыточность, 189

I
INSERT (SQL), 126
INSERT как сокращенная запись, 125
IS_EMPTY, 92

N

O
ORDER BY, 40, 194
OR (агрегатный оператор), 180

P
PAD SPACE, 73

Q
Query-By-Example, 468

R
REF, тип, 76
RVA-атрибут, 61, 191
противопоказания, 348, 419

470

Алфавитный указатель

S

значение, 82
и столбец, 23
ограничение, 204

SELECT *, 307
SUMD, 423

T
TABLE_DEE, 93, 153, 233
и TRUE, 391
TABLE_DUM, 93
и FALSE, 391
THE_, оператор, 57, 202, 381
в SQL, 57, 205
TransRelational ™ Model, 333, 365, 465
TUPLE FROM, 90

U
UNIQUE (SQL), 207, 272, 298, 397
UNIQUE, квантор, 271
UNKNOWN, 109, 112
и null, 394
UPDATE
как оператор чтения, 193
как сокращенная запись, 194
U_, операторы, 335

V
VALUES, 95

Б
база данных и СУБД, 53
базовая переменная-отношение, 43, 121
бизнес-правило, 200, 278, 338
Бойса/Кодда нормальная форма, 343

В
Виттгенштейн Людвиг, 42, 183, 366
включение, 92, 145
внешнее соединение, 115
внешний ключ, 29
определение, 130
вывод типа отношения, 146
в SQL, 148
выражение и предложение, 53, 386
выражение, определяющее представление, 229
высказывание, 135, 251
простое, 252
составное, 252

Г

WITH, 158

генератор типа, 67
RELATION, 67
TUPLE, 67
группирование, оператор, 192, 420

X

Д

W

XML, 47, 61, 62, 63, 69, 371
XOR (агрегатный оператор), 180

А
агрегатный оператор, 179
аксиома, 138
альтернативный ключ, 129
аномалии при обновлении, 358
аргумент, 255, 381
и параметр, 381
арность, 39
ассоциативность, 164, 408
атомарность
значений данных, 59
предложений, 218
семантическая, 125, 218
синтаксическая, 218
транзакций, 212
атрибут, 27, 39, 82
FROM, 85
зависимость от имен, 165

двойное подчеркивание, 29
двузначная логика, 109
декларативность, 48
декомпозиция без потери информации,
345
деление, 177, 405
изъяны, 293
определение, 178
де Моргана законы, 283
денормализация, 359
дизъюнктное объединение, 125, 156
n-местное, 156
дистрибутивность, 163
дистрибутивные законы, 283
долговечность, 212
домен, 27
в SQL, 70
допустимое представление, 201
допущение замкнутости мира, 136, 141,
332, 403
допущение открытости мира, 141, 403

471

Алфавитный указатель

зависимость соединения, 349
обусловленная суперключами, 349
тривиальная, 350
зависящий от реализации, 306
заголовок
кортежа, 82
отношения, 39, 88
закон двойного отрицания, 283
замкнутость, 31, 142, 145, 168, 235
замыкание, 450
запрос, 313
Злуф Мойше, 468
значение, 46
и переменная, 46
значения по умолчанию, 117
золотое правило, 220, 332

кванторы, 257
другие виды, 271
и COUNT, 275
ненужность, 267
квотированный запрос, 194, 198
ключ, 27, 28
неприводимость, 127, 368
определение, 127
отношения и переменные-отношения,
129
уникальность, 127
коммутативность, 164, 254, 408
константа, 233
константа-отношение, 232
конструктор значения строки, 86
конструктор табличных значений, 94
конструктор типа
ROW, 74
не для таблиц, 75
строки, 86
контрапозиция, 448
правило, 296
корреляционное имя, 144, 309
кортеж, 27, 81
и строка, 23
определение, 82
курсор, 123

И

Л

дубликаты, 39, 85, 101
и null, 397
и объединение, 156, 392
и ограничение, 392
и проекция, 149
порождение в SQL, 107

Е
естественное соединение, 151

З

идемпотентность, 169, 410
идентификатор строки, 24, 232
избыточность, 340, 352, 357, 367
изолированность, 212
именование столбцов, дисциплина, 96,
132, 148
импликация, 252
в SQL (эмуляция), 284
правило, 282
интенция, 134
информационная эквивалентность, 390
исчисление предикатов, 250

К
кардинальность, 39, 89
квалификация имен, 144, 261, 307
квантор всеобщности, 256
в SQL (эмуляция), 285
не в SQL, 262, 268, 275
повторяющиеся AND, 270
квантор существования, 257
в 3VL, 262
в SQL, 261, 274
повторяющиеся OR, 270

латеральный подзапрос, 312
литерал, 66, 380
и константа, 233
ловушка связи, 356

М
материализация (представления), 236
многозначная зависимость, 356, 370,
456
обусловленная суперключом, 370
тривиальная, 370
множественное присваивание, 330
модель данных, 35, 38
модель и реализация, 35
мультимножество, 61
объединение, 329

Н
наследие SQL, 337
независимость от данных
логическая, 166, 244, 438
физическая, 37, 373
неоднозначность, 294

472

Алфавитный указатель

неприводимость переменной-отношения,
354
непротиворечивость, 212
логической системы, 221, 276
нормализация, 41, 209, 341
нормальная форма проекции-соедине­
ния, 353

определение, 88
происхождение термина, 89
отношение-образ, 183, 190
определение, 175
отсутствующая информация без null,
116, 365, 461, 462

О

параметр в предикатах, 255
первая нормальная форма, 41, 59, 383
первичный ключ, 28, 129
переименование
непримитивный оператор, 418
определение, 147
переменная, 47
переменная кортежа, 261, 263, 446
в SQL, 261, 308
переменная-отношение, 45, 46, 121
на которую указывает ссылка, 131
переписывание запроса, 102, 281
пересечение, 33
n-местное, 152
и соединение, 151
определение, 156
плоское отношение, 91
повторяющиеся группы, 59, 80, 387
подавление проверки доменов, 54
подзапрос, 72
коррелированный, 288, 312
латеральный, 312
однострочный, 311
скалярный, 311
табличный, 311
подключ, 400
подразумеваемая интерпретация, 134
подстановка (представления), 235
позиционное обновление, 123, 398
поле, 74
полуразность, 172
полусоединение, 171
порождение предиката, 135
порядковые номера столбцов (в SQL), 98
посылка, 252
потенциально недетерминированное
выражение, 73, 195, 314
потенциальный ключ, 27, 127
правила вывода, 138
правило квантификации, 284
правило перезаписи, 282
предваренная нормальная форма, 263,
280
предикат, 134, 255
и булево выражение, 283

обновление как операция над множест­
вом, 122
обобщение, определение, 185
обозначение, 221, 442
объединение, 33
n-местное, 156
определение, 155
объектно-реляционная, 61
ограничение (constraint)
атрибута, 204
базовой таблицы, 210
базы данных, 206
в SQL, 210
и высказывание, 221
и предикат, 219
кортежа, 206
переменной-отношения, 209
перехода, 222, 432
полное (базы данных), 220
полное (переменной-отношения), 220
представления, 236
проверка, 124, 211
с несколькими переменными-отноше­
ниями, 209
с одной переменной-отношением, 209
ссылочной целостности, 131
столбца, 211
типа, 201
в SQL, 205
проверка, 204
ограничение (restriction), 32
определение, 149
ограничение типа, 201
в SQL, 205
проверка, 204
оператор, 23, 65
определяемый реализацией, 306
оптимизатор, 102, 161
ортогональность (проектирование баз
данных), 361
открытая таблица, 166, 249
отношение, 27, 46
и таблица, 23, 49, 99, 375, 388
и тип, 137

П

473

Алфавитный указатель
порождение, 255
представления, 233
простой, 256
реляционного выражения, 159
составной, 256
представление, 42
выборка из, 234
назначение, 244
обновление, 240
в SQL, 242
определение, 229
предикат, 233
приведение типов, 57
в SQL, 70, 91
принцип взаимозаменяемости, 213, 230,
332
принцип информации, 328, 332
принцип однородного представления,
329, 332
принцип однородности представления,
329, 332
принцип ортогонального проектирования, 362
принцип присваивания, 80, 126, 332,
399
принцип тождества неразличимых, 332,
392
присваивание, 45, 63
в SQL, 69
единственный оператор обновления,
46
кортежа, 67
множественное, 217
реляционное, 31, 125
строк, 74, 86
проектирование баз данных, 338
физическое, 364
проекция, 33
определение, 149
произведение, 33, 152
n-местное, 152
и соединение, 152
производная переменная-отношение, 42
противоречие, 114
в 3VL, 396
прототип кортежа, 261
прямое отображение, 365
псевдонимы, 310
пустое множество, 85
в SQL, 315
значений, 269
пустое отношение, 90
пустой ключ, 403
пустой кортеж, 85

пустой тип, 79, 385
пятая нормальная форма, 348
значимость, 350

Р
равенство, 58, 63, 69
равенство кортежей, 85, 129
равные, но различимые, 73
разгруппирование, оператор, 191, 420,
421
разность, 33
определение, 157
разузлование, 141, 402
разыменование, 76
Рассел Бертран, 256, 325
расширение, оператор, 172
определение, 174
реализация, 35
реляционная алгебра, 31, 142
реляционная модель, 320
определение, 325
цели, 331
реляционное исчисление, 145, 250, 260
реляционное сравнение, 92

С
самоссылающаяся переменная-отноше­
ние, 140
свободная переменная, 259
связка, 109, 118, 251, 394
в 3VL, 395
в nVL, 395
сгенерированный тип, 67
селектор, 56, 202, 380
в SQL, 56, 205
кортежа, 83
отношения, 89
Селко Джо, 323
семантическая оптимизация, 214
скаляр, 66
в SQL, 68
следствие, 252
снимок, 246, 439
собственное надмножество, 84
собственное подмножество, 84
собственный суперключ, 130
соединение, 33
n-местное, 152
в SQL, 153
определение, 151
с оператором больше, 154
соединяемость, 151, 170, 411
сохранение зависимостей, 358

474
список, разделенный запятыми, 28
сравнение строк, 87
ссылающаяся переменная-отношение,
131
ссылки, 75
и внешние ключи, 76
на переменную-отношение, 143, 384
на таблицу, 154
ссылочная целостность, 29, 30, 373
метаограничение, 222
ссылочное значение, 76
статистика базы данных, 162
степень
ключа, 127
кортежа, 82
отношения, 39
Стоунбрейкер Майк, 323
стратегия использования представлений, 97, 167
стрелка Пирса, 441
строгая типизация, 57
в SQL, 70
строгое включение, 92
суперключ, 130, 207, 343
суррогатный ключ, 369, 453
сущность-связь, моделирование, 338,
371
схема упорядочения, 72

Т
таблицы и представления, 44, 229
таблицы истинности
в 2VL, 394
в 3VL, 109, 118
табличное выражение, 24
в грамматике SQL, 315
вычисление, 160
тавтология, 119
в 3VL, 395
тело, 39, 89, 90
темпоральные данные, 335, 353
теорема, 138
тип, 27, 62
кортежа, 83
строки, 86
типизированная таблица, 75
тождества
правила трансформации, 282
тождественность
ограничение, 149
проекция, 150
транзакция, 211
трансформация выражений, 105, 161

Алфавитный указатель
правила, 282
трехзначная логика, 109
триггер, 124, 134

У
указатель, 76
уникальный индекс, 36
упорядочение
не для атрибутов, 40
не для кортежей, 40
условие ограничения, 149

Ф
Фейджина теорема, 370
доказательство, 456
функциональная зависимость, 130, 190,
342, 462
обусловленная суперключом, 130
определение, 130
тривиальная, 343
функциональная полнота, 118
функция множества, 108, 187

X
Хита теорема, 346, 349, 368, 451

Ц
целостность сущностей, 29, 30, 231

Ч
четвертая нормальная форма, 356
что если, запросы, 193

Ш
шестая нормальная форма, 353
штрих Шеффера, 441

Э
эквиваленция, 251
правила трансформации, 282
эквисоединение, 161, 404

Я
явная таблица, 307

По договору между издательством «Символ-Плюс» и Интернет-мага­
зином «Books.Ru – Книги России» единственный легальный способ
получения данного файла с книгой ISBN 978-5-93286-173-8, назва­ние
«SQL и реляционная теория. Как грамотно писать код на SQL» – покупка в Интернет-магазине «Books.Ru – Книги России». Если Вы
получили данный файл каким-либо другим образом, Вы нарушили
междуна­родное законодательство и законодательство Российской Федерации об охране авторского права. Вам необходимо удалить данный
файл, атакже сообщить издательству «Символ-Плюс» (piracy@symbol.
ru), где именно Вы получили данный файл.