    Глава 8 

    Язык SQL 

    8.1. Введение 

    Как уже отмечалось ранее, язык SQL весьма далек от полноценной реализации 
реляционной модели. И тем не менее, он является стандартным реляционным языком 
и в настоящее время поддерживается практически всеми продуктами, 
представленными на рынке, поэтому каждый специалист по базам данных должен 
быть знаком с ним. По крайней мере, в пределах этой главы. 
    Необходимо подчеркнуть, что SQL — очень объемный язык. Документ по его 
стандарту [8.1] содержит более 600 страниц. Поэтому в книге, такой как наша, 
невозможно дать исчерпывающее описание этого языка; достаточно полно можно 
описать лишь самые главные его аспекты. Хотелось бы предупредить читателя, что 
приведенное здесь описание языка во многих случаях беглое и поверхностное. В 
частности, мы не колеблясь опускали материал, не относящийся непосредственно к 
обсуждаемой теме и для краткости делали существенные упрощения. Более полное 
(но также учебное) описание можно найти в [8.5-8.7]. 
    В этой главе затронуты следующие вопросы. Сначала обсуждаются средства 
определения данных; затем рассматриваются средства обработки данных (операции 
выборки и операции обновления). После этого описываются три ключевых 
конструкции SQL: табличные выражения (включая, в частности, выражения 
выборки), условные выражения и скалярные выражения. Далее приводятся некоторые 
соображения по применению "встроенного" языка SQL (т.е. средств для 
встраивания операторов SQL в программу на базовом языке). И наконец, вкратце 
подводятся итоги главы. 
    Замечание. Дополнительные аспекты SQL, касающиеся таких тем, как 
восстановление, параллельная обработка и т.д., кратко обсуждаются в 
последующих главах, посвященных этим темам. 
    Сделаем еще несколько предварительных замечаний. Прежде всего, приведенное 
обсуждение касается текущего стандарта [8.1], неформально известного как 
"SQL/92" ("SQL-92" или просто "SQL2"); официальное название стандарта— 
Международный стандарт языка баз данных SQL (1992) (International Standard 
Database Language SQL). Наше изложение основано на материалах [8.5], в 
значительной степени пересмотренных в соответствии с требованиями данной 
книги. Обратите внимание на следующие отличия: 
    • Символ "#", который мы часто используем в именах доменов и столбцов, на 
самом деле в стандарте SQL/92 не допустим. 
    • Мы используем символ ";" как признак конца оператора, хотя согласно 
стандарту SQL/92 этот символ предусмотрен только для встроенного SQL и только 
для определенных базовых языков. 
    • Мы часто используем названия для синтаксических категорий, которые 
отличаются от стандартных названий, потому что терминология стандарта не 
всегда подходит для наших целей. 
    Хотя здесь речь идет только об уровне SQL/92, следует отметить, что ни 
один из коммерческих продуктов к моменту написания книги не поддерживал в 
полной мере 

    Глава 8. Язык SQL 211 

    стандарта SQL/92. Те языки, которые обычно поддерживаются продуктами, 
можно назвать "надмножествами подмножества" языка SQL/92. Другими словами, 
любой данный продукт, не поддерживая некоторых аспектов стандарта, в других 
отношениях, возможно, превосходит его. Например, продукт IBM DB2, безусловно, 
не поддерживает всех средств SQL/92, касающихся целостности, но он превосходит 
стандарт в отношении правил, связанных с обновлением представлений. 
    И последнее замечание: в SQL используются такие термины, как таблицы, 
строки и столбцы вместо реляционных терминов отношение, кортеж и атрибут. 
Поэтому для согласованности со стандартом SQL и продуктами, поддерживающими 
SQL, мы также будем использовать эти термины в данной главе (и везде, где речь 
идет о SQL). 

    8.2. Определение данных 

    В этом разделе рассматриваются основные объекты данных и соответствующие 
операторы языка определения данных (data definition language — DDL) в SQL. 
Основные операторы DDL: 

    СВЕЙТЕ DOMAIN	CREATE TABLE 
    ALTER DCMAIN	ALTER TABLE 
    DROP DCMAIN	DROP TABLE 

    Существуют также операторы для создания и уничтожения ("сбрасывания" — 
"dropping") представлений, но обсуждение представлений откладывается до главы 17. 

    Домены 

    К сожалению, "домены" в SQL далеки от настоящих реляционных доменов, 
описываемых в этой книге; более того, эти два понятия настолько различны, что 
желательно было бы для этой конструкции SQL использовать другое название. 
Единственная цель доменов в SQL — обеспечить возможность один раз определить 
элементарную спецификацию типа данных (такую как "S# CHAR(5)"), а затем 
использовать ее одновременно для нескольких столбцов в нескольких базовых 
таблицах. Ниже приводится список некоторых основных различий между настоящими 
доменами и конструкциями SQL (смысл многих пунктов этого списка будет 
разъяснен в последующих главах книги). 
    •  Как уже отмечалось, на самом деле домены SQL— просто синтаксические 
сокращения. Они, несомненно, не относятся к истинному типу данных, 
определяемому пользователем. 
    •   Нет никаких требований, которые применялись бы для доменов SQL: 
столбцы в базовых таблицах могут определяться непосредственно в терминах 
встроенных типов данных, определяемых системой, таких как FLOAT и INTEGER 
    •   Отсутствует какая-либо поддержка SQL для "доменов доменов". Домен SQL 
должен определяться в терминах одного из встроенных типов данных, определяемых 
системой, а не в терминах другого домена, определенного пользователем. 
    •  Язык SQL не осуществляет строгого контроля типов. Нет никакой проверки 
правильности типов. В частности, домены не "ограничивают сравнения" — 
единственное требование для сравнений состоит в том, чтобы сравниваемые 
операнды были одного и того же базового типа, т.е. оба были или числовыми, или 
символьными строками и т.п. Аналогичное замечание применимо и к числовым 
выражениям, выражениям с символьными строками и т.д. Во всех случаях домены 
как таковые, по существу, ими не являются. 
    •  Язык SQL не поддерживает возможность определения пользователем 
операций, применяемых к данному домену. 
    •  В языке SQL не делается четкого разграничения между доменом как таковым 
(т.е. типом данных, определяемым пользователем) и представлением этого домена 
в терминах одного из определяемых системой типов данных. 

    212	Часть II. Реляционная модель 

    •  В языке SQL нет никакой концепции подтипов, надтипов или наследования. 
    •  И наконец, в SQL нет даже поддержки, бесспорно, самого фундаментального 
из всех доменов — домена истинностных значений! 

    Ниже приводится синтаксис для создания домена SQL. 

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

    CREATE DOMAIN domain data-type 
    I default-definition } 
    [ domain-constraint-definition-list ]   ; 

    Пояснения: 
    1. Элемент domain— это название создаваемого домена с указанным типом 
данных (data-type). В языке SQL поддерживаются приведенные ниже скалярные типы 
данных, назначение большинства из которых понятно по названию. (Также 
поддерживаются некоторые значения по умолчанию, сокращения, альтернативные 
написания, например CHAR для CHARACTER. Подробности мы опускаем.) 

    CHARACTER [ VARYING ]	(л)   INTEGER	DATE 
    BIT [ VARYING ] (л)	SMALLINT	TIME 
    NUMERIC (p, g)	FLOAT (p)	TIMESTAMP 
    DECIMAL (p,g)	INTERVAL 

    2. Необязательный элемент определения по умолчанию (default-definition) 
указывает значение по умолчанию, которое применяется к каждому столбцу, 
определенному на домене и не имеющему своего собственного определения значения 
по умолчанию, записанного в виде "DEFAULT default", где default, в свою 
очередь, является литеральным значением, ссылкой к встроенной niladic-функции 
(например, CURRENT_DATE) или NULL.1 
    Замечание. Niladic-функцией называется функция, не использующая никаких аргументов. 
    3. Необязательный список определений ограничений доменов 
(domain-constraint-definition-list) представляет набор ограничений 
целостности, применяемых к каждому столбцу, основанному на этом домене. Как 
будет объяснено в последующих главах книги, ограничения целостности доменов 
концептуально не что иное, как перечисление значений, составляющих домен (по 
крайней мере, так должно быть). Однако в SQL допускается использование в 
ограничениях доменов логических выражений произвольной сложности. Мы 
предлагаем читателю в качестве упражнения поразмышлять о некоторых 
нежелательных последствиях такой вседозволенности. 

    Приведем пример определения домена: 


    CREATE DCMAIN COLOR CHAR(6) DEFAULT '???' CONSTRAINT VALID_COLORS CHECK ( 
VALUE IN 
    ( 'Red', 'Yellow1, 'Blue', 'Green', '???' ) ) ; 

    Тогда оператор CREATE TABLE для базовой таблицы Р (таблицы деталей) может 
выглядеть следующим образом: 

    CREATE TABLE P   (   . . .   ,   COLOR COLOR,   ...   )   ; 

    Если пользователь при вставке строки в таблицу Р не указывает значение 
столбца COLOR для этой строки, то по умолчанию в эту позицию помещается 
значение "???". А если пользователь укажет значение COLOR, но это значение не 
будет принадлежать 
    1 Подробное обсуждение поддержки пустых значений (называемых нулями) в SQL 
приводится в последующих главах книги. Однако случайные ссылки к нулям в этой 
главе неизбежны. 

    Глава 8. Язык SQL	213 

    диапазону допустимых значений, то операция не будет выполнена и система 
сгенерирует сообщение, в котором упоминается ограничение VALID_COLORS. 
    Существующий домен можно изменить в любое время с помощью оператора ALTER 
DCMAIN. В частности, этот оператор позволяет определить для такого домена 
новое значение по умолчанию (заменяя при этом старое значение, если оно было 
указано) или удалить существующее. Он также позволяет ввести новое ограничение 
целостности для данного домена или удалить уже существующее ограничение. 
Подробное изложение всех этих опций (а они довольно сложны) выходит за рамки 
этой книги. Интересующийся читатель может обратиться к [8.1, 8.5]. 
    И наконец, существующий домен можно уничтожить с помощью оператора DROP 
DOMAIN, имеющего следующий синтаксис: 

    DROP DCMAIN domain option ; 

    Здесь опция option может принимать значения RESTRICT или CASCADE. Общая 
идея такова: при выборе опции RESTRICT домен не будет уничтожен, если на него 
будут какие-либо ссылки; при выборе опции CASCADE операция будет выполнена и 
"каскадно" продолжена в различных направлениях (например, столбцы, которые 
были ранее определены на основе этого домена, будут рассматриваться как 
определенные непосредственно на основе типа данных этого домена). Механизм 
этой операции достаточно сложен и потому подробности опускаются. 
Дополнительные сведения можно найти в [8.1, 8.5]. 

    Базовые таблицы 

    Прежде чем обсуждать базовые таблицы, необходимо сделать пару замечаний 
относительно таблиц SQL вообще. Во-первых, в таблицах SQL, в отличие от 
настоящих отношений, допустимы идентичные строки, поэтому им не требуется 
иметь каких-либо потенциальных ключей. Во-вторых, в таблицах SQL, в отличие от 
настоящих отношений, столбцы рассматриваются в порядке слева направо; например 
в таблице поставщиков S столбец S# может быть первым столбцом, столбец SNAME — 
вторым и т.д. 
    Перейдем непосредственно к базовым таблицам. Базовые таблицы определяются 
с помощью оператора CREATE TABLE (обратите внимание, что ключевое слово TABLE 
означает здесь базовую таблицу, то же самое касается и операторов ALTER TABLE 
и DROP TABLE, которые описываются ниже). Синтаксис выражения следующий: 
    CREATE TABLE base-table  ( base-table-element-coimalist )   ; 
    Здесь каждый элемент базовой таблицы (base-table-element) является либо 
определением столбца (column-definition), либо определением ограничения 
базовой таблицы (base-tabie-constraint-definition). Каждое определение столбца 
(по крайней мере одно такое определение должно быть), в свою очередь, выглядит 
следующим образом: 

    column representation  [ default-definition ] 

    Здесь column означает название столбца, representation указывает 
необходимый тип данных или домен, а необязательное определение по умолчанию 
(default-definition) указывает значение по умолчанию для столбца, игнорирующее 
значение по умолчанию, указанное на уровне домена (если такое имеется). Если 
для данного столбца не определено его собственное явное значение по умолчанию, 
а также нет такового, наследуемого из домена, то предполагается, что значение 
по умолчанию— NULL, т.е. NULL — это "значение по умолчанию, используемое по умолчанию". 
    Существует три вида определений ограничения базовой таблицы'. 
    •  определение потенциального ключа, 
    •  определение внешнего ключа, 
    •  определение "проверочного условия". 
    Ниже каждое из них обсуждается подробнее. 

    214	Часть II. Реляционная модель 

    Замечание. Каждому определению может предшествовать инструкция вида 
"CONSTRAINT constraint", указывающая название для нового ограничения (то же 
самое верно и для ограничений доменов). Для краткости эта опция в дальнейшем игнорируется. 

    Потенциальные ключи 

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

    UNIQUE  (  column-commalist ) 

    ИЛИ 

    PRIMARY KEY   (  column-commalist ) 

    В обоих случаях список coiwn-commaiist не должен быть пустым. Для данной 
базовой таблицы существует не более одной спецификации PRIMARY KEY (первичный 
ключ) и любое количество спецификаций UNIQUE (альтернативные ключи). В случае 
первичного ключа для каждого из указанных столбцов дополнительно 
подразумевается спецификация NOT NULL, даже если эта спецификация не указана явно. 
    Замечание. Применяемые здесь условные обозначения (типа commaiist) 
описывались ранее в этой книге. 

    Внешние ключи 

    Определение внешнего ключа записывается следующим образом: 

    FOREIGN KEY   (   column-commaiist  ) 
    REFERENCES base-table  [   {  column-commalist )   ] [ ON DELETE option ] 
[  ON UPDATE option  ] 

    Здесь option принимает значения NO ACTION, CASCADE, SET DEFAULT или SET 
NULL. Опции 

    CASCADE  И   SET NULL  ПОЛНОСТЬЮ   СООТВСТСТВуЮТ   ОПИСЗННЫМ   ранее   
CASCADES   И   NULLIFIES 

    (если вам необходимо освежить в памяти этот материал, вернитесь к главе 
5); опция го ACTION,   которая   устанавливается   по   умолчанию,   идентична 
 описанной   ранее RESTRICTED (подробнее см. [8.5]), а опция SET DEFAULT 
очевидна и говорит сама за себя. Замечание. Второй список столбцов 
column-commalist требуется, если внешний ключ ссылается на потенцизльный, 
который не является первичным ключом. 

    Проверочные условия 

    Определение проверочного условия (проверочного ограничения) имеет вид: 

    CHECK  (  conditional-egression ) 

    Попытка создания строки в базовой таблице В рассмзтривается как нарушение 
проверочного условия для таблицы В, если в результате вычисления условного 
выражения, укзззн-ного в этом офэничении, получено знзчение ложь. Заметьте, 
что условное выражение может быть сколь угодно сложным; фактически не 
требуется, чтобы условие ограничения имело ссылки только на таблицу В, оно 
может иметь ссылки нз что угодно в бззе данных. В [8.19] предлагаются 
некоторые критические комментарии относительно такого подхода. Вот пример 
создания таблицы с помощью оператора CREATE TABLE: 

    CREATE TABLE SP 
    ( S# S# NOT NULL,   P#  P# NOT NULL,   OTY QTY NOT NULL, PRIMARY KEY   (  
S#,   P#   ), FOREIGN KEY   (  S#   )   REFERENCES S 
    ON DELETE CASCADE 
    ON UPDATE CASCADE, FOREIGN KEY   (   P#   )   REFERENCES P 
    ON DELETE CASCADE 
    ON UPDATE CASCADE, CHECK  ( QTY > 0 AND QTY < 5001  )   )   ; 

    Здесь подразумевается, что домены S#, Р# и QTY уже определены, a S# и Р# 
явно определены как первичные ключи для таблиц S и Р соответственно. Также 
здесь умышленно применяется соглашение по сокращению, благодаря которому 
проверочное условие вида 

    CHECK  (  column IS NOT NULL  ) 

    Глава 8. Язык SQL	215 

    в определении рассматриваемого столбца (column) можно заменить простой 
спецификацией NOT NULL. Таким образом, в этом примере три несколько громоздких 
проверочных условия заменены тремя простыми спецификациями NOT NULL. 
    Далее существующая базовая таблица может быть изменена в любое время с 
помощью оператора ALTER TABLE. Поддерживаются следующие изменения: 
    •  добавление новых столбцов; 
    •  определение   для   существующего   столбца   нового   значения   по   
умолчанию (заменяющего предыдущее значение, если оно было); 
    •  удаление для столбца существующего значения по умолчанию; 
    •  удаление существующего столбца; 
    •  указание нового ограничения целостности для базовой таблицы; 
    •  удаление существующего ограничения целостности для базовой таблицы. 
Приведем пример лишь для первого случая: 

    ALTER TABLE S ADD COLUMN DISCOUNT INTEGER DEFAULT -1 ; 

    С помощью этого оператора добавляется столбец DISCOUNT (типа INTEGER) в 
базовой таблице поставщиков S. Все существующие строки в этой таблице 
расширяются с четырех столбцов до пяти; во всех случаях значение нового пятого 
столбца равно -1. 
    И наконец, существующая базовая таблица может быть уничтожена с помощью 
оператора DROP TABLE, который имеет следующий синтаксис: 

    DROP TABLE base-table option ; 

    Здесь опция option, как и в случае оператора DROP DOMAIN, будет или 
RESTRICT, или CASCADE. Если указана опция RESTRICT и на базовую таблицу есть 
ссылки в каком-нибудь определении представления или ограничении целостности, 
операция DROP будет отвергнута. Если указана опция CASCADE, то операция DROP 
будет выполнена (таблица будет удалена со всеми ее строками), а любые 
определения представлений, которые имеют ссылки на эту таблицу, и ограничения 
целостности будут также уничтожены. 

    Информационная схема 

    В SQL существует аналог того, что принято называть каталогом, — это 
информационная схема. В действительности знакомые нам термины "каталог" и 
"схема" используются в SQL, но с весьма специфическими для SQL значениями. 
Нестрого говоря, каталог в SQL состоит из дескрипторов для отдельной базы 
данных,1 а схема состоит из дескрипторов той части этой базы данных, которая 
относится к некоторому отдельному пользователю. Другими словами, может быть 
любое число каталогов, каждый из которых делится на произвольное число схем. 
Однако требуется, чтобы каждый каталог содержал ровно одну схему, называемую 
INFORMATION_SCHEMA, которая, с точки зрения пользователя, является схемой, 
выполняющей функцию обычного "каталога". 
    Таким образом, информационная схема состоит из набора таблиц SQL, 
содержимое которых фактически отражает (точно определенным образом) все 
определения из всех остальных схем рассматриваемого каталога. Точнее, 
информационная схема согласно определению содержит набор представлений 
гипотетической "схемы определения". Для поддержки схемы определения реализации 
не требуется, но она требуется, во-первых, для поддержки некоторых видов 
"схемы определения" и, во-вторых, для поддержки представлений таких "схем 
определения", которые имеют вид, подобный информационной схеме. Необходимо 
отметить следующие моменты: 
    ' Если быть точным, то необходимо отметить, что в стандарте SQL вообще не 
существует такого понятия, как "база данных"! Есть набор данных, который 
описан определенным каталогом. Однако неоправданно представлять это как базу данных. 

    216	Часть II. Реляционная модель 

    1.  Смысл формулирования двух отдельных описанных только что требований 
заключается в следующем. Существующие продукты, конечно, поддерживают нечто 
близкое к "схеме определения". Однако такие "схемы определения" заметно 
различаются в разных продуктах (даже если рассматриваемые продукты имеют 
одного изготовителя). Поэтому требование, чтобы в реализации поддерживались 
предопределенные представления ее "схемы определения", имеет смысл. 
    2.  На самом деле нужно говорить "одна из информационных схем", а не 
просто "информационная схема", так как мы видели, что одна такая схема 
содержится в каждом каталоге. Вообще говоря, совокупность данных, доступных 
конкретному пользователю, не будет описана с помощью единственной 
информационной схемы. Однако, чтобы упростить изложение материала, мы будем 
продолжать считать, что в действительности существует только одна схема. 
    Нет причин вдаваться здесь в детали содержимого информационной схемы. 
Просто приведем некоторые наиболее важные представления информационной схемы в 
надежде, что сами их названия позволят читателю получить некоторое понятие о 
том, из чего состоит схема (однако следует отметить, что представление TABLES 
(таблицы) включает информацию обо всех именованных таблицах, представлениях и 
базовых таблицах; а представление VIEWS (представления) содержит информацию, 
конечно же, только о представлениях). Более сложные возможности умышленно пропускаются. 

    SCHEMATA 
    (СХЕМЫ) 
    DCMAINS 
    ЩСМЕНЫ) 
    TABLES 
    (ТАБЛИЦЫ) 
    VIEWS 
    (ПРЕДСТАВЛЕНИЯ) 
    COLUMNS (СТОЛБЦЫ) TABLE_PRI VI LEGES (ПРИВИЛЕГИИ ДЛЯ ТАБЛИЦ) 
    COLUMN_PRIVILEGES (ПРИВИЛЕГИИ ДЛЯ СТОЛБЦОВ) USAGE_PRIVILEGES (ПРИМЕНЯЕМЫЕ ПРИВИЛЕГИИ) 
    DCMAIN_CONSTRAINTS (ОГРАНИЧЕНИЯ ДЛЯ ДСМЕНА) 
    TABLE_CCNSTRAINTS (ОГРАНИЧЕНИЯ ДЛЯ ТАБЛИЦЫ) 
    REFERENTIALJXNSTRAINTS 
    (ССЫЛОЧНЫЕ ОГРАНИЧЕНИЯ) 
    CHECKJCONSTRAINTS 
    (ПРОВЕРОЧНЫЕ ОГРАНИЧЕНИЯ) 
    KEY_COLUMN__USAGE 
    (ИСПОЛЬЗОВАНИЕ СТОЛБЦА КЛЮЙ) 
    ASSERTIONS 
    (УТВЕРЖДЕНИЯ) 
    VIEWJTABLEJJSAGE 
    (ИСПОЛЬЗОВАНИЕ ТАБЛИЦЫ ПРЕДСТАВЛЕНИЯ) 
    VI EW_COLUMN_USAGE 
    (ИСПОЛЬЗОВАНИЕ СТОЛБЦА ПРЕДСТАВЛЕНИЯ) 
    CONSTRAINT_TABLE_USAGE 
    (ИСПОЛЬЗОВАНИЕ ТАБЛИЦЫ ОГРАНИЧЕНИЯ) 
    CONSTRAINT_COLUMN_USAGE 
    (ИСПОЛЬЗОВАНИЕ СТОЛБЦА ОГРАНИЧЕНИЯ) 
    CCNSTRAINT_DCMAIN_USAGE 
    (ИСПОЛЬЗОВАНИЕ ДСМЕНА ОГРАНИЧЕНИЯ) 


    8.3. Обработка данных: операции выборки 

    Теперь перейдем к операторам SQL языка обработки данных (data manipulation 
language— DML). Основные операторы DML— это SELECT, INSERT, UPDATE и DELETE. 
Вначале рассматриваются операции выборки (SELECT), а далее — операции 
обновления (INSERT, UPDATE, DELETE). Для простоты будем предполагать, что все 
операторы вводятся интерактивно; отдельные соображения, касающиеся встроенных 
в прикладные программы выражений SQL, приведены ниже в этой главе. 
    Операция выборки в SQL — это, по существу, табличное выражение, которое 
может быть сколь угодно сложным. Не углубляясь в детали, просто приведем 
несколько примеров в надежде, что они послужат иллюстрацией наиболее важных моментов. 

    Глава 8. Язык SQL 217 

    (Чтобы облегчить сравнение с реляционной алгеброй и реляционным 
исчислением, мы также дадим ссылки на аналогичные примеры в главах 6 и 7.) 
Более полная и более формальная трактовка табличных выражений приведена ниже в 
этой главе. 

    8.3.1. Получить цвета и города для деталей "не из Парижа" с весом, большим десяти 


    SELECT P.COLOR, P.CITY FRCM  Р 
    WHERE P.CITY о 'Paris' AND   P.WEIGHT > 10 ; 

    Прежде всего обратите внимание на использование в этом примере символа о 
(не равно). Обычные скалярные операторы сравнения записываются в SQL так: =, 
о, <, >, <= и >=. 
    Затем (что еще важнее) обратите внимание, что для используемых нами 
примерных данных (эти значения приводились ранее в книге) этот запрос будет 
возвращать четыре строки, а не две, несмотря на то что три из этих четырех 
строк идентичны и имеют вид (Red, London). Язык SQL не удаляет лишних 
дублирующих строк из результата оператора SELECT, пока пользователь явно не 
потребует этого с помощью ключевого слова DISTINCT, как показано ниже: 

    SELECT DISTINCT P.COLOR, P.CITY 
    FRCM  P 
    WHERE P.CITY <> 'Paris' 
    AND   P.WEIGHT > 10 ; 

    Этот запрос будет возвращать только две строки. 
    Между прочим, в этом примере можно было прекрасно обойтись и без 
уточнителей "Р.". Согласно общему правилу относительно уточнения имени в SQL 
неуточненные имена допускаются, если они не вызывают неоднозначности. Однако в 
наших примерах будут использоваться все уточнители, даже если они формально 
излишни. (К сожалению, в определенных контекстах явно требуется, чтобы имена 
столбцов были неуточнены\ Например, это требуется в инструкции ORDER BY— см. ниже.) 
    И наконец, заметьте, что последовательность строк в данной результирующей 
таблице в общем непредсказуема, пока пользователь не задаст некоторую 
определенную последовательность, как в этом примере: 

    SELECT DISTINCT P.COLOR, P.CITY 
    FRCM  P 
    WHERE P.CITY <> 'Paris' 
    AND   P.WEIGHT > 10 
    ORDER BY CITY DESC ; 

    В общем виде инструкция ORDER BY записывается так: 

    ORDER BY order-item-commalist 

    Здесь СПИСОК commalist НС ДОЛЖСН быть Пустым И КЗЖДЫЙ ЭЛСМСНТ order-item ДОЛЖ6Н 
    содержать неуточненное имя столбца, за которым (не обязательно) следуют 
ключевые слова ASC или DESC (которые означают возрастающий или убывающий 
порядок соответственно); по умолчанию принимается ASC. 

    8.3.2. Для всех деталей получить номер детали и ее вес в граммах 

    SELECT P.P#, P.WEIGHT * 454 AS GMWT FRCM  P ; 

    Спецификация AS GMWT вводит соответствующее имя результирующего столбца. 
Таким образом, два столбца результирующей таблицы будут называться Р# и GMWT 
соответственно. Если бы спецификация AS GMWT была опущена, то соответствующий 
столбец был бы фактически безымянным. Обратите внимание, что хотя SQL на самом 
деле не требует от пользователя задавать имя результирующего столбца в таких 
случаях, но мы будем так делать в наших примерах всегда. 

    218	Часть II. Реляционная модель 

    8.3.3. Получить полную информацию обо всех поставщиках 

    SELECT *	— или "SELECT S.*"   (т.е.   "*" можно уточнить) FRCM S  ; 

    Результатом будет копия всей таблицы S; звездочка — это сокращение для 
списка всех имен столбцов в таблице (или таблицах), на которую делается ссылка 
в инструкции FROM, в порядке слева направо, как эти столбцы определены в 
таблице (таблицах). Обратите внимание на комментарий в этом примере 
(комментарии в SQL начинаются с двойного дефиса и заканчиваются символом новой строки). 
    Отметим, что звездочку очень удобно использовать в интерактивных запросах, 
так как вводится меньше символов. Однако использование звездочки во встроенном 
SQL (т.е. SQL в прикладных программах) потенциально опасно, так как смысл 
звездочки (*) может измениться (например, в случае, если из таблицы был убран 
или в таблицу был добавлен столбец с помощью оператора ALTER TABLE). 
    Замечание. Согласно стандарту SQL/92 выражение SELECT * ERCM г (где Т— 
название таблицы) можно упростить до TABLE т. 
    8.3.4. Получить информацию обо всех парах поставщиков и деталей, 
совмещенных в одном городе 
    В SQL существует несколько способов выразить этот запрос. Приведем три 
самых простых: 

    1.   SELECT S.S#,   S.SNAME,   S.STATUS,   S.CITY, P.P#,   P.PNAME,   
P.COLOR,   P.WEIGHT FRCM      S,   P WHERE    S.CITY = P.CITY ; 
    2.   S JOIN P USING CITY ; 
    3.   S NATURAL JOIN P  ; 

    Результатом в каждом случае будет естественное соединение таблиц S и Р (по городам). 
    Первая формулировка заслуживает более подробного обсуждения; она 
единственная из трех допустима в SQL, который был первоначально определен 
(явная операция JOIN была добавлена в SQL/92). Концептуально можно 
рассматривать реализацию этой версии запроса следующим образом: 
    • Во-первых, после выполнения инструкции FROM мы получаем декартово 
произведение S TIMES P. 
    • Далее, после выполнения WHERE мы получаем выборку из этого произведения, 
в которой два значения CITY в каждой строке равны (иначе говоря, построено 
соединение поставщиков и деталей по эквивалентности городов). 
    • И наконец, после выполнения оператора SELECT мы получаем проекцию 
выборки по столбцам, указанным в инструкции SELECT. Конечным результатом будет 
естественное соединение. 
    Следовательно, нестрого говоря, инструкция FROM в SQL соответствует 
декартову произведению, ИНСТРУКЦИЯ WHERE —— Выборке, 3 СОВМбСТНая ИНСТРУКЦИЯ 
SELECT-FROM-WHERE представляет проекцию выборки произведения. 

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

    SELECT DISTINCT S.CITY AS SCITY,   P.CITY AS PCITY MM      S JOIN SP USING 
S# JOIN P USING P#   ; 

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

    SELECT DISTINCT S.CITY AS SCITY,   P.CITY AS PCITY FRCM     S NATURAL JOIN 
SP NATURAL JOIN P  ; 

    Глава 8. Язык SQL	219 

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

    SELECT FIRST.S# AS SA, SECOND.S# AS SB FRCM  S AS FIRST, S AS SECOND WHERE 
FIRST. CITY = SECOND. CITY AND   FIRST.S# < SECOND.S# ; 

    Обратите внимание в этом примере на явные переменные области значений 
FIRST и SECOND. В наших предыдущих примерах все переменные области значений 
были неявными. Также следует отметить, что вводимые имена столбцов SA и SB 
относятся к столбцам результирующей таблицы и потому не могут быть 
использованы В ИНСТРУКЦИИ WHERE. 

    8.3.7. Получить общее число поставщиков 

    SELECT CCUNT(*)   AS N FROM      S  ; 

    Результатом будет таблица с одним столбцом, названным N, и одной строкой, 
содержащей значение 5. Язык SQL поддерживает обычный набор итоговых функций 
(COUNT, SUM, AVG, МАХ и MIN), но есть несколько специфических для SQL 
моментов, которые пользователю необходимо знать, а именно: 
    •  Вообще аргументу функции не обязательно должно предшествовать ключевое 
слово DISTINCT, указывающее, что перед применением функции дублирующиеся 
строки должны удаляться. А для функций МАХ и MIN ключевое слово DISTINCT 
излишне и не вызывает никакого действия. 
    •  Специальная функция COUNT(*), не допускающая использования слова 
DISTINCT, предназначена для подсчета всех строк в таблице без единого удаления 
дублирующихся строк. 
    •  Любые null-значения в столбце аргументе всегда удаляются перед 
применением функции, в зависимости от того, указано ли слово DISTINCT, кроме 
случая с функцией COUNT (*), когда null-значения обрабатываются точно так же, 
как обычные значения. 
    •   Если аргумент будет пустым множеством, функция COUNT возвратит 
значение нуль; все другие функции возвращают null-значение (неопределенное 
значение). (В [8.19] показано, что такое поведение функций некорректно, но 
именно таким образом определяет их SQL.) 

    8.3.8. Получить максимальное и минимальное количество для детали Р2 

    SELECT МАХ ( SP.QTY ) AS MAXQ, MIN ( SP.OTY ) AS MINQ 
    FROM  SP 
    WHERE SP.P# = 'P21 ; 

    Здесь обе инструкции FROM и WHERE фактически предоставляют часть 
аргументов для двух итоговых функций. Следовательно, они должны были по логике 
вещей записываться в скобках, заключающих аргументы. Тем не менее, запрос 
действительно записывается так, как показано. Этот неортодоксальный подход к 
синтаксису оказывает значительное отрицательное влияние на структуру, удобство 
использования и орто-гональносты языка SQL. Например, одно из следствий 
состоит в том, что итоговые функции не могут быть вложенными, в результате 
чего такой запрос, как "Получить среднее итоговое количество деталей", нельзя 
сформулировать без громоздких выражений (поскольку выражение AVG(SUM<QTY) ) 
недопустимо). Более подробное обсуждение этого вопроса выходит за рамки нашей книги. 
    ' Ортогональность означает независимость. Язык является ортогональным, 
если независимые понятия сохраняют свою независимость и не смешиваются друг с 
другом непонятным образом. Ортогональность весьма желательна, поскольку чем 
менее ортогонален язык, тем он более сложен и, как это не парадоксально, менее мощен. 

    220	Часть II. Реляционная модель 

    8.3.9. Для каждой поставляемой детали получить номер детали и общее 
количество поставки 
    См. аналогичный пример в главе 7. 

    SELECT SP.P#,   SUM   (  SP.QTY  )   AS TOTOTY 
    FROM      SP 
    GROUP    BY SP.P#  ; 

    Это выражение в SQL является аналогом такого выражения реляционной алгебры: 

    SUMMARIZE SP BY   (  Р#  )   ADD SUM   (  CTY )   AS TOTQTY 

    В частности, стоит отметить, что если указана инструкция GROUP BY, то 
выражения в инструкции SELECT должны быть однозначными в группе. Вот 
альтернативная формулировка того же запроса: 

    SELECT P.P#,    (  SELECT SUM   (   SP.OTY  ) 
    FROM      SP 
    WHERE    SP.P# = P.P#   )   AS TOTQTY FROM      P ; 

    Возможность использования вложенных выражений выборки для представления 
скалярных элементов (например, в инструкции SELECT, как здесь) была добавлена 
в SQL/92 и является основным усовершенствованием по сравнению с первоначальным 
вариантом SQL. В рассматриваемом примере это дает возможность генерировать 
результат, который включает строки для деталей, не поставляемых совсем, а 
предыдущая формулировка (использующая инструкцию GROUP BY) этого не позволяла. 
(Однако значение TOTQTY для таких деталей будет, к сожалению, представлено как 
null-значение, а не нуль.) 

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

    SELECT SP.P# 
    FROM      SP 
    GROUP    BY SP.P# 
    HAVING COUNT   (   SP.Sft   )   >  1   ; 

    Инструкция HAVING для групп — это то же самое, что и инструкция WHERE для 
строк; другими словами, инструкция HAVING используется для исключения групп 
точно так же, как инструкция WHERE используется для исключения строк. 
Выражение в инструкции HAVING должно быть однозначным для группы. 

    8.3.11. Получить имена поставщиков, поставляющих деталь Р2 

    См. аналогичные примеры в главах 6 и 7. 

    SELECT DISTINCT S.SNAME 
    FRCM      S 
    WHERE    S.Sft  IN 
    (  SELECT SP.S# FRCM      SP WHERE    SP.P# =   'P2'   )   ; 

    Пояснения. В этом примере используется так называемый подзапрос. Проще 
говоря, подзапрос — это выражение SELECT-FRCM-WHERE-GROUP BY-HAVING, которое 
вложено в другое такое же выражение. Подзапрос обычно используется для 
представления множества значений, поиск которых осуществляется с помощью 
инструкции IN condition (где condition — это условие), как проиллюстрировано в 
примере. Система вычисляет полностью запрос, вычислив сначала подзапрос (по 
крайней мере концептуально). Этот подзапрос возвращает множество номеров 
поставщиков, поставляющих деталь Р2: {SI, S2, S3, S4}. Таким образом, 
первоначальное выражение эквивалентно следующему более простому: 

    SELECT DISTINCT S.SNAME 
    FRCM     S 
    WHERE    S.S# IN   (   'SI',    'S2',    'S31,    'S4'   )   ; 

    Глава 8. Язык SQL	221 

    Стоит отметить, что первоначальную задачу— "Получить имена поставщиков, 
поставляющих деталь Р2" — можно равносильно выразить с помощью операции 
соединения, например, так: 

    SELECT DISTINCT S.SNAME FRCM  S, SP WHERE S.S# = SP.S# AND   SP.P# = 'P21 ; 

    8.3.12. Получить имена поставщиков, поставляющих по крайней мере одну 
красную деталь 

    См. аналогичные примеры в главах 6 и 7. 

    SELECT DISTINCT S.SNAME FRCM  S WHERE S.S# IN 
    ( SELECT SP.Sft FRCM  SP WHERE SP.P# = IN ( SELECT P.P# FRCM  P WHERE 
P.COLOR = 'Red' ) ) ; 

    Подзапросы могут иметь произвольную глубину вложения. 
    Упражнение. Приведите эквивалентную формулировку этого запроса с 
использованием операции соединения. 

    8.3.13. Получить номера поставщиков, статус которых меньше текущего 
максимального статуса в таблице S 

    SELECT S.S# 
    FRCM  S 
    WHERE S.STATUS < 
    ( SELECT MAX ( S.STATUS ) FRCM  S ) ; 

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

    8.3.14. Получить имена поставщиков, поставляющих деталь Р2 

    Такой же пример, как 8.3.11. 

    SELECT DISTINCT S.SNAME FRCM  S WHERE EXISTS 
    ( SELECT * 
    FRCM  SP 
    WHERE SP.S# = S.S# 
    AND   SP.P# = 'P2' ) ; 

    Пояснения. Выражение SQL "EXISTS (SELECT. . .FROM. ..)" будет истинным 
тогда и только тогда, когда результат вычисления выражения "SELECT. . .FROM. 
.." будет непустым. Другими словами, в SQL функция EXISTS соответствует 
квантору существования реляционного исчисления (более подробно это обсуждается 
в последующих главах книги). 

    8.3.15. Получить имена поставщиков, которые не поставляют деталь Р2 

    См. аналогичные примеры в главах 6 и 7. 

    SELECT DISTINCT S.SNAME FRCM  S 
    WHERE NOT EXISTS ( SELECT * 
    FRCM  SP 
    WHERE SP.Stt = S.S# 
    AND   SP.P# = 'P2' ) ; 

    222	Часть II. Реляционная модель 

    Или альтернативная формулировка: 

    SELECT DISTINCT S.SNAME FRCM      S 
    WHERE    S.S# NOT IN (  SELECT SP.S# FRCM      SP WHERE    SP.P# =   'P21  
 )   ; 

    8.3.16. Получить имена поставщиков, поставляющих все детали 
    См. аналогичные примеры в главах 6 и 7. 

    SELECT DISTINCT S.SNAME FRCM      S 
    WHERE    NOT EXISTS (  SELECT * FRCM      P 
    WHERE    NOT EXISTS (  SELECT * FRCM      SP 
    WHERE    SP.S# = S.S# AND         SP.P# = P.Pt   )   )   ; 

    Язык SQL не включает какой-либо непосредственной поддержки универсального 
квантора FORALL; следовательно, запросы типа FORALL обычно выражаются через 
отрицание кванторов существования, как в этом примере. 
    Стоит отметить, что такие выражения, как только что показанное, хотя на 
первый вчгляд и выглядят несколько устрашающе, легко составляются 
пользователями, знакомыми с реляционным исчислением, как отмечалось в [7.6]. 
Или с другой стороны, если эти примеры еще кажутся слишком сложными, то 
существует несколько "обходных" приемов, которые помогут избежать 
использования негативных кванторов. В нашем случае можно, например, записать: 

    SELECT DISTINCT S.SNAME 
    FRCM      S 
    WHERE     (  SELECT COUNT   {  SP.P#   ) 
    FRCM      SP 
    WHERE SP.S# = S.S# ) = ( SELECT COUNT ( P.P# ) FRCM  P ) ; 

    ("имена поставщиков, для которых количество поставляемых деталей равно 
количеству всех деталей"). Однако обратите внимание на следующие обстоятельства. 
    • Во-первых, в последней формулировке предполагается, в отличие от 
формулировки с выражением NOT EXISTS, что нет номеров деталей в отношении SP, 
которые не содержатся в отношении Р. Другими словами, эти две формулировки 
эквивалентны (а вторая — верная) только благодаря действию определенного 
ограничения целостности. 
    • Во-вторых, метод, примененный во второй формулировке (сравнение двух 
количеств), изначально не поддерживался в SQL и был добавлен в стандарт 
SQL/92. Он все еще не поддерживается во многих продуктах. 
    • Отметим также, что на самом деле предпочтительнее было бы сравнивать две 
таблицы (см. обсуждение реляционных сравнений в главе 6), и тогда запрос 
записывался бы так: 

    SELECT DISTINCT S.SNAME 
    FRCM      S 
    WHERE   (  SELECT SP.P#   ) 
    FRCM      SP 
    WHERE SP.S# = S.S# ) = ( SELECT P.P# FRCM  P ) ; 

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

    Глава 8. Язык SQL	223 

    8.3.17. Получить номера деталей, которые или весят более 16 фунтов, или 
поставляются поставщиком S2, или и то и другое 

    См. аналогичный пример в главе 7. 

    SELECT Р.Р# 
    FRCM  Р 
    WHERE P.WEIGHT > 16 
    UNION 
    SELECT SP.P# 
    FRCM  SP 
    WHERE SP.S# = 'S21 ; 

    Лишние повторяющиеся строки всегда исключаются из результата безусловных 
операторов UNION, INTERSECT или EXCEPT (оператор EXCEPT в языке SQL служит 
аналогом операции MINUS реляционной алгебры). Но язык SQL также предоставляет 
и уточненные варианты операторов: UNION ALL, INTERSECT ALL И EXCEPT ALL, ГфИ 
КОТОрЫХ ПОВТОрЯЮЩИССЯ СТрОКИ (бСЛИ ОНИ есть) сохраняются. Примеры с этими 
вариантами операторов умышленно опускаются. 
    В заключение отметим, что хотя список примеров выборок был довольно 
большим, тем не менее, многие возможности SQL даже не упоминались. Язык SQL 
действительно чрезвычайно избыточный в том смысле, что почти всегда 
предоставляет множество разных способов формулировки одного и того же запроса, 
и нам не хватит места, чтобы описать все возможные формулировки и все 
возможные опции даже для сравнительно небольшого числа примеров, которые 
рассматривались в этой главе. 

    8.4. Обработка данных: операции обновления 

    Как уже упоминалось, язык обработки данных DML включает три операции 
обновления: INSERT (вставка), UPDATE (изменение) и DELETE (удаление). Приведем 
несколько простых примеров, которые, как мы полагаем, понятны без комментариев. 

    8.4.1. Вставка одной строки 

    INSERT 
    INTO  Р ( Р#, PNAME, COLOR, WEIGHT, CITY ) 
    VALUES ( 'P81, 'Sprocket1, 'Pink', 14, 'Nice' ) ; 

    8.4.2. Вставка нескольких строк 

    INSERT 
    INTO  TEMP ( Stf, CITY ) 
    SELECT S.S#, S.SITY 
    FRCM  S 
    WHERE S.STATUS > 15 ; 
    8.4.3. Обновление нескольких строк 
    UPDATE Р 
    SET   COLOR = 'Yellow1, 
    WEIGHT = P. WEIGHT + 5 WHERE P.CITY = 'Paris' ; 

    8.4.4. Обновление нескольких строк 

    UPDATE Р 
    SET   CITY = ( SELECT S.CITY 
    FRCM  S 
    WHERE    S.S# =   'S51   ) WHERE    P.COLOR =  'Red'   ; 

    8.4.5. Удаление нескольких строк 

    DELETE 
    FRCM  SP 
    WHERE 'London' = 
    ( SELECT S.CITY FROM  S WHERE S.S# = SP.S# ) ; 

    224	Часть II. Реляционная модель 

    8.5. Табличные выражения 

    Для исчерпывающего освещения табличных выражений в этой книге не хватило 
бы места. Однако, по крайней мере для ссылок, на рис. 8.1 приводится 
достаточно полное описание BNF-грамматики для таких выражений (грамматика 
описана полностью, за исключением нескольких опций, имеющих отношение к 
null-значениям). Также в этом разделе делается попытка разобраться с одним 
специальным случаем, бесспорно, наиболее важным на практике, а именно 
выражениями выборки. 
    Выражением выборки можно считать, нестрого говоря, табличное выражение, не 
использующее операторов UNION, INTERSECT или EXCEPT ("нестрого", поскольку 
такие операторы могут быть включены в выражения, которые вложены внутри 
выражения выборки). Как показано на рис. 8.1, выражение выборки содержит 
несколько компонентов: ИНСТРУКЦИИ SELECT, FROM, WHERE, GROUP BY И HAVING 
(последние Три ИЗ НИХ НС обязательны). Теперь рассмотрим последовательно 
каждый из этих компонентов. 

    Инструкция SELECT 

    Инструкция SELECT записывается в виде 

    SELECT  [ ALL  |   DISTINCT ]   select-item-commalist 

    Пояснения'. 
    1.   СПИСОК элементов  выборки   select-item-commalist НС  ДОЛЖСН  быть  
пуСТЫМ.   Ниже элементы выборки рассматриваются подробно. 
    2. Если ни ключевое слово ALL, ни DISTINCT не указаны, то подразумевается ALL. 
    3. В данный момент подразумевается, что вычисления для инструкций FROM, 
WHERE, GROUP BY и HAVING уже проведены. Не имеет значения, какая из этих 
инструкций указана, а какая опущена: концептуальный результат их вычисления 
всегда является таблицей (возможно, "сгруппированной" таблицей — см. ниже), на 
которую мы будем ссылаться как на таблицу 77 (хотя концептуальный результат в 
действительности не именован). 
    4. Пусть 72 будет таблицей, производной от таблицы 77 путем вычисления 
указанного списка элементов по таблице 77 (см. ниже). 
    5. Пусть ТЗ будет таблицей, производной от таблицы Т2 в результате 
исключения лишних повторяющихся строк из таблицы 72, если указано ключевое 
слово DISTINCT, или таблицей, идентичной 72, в противном случае. 
    6. Таблица ТЗ будет конечным результатом. 
    А теперь возвратимся к элементам выборки. Необходимо рассмотреть два 
случая, однако второй случай — это просто сокращение списка элементов выборки, 
полученного в первом случае, т.е. основным является первый случай. 
    Случай I. Элемент выборки имеет следующий вид: 
    scalar-egression  [   [ AS  ]   column ] 
    • Скалярное выражение (scalar-egression) обычно (но не обязательно) 
включает один или более столбцов таблицы 77 (см. пояснения выше). Для каждой 
строки таблицы 77 вычисляется скалярное выражение, дающее скалярный результат. 
Список таких результатов (соответствующий вычислению всех элементов выборки в 
инструкции SELECT по отдельной строке таблицы 77) состоит из единственной 
строки таблицы 72. Если в элементе выборки присутствует ключевое слово AS, то 
неуточненное имя столбца из этой инструкции назначается как имя 
соответствующего столбца таблицы 72' (необязательное ключевое слово AS здесь 
просто помеха и может быть опущено без какого-либо ущерба). Если в элементе выборки 
    1 Вследствие того что это фактически имя столбца таблицы Т2, а не 77, 
любое имя, представленное инструкцией AS, не может использоваться в 
инструкциях WHERE, GROUP BY и HAVING (если они есть), непосредственно 
включаемых в конструкцию таблицы TI. Однако на это имя можно ссылаться в 
соответствующей инструкции ORDER BY, а также во "внешнем" табличном выражении, 
которое содержит рассматриваемое вложенное в него выражение выборки. 
    
    Глава 8. Язык SQL	225 8 Введение в системы баз данных 

    ключевое слово AS отсутствует, возможны два варианта: для элемента 
выборки, состоящего просто из имени столбца (возможно, уточненного), такое имя 
назначается как имя соответствующего столбца таблицы 72; в противном случае 
соответствующий столбец таблицы 72 фактически не имеет имени. 
    1 1 
    table-expression 
    ::=       join-table-expression I  nonjoin-table-expression 
    join-table-expression 
    ::=        table-reference [ NATURAL ]  JOIN 
    table-reference [ ON conditional-expression 
    I  USING  (  column-commalist )   ] I   table-reference CROSS JOIN 
table-reference I   ( join-table-expression ) 
    table-reference 
    ::=        table [   [ AS ]  range-variable 
    [   ( column-commalist )   ]   ] I   (  table-expression )   [ AS ]   range-variable 
    [   ( column-comraalist )   ] I  join-table-expression 
    nonjoin-table-expression 
    : '•=       nonjoin-table-term 
    I   taile-expression UNION  [ ALL ] 
    [ CORRESPONDING  [ BY   (  column-commalist )   ]   ] 
    table-terra I   table-expression EXCEPT  [ ALL  ] 
    t CORRESPONDING  [ BY  (  column-commalist ) table-term 
    nonjoin- table-term 
    ::=       поп join-table-primary 
    I   table-term INTERSECT  [ ALL ] 
    [ CORRESPONDING  [ BY  {  column-commalist ) table-primary 
    table-term 
    ::=       nonjoin-table-term I join-table-expression 
    table-primary 
    ::=       попjoin-table-primary I join-tafele-expression 
    nonjoin-tai>le-primary :: =        TABLE table 
    I table-constructor I select-expression I ( nonjoin-table-expression ) 
    table-constructor 
    ::=        VALUES row-constructor-commalist 
    row-constructor 
    ::=   scalar-expression 
    I ( scalar-expression-commalist ) \ ( table-expression ) 
    select-expression 
    ::=   SELECT [ ALL | DISTINCT ] select-item-commalist FROM 
table-reference-comma list 
    [ WHERE conditional-expression ] [ GROUP BY column-commalist ] 
    [ HAVING conditional-expression ] 
    select-item 
    ::=        scalar-expression [ I   [ range-variable .  ] 
    AS ]  column 

    Рис. 8.1. BNF-грамматика для табличных выражений SQL 


226 Часть II. Реляционная модель 

    •  Если элемент выборки включает ссылку на итоговую функцию и выражение 
выборки не включает инструкцию GROUP BY (см. ниже), то ни один элемент выборки 
в инструкции SELECT не может включать никаких ссылок на столбец таблицы 77, 
кроме такой, которая является ссылкой на аргумент (или часть аргумента) 
итоговой функции. 

    Случай 2. Элемент выборки имеет следующий вид: 

    [ range-variable .   }   * 
    •  Если уточнение (range-variable) опущено (т.е. элемент выборки 
представлен как неуточненный символ "*"), то этот элемент выборки должен быть 
только элементом выборки в инструкции SELECT. Эта форма является сокращением 
для списка всех столбцов таблицы 77 в порядке слева направо. 
    • Если уточнение включено (т.е. элемент выборки состоит из символа "*", 
уточненного именем переменной области значений R, записывается как "Я. *" ), 
то этот элемент выборки представляет список всех столбцов таблицы, 
соответствующей переменной области значений R, в порядке слева направо. 
(Напомним, что имя таблицы может и часто будет использоваться как явная 
переменная области значений. Таким образом, элемент выборки часто будет иметь 
форму "Т. *", а не "Л. *".) 

    Инструкция FROM 

    Инструкция FROM записывается в виде 

    FRCM table-reference-commalist 

    СПИСОК   ССЫЛОК   на  таблицы   (table-reference-commalist)   Не   ДОЛЖСН  
 быть   пуСТЫМ. 
    Пусть вычисление указанных табличных ссылок дает таблицы А, В,..., С. 
Тогда результат вычисления инструкции FROM будет таблицей, эквивалентной 
декартову произведению таблиц А, В,..., С. 
    Замечание. Декартово произведение отдельной таблицы Т определяется как 
эквивалентное Г, т.е. оно, конечно, допустимо для инструкции FROM, содержащей 
просто отдельную табличную ссылку. 

    Инструкция WHERE 

    Инструкция WHERE записывается в виде 

    WHERE conditional-expression 

    Пусть Т— результат вычисления предыдущей инструкции FRCM. Тогда 
результатом инструкции WHERE будет таблица, производная от Т и исключающая все 
строки, для которых вычисление условного выражения (conditional-expression) не 
дает истину. Если инструкция WHERE опущена, результатом будет просто Т. 

    Инструкция GROUP BY 

    Инструкция GROUP BY записывается в виде 

    GROUP BY column-comnalist 

    Список столбцов (coiumn-coimaiist) не должен быть пустым. Пусть Т— 
результат вычисления предыдущих инструкций FROM и WHERE (если они 
использовались). Каждый столбец, указанный в списке инструкции GROUP BY, 
должен быть представлен (не обязательно) уточненным именем столбца таблицы Т. 
А результатом этой инструкции будет сгруппированная таблица, т.е. набор групп 
строк, производных от таблицы Т с помощью концептуальной перегруппировки 
таблицы Т в минимальное количество таких групп, что в пределах одной группы 
все строки имеют одинаковое значение для комбинации столбцов, указанных в 
инструкции GROUP BY. Поэтому заметьте, что результат — это не совсем таблица. 
Однако инструкция GROUP BY никогда не применяется без соответствующей 
инструкции SELECT, которая преобразует этот промежуточный результат в 
правильную таблицу, поэтому нет ничего плохого в том, что временно мы 
отклонились от точной табличной структуры. 

    Глава 8. Язык SQL	227 

    Если выражение выборки включает инструкцию GROUP BY, то есть ограничения 
на форму, которую может принимать инструкция SELECT. Точнее, каждый элемент 
выборки в инструкции SELECT (включая любой такой, который подразумевается по 
сокращению "*") должен быть однозначным в группе. Итак, каждый элемент выборки 
не должен включать никаких ссылок на какой-либо столбец таблицы Т, не 
указанной в инструкции GROUP BY, кроме ссылок на аргументы или часть аргумента 
одной из итоговых функций — COUNT, SUM, AVG, МАХ или MiN, в результате 
выполнения которых некоторый набор скалярных значений группы сводится к 
единственному такому значению. 

    Инструкция HAVING 

    Инструкция HAVING записывается в виде 

    HAVING conditional-expression 

    Пусть G (сгруппированная таблица) — результат выполнения предыдущей 
инструкции FROM, ИНСТРУКЦИИ WHERE (бСЛИ ОНЗ Присутствует) И ИНСТруКЦИИ GROUP 
BY (бСЛИ ОНЗ 
    есть). Если инструкции GROUP BY нет, то в качестве G берется результат 
выполнения предыдущей инструкции FRCM или инструкции WHERE и рассматривается 
как сгруппированная таблица, состоящая только из одной группы;1 иначе говоря, 
в этом случае есть неявная концептуальная инструкция GROUP BY, которая 
указывает, что группируемых столбцов нет совсем. Результат инструкции HAVING— 
это сгруппированная таблица, производная от G и исключающая все группы, для 
которых условное выражение не является истиной. 
    Замечание /. Если инструкция HAVING опущена, а инструкция GROUP BY 
присутствует, то результатом будет просто G. Если обе инструкции HAVING и 
GROUP BY опущены, результатом будет "правильная", т.е. не сгруппированная, 
таблица Т, вычисленная в соответствии с инструкциями WHERE и FROM. 
    Замечание 2. Скалярные выражения инструкции HAVING в каждой группе должны 
быть однозначными (так же как и скалярные выражения инструкции SELECT, если 
присутствует ИНСТРУКЦИЯ GROUP BY, КЭК ОбсужДЗЛОСЬ ВЫШб). 
    Замечание 3. Стоит упомянуть, что инструкция HAVING полностью излишняя, 
т.е. для любого выражения выборки, в котором используется эта инструкция, 
существует семантически эквивалентное выражение выборки, в котором она не 
используется (упражнение для читателя!). 

    Обширный пример 

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

    SELECT Р.Р#, 
    'Weight in grains = ' AS TEXTl, 
    P.WEIGHT * 454 AS GMWT, 
    P. COLOR, 
    'Max quantity =  '  AS TEXT2, 
    MAX   (  SP.OTY  )   AS MQY FROM      P,   SP WHERE    P.P# = SP.PS 
    AND     (  P.COLOR =  'Red'  OR P.COLOR =  'Blue1   ) AND        SP.OTY > 
200 GROUP    BY P.Ptt,   P.WEIGHT,   P.COLOR HAVING SUM  ( SP.OTY )   > 350 ; 

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

    228	Часть II. Реляционная модель 

    Пояснения. Следует отметить, что (как уже пояснялось) инструкции выражения 
выборки концептуально выполняются в том порядке, в котором они написаны, с 
единственным исключением самой инструкции SELECT, которая выполняется 
последней. Поэтому можно представить результат, построенный следующим образом: 
    1.   FROM: вычисление инструкции гаем дает новую таблицу, которая является 
декартовым произведением таблиц Р и SP. 
    2.  WHERE: результат шага 1 сокращается путем исключения всех строк, 
которые не удовлетворяют условию инструкции WHERE. В данном примере 
исключаются строки, не удовлетворяющие условному выражению 

    Р.Р# = SP.P# AND 
    ( P.COLOR = 'Red' OR P.COLOR = 'Blue1 ) AND 
    SP.QTY > 200 

    GROUP BY: результат шага 2 группируется по значениям столбца или столбцов, 
перечисленных в инструкции GROUP BY. В данном примере это столбцы Р.Р#, 
P.WEIGHT и P.COLOR. 
    Замечание. Теоретически здесь было бы достаточно одного столбца Р.Р# в 
качестве группируемого, поскольку P.WEIGHT и P.COLOR однозначно определяются 
по номеру детали. Однако в SQL не учитывается этот момент, и если в инструкции 
GROUP BY опустить P.WEIGHT и P.COLOR, то, поскольку они упоминаются в 
инструкции SELECT, будет сгенерирована ошибка. 
    4.  HAVING: группы, не удовлетворяющие условию 

    SUM  (  SP.QTY )   > 350, 

    из результата шага 3 исключаются. 
    5.  SELECT: каждая группа в результате шага 4 генерирует отдельную строку 
результата следующим образом. Во-первых, из группы извлекаются номер детали, 
вес, цвет и максимальное количество поставки. Во-вторых, вес преобразуется в 
граммы. И в-третьих, две литеральные строки "Weight in grams =" (вес в 
граммах) и "Max quantity =" (максимальное количество) ставятся на 
соответствующее место в строке. Кстати, "соответствующее место в строке" 
предполагает, что столбцы таблицы в SQL расположены в порядке слева направо. 
Литеральные строки не имели бы смысла, если бы они не располагались в 
"соответствующих местах". 
    Окончательный результат выглядит следующим образом: 

    р#	ТЕХТ1				GMWT		COLOR	TEXT2			MQY 

    Р1	Weight in grams =		5448		Red	Max quantity =	300 
    Р5	Weight in grains =	5448		Blue	Max quantity =	400 
    РЗ	Weight in grains =	7718		Blue	Max quantity =	400 

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

    8.6. Условные выражения 

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

    Глава 8. Язык SQL 229 

    некоторые наиболее важные свойства таких выражений. При этом обратите 
внимание, что наша трактовка, несомненно, не претендует на исчерпывающую; в 
частности, мы пропускаем все, что касается null-значений. (Условные выражения, 
возможно, больше остальных элементов языка требуют расширенного рассмотрения, 
если учитывать наличие null-значений и сложность вопросов, связанных с ними. 
Кроме того, здесь не рассматриваются определенные форматы условных выражений, 
предназначенные исключительно для определенных аспектов поддержки null-значений.) 
    Как и выше, начнем с BNF-грамматики (рис. 8.2). Читатель убедится, что 
большинство форматов условных выражений или уже были проиллюстрированы в 
предыдущих разделах, или понятны сами по себе; здесь же просто делается 
попытка в нескольких словах дать пояснения относительно таких характерных 
случаев, как условия MATCH и "все-или-какой-нибудь". 

    condi tiопа1-express!on 
    ::=        conditional- term 
    I  conditional-egression OR condi tional-terra 
    condi tional- term 
    : :=        conditional-factor 
    I   conditional-term AND conditional-factor 
    conditional-factor 
    ::=        [ NOT ]   condi tional-primary 
    condi tional -primary 
    ::=        simple-condition  |    (  conditional-expression ) 
    simple-condition 
    :: =        comparison-condition I   in-condition I  match-condition I  
all-or-any-condition I  exists-condition 
    comparison-condition 
    '.:=       row-constructor comparison-operator row-constructor 
    compa ri son-opera tor 
    : :=       =  |  <  |  <=  |   >  |  >=  |  о 
    in-condition 
    : :=       row-constructor [ NOT ]   IN  (  table-expression ) I  
scalar-expression [ NOT ]   IN 
    ( scalar-expression-commalist ) 
    match-conditi on 
    ::=        row-constructor MATCH UNIQUE   (   table-expression ) 
    all-or-any-condition 
    ::=        row-constructor 
    comparison-operator ALL  (  table-expression } |  row-constructor 
    comparison-operator ANY  (  table-expression ) 
    exists-condition 
    ::=   EXISTS ( table-expression ) 

    Рис. 8.2, BNF-грамматика для условных выражений SQL 

    230	Часть II. Реляционная модель 

    Условие MATCH 

    Условие MATCH имеет следующий вид: 
    row-constructor MATCH UNIQUE  (  table-expression } 
    Пусть rl — это строка, полученная в результате вычисления row-constructor, 
и пусть Т— таблица, полученная в результате вычисления table-egression. Тогда 
вычисление условия MATCH будет давать истину тогда и только тогда, когда 
таблица Г будет содержать ровно одну строку, скажем г2, такую, что сравнение 

    rl = г2 

    будет давать истину. Вот один пример: 

    SELECT SP.* 
    FRCM      SP 
    WHERE    NOT   (   SP.Stt MATCH UNIQUE   (  SELECT S.S# FRCM S   )   )   ; 

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

    Условие "все-или-какой-нибудь" 

    Условие "все-или-какой-нибудь" имеет такую общую форму: 

    row-constructor 
    comparison-operator qualifier  ( table-expression ) 

    Здесь оператор сравнения (comparison-operator) берется из обычного набора 
(=, о и т.д.), а квантор (qualifier) — это ключевое слово ALL (все) или ANY 
(какой-нибудь, существует хотя бы один). В общем, условие 
"все-или-какой-нибудь" дает значение истина тогда и только тогда, когда 
соответствующее сравнение без квантора ALL (или ANY) дает значение истина для 
всех (или соответственно для какой-нибудь) строк (строки) таблицы, 
представляемой выражением table-expression. (Если эта таблица пустая, то 
условие с квантором ALL имеет значение истина, а с квантором ANY — ложь.) 
Приведем пример ("Получить названия таких деталей, вес которых больше веса 
любой голубой детали"): 

    SELECT DISTINCT PX.PNAME 
    FRCM      P AS PX 
    WHERE PX.WEIGHT > ALL ( SELECT PY.WEIGHT 
    FRCM  P AS PY 
    WHERE PY.COLOR = 'Blue' ) ; 


    Результат будет выглядеть следующим образом: 

    Пояснения. Вложенное табличное выражение возвращает множество весов 
голубых (Blue) деталей, а именно множество (17, 12}. Тогда внешнее выражение 
SELECT возвращает название только одной детали (а именно детали Р6), вес 
которой больше каждого значения в этом множестве. В общем случае, конечно, 
результат может содержать любое количество деталей (включая нуль). 

    8.7. Скалярные выражения 

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

CASE и 

    Глава 8. Язык SQL	231 

    CAST, значение которых сразу, возможно, и не очевидно. Подчеркнем, что в 
таких выражениях также могут встречаться итоговые функции, поскольку они 
возвращают скалярный результат. Более того, табличные выражения, заключенные в 
скобки, могут быть также истолкованы как скалярные значения, если только они в 
результате вычисления сводятся к таблице с одной строкой и одним столбцом. Как 
указывалось ранее, эта возможность, которая была введена в SQL/92, 
представляет главное усовершенствование новой версии SQL по сравнению с 
первоначальным вариантом. 
    Вот список основных операторов в алфавитном порядке: арифметические 
операторы (+,-,*,/); BIT_LENGTH; CASE; CAST; CHARACTER_LENGTH; CURRENTJJSER; 
LOWER; OCTET_LENGTH; POSITION; SESSIONJJSER; SUBSTRING; SYSTEMJJSER; TRIM; 
UPPER; USER. 
    Ниже кратко описаны операторы CASE и CAST. 

    Оператор CASE 

    Оператор CASE возвращает одно значение из указанного набора значений в 
зависимости от определенных условий. Например: 

    CASE 
    WHEN S.STATUS WHEN S.STATUS WHEN S.STATUS 
    5 THEN 'Обращаться в последнюю очередь' 10 THEN 'Сомнительный' 
    < 15 THEN 'He очень хороший' 
    WHEN S.STATUS < 20 THEN 'Посредственный' WHEN S.STATUS < 25 THEN 

'Приемлемый' ELSE	'Хороший' END 

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

    CAST   (   'S8'  AS  S#  ) 

    Не все пары типов данных взаимно конвертируемы. Более подробно о том, 
какие преобразования допустимы и как именно они выполняются, можно узнать из [8.1]. 

    8.8. Встроенный SQL 

    Как отмечалось ранее, выражения SQL могут выполняться или интерактивно, 
или как часть прикладной программы (в этом случае выражения SQL физически 
встраиваются в исходный код программы, смешиваясь с выражениями базового 
языка). До сих пор последний случай не рассматривался и подразумевалось (где 
это имело значение), что язык используется интерактивно. Теперь рассмотрим 
встроенный SQL. 
    Фундаментальный принцип, лежащий в основе встроенного SQL, который мы 
будем называть двухрежимным принципом, заключается в том, что любое выражение 
SQL, которое можно использовать интерактивно, можно также использовать и в 
прикладной программе. Конечно, существует множество различий в деталях между 
интерактивными и встроенными выражениями SQL, а операции выборки, в частности, 
требуют определенной внутренней обработки в среде базового языка; но несмотря 
на это, двухрежимный принцип всегда верен. (Обратное, между прочим, не верно, 
т.е. существует несколько операторов встроенного SQL, которые не могут 
использоваться интерактивно, как мы вскоре убедимся.) 
    Необходимо также отметить, что двухрежимный принцип применяется ко всему 
языку SQL, а не только к операциям обработки данных. Верно и то, что операции 
подъязыка обработки данных, несомненно, одни из наиболее часто используемых в 
контексте программирования, но нет ничего плохого, например, во встроенном в 
программу операторе CREATE TABLE, если приложение это допускает. 

    232 Часть II. Реляционная модель 

    Прежде чем начать обсуждение конкретных операторов встроенного SQL, 
необходимо рассмотреть некоторые предварительные детали. Большинство из них 
иллюстрируется программным фрагментом на рис. 8.3. (Для констатации наших 
представлений будем считать, что базовым языком является PL/I. Большинство 
представлений транслируется на другие базовые языки лишь с незначительными изменениями.) 

    ЕХЕС SQL BEGIN DECLARE SECTION ; 
    DCL SQLSTATE CHAR(5) ; DCL P# CHAR(6) ; DCL WEIGHT FIXED DECIMAL (3) ; 
    EXEC SQL ЕЮ DECLARE SECTION ; 
    P# = 'P21 ;        /* например */ EXEC SQL SELECT P.WEIGHT 
    INTO  : WEIGHT 
    FROM  P 
    WHERE P.P# = :P# ; IF SQLSTATE = '00000' 
    THEN ... ;         /* WEIGHT (ВЕС) = выбираемому значению */ ELSE ... ;    

     /* возникла какая-то исключительная ситуация */ 

    Рис. 8.3. Фрагмент программы PL/1 с встроенным языком SQL 

    Рассмотрим все по порядку. 
    1. Операторы встроенного SQL предваряются инструкцией EXEC SQL, так что их 
легко отличить от других операторов базового языка, и заканчиваются 
специальным завершающим символом (точка с запятой ";" для PL/I). 
    2. Выполняемый оператор SQL (здесь и далее термин "встроенный" будет 
обычно опускаться) может быть в программе везде, где могут быть выполняемые 
базовые операторы. Обратите внимание на уточнение "выполняемые": в отличие от 
интерактивного SQL, встроенный SQL включает отдельные операторы, которые 
являются чисто декларативными, а не выполняемыми. Например, оператор DECLARE 
CURSOR— не выполняемый оператор, таковыми не являются и операторы BEGIN, END, 

    DECLARE SECTION, а ТЗКЖС WHENEVER (CM. НИЖб). 

    3. Операторы SQL могут включать ссылки на базовые переменные (т.е. 
переменные встроенного, или базового, языка); такие ссылки должны включать 
префикс в виде двоеточия для отделения их от имен столбцов SQL. 
    Базовые переменные могут использоваться во встроенном языке SQL (но только 
в операторах DML) везде, где могут использоваться литералы в интерактивном 
языке SQL. Они могут также находиться в инструкции INTO операторов SELECT или 
FETCH (см. ниже), обозначая результирующие переменные для выборки, и в 
определенных операторах "динамического SQL" (также см. ниже). 
    4. Обратите внимание на инструкцию INTO оператора SELECT на рис. 8.3. 
Назначение этой инструкции (как только что отмечалось) — указать 
результирующие (целевые) переменные, в которые будут возвращены выбранные 
значения; /-я целевая переменная, указанная в инструкции INTO, соответствует 
/-му значению, которое будет выбрано, как указано в инструкции SELECT. 
    5. Все базовые переменные, на которые будут ссылаться операторы SQL, 
должны быть определены в разделе описаний встроенного SQL, который 
ограничивается операторами BEGIN и END раздела описаний (DEOARE SECTICN). 

    Глава 8. Язык SQL	233 

    6.   Каждая программа встроенного SQL должна включать базовую переменную, 
называемую SQLSTAiE1. После выполнения какого-либо оператора SQL в такую 
переменную в программе возвращается код состояния; в частности, код состояния 
00000 означает, что оператор выполнился успешно, а значение 02000 — что 
оператор выполнился, но никаких данных, удовлетворяющих запрос, не было 
найдено. Таким образом, каждый оператор SQL в программе должен завершаться 
проверкой переменной SQLSTATE, а соответствующие действия должны 
предприниматься, если значение не то, которое ожидалось. На практике, однако, 
такая проверка обычно выполняется неявно. 
    7.  Базовые переменные должны иметь подходящие типы данных в соответствии 
с тем, как эти переменные используются. В частности, базовая переменная, 
используемая в качестве целевой (например, для операции FETCH), должна иметь 
тип данных, совместимый с типом выражения, которое представляет значение, 
присваиваемое этой целевой переменной; точно так же, если базовая переменная 
служит источником (например, для операции UPDATE), она должна иметь тип 
данных, совместимый с типом SQL того столбца, которому присваивается значение 
этого источника. Подобные замечания касаются также базовых переменных, 
используемых в сравнениях или в любых других скалярных выражениях. В 
официальном документе стандарта [8.1] подробно объясняется, что означает для 
двух типов быть совместимыми. 
    8.  Базовые переменные для столбцов SQL должны иметь те же имена. 
    9.   Как уже упоминалось, каждое выражение SQL в принципе должно 
сопровождаться проверкой возвращаемого значения SQLSTATE. Оператор WHENEVER 
предназначен для упрощения этого процесса и имеет следующий синтаксис: 

    ЕХЕС SQL WHENEVER condition action terminator 

    Здесь terminator— это завершающий символ, упомянутый в пункте 1, условие 
condition может принимать значения SQLERROR (ошибка SQL) или NOT FOUND (не 
найдено), а действие action— это либо оператор CONTINUE (продолжить), либо 
оператор со то (перейти к). Оператор WHENEVER не является выполняемым; это 
просто директива для процессора языка SQL. Наличие в программе выражения 
"WHENEVER condition со то label" приведет к тому, что процессор вставит 
оператор "IF condition со то label" после каждого встретившегося ему 
выполняемого оператора SQL, а вот по выражению "WHENEVER condition CONTINUE" 
процессор не вставляет никаких операторов, следовательно, программист должен 
будет вставлять такие операторы вручную. Два условия определяются так: NOT 
FOUND означает никаких данных не найдено 

    (SQLSTATE = 02000) SQLERROR  означает возникла ошибка 

    (см. описание стандарта [8.1] для SQLSTATE) 
    Каждый оператор WHENEVER, который процессор SQL встречает при 
последовательном сканировании текста программы (для определенного условия), 
отменяет предыдущий (для этого условия). 
    Итак, для предварительного обсуждения достаточно. Ниже мы сосредоточимся 
на операторах языка обработки данных. Как уже отмечалось, большинство из них 
можно использовать непосредственным образом (т.е. с незначительными 
изменениями в синтаксисе). Однако операции выборки имеют специальную 
трактовку. Проблема состоит в том, что такие операторы выбирают не просто 
одну, а много строк (в общем 
    ' Ранние версии SQL использовали переменную, называемую SQLCODE; 
переменная SQLSTATE была добавлена в SQL/92, а переменная SQLCODE теперь 
официально "осуждается", поскольку большинство ее значений (в отличие от 
SQLSTATE) определяется реализацией, вместо того чтобы быть предписанными стандартом, 

    234	Часть II. Реляционная модель 

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

    Операции, не использующие курсоров 

    Операторы обработки данных, для которых не требуется использовать курсоры, следующие: 
    •  "единичный SELECT", 
    •    INSERT, 
    • UPDATE (кроме формы CURRENT), 
    •    DELETE (тоже КрОМб формы CURRENT). 
    Ниже приводятся примеры для каждого из этих операторов. 

    8.8.1. Единичный SELECT. Получить статус и город для поставщика, чей номер 
поставки задан базовой переменной GIVENS# 

    ЕХЕС SQL SELECT STATUS, CITY INTO  :RANK, :CITY FRCM  S WHERE S# = 
:GIVENS# ; 

    Термин "единичный SELECT" используется для обозначения выражения выборки1, 
значением которого будет таблица, содержащая не более одной строки. В данном 
примере, если существует ровно одна строка в таблице S, удовлетворяющая 
условию WHERE, то значения STATUS и CITY из этой строки в соответствии с 
запросом будут присвоены базовым переменным RANK и CITY, а значение SQLSTATE 
будет равно 00000. Если в таблице S нет ни одной строки, удовлетворяющей 
условию WHERE, то переменной SQLSTATE будет присвоено значение 02000; а если 
таких строк будет больше одной, то будет сгенерирована ошибка и переменная 
SQLSTATE будет содержать код ошибки. 

    8.8.2. INSERT. Вставить новую деталь в таблицу Р (номер детали, ее 
название и вес определены базовыми переменными Р#, PNAME, PWT соответственно; 
цвет и город неизвестны) 

    ЕХЕС SQL INSERT 
    INTO   P ( Р#, PNAME, COLOR, WEIGHT, CITY ) VALUES  ( :P#, :PNAME, 
DEFAULT, :PWT, DEFAULT ) ; 

    8.8.3. UPDATE. Увеличить статус всех поставщиков из Лондона на значение, 
определенное базовой переменной RAISE 

    ЕХЕС SQL UPDATE S 
    SET   STATUS = STATUS + : RAISE WHERE CITY = 'London' ; 

    Если нет строк поставщиков, удовлетворяющих условию WHERE, переменной 
SQLSTATE присваивается значение 02000. 

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

    Глава 8. Язык SQL 235 

    8.8.4. DELETE. Удалить все поставки для поставщиков из города, 
определенного базовой переменной CITY 

    ЕХЕС SQL DELETE 
    FRCM SP WHERE :CITY = 
    (  SELECT CITY FRCM      S WHERE    S.S# = SP.S#  )   ; 

    И снова, если нет строк, удовлетворяющих условию WHERE, переменной 
SQLSTATE присваивается значение 02000. 

    Операции, использующие курсоры 

    Теперь перейдем к вопросу о выборках на уровне множеств, т.е. выборках 
множества из нескольких строк, а не из одной строки. Как объяснялось ранее, 
это требует поочередного доступа к строкам множества, и механизмом такого 
доступа являются курсоры. Этот процесс схематически проиллюстрирован (рис. 
8.4) на примере выборки информации о поставщиках (S#, SNAME и STATUS) для всех 
поставщиков в городе, определенном базовой переменной Y. 

    EXEC SQL	DECLARE	X CURSOR	TOR	— определить курсор X      */ 
    SELECT	S.S#, S. SNAME, S		STATUS 
    FRCM	S 
    WHERE	S.CITY =	:Y ; 
    ЕХЕС SQL	OPEN X;			/* выполнить запрос	*/ 
    DO for all S rows			accessible via X ; /* для всех строк S,		*/ 
    /* доступных через X	*/ 
    EXEC	SQL FETCH	X INTO	:S#, :SNAME, :STATUS ; 
    /* получить следующего поставщика	* / 
    END ; 
    EXEC SQL	CLOSE X	•'	/	'* деактивировать курсор X    */ 

    Рис. 8.4. Выборка нескольких строк 

    Пояснение. Оператор DECLARE x CURSOR. .. определяет курсор с именем X, 
связанный с табличным выражением, определенным оператором SELECT, который 
является частью всего выражения DECLARE. Причем такое табличное выражение не 
вычисляется в этом месте; DECLARE CURSOR— чисто декларативное выражение. 
Табличное выражение вычисляется только при открытии курсора. Затем для выборки 
строк из результирующего множества по одной за раз используется оператор 
FETCH, присваивающий выбранные значения базовым переменным в соответствии со 
спецификациями в инструкции INTO. 
    (Для простоты базовым переменным даны те же имена, что и у соответствующих 
столбцов базы данных. Обратите внимание, что в выражении SELECT при 
определении курсора нет своей инструкции INTO.) Поскольку в результирующем 
множестве большое количество строк, оператор FETCH обычно используется в цикле 
(DO. . .END в PL/1); цикл будет повторяться до тех пор, пока не закончатся 
строки для результирующего множества. При выходе из цикла курсор X деактивируется. 
    А теперь рассмотрим курсоры и операции с курсорами более подробно. Курсор 
определяется с помощью оператора DECLARE CURSOR, общая форма которого следующая: 

    ЕХЕС SQL DECLARE cursor CURSOR 
    TOR table-expression [ ORDER BY order-item-commalist ] ; 

    Здесь   cursor—  это   имя   определяемого   курсора.   Что   такое   
table-expression  и ozder-item-commaiist, объяснялось ранее в этой главе. 
Пример показан на рис. 8.4. Замечание. Для краткости здесь не рассматриваются 
некоторые необязательные спецификации. Подробности смотрите в [8.1, 8.5]. 

    236 Часть II. Реляционная модель 

    Как утверждалось ранее, оператор DECLARE CURSOR— декларативный, а не 
выполняемый; он предназначен для объявления курсора с определенным именем и 
постоянно связанного с ним табличного выражения. Табличное выражение может 
включать ссылки на базовые переменные. Программа может включать любое 
количество операторов DECLARE CURSOR, каждый из которых, конечно, предназначен 
для разных курсоров. 
    Для работы с курсорами существует три выполняемых оператора: OPEN, FETCH и CLOSE. 
    1.  Инструкция 

    ЕХЕС SQL OPEN cursor ; 

    предназначена для открытия (или активизации) определенного курсора 
(который не должен быть открыт в данный момент). В результате вычисляется 
связанное с этим курсором табличное выражение (для всех базовых переменных, 
упоминаемых в этом выражении, используются текущие значения); таким образом, 
идентифицируется определенное множество строк, которое становится текущим 
активным множеством для данного курсора. Курсор определяет позицию в этом 
активном множестве, а именно позицию перед первой строкой. (Активные множества 
всегда рассматриваются как упорядоченные, а значит, понятие позиции имеет 
смысл. Порядок определяется инструкцией ORDER BY, но если эта инструкция 
отсутствует, то порядок определяется системой.) 
    2.  Инструкция 

    ЕХЕС SQL FETCH cursor INTO host-variable-coimalist ; 

    служит для передвижения определенного курсора cursor (который должен быть 
открыт) к следующей строке в активном множестве с последующим присвоением 
значений этой строки базовым переменным, указанным в инструкции INTO. Если при 
выполнении оператора FETCH следующей строки нет, то данные не выбираются и 
переменной SQLSTATE присваивается значение 02000. 
    3. Инструкция 

    ЕХЕС SQL CLOSE cursor ; 

    служит для деактивации (отключения) определенного курсора (который должен 
быть в данный момент открыт). После выполнения этого оператора у курсора нет 
активного множества. Однако в дальнейшем курсор может быть вновь открыт, в 
этом случае он снова получит активное множество — возможно, не такое как 
раньше, в особенности если базовые переменные, указанные в объявлении курсора, 
за это время изменились. Заметьте, что изменение этих переменных при открытом 
курсоре не окажет влияния на активное множество. 
    Есть еще два оператора, в которых используются курсоры, — это 
сикгант-формы операторов итокпз и DEIETE. Если курсор, скажем X, в текущий 
момент находится в позиции определенной строки, то можно обновить или удалить 
"текущую строку X", т.е. строку, на которую "указывает" X. Например: 

    ЕХЕС SQL UPDATE S 
    SET   STATUS = STATUS + : RAISE WHERE CURRENT OF X ; 

    Выражения UPDATE. . .WHERE CURRENT и DELETE. . .WHERE CURRENT были бы не 
допустимы, если бы табличное выражение в объявлении курсора было определено 
как необновляемое представление с помощью инструкции CREATE VIEW (подробнее 
представления обсуждаются в последующих главах книги). 

    Динамический SQL 

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

    Глава 8. Язык SQL	237 

    тивные приложения — это приложения, которые поддерживают доступ к базе 
данных с оперативного терминала.) Рассмотрим, что должно делать типичное 
оперативное приложение. Схематически оно должно выполнять следующие операции: 
    1.	Принять команду терминала. 
    2.	Проанализировать команду. 
    3.	Сгенерировать соответствующие SQL-операторы для базы данных. 
    4.	Возвратить сообщение и (или) результаты на терминал. 
    Если набор команд, которые может обработать программа, довольно мал, как, 
например, в случае обработки предварительных заказов мест на авиалиниях, то 
набор всех возможных выполняемых SQL-операторов будет также невелик и его 
можно "вшить" в программу. В этом случае операции 2 и 3 будут состоять из 
логической проверки введенной команды с последующим переходом к части 
программы, выполняющей предопределенные операторы SQL. В противном случае, 
если вводимые команды достаточно разнообразны, было бы непрактично 
предопределять и "вшивать" все SQL-выражения для всех возможных команд. Вместо 
этого, вероятно, целесообразней конструировать необходимые SQL-запросы 
динамически, а затем динамически компилировать и выполнять сконструированные 
запросы. Средства динамического SQL предназначены для поддержки этого процесса. 
    Два основных динамических оператора — PREPARE и EXECUTE. Их использование 
проиллюстрировано на следующем (не реальном, но правильном) примере: 

    DCL SQLSOURCE CHAR VARYING (65000) ; 
    SQLSOURCE = 'DELETE FRCM SP WHERE SP.QTY < 300' ; EXEC SQL PREPARE 
SQLPREPPED FRCM : SQLSOURCE ; EXEC SQL EXECUTE SQLPREPPED ; 

    Пояснения: 
    \. Имя SQLSOURCE идентифицирует переменную PL/1 символьной строки 
переменной длины, в которой программой каким-либо образом сконструирована 
исходная форма (т.е. представление в виде символьной строки) некоторого 
оператора SQL, в нашем конкретном примере — оператора DELETE. 
    2.  Имя SQLPREPPED, напротив, идентифицирует переменную SQL, а не 
переменную базового языка PL/1, которая (концептуально) будет использоваться 
для хранения скомпилированной формы SQL-оператора (его исходная форма 
представлена в переменной SQLSOURCE) . Конечно, имена SQLSOURCE и SQLPREPPED 
можно изменить. 
    3.  С помощью инструкции "SQLSOURCE =...„•" переменной SQLSOURCE 
присваивается исходная форма SQL-оператора DELETE. Конечно, на практике 
процесс конструирования такого исходного оператора будет значительно сложнее 
и, возможно, в нем будет использоваться ввод и анализ некоторых запросов от 
конечного пользователя, выраженных на обычном языке или в другой, более 
"дружественной для пользователя" форме, чем обыкновенный язык SQL. 
    4.  Оператор PREPARE берет это исходное выражение и "подготавливает" его 
(т.е. компилирует), создавая выполняемую версию, которая сохраняется в переменной 

    SQLPREPPED. 

    5.  И наконец, оператор EXECUTE выполняет эту версию SQLPREPPED, в 
результате появляется настоящий оператор DELETE. Информация SQLSTATE оператора 
DELETE возвращается точно так же, как при выполнении этого оператора обычным образом. 
    Обратите внимание, что так как имя SQLPREPPED идентифицирует переменную 
SQL, а не PL/1, то перед ним нет двоеточия, когда оно используется в 
операторах PREPARE и EXECUTE. Заметьте также, что такие переменные SQL не 
объявляются явно. 

    238	Часть II. Реляционная модель 

    Описанный выше процесс в точности совпадает с тем, который происходит, 
если выражения SQL вводятся интерактивно. Во многих системах имеется некоторое 
подобие процессора запросов SQL. Этот процессор в действительности не что 
иное, как обобщенное оперативное приложение; оно способно обрабатывать весьма 
широкий спектр вводимых команд, а именно любой допустимый (или недопустимый!) 
оператор SQL. В нем используются средства динамического SQL для 
конструирования подходящих операторов SQL, соответствующих этим вводимым 
командам, для компиляции и выполнения этих сконструированных операторов и для 
возвращения сообщений и результатов обратно на терминал. 
    Более подробная информация о динамическом SQL содержится в [8.1, 8.5]. 

    8.9. Резюме 

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

    CREATE DOMAIN	CREATE TABLE 
    ALTER DOMAIN	ALTER TABLE 
    DROP DOMAIN	DROP TABLE 

    Еще два оператора DDL, CREATE и DROP VIEW, обсуждаются далее в этой книге. 
    1. Относительно доменов было указано, что в SQL они весьма далеки от 
настоящих реляционных доменов; в сущности, домены SQL — это не более чем 
просто сокращение. Точнее, домены SQL обеспечивают: спецификации типов данных 
на уровне доменов; определения по умолчанию на уровне доменов; ограничения 
целостности на уровне доменов. Были кратко описаны скалярные типы данных SQL, 
но опущены многие сложные аспекты доменов в стиле SQL. 
    2. Относительно базовых таблиц отмечалось, что таблицы SQL в общем 
отличаются от настоящих отношений по крайней мере следующим: в них допустимы 
одинаковые строки и столбцы упорядочены слева направо. В базовых таблицах 
должно быть не менее одного столбца, нуль или более объявленных потенциальных 
ключей (среди которых по крайней мере один может быть объявлен как первичный 
ключ), нуль или более объявленных внешних ключей, нуль или более объявленных 
проверяемых ограничений. Поддерживаются следующие правила обновления и 
удаления внешних ключей: NO ACTION, CASCADE, SET DEFAULT и SET NULL. 
    Также была кратко описана информационная схема, которая содержит набор 
предписанных представлений гипотетической "схемы определения". Далее 
обсуждались операции обработки данных. 
    1. Операции выборки (которые в основном предназначены для табличных 
выражений). Обычно такие операции содержат единственное выражение выборки, но 
также поддерживаются различные виды явных выражений операции оош, а выражения 
соединения и выборки могут комбинироваться вместе произвольным образом с 
помощью операторов UNION, INTERSECT и EXCEPT. Упоминалось также использование 
оператора ORDER BY для назначения таблицы, которая является результатом 
вычисления табличного выражения (любого вида). 
    2.  Выражения выборки. В частности, были описаны следующие компоненты: 
    • Базовая инструкция SELECT, включая использование ключевого слова 
DISTINCT, скалярные выражения, инструкцию "SELECT *" и введение имен 
результирующих столбцов. 
    • Инструкция FROM, включая использование переменных области значений и 
табличных ссылок в инструкции FROM, более сложных, чем просто имя таблицы. 

    1 Инструкция WHERE, включая использование подзапросов и функции EXISTS. 
    • Инструкции GROUP нс и HAVING, включая использование итоговых функций ссшт, 
    SUM, AVG, МАХ И MIN. 

    Глава 8. Язык SQL	239 

    Кроме того, описан концептуальный алгоритм вычисления (т.е. схема 
формального определения) для выражений выборки. 
    3.  Операции обновления INSERT, UPDATE и DELETE. 
    Затем подробно рассматривались табличные выражения (включая 
BNF-граммати-ку), условные выражения (в частности, условия MATCH и 
"все-или-какой-нибудь") и скалярные выражения (на примере операторов 
CASE и CAST). Особо отмечалось, что выражение выборки, в результате 
вычисления которого получается таблица с одной строкой и одним столбцом, 
может быть использовано как скалярное значение (например, в инструкциях 
SELECT или WHERE). 
    И наконец, были описаны основные возможности встроенного языка SQL. 
Главная идея встроенного SQL заключается в двухрежимном принципе, в 
соответствии с которым (насколько это возможно) любое выражение SQL, 
используемое интерактивно, можно также использовать и в прикладной программе. 
Исключение из этого принципа возникает при использовании многострочных 
операций выборки, которые требуют использования курсора для преодоления 
разрыва между уровнями выборки "множество-за-раз" в SQL и "строка-за-раз" в 
базовом языке, таком как PL/I. (Возможно, здесь стоит упомянуть, что кроме 
PL/1, стандарт SQL [8.1] также поддерживают языки Ada, С, COBOL, Fortran, 
MUMPS и Pascal). 
    После ряда необходимых (хотя большей частью синтаксических) 
предварительных замечаний, включая, в частности, краткое объяснение назначения 
переменной SQLSTATE, обсуждались операции (единичный SKTKCT, INSERT, UPDATE и 
DELETE), для которых не нужен курсор. Затем рассматривались операции, для 
которых необходимы курсоры, в частности операторы DECLARE сшэсж, OPEN, иггсн, 
CXOSE и сшквит-форму операторов UPDATE и DELETE. И наконец, было дано краткое 
описание понятия динамиче-ского SQL (упоминались операторы PREPARE и EXECUTE). 

    Упражнения 

    8.1. Дайте определение данных с помощью SQL для базы данных поставщиков, 
деталей и проектов. 
    8.2. Напишите последовательность инструкций DROP для удаления всего 
содержимого базы данных поставщиков, деталей и проектов. 
    8.3. В этой главе мы описали оператор CREATE TABLE, как он определен в 
стандарте SQL [8.1]. Однако многие коммерческие продукты поддерживают 
дополнительные опции этого оператора, обычно связанные с индексами, 
размещением на дисковом пространстве и другими вопросами реализации, что 
противоречит цели физической независимости данных и межсистемной 
совместимости. Исследуйте доступный вам продукт, поддерживающий SQL. Верны ли 
предыдущие замечания для этого продукта? В частности, какие дополнительные 
опции оператора CREATE TABLE поддерживаются в этом продукте? 
    8.4. И снова исследуйте доступный вам продукт, поддерживающий SQL. 
Поддерживается ли в нем информационная схема? Если нет, то каким образом 
поддерживается каталог? 
    8.5. Покажите, что язык SQL реляционно полный (см. главу 6) в том смысле, 
что для любого выражения реляционной алгебры существует семантически 
эквивалентное выражение SQL. 
    8.6. Есть ли в SQL эквиваленты операций EXTEND и SUMMARIZE? 
    8.7. Есть ли в SQL эквивалент реляционной операции присвоения? 
    8.8. Есть ли в SQL эквиваленты операции сравнения? 

    240	Часть II. Реляционная модель 

    8.9. Дайте как можно больше различных формулировок на языке SQL запроса 
"Получить имена поставщиков, поставляющих деталь Р2" (см. примеры выше в этой главе). 
    8.10. Есть два формально эквивалентных подхода к части реляционной модели, 
связанной с обработкой данных, — исчисление и алгебра. А значит, существует 
два стиля построения языка запросов. Например, язык QUEL, бесспорно, основан 
на исчислении, так же и язык QBE; а язык ISBL системы PRTV [6.8] основан на 
алгебре. A SQL? Основан на алгебре или на исчислении? 
    8.11. Дайте решения на языке SQL к упр. 6.13-6.48. 
    8.12. Сформулируйте на языке SQL следующие задачи обновления: 
    а) вставить нового поставщика S10 в таблицу S с именем Smith, город New 
York; статус еще не известен; 
    б) изменить цвет всех красных деталей на оранжевый; 
    в) удалить все проекты, для которых нет поставок. 
    8.13. Используя базу данных поставщиков, деталей и проектов, напишите 
программу со встроенными выражениями SQL для выдачи списка всех 
строк-поставщиков по порядку их номеров. За каждой строкой поставщика должны 
немедленно следовать строки проектов, обеспечиваемых этим поставщиком, по 
порядку номеров проектов. 
    8.14. Даны таблицы 
    CREATE TABLE PARTS 
    ( Р# ... , DESCRIPTION ... , 
    PRIMARY KEY ( P# ) ) ; CREATE TABLE PART_STRUCTURE 
    ( MAJOR_P# . . . , MINOR_P# .. . , QTY . . . , PRIMARY KEY ( MAJOR_P#, 
MINOR_P# ), FOREIGN KEY ( MAJOR_P# ) REFERENCES PARTS, FOREIGN KEY ( MINOR_P# 
) REFERENCES PARTS ) ; 
    В таблице PARTJSTRUCTURE показано, какая деталь (MAJOR_P#) содержит другую 
деталь (MINOR_P#) как компонент первого уровня. Напишите программу на языке 
SQL для получения списка всех компонентов данной детали на все уровнях. 
Следующие значения для примера могут помочь вам наглядно представить задачу. 
    PART STRUCTURE 
    MAJOR P#	MINOR P#	OTY 
    PI	Р2	2 
    PI	РЗ	4 
    Р2	РЗ	1 
    Р2	Р4	3 
    РЗ	Р5	9 
    Р4	Р5	8 
    Р5	Р6	3 

    Список литературы 

    8.1. Документ ISO/IEC 9075:1992. Database Language SQL. (Также доступен 
как документ ANSI X3.135-1992.) 

    Глава 8. Язык SQL 241 


    Текущая версия официального (ISO/ANSI) стандарта SQL, неформально 
известного как SQL2, SQL-92 или SQL/92. Интересная деталь: хотя SQL широко 
признан как международный "реляционный" стандарт, в документе самого стандарта 
об этом не упоминается. 
    8.2. X/Open. Structured Query Language (SQL): CAE Specification C201. — 
1992. Описание стандарта X/Open SQL. 
    8.3. FIPS PUB 127-2. Database Language SQL. U.S. Department of Commerce, 
National Institute of Standards and Technology, 1992. 
    Описание стандарта SQL организации Федеральной обработки информации 
(Federal Information Processing — FIPS). 
    8.4. Документ IBM № SC26-4348. Systems Application Architecture Common 
Programming Interface: Database Reference. IBM Corp. 
    Описание стандарта IBM SAA SQL. 
    8.5. Date C.J., Darwen H. A Guide to the SQL Standard. — Reading, Mass.: 
    Addison-Wesley, 1993. 
    Некоторые из разделов настоящей главы основаны на материале этой книги, 
которая является полным руководством по SQL/92. К учебным пособиям по SQL/92 
относятся также [8.6, 8.7]. 
    8.6. Cannan S., Otten G. SQL— The Standard Handbook.— Maidenhead, UK: 
    McGraw-Hill Intern., 1993. 
    8.7. Melton J., Simon A.R. Understanding The New SQL: A Complete Guide. — 
San Mateo, Calif: Morgan Kaufmann, 1993. 
    8.8. Chamberlin D.D., Boyce R.F. SEQUEL: A Structured English Query 
Language // Proc. ACM SIGMOD Workshop on Data Description, Access, and 
Control. — Ann Arbor, Mich., 1974. 
    В статье впервые представлен язык SQL (или SEQUEL, как он назывался 
вначале; название впоследствии по юридическим причинам было изменено). 
    8.9. Astrahan M.M, Lorie R.A. SEQUEL-XRM: A Relational System // Proc. ACM 
Pacific Regional Conference. — San Francisco, Calif, 1975. 
    Описан первый прототип реализации языка SEQUEL — начальной версии SQL 
[8.8]. Смотрите также [8.12, 8.13], которые выполняют аналогичную функцию для 
System R. 
    8.10. Reisner P., Boyce R.F., Chamberlin D.D. Human Factors Evaluation of 
Two Data Base Query Languages: SQUARE and SEQUEL // Proc. NCC 44. — Anaheim, 
Calif; Montvale, N.J.: AFIPS Press, 1975. 
    Язык SEQUEL [8.8], предшественник SQL, был основан на более раннем языке 
SQUARE. Эти два языка в основном совпадали, но в SQUARE использовался 
математический синтаксис, а в SEQUEL — ключевые слова из английского языка, 
такие как SELECT, FROM, WHERE и т.д. В статье приведены отчеты по ряду 
исследований, в которых изучалась практичность двух языков, при этом 
использовались студенты колледжа. В результате этой работы в SEQUEL были 
внесены некоторые изменения [8.11]. 
    8.11. Chamberlin D.D. et al. SEQUEL/2: A Unified Approach to Data 
Definition, Manipulation, and Control // IBM J. R&D. — 1976. —20, № 6; 1977. 
—21, № 1. 
    Опыт реализации предыдущего прототипа SEQUEL, описанного в [8.9], и 
результаты проверок практичности, отчет о которых содержится в [8.10], привели 
к разработке новой версии языка, названной SEQUEL/2. Язык, поддер


    242                                            Часть II. Реляционная модель 


    живаемый системой System R [8.12,8.13], был в основном похож на SEQUEL/2 
(с заметным отсутствием возможностей так называемых "утверждений" и 
"триггеров"), плюс некоторые расширения, появившиеся в результате опыта 
пользователей [8.14]. 
    8.12. Astrahan M.M. et al. System R: Relational Approach to Database 
Management // ACM TODS. — 1976. — 1, № 2. 
    Система System R была реализацией основного прототипа (ранней версии) 
языка SQL. В статье описывается архитектура System R в том виде, в каком она 
была изначально запланирована. 
    8.13. Blasgen M.W. et al. System R: An Architectural Overview // IBM Sys. 
J.— 1981.—20, № 1. 
    Описывается архитектура System R на момент, когда система была полностью реализована. 
    8.14. Chamberlin D.D. A Summary of User Experience with the SQL Data 
Sublanguage // Proc. Intern. Conf. on Database.— Aberdeen, Scotland, 1980. 
(Также IBM Research Report RJ2767. — 1980.) 
    В статье обсуждается ранний опыт использования системы System R и 
предложены некоторые дополнения языка SQL в свете этого опыта. Некоторые из 
этих дополнений— операторы EXISTS, LIKE (не обсуждались в этой главе), PREPARE 
и EXECUTE — действительно были реализованы в окончательной версии System R. 
    8.15. Chamberlin D.D., Gilbert A.M., Yost R.A. A History of System R and 
SQL / Data System // Proc. 7th Intern. Conf. on Very Large Data Bases. — 
Cannes, France, 1981. 
    Обсуждаются уроки, извлеченные из прототипа системы System R, а также 
описана эволюция этого прототипа до первого семейства реляционных продуктов 
IBM, а именно SQL/DS (переименованного в "DB2 for VM and VSE"). 
    8.16. Chamberlin D.D. et al. A History and Evaluation of System R // 
CACM.— 1981.—24, № 10. 
    Описываются  три  основные  фазы  развития  проекта  System  R 
(предварительный прототип, многопользовательский прототип и оценка); основное 
внимание уделено технологиям компиляции и оптимизации, которые впервые 
использовались в System R. Некоторая часть этой статьи пересекается с [8.15]. 
Замечание. Интересно сравнить эту статью с [7.14], в которой описана история 
университетского проекта INGRES. 
    8.17. Date C.J. A Critique of the SQL Database Language // ACM SIGMOD 
Record. — 1984.— 14, №3. (Переиздано: C.J. Date. Relational Database: Selected 
Writings. — Reading, Mass.: Addison-Wesley, 1986.) 
    Язык SQL далек от совершенства. В статье представлен критический анализ 
принципиальных недостатков этого языка (в основном исходя из требований к 
формальному компьютерному языку вообще, а не из требований к языку баз 
данных). Замечание. Некоторая критика из этой статьи не касается SQL/92. 
    8.18. Date C.J. What's Wrong with SQL? // C.J. Date. Relational Database 
Writings 1985-1989. — Reading, Mass.: Addison-Wesley, 1990. 
    Обсуждаются некоторые недостатки языка SQL в дополнение к описанным в 
[8.17]. Замечание. И опять некоторая критика из этой статьи не касается SQL/92. 
    8.19. Date C.J. How SQL Missed the Boat // Database Programming & Design.— 
1993.—6, №9. 


    Глава 8. Язык SQL 243 


    Краткое заключение о недостатках SQL, имеющих отношение к поддержке (или 
отсутствию таковой) некоторых аспектов реляционной модели: структурных, 
аспектов обработки и целостности. 
    8.20. Date C.J. SQL Dos and Don'ts // C.J. Date. Relational Database 
Writings 1985-1989. — Reading, Mass.: Addison-Wesley, 1990. 
    В статье предложены некоторые практические советы о том, как использовать 
SQL так, чтобы избежать потенциальных ловушек, вызванных недостатками SQL, 
описанными в [8.17—8.19], и получить максимальные преимущества по 
продуктивности, портативности, связности и т.п. 
    8.21. Negri M., Pelagatti S., Sbattella L. Formal Semantics of SQL Queries 
// ACM TODS.—1991.—16, №3. 
    Немного переработанная цитата из резюме: "Семантика запросов SQL формально 
определена с помощью набора правил, определяющих преобразование на основе 
синтаксиса запроса SQL в формальную модель, называемую расширенным исчислением 
трехзначных предикатов (Extended Three Valued Predicate ^alculus — E3VPC), 
которая большей частью основана на хорошо известных математических понятиях. 
Приведены также правила для преобразования общего выражения E3VPC в 
каноническую форму; ...проблемы, подобные анализу эквивалентности запросов 
SQL, полностью решены." Однако заметьте, что рассматриваемый здесь диалект SQL 
— это только первая версия стандарта ("SQL/86"), а не SQL/92. 

    Ответы к некоторым упражнениям 

    8.1. CREATE DCMAIN S#    CHAR(5) ; CREATE DOMAIN NAME  CHAR(20) ; CREATE 
DCMAIN STATUS NUMERIC(5) ; CREATE DCMAIN CITY  CHAR (15) ; CREATE DCMAIN P#    
CHAR(6) ; CREATE DCMAIN COLOR CHAR (6) ; CREATE DCMAIN WEIGHT NUMERIC (5) ; 
CREATE DOMAIN J#    CHAR(4) ; CREATE DCMAIN QTY   NUMERIC(9) ; 
    CREATE TABLE S 
    ( S#     S#, SNAME  NAME, STATUS STATUS, CITY   CITY, PRIMARY KEY ( S# ) ) ; 
    CREATE TABLE P 
    ( P*     P#, PNAME  NAME, COLOR  COLOR, WEIGHT WEIGHT, CITY   CITY, 
PRIMARY KEY ( P# ) ) ; 

    244	Часть II. Реляционная модель 

    CREATE TABLE J 
    ( J#     J#, JNAME  NAME, CITY   CITY, PRIMARY KEY ( J# ) ) ; 
    CREATE TABLE SPJ ( S#     S#, P#     P#, J#     J#, 
    CTY   QTY, 
    PRIMARY KEY ( S#, P#, J# ), TOREIGN KEY ( S# ) REFERENCES S, FOREIGN KEY ( 
P# ) REFERENCES P, FOREIGN KEY ( J# ) REFERENCES J ) ; 8.2. DROP TABLE SPJ 
RESTRICT ; DROP TABLE S  RESTRICT ; DROP TABLE P  RESTRICT ; DROP TABLE J 
RESTRICT ; 
    DROP DCMAIN DROP DCMAIN DROP DCMAIN DROP DCMAIN DROP DCMAIN DROP DCMAIN 
DROP DCMAIN DROP DCMAIN DROP DCMAIN 
    S# 
    NAME 
    STATUS 
    CITY 
    P# 
    COLOR 
    WEIGHT 
    J# 
    QTY 
    RESTRICT RESTRICT RESTRICT RESTRICT RESTRICT RESTRICT RESTRICT RESTRICT RESTRICT 

    8.5. Для подтверждения реляционной полноты языка SQL необходимо сначала 
показать, что существуют выражения SQL для каждой из пяти примитивных 
операций— выборки, проекции, произведения, объединения и вычитания, а затем, 
что операнды таких выражений SQL могут, в свою очередь, быть произвольными 
выражениями SQL. 
    Начнем с замечания, что язык SQL фактически поддерживает оператор 
"переименования" реляционной алгебры RENAME благодаря введению в SQL/92 
необязательной спецификации "AS column", где column— новое имя столбца. 
Следовательно, мы можем быть уверены, что все таблицы имеют правильные имена 
столбцов, в частности операнды произведения, объединения и вычитания 
удовлетворяют требованиям (в нашей версии) алгебры по отношению к наименованию 
столбцов. Более того, указанные требования к наименованию столбцов операндов 
действительно удовлетворяются: правила наследования имен столбцов в SQL 
фактически совпадают с такими же в алгебре (в нашей версии). 
    Вот выражения SQL, соответствующие пяти примитивным операциям: 

    Алгебра     			SQL 

    A WHERE р     		А [х, у,...,   z] 
    A TIMES В     		A UNION В 
    A MINUS В     		SELECT *  FROM A WHERE p 
    					SELECT DISTINCT X,  у,. . . ,z FROM A 
    A CROSS JOIN В     		SELECT * FRCM A UNION SELECT * FROM В 
    					SELECT * FRCM A EXCEPT SELECT * FRCM В Ib 

    Глава 8. Язык SQL 245 

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

    EXTEND A ADD exp AS 2 

    может быть представлено в SQL как 

    SELECT А. *,   exp AS Z FRCM   (  A )   AS A 

    Здесь заключенное в скобки А представляет собой табличную ссылку 
произвольной сложности (соответствующую операнду А оператора EXTEND); второе А 
— переменная области значений. 
    Второе выражение 

    SUMMARIZE A BY   (  Al,   A2, ...,   An)   ADD exp AS  Z 

    может быть представлено в SQL как 

    SELECT A.AZ, A.A2, ..., A.An, exp AS Z 
    FRCM ( A ) AS A 
    GROUP BY A.AZ, A.A2, ..., A.An 

    8.7. Язык SQL не поддерживает реляционного присвоения непосредственно. 
Однако при условии, что мы считаем целевое отношение именованным отношением, 
можно моделировать такую операцию довольно просто. Например, присвоение 
    R := X ; 
    (где R— именованное отношение, а X— произвольное реляционное выражение) 
можно успешно воспроизвести такой последовательностью операций SQL: 
    DELETE ERCM R ; INSERT INTO R QX ; 
    (где QX— эквивалентов языке SQL). 
    8.8. Язык SQL не поддерживает реляционного сравнения непосредственно. 
Однако такую операцию можно смоделировать, хотя и в виде очень громоздкого 
выражения. Например, сравнение 
    А = В ; 
    (где А и В — отношения) можно успешно смоделировать таким выражением SQL: 
    NOT EXISTS ( SELECT * FRCM A 
    WHERE NOT EXISTS ( SELECT * FRCM В 
    WHERE A-row = B-row ) ) 
    (где A-rown в-row— конструкции строк, представляющие соответственно целую 
строку А и целую строку 5). 
    8.9. Приводим несколько таких формулировок. Заметьте, что следующий список 
нельзя считать даже близким к исчерпывающему. Также заметьте, что это очень 
простой запрос! 

    SELECT DISTINCT S.SNAME FRCM  S 

    246	Часть II. Реляционная модель 

    WHERE S.S# IN 
    ( SELECT SP.S# FRCM  SP WHERE SP.P# = 'P2' ) ; 
    SELECT DISTINCT T.SNAME FRCM { S NATURAL JOIN SP ) AS Т WHERE T.P# = 'P2' ; 
    SELECT DISTINCT T.SNAME 
    FRCM ( S JOIN SP CN S.S# = SP.S# AND SP.P# = 'P2' 
    AS Т 
    SELECT DISTINCT T.SNAME 
    FRCM ( S JOIN SP USING S# ) AS Т 
    WHERE T.P# = 'P21 ; 
    SELECT DISTINCT S.SNAME 
    FRCM  S 
    WHERE S.St = ANY 
    ( SELECT SP.S# FRCM  SP WHERE SP.PS = 'P2' ) ; 
    SELECT DISTINCT S.SNAME 
    FRCM  S 
    WHERE EXISTS 
    ( SELECT * FRCM  SP 
    WHERE SP.S# = S.S# АЮ   SP.P# = 'P2' ) ; 
    SELECT DISTINCT S.SNAME FRCM  S, SP WHERE S.S# = SP.S# AND   SP.Ptt = 'P2' ; 
    SELECT DISTINCT S.SNAME 
    FRCM  S 
    WHERE 0 < 
    ( SELECT COUNT(*) FRCM  SP 
    WHERE SP.S# = S.S# AND   SP.P# = 'P21 ) ; 

    Глава 8. Язык SQL 247 

    SELECT DISTINCT S.SNAME FRCM  S WHERE  'P2' IN 
    ( SELECT SP.P# FRCM  SP WHERE SP.S# = S.S# ) ; 
    SELECT DISTINCT S.SNAME 
    FRCM  S 
    WHERE  'P2' = ANY 
    ( SELECT SP.P# FRCM  SP WHERE SP.S# = S.S# ) ; 
    SELECT	S.SNAME 
    FRCM	S, SP 
    WHERE	S.S# = SP.S# 
    AND	SP.P# = 'P21 
    GROUP	BY S.SNAME ; 

    Дополнительный вопрос: какие выводы можно сделать из этого? 

    8.10. Язык SQL в действительности является гибридом алгебры и исчисления. 
Например, в нем представлены и квантор существования EXISTS (исчисление), и 
оператор UNION (алгебра). Интересно, что первоначально язык SQL разрабатывался 
как отличный и от алгебры и от исчисления (см. [8.8]); считалось, что 
конструкция "IN подзапрос" более дружественная по отношению к пользователю, 
чем явные соединения (и пр.) алгебры и кванторы исчисления. Однако, как 
оказалось, конструкция "IN подзапрос" (очевидно) не отвечала предъявляемым 
требованиям и поэтому возникла необходимость в расширении первоначального 
языка различными способами. Сейчас ситуация такова, что, по иронии судьбы, 
возможности конструкции "IN подзапрос" из языка SQL могли бы быть исключены 
полностью фактически без потери каких-либо функций языка (хотя можно 
согласиться с тем, что конструкция "IN подзапрос" иногда воспринимается 
интуитивно легче, чем альтернативные конструкции). Более подробно этот вопрос 
изложен в [8.17-8.19]. 
    8.11. Мы перенумеровали решения в виде 8.1 l.n, что соответствует номерам 
б.п в упражнениях главы 6. 
    8.11.13. SELECT * 

    FRCM  J ; или просто: TABLE J ; 

    8.11.14. SELECT J.* FRCM  J WHERE J.CITY = 'London' ; 
    8.11.15. SELECT DISTINCT SPJ.Stf FRCM  SPJ WHERE SPJ.J# = 'Jl' ; 

    248	Часть II. Реляционная модель 

    8.11.16. SELECT SPJ.* FRCM  SPJ 
    WHERE SPJ.QTY >= 300 AND   SPJ.QTY <= 750 ; 
    8.11.17. SELECT DISTINCT P.COLOR, P.CITY FRCM  P ; 
    8.11.18. SELECT S.S#, P.P#, J.J# FROM  S, P, J WHERE S.CITY = P. CITY AND  
 P.CITY = J. CITY ; 
    8.11.19. SELECT S.S#, P.P#, J.J# FRCM  S, P, J 
    WHERE NOT ( S.CITY = P.CITY AND P.CITY = J.CITY ) ; 
    8.11.20. SELECT S.S#, P.P#, J.J# FRCM  S, P, J WHERE S.CITY 0 P.CITY AND   
P.CITY 0 J.CITY AND   J.CITY 0 P.CITY ; 
    8.11.21. SELECT DISTINCT SPJ.P# FRCM  SPJ 
    WHERE  ( SELECT S.CITY FRCM  S WHERE S.S# = SPJ.S* ) = 'London' ; 
    8.11.22. SELECT DISTINCT SPJ.Pjt FRCM  SPJ WHERE  ( SELECT S.CITY 
    FRCM  S 
    WHERE S.St = SPJ.S* ) = 'London1 AND   ( SELECT J.CITY 
    FROM  J 
    WHERE J.J# = SPJ.J* ) = 'London' ; 
    8.11.23. SELECT DISTINCT S.CITY AS SCITY, J.CITY AS JCITY FRCM  S, J WHERE EXISTS 
    ( SELECT * FRCM  SPJ WHERE SPJ.S# = S.S# AND   SPJ.J# = J.J# ) ; 
    8.11.24. SELECT DISTINCT SPJ.P# FROM  SPJ 
    WHERE  ( SELECT S.CITY FROM  S 

    Глава 8. Язык SQL	249 

    WHERE S.S# = SPJ.S# ) = ( SELECT J.CITY FRCM  J WHERE J.J# = SPJ.Jt ) ; 
    8.11.25. SELECT DISTINCT SPJ.J# FRCM  SPJ 
    WHERE  ( SELECT S.CITY FRCM  S 
    WHERE S.S# = SPJ.Sfl ) <> ( SELECT J.CITY FRCM  J WHERE J.J# = SPJ.JS ) ; 
    8.11.26. SELECT DISTINCT SPJX.P* AS PA, SPJY.P# AS PB FRCM  SPJ AS SPJX, 
SPJ AS SPJY WHERE SPJX.SS = SPJY.S* AND   SPJX.P# < SPJY.P* ; 
    8.11.27. SELECT COUNT ( DISTINCT SPJ.JS ) AS N FRCM  SPJ WHERE SPJ.S# = 
'SI1 ; 
    8.11.28. SELECT SUM ( SPJ.CTY ) AS x 
    FRCM  SPJ 
    WHERE SPJ.Sfl = 'SI1 
    AND   SPJ.P# = 'PI' ; 
    8.11.29. SELECT SPJ.P#, SPJ.J#, SUM ( SPJ.QTY ) AS Y FRCM  SPJ GROUP BY 
SPJ.Ptf, SPJ.J* ; 
    8.11.30. SELECT DISTINCT SPJ.Ptf FRCM  SPJ 
    GROUP BY SPJ.Pt, SPJ.Jt HAVING AVG ( SPJ.OTY ) > 320 ; 
    8.11.31. SELECT DISTINCT J.JNAME FRCM  J, SPJ WHERE J.J# = SPJ.Jtf AND   
SPJ.S# = 'SI' ; 
    8.11.32. SELECT DISTINCT P.COLOR FRCM  P, SPJ WHERE P.P# = SPJ.P# AND   
SPJ.S* = 'SI' ; 
    8.11.33. SELECT DISTINCT SPJ.P# FRCM  SPJ, J WHERE SPJ.J# = J.J# AND   
J.CITY = 'London' ; 
    8.11.34. SELECT DISTINCT SPJX.Jt 
    FRCM  SPJ AS SPJX, SPJ AS SPJY 

    250	Часть И. Реляционная модель 

    WHERE SPJX.Pjf = SPJY.P# AND   SPJY.Stt = 'SI' ; 
    8.11.35. SELECT DISTINCT SPJX.S# 
    FRCM  SPJ AS SPJX, SPJ AS SPJY, SPJ AS SPJZ WHERE SPJX.P# = SPJY.Ptt AND   
SPJY.S# = SPJZ.S# AND  ( SELECT P.COLOR 
    FRCM  P 
    WHERE P.P# = SPJZ.P# ) = 'Red' ; 
    8.11.36. SELECT S.S# FRCM  S 
    WHERE S.STATUS < ( SELECT S.STATUS FRCM  S WHERE S.S# = 'SI' ) ; 
    8.11.37. SELECT J.J# FRCM  J 
    WHERE J.CITY = ( SELECT MIN ( J.CITY ) FRCM  J ) ; 
    8.11.38. SELECT DISTINCT SPJX.J# FRCM  SPJ AS SPJX WHERE SPJX.Ptt = 'PI' 
AND  ( SELECT AVG ( SPJY.OTY ) 
    FRCM  SPJ AS SPJY 
    WHERE SPJY.J# = SPJX.J# 
    AND   SPJY.P* = 'PI' ) > ( SELECT MAX ( SPJZ.QTY ) 
    FRCM  SPJ AS SPJZ 
    WHERE SPJZ.J# = 'Jl1 ) ; 
    8.11.39. SELECT DISTINCT SPJX.S# 
    FRCM  SPJ AS SPJX 
    WHERE SPJX.P* = 'PI' 
    AND        SPJX.QTY >   (  SELECT AVG   (  SPJY.OTY  ) FRCM      SPJ AS 
SPJY WHERE    SPJY.P# =   'PI' AND         SPJY.Jff  = SPJX.Jtf   ) 
    8.11.40. SELECT J.J# FRCM      J 
    WHERE NOT EXISTS ( SELECT * FRCM  SPJ, P, S WHERE SPJ.J# = J.J# AND   
SPJ.Ptf = P.P# AND   SPJ.S# = S.S# 
    !ЛЬ 

    Глава 8. Язык SQL 251 

    AND   P.COLOR = 'Red' AND   S.CITY = 'London' ) ; 8.11.41. SELECT J.J# 
FROM  J 
    WHERE NOT EXISTS ( SELECT * FROM  SPJ " WHERE SPJ.J# = J.J# AND   NOT ( 
SPJ.Stt = 'SI' ) ) ; '8.11.42. SELECT P.P# 
    FROM  P 
    WHERE NOT EXISTS ( SELECT * FROM  J 
    WHERE J.CITY = 'London' AND   NOT EXISTS ( SELECT * FROM  SPJ WHERE 
SPJ.Ptt = P.P# AND   SPJ.J* = J.J# ) ) ; 
    8.11.43. SELECT S.S# FROM  S WHERE EXISTS 
    ( SELECT *' FROM  -P 
    WHERE NOT EXISTS ( SELECT * FROM  J 
    WHERE NOT EXISTS ( SELECT * FROM  SPJ WHERE SPJ.S# = S.S# AND   SPJ.PS = 
P.P# AND   SPJ.JS = J.J# ) ) ) ; 
    8.11.44. SELECT J.J# FROM  J 
    WHERE NOT EXISTS ( SELECT * FROM  SPJ AS SPJX WHERE SPJX.S* = 'SI1 AND   
NOT EXISTS ( SELECT * FROM  SPJ AS SPJY WHERE SPJY.P* = SPJX.P# AND   SPJY.J# 
= J.J# ) ) ; 

    252	Часть II. Реляционная модель 

    8.11.45. SELECT S.CITY FRCM S UNION 
    SELECT P.CITY FRCM P UNICN SELECT J.CITY FRCM J ; 
    8.11.46. SELECT DISTINCT SPJ.P* FRCM  SPJ WHERE  ( SELECT S.CITY 
    FRCM  S 
    WHERE S.St = SPJ.S# ) = 'London' OR    ( SELECT J.CITY 
    FRCM  J 
    WHERE J.Jl = SPJ.J# ) = 'London1 ; 
    8.11.47. SELECT S.S#, P.P# 
    FRCM  S CROSS JOIN P 
    EXCEPT 
    SELECT SPJ.Stt,   SPJ.P# 
    FRCM      SPJ ; 
    8.11.48. Оставляем для читателя. 
    8.12. a) INSERT INTO S ( S#, SNAME, STATUS, CITY ) 
    VALUES ( 'S10', 'Smith', DEFAULT, 'New York' ) ; 6) UPDATE P 
    SET   COLOR = 'Orange' WHERE P.COLOR = 'Red' ; B) DELETE FROM  J WHERE NOT EXISTS 
    ( SELECT * FRCM SPJ WHERE SPJ.J* = J.J# ) ; 

    8.13. Обратите внимание, что могут существовать поставщики, которые не 
обеспечивают никаких проектов совсем; в следующем решении учитываются и такие 
поставщики (как именно?). Сначала определим два курсора, cs и сз: 

    ЕХЕС SQL DECLARE CS CURSOR FOR 
    SELECT S.S#, S.SNAME, S.STATUS, S.CITY FRCM  S ORDER BY S# ; EXEC SQL 
DECLARE CJ CURSOR TOR 
    SELECT J.J#, J.JNAME, J.CITY FROM  J WHERE J.J# IN 
    ( SELECT SPJ.Jtt FRCM  SPJ 
    WHERE SPJ.S* = :CS_S# ) ORDER BY J# ; 

    Глава 8. Язык SQL	253 

    Если курсор GJ открыт, базовая переменная cs_s# будет содержать значение 
номера поставщика, получаемое через курсор cs. Процедурная логика, по 
существу, будет следующей (для некоторых пояснений приводятся комментарии, 
заключенные в символы "/*" и "*/", как и ранее в этой книге): 
    
    ЕХЕС SQL OPEN CS ; 
    DO for all S rows accessible via CS ; 
    /* выполнить для всех строк S, доступных через курсор CS */ ЕХЕС SQL FETCH 
CS INTO :CS_S#, :CS_SN, :CS_ST, :CS_SC ; print CS_S#, CS_SN, CS_ST, CS_SC ; /* 
вывести значения CS_S#, CS_SN, CS_ST, CS_SC */ EXEC SQL OPEN CJ ; 
    Ю for all J rows accessible via CJ ; /* выполнить для всех строк J, */ /* 
доступных через курсор CJ  */ ЕХЕС SQL FETCH CJ INTO :CJ_J#, :CJ_JN, :CJ_JC ; 
print CJ_J#, CJ_JN, CJ_JC ; /* вывести значения CJ_J#, CJ_JN, CJ_JC */ END ; 
    EXEC SQL CLOSE CJ ; END ; EXEC SQL CLOSE CS ; 

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

    GET LIST ( GIVENP* ) ; 
    CALL RECURSION ( GIVENP* ) ; 
    RETURN ; 
    RECURSION: PROC ( UPPER_P# ) RECURSIVE ; DCL UPPER_P# ... ; DCL LCWER_P# ...; 
    EXEC SQL DECLARE С "reopenable" CURSOR FOR SELECT MINOR_P# FROM  
PART_STRUCTURE WHERE MAJOR_P# = :UPPER_P# ; print UPPER_P# ; /* вывести 
значение UPPER_P# */ EXEC SQL OPEN С ; 
    DO for all PART_STRUCTURE rows accessible via С ; /* выполнить для всех 
строк PART_STRUCTURE, */ /* доступных через курсор С	*/ ЕХЕС SQL FETCH С INTO 
:LCWER_Pt ; CALL RECURSION ( LCWER_P# ) ; END ; 
    EXEC SQL СШЗЕ С ; END ; /* Конец рекурсии */ 

    254	Часть II. Реляционная модель   I     Гл 

    Здесь подразумевается, что фиктивная спецификация "георепаЫе" ("повторно 
открываемый") к оператору DECLARE CURSOR означает допустимость операции OPEN 
даже в том случае, если курсор уже открыт, и в результате такой операции 
создается новый экземпляр курсора для данного табличного выражения 
(использующего текущие значения любых базовых переменных, на которые есть 
ссылки в этом выражении). В дальнейшем подразумевается, что ссылки к такому 
курсору в операторе FETCH (и др.) являются ссылками к "текущему" экземпляру и 
что оператор CLOSE уничтожает именно этот экземпляр и восстанавливает 
предыдущий экземпляр как "текущий". Другими словами, подразумевается, что 
повторно открываемый курсор формирует стек, который обслуживается операторами 
OPEN и CLOSE так же, как стек обрабатывался бы операторами "push" и "pop". 
(Операторы работы со стеком "push" и "pop" применяются в языке Assembler. — 
Прим. ред.) 
    К сожалению, такие допущения на сегодняшний день чисто гипотетические. В 
SQL пока нет таких средств, как повторно открываемый курсор (в 
действительности попытка повторно открыть курсор приведет к ошибке). 
Приведенный код недопустим. Но этот пример наглядно показывает, что "повторно 
открываемые курсоры" были бы очень полезным дополнением к современному SQL. 
    Поскольку предыдущая процедура не справляется с поставленной задачей, мы 
вкратце приведем одну из возможных процедур (но не наиболее эффективную), 
которая делает это. 

    GET LIST ( GIVENP* ) ; 
    CALL RECURSION ( GIVENP* ) ; 
    RETURN ; 
    RECURSION: PRCC ( UPPER_P# ) RECURSIVE ; DCL UPPER_P# ... ; 
    DCL LOWER_P# . . . INITIAL ( '      ' ) ; EXEC SQL DECLARE С CURSOR FOR 
SELECT MINOR_P# FROM  PART_STRUCTURE WHERE MAJOR_P# = :UPPER_P# AND   MINOR_P# 
> :LCWER_P# ORDER BY MINOR_P# ; print UPPER_P# ; /* вывести значение UPPER_P# 
*/ DO "forever" ; 
    /* выполнить инструкции ниже */ EXEC SQL OPEN С ; EXEC SQL FETCH С INTO 
:LCWER_P# ; EXEC SQL CLOSE С ; 
    IF no "lower P#" retrieved THEN RETURN ; 
    IF "lower P#" retrieved THEN CALL RECURSION ( LCWER_P# ) ; /* если найден еще один уровень,	
	*/ /* то вызвать рекурсивную процедуру RECURSION 
	*/ /* с параметром LCWER_P#,                
	*/ /* иначе — выход из текущей процедуры       
	*/ END ; 
END ; /* Конец рекурсии */ 

    Глава 8. Язык SQL 255 

    Заметьте, что в этом решении тот же курсор используется в каждом вызове 
рекурсии (но новые экземпляры UPPER_P# и LOWER_P# создаются динамически каждый 
раз при вызове рекурсии; в конце вызова эти экземпляры уничтожаются.) В связи 
с этим фактом мы использовали трюк: 

    ... AND   MINOR_P# > :LOWER_P# ORDER BY MINOR_P# 

    Так что на каждом вызове рекурсии просто игнорируются промежуточные 
компоненты (LOWER_P#) текущего значения UPPER_P#, которые уже обработаны. 
    Обсуждение некоторых альтернативных подходов к этой задаче вы найдете в [8.14]. 

    256 Часть II. Реляционная модель i 
