Структуризированный язык запросов (SQL)

         

Фраза GROUP BY


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

SELECT ПР, SUM(К_во) FROM Поставки GROUP BY ПР;

Результат показан на рис. 2.3,а.

а) б) в) г)
ПР

90 11150 1230 15370 1370 3250 5170 6220 8150 7200 20 4100 13190 1470 16250 1750 10220 ПСПРЦенаК_во . . .

Рис. 2.3. Иллюстрации к фразе GROUP BY

Фраза GROUP BY (группировать по) инициирует перекомпоновку указанной во FROM таблицы по группам, каждая из которых имеет одинаковые значения в столбце, указанном в GROUP BY. В рассматриваемом примере строки таблицы Поставки группируются так, что в одной группе содержатся все строки для продукта с ПР = 1, в другой – для продукта с ПР = 2 и т.д. (см. рис. 2.3.б). Далее к каждой группе применяется фраза SELECT. Каждое выражение в этой фразе должно принимать единственное значение для группы, т.е. оно может быть либо значением столбца, указанного в GROUP BY, либо арифметическим выражением, включающим это значение, либо константой, либо одной из SQL-функций, которая оперирует всеми значениями столбца в группе и сводит эти значения к единственному значению (например, к сумме).

Отметим, что фраза GROUP BY не предполагает ORDER BY. Чтобы гарантировать упорядочение по ПР результата рассматриваемого примера (рис. 2.3,в) следует дать запрос

SELECT ПР, SUM(К_во) FROM Поставки GROUP BY ПР ORDER BY ПР;


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

SELECT Т, БЛ, COUNT(БЛ) FROM Заказ GROUP BY Т, БЛ;

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

19-0--0-
39-0--0-
59-0--0-
1111.5050
511-0--0-
611-0--0-
8111.00100
1123.0010
3122.5020
612-0--0-
1152.00170
3151.50200
213.60300
714.2070
23-0--0-
734.00250
ПР
1370
20
3250
4100
5170
6220
7200
8150
90
10220
11150
1230
13190
1470
15370
16250
1750
ПР
90
11150
1230
1570
1370
3250
570
6140
8150
7200
20
4100
13190
1470
16250
1750
10220
ТБЛCOUNT(БЛ) ... Если в запросе используются фразы WHERE и GROUP BY, то строки, не удовлетворяющие фразе WHERE, исключаются до выполнения группирования.

Например, выдать для каждого продукта его код и общий объем возможных поставок, учитывая временную недееспособность поставщика с ПС=2:

SELECT ПР, SUM(К_во) FROM Поставки WHERE ПС <> 2 GROUP BY ПР;

Результат, приведенный на рис. 2.3,г, отличается от результата (рис. 2.3,а) аналогичного запроса для всех поставщиков объемом поставок продуктов с кодами 15, 5 и 6.

2.5.2 | Содержание | 2.5.4


Функции без использования фразы GROUP BY


Если не используется фраза GROUP BY, то в перечень элементов_SELECT можно включать лишь SQL-функции или выражения, содержащие такие функции. Другими словами, нельзя иметь в списке столбцы, не являющихся аргументами SQL-функций.

Например, выдать данные о массе лука (ПР=10), проданного поставщиками, и указать количество этих поставщиков:

1318
1 6 14
1 19 17
121 15

Результат:

SELECT SUM(К_во),COUNT(К_во) FROM Поставки WHERE ПР = 10;

SUM(К_во) COUNT(К_во)

Если бы для вывода в результат еще и номера продукта был сформирован запрос

SELECT ПР,SUM(К_во),COUNT(К_во) FROM Поставки WHERE ПР = 10;

то было бы получено сообщение об ошибке. Это связано с тем, что SQL-функция создает единственное значение из множества значений столбца-аргумента, а для "свободного" столбца должно быть выдано все множество его значений. Без специального указания (оно задается фразой GROUP BY) SQL не будет выяснять, одинаковы значения этого множества (как в данном примере, где ПР=10) или различны (как было бы при отсутствии WHERE фразы). Поэтому подобный запрос отвергается системой.

Правда, никто не запрещает дать запрос

SELECT 'Кол-во лука =',SUM(К_во),COUNT(К_во) FROM Поставки WHERE ПР = 10;

2202

Результат:'Кол-во лука ='SUM(К_во)COUNT(К_во)

Отметим также, что в столбце-аргументе перед применением любой функции, кроме COUNT(*), исключаются все неопределенные значения. Если оказывается, что аргумент - пустое множество, функция COUNT принимает значение 0, а остальные - NULL.

Например, для получения суммы цен, средней цены, количества поставляемых продуктов и количества разных цен продуктов, проданных коопторгом УРОЖАЙ (ПС=5), а также для получения количества продуктов, которые могут поставляться этим коопторгом, можно дать запрос

SELECT SUM(Цена),AVG(Цена),COUNT(Цена), COUNT(DISTINCT Цена),COUNT(*) FROM Поставки WHERE ПС = 5;

и получить

Кол-во лука =2202

SUM(Цена)AVG(Цена)COUNT(Цена)COUNT(DISTINCT Цена)COUNT (*)

В другом примере, где надо узнать "Сколько поставлено моркови и сколько поставщиков ее поставляют?":

SELECT SUM(К_во),COUNT(К_во) FROM Поставки WHER ПР = 2;

будет получен ответ:

6.21.24 5 4 7

SUM(К_во)COUNT (К_во)

Наконец, попробуем получить сумму массы поставленного лука с его средней ценой ("Сапоги с яичницей"):

-0-0

Результат:

SELECT (SUM(К_во) +AVG(Цена)) FROM Поставки WHERE ПР = 10;

SUM(К_во)+AVG(Цена)

2.5.1 | Содержание | 2.5.3



Исключение дубликатов


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

SELECT DISTINCT Основа FROM Блюда;

Результат приведен на рис. 2.1,в.

2.1 | Содержание | 2.2.3



Использование BETWEEN


С помощью BETWEEN ... AND ... (находится в интервале от ... до ...) можно отобрать строки, в которых значение какого-либо столбца находятся в заданном диапазоне.

Например, выдать перечень продуктов, в которых значение содержания белка находится в диапазоне от 10 до 50:

220.6

Результат:

SELECT Продукт, Белки FROM Продукты WHERE Белки BETWEEN 10 AND 50;

Продукт Белки

Можно задать и NOT BETWEEN (не принадлежит диапазону между), например:

Майонез31.
Сметана26.
Молоко28.
Морковь13.
Лук17.

Результат:

SELECT Продукт, Белки, Жиры FROM Продукты WHERE Белки NOT BETWEEN 10 AND 50 AND Жиры > 100;

Продукт Белки Жиры

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

Для примера воспользуемся таблицей "минимальных окладов" (табл. 2.1), величина которых непосредственно связана со студенческой стипендией. В этой таблице для текущего значения минимального оклада установлена запредельная дата окончания 9 сентября 9999 года.

Таблица 2.1


Минимальные оклады

Говядина189.124.
Масло 60.825.
Яйца127.115.

МинокладНачалоКонец

Если, например, потребовалось узнать, какие изменения минимальных окладов производились в 1993/94 учебном году, то можно выдать запрос

SELECT Начало, Миноклад FROM Миноклады WHERE Начало BETWEEN '1-9-1993' AND '31-8-1994'

и получить результат:

225001-01-199331-03-1993
427501-04-199330-06-1993
774001-07-199330-11-1993
1462001-12-199330-06-1994
2050001-07-199409-09-9999

НачалоМиноклад

Отметим, что при формировании запросов значения дат следует заключать в апострофы, чтобы СУБД не путала их с выражениями и не пыталась вычитать из 31 значение 8, а затем 1994.

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

SELECT * FROM Миноклады WHERE Начало BETWEEN '1-9-1993' AND '31-8-1994' OR Конец BETWEEN '1-9-1993' AND '31-8-1994'

01-12-199314620
01-07-199420500

МинокладНачалоКонец

Наконец, для получения минимального оклада на 15-5-1994:

774001/07/199330/11/1993
1462001/12/199330/06/1994
2050001/07/199409/09/9999

Результат:

SELECT Миноклад FROM Миноклады WHERE '15-05-1994' BETWEEN Начало AND Конец

Миноклад

2.3.1 | Содержание | 2.3.3



Использование фразы HAVING


Фраза HAVING (рис.2.3) играет такую же роль для групп, что и фраза WHERE для строк: она используется для исключения групп, точно так же, как WHERE используется для исключения строк. Эта фраза включается в предложение лишь при наличии фразы GROUP BY, а выражение в HAVING должно принимать единственное значение для группы.

Например, выдать коды продуктов, поставляемых более чем двумя поставщиками:

14620

SELECT FROM Поставки GROUP BY ПС HAVING COUNT(*) > 2;

Результат:

В п.3.6 можно познакомиться с более содержательным примером использования этой фразы.

2.5.3 | Содержание | 3.1



Использование IN


Выдать сведения о блюдах на основе яиц, крупы и овощей

SELECT * FROM Блюда WHERE Основа IN (Яйца Крупа Овощи);

Результат:

ПР
9
11
12

БЛБлюдоВОснова Выход Труд

Рассмотренная форма IN является в действительности просто краткой записью последовательности отдельных сравнений, соединенных операторами OR. Предыдущее предложение эквивалентно такому:

SELECT * FROM Блюда WHERE Основа=Яйца OR Основа=Крупа OR Основа=Овощи;

Можно задать и NOT IN (не принадлежит), а также возможность использования IN (NOT IN) с подзапросом (см. главу 3).

2.3.2 | Содержание | 2.3.4



Использование LIKE


Выдать перечень салатов

1Салат летнийЗОвощи200.3
3Салат витаминныйЗОвощи200.4
16ДраченаГЯйца180.4
17Морковь с рисомГОвощи260.3
19Омлет с лукомГЯйца200.5
20Каша рисоваяГКрупа210.4
21Пудинг рисовыйГКрупа160.6
23Помидоры с лукомГОвощи260.4

Результат:

SELECT Блюдо FROM Блюда WHERE Блюдо LIKE 'Салат%';

Блюдо

Обычная форма "имя_столбца LIKE текстовая_константа" для столбца текстового типа позволяет отыскать все значения указанного столбца, соответствующие образцу, заданному "текстовой_константой". Символы этой константы интерпретируются следующим образом:

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

Следовательно, в приведенном примере SELECT будет осуществлять выборку записей из таблицы Блюда, для которых значение в столбце Блюдо начинается сочетанием 'Салат' и содержит любую последовательность из нуля или более символов, следующих за сочетанием 'Салат'. Если бы среди блюд были "Луковый салат", "Фруктовый салат" и т.п., то они не были бы найдены. Для их отыскания надо изменить фразу WHERE:

WHERE Блюдо LIKE '%салат%'

или при отсутствии различий между малыми и большими буквами (такую настройку допускают некоторые СУБД):

WHERE Блюдо LIKE '%Салат%'

Это позволит отыскать все салаты.

2.3.3 | Содержание | 2.3.5



Использование операторов сравнения


В синтаксисе фразы WHERE (п.2.1) показано, что для отбора нужных строк таблицы можно использовать операторы сравнения = (равно), <> (не равно), < (меньше), <= (меньше или равно), > (больше), >= (больше или равно), которые могут предваряться оператором NOT, создавая, например, отношения "не меньше" и "не больше".

Так, для получения перечня продуктов, практически не содержащих углеводов, можно сформировать запрос

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C FROM Продукты WHERE Углев = 0;

и получить:

Салат летний
Салат мясной
Салат витаминный
Салат рыбный

ПродуктБелкиЖирыУглевKCaNaB2PPC

Возможность использования нескольких условий, соединенных логическими операторами AND, OR, AND NOT и OR NOT, позволяет осуществить более детальный отбор строк. Так, для получения перечня продуктов, практически не содержащих углеводов и натрия, можно сформировать запрос:

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C FROM Продукты WHERE Углев = 0 AND Na = 0;

Результат запроса имеет вид

Говядина189.124.0.3150906001.528.0
Судак190.80.0.1870270 01.110.30

ПродуктБелкиЖирыУглевKCaNaB2PPC

Добавим к этому запросу еще одно условие

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C FROM Продукты WHERE Углев = 0 AND Na = 0 AND Продукт <> 'Судак';

и получим на экране сообщение "No rows exist or satisfy the specified clause" или аналогичное (в зависимости от вкусов разработчиков разных СУБД), информирующее об отсутствии строк, удовлетворяющих заданному(ым) условию(ям).

2.2.3 | Содержание | 2.3.2



О предложении SELECT


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

Предложение SELECT может использоваться как:

самостоятельная команда на получение и вывод строк таблицы, сформированной из столбцов и строк одной или нескольких таблиц (представлений); элемент WHERE- или HAVING-условия (сокращенный вариант предложения, называемый "вложенный запрос"); фраза выбора в командах CREAT VIEW, DECLARE CURSOR или INSERT; средство присвоения глобальным переменным значений из строк сформированной таблицы (INTO-фраза).

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

звездочка (*) для обозначения "все" - употребляется в обычном для программирования смысле, т.е. "все случаи, удовлетворяющие определению"; квадратные скобки ([]) – означают, что конструкции, заключенные в эти скобки, являются необязательными (т.е. могут быть опущены); фигурные скобки ({}) – означают, что конструкции, заключенные в эти скобки, должны рассматриваться как целые синтаксические единицы, т.е. они позволяют уточнить порядок разбора синтаксических конструкций, заменяя обычные скобки, используемые в синтаксисе SQL; многоточие (...) – указывает на то, что непосредственно предшествующая ему синтаксическая единица факультативно может повторяться один или более раз; прямая черта (|) – означает наличие выбора из двух или более возможностей. Например обозначение ASC|DESC указывает, можно выбрать один из терминов ASC или DESC; когда же один из элементов выбора заключен в квадратные скобки, то это означает, что он выбирается по умолчанию (так, [ASC]|DESC означает, что отсутствие всей этой конструкции будет восприниматься как выбор ASC); точка с запятой (;) – завершающий элемент предложений SQL; запятая (,) – используется для разделения элементов списков; пробелы ( ) – могут вводиться для повышения наглядности между любыми синтаксическими конструкциями предложений SQL; прописные жирные латинские буквы и символы – используются для написания конструкций языка SQL и должны (если это специально не оговорено) записываться в точности так, как показано; строчные буквы – используются для написания конструкций, которые должны заменяться конкретными значениями, выбранными пользователем, причем для определенности отдельные слова этих конструкций связываются между собой символом подчеркивания (_); термины таблица, столбец, ... – заменяют (с целью сокращения текста синтаксических конструкций) термины имя_таблицы, имя_столбца, ..., соответственно; термин таблица – используется для обобщения таких видов таблиц, как базовая_таблица, представление или псевдоним; здесь псевдоним служит для временного (на момент выполнения запроса) переименования и (или) создания рабочей копии базовой_таблицы (представления).


Предложение SELECT (выбрать) имеет следующий формат:

подзапрос [UNION [ALL] подзапрос] ... [ORDER BY {[таблица.]столбец | номер_элемента_SELECT} [[ASC] | DESC] [,{[таблица.]столбец | номер_элемента_SELECT} [[ASC] | DESC]] ...;

и позволяет объединить (UNION) а затем упорядочить ( ORDER BY) результаты выбора данных, полученных с помощью нескольких "подзапросов". При этом упорядочение можно производить в порядке возрастания - ASC (ASCending) или убывания DESC (DESCending), а по умолчанию принимается ASC.

В этом предложении подзапрос позволяет указать условия для выбора нужных данных и (если требуется) их обработки

SELECT (выбрать) данные из указанных столбцов и (если необходимо) выполнить перед выводом их преобразование в соответствии с указанными выражениями и (или) функциями FROM (из) перечисленных таблиц, в которых расположены эти столбцы WHERE (где) строки из указанных таблиц должны удовлетворять указанному перечню условий отбора строк GROUP BY (группируя по) указанному перечню столбцов с тем, чтобы получить для каждой группы единственное агрегированное значение, используя во фразе SELECT SQL-функции SUM (сумма), COUNT (количество), MIN (минимальное значение), MAX (максимальное значение) или AVG (среднее значение) HAVING (имея) в результате лишь те группы, которые удовлетворяют указанному перечню условий отбора групп

и имеет формат

SELECT [[ALL] | DISTINCT]{ * | элемент_SELECT [,элемент_SELECT] ...} FROM {базовая_таблица | представление} [псевдоним] [,{базовая_таблица | представление} [псевдоним]] ... [WHERE фраза] [GROUP BY фраза [HAVING фраза]];

Элемент_SELECT - это одна из следующих конструкций:

[таблица.]* | значение | SQL_функция | системная_переменная

где значение – это:

[таблица.]столбец | (выражение) | константа | переменная

Синтаксис выражений имеет вид

( {[ [+] | - ] {значение | функция_СУБД} [ + | - | * | ** ]}... )

а синтаксис SQL_функций – одна из следующих конструкций:

{SUM|AVG|MIN|MAX|COUNT} ( [[ALL]|DISTINCT][таблица.]столбец )

{SUM|AVG|MIN|MAX|COUNT} ( [ALL] выражение )



COUNT(*)

Фраза WHERE включает набор условий для отбора строк:

WHERE [NOT] WHERE_условие [[AND|OR][NOT] WHERE_условие]...

где WHERE_условие – одна из следующих конструкций:

значение { = | <> | < | | >= } { значение | ( подзапрос ) }

значение_1 [NOT] BETWEEN значение_2 AND значение_3

значение [NOT] IN { ( константа [,константа]... ) | ( подзапрос ) }

значение IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов' [ESCAPE 'символ']

EXISTS ( подзапрос )

Кроме традиционных операторов сравнения (= | <> | < | <= | > | >=) в WHERE фразе используются условия BETWEEN (между), LIKE (похоже на), IN (принадлежит), IS NULL (не определено) и EXISTS (существует), которые могут предваряться оператором NOT (не). Критерий отбора строк формируется из одного или нескольких условий, соединенных логическими операторами:

AND - когда должны удовлетворяться оба разделяемых с помощью AND условия; OR - когда должно удовлетворяться одно из разделяемых с помощью OR условий; AND NOT - когда должно удовлетворяться первое условие и не должно второе; OR NOT - когда или должно удовлетворяться первое условие или не должно удовлетворяться второе,

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

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

Наконец, синтаксис фразы GROUP BY имеет вид

GROUP BY [таблица.]столбец [,[таблица.]столбец] ... [HAVING фраза]

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

С помощью фразы HAVING (синтаксис которой почти не отличается от синтаксиса фразы WHERE)

HAVING [NOT] HAVING_условие [[AND|OR][NOT] HAVING_условие]...

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

значение { = | <> | < | <= | > | >= } { значение | ( подзапрос ) | SQL_функция }

{значение_1 | SQL_функция_1} [NOT] BETWEEN {значение_2 | SQL_функция_2} AND {значение_3 | SQL_функция_3}

{значение | SQL_функция} [NOT] IN { ( константа [,константа]... ) | ( подзапрос ) }

{значение | SQL_функция} IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов' [ESCAPE 'символ']

EXISTS ( подзапрос )

1.3 | Содержание | 2.2.1


Простая выборка


Запрос выдать название, статус и адрес поставщиков

SELECT Название, Статус, Адрес FROM Поставщики;

дает результат, приведенный на рис. 2.1,а.

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

SELECT ПС, Название, Статус, Город, Адрес, Телефон FROM Поставщики;

или использовать его более короткую нотацию:

SELECT * FROM Поставщики;

Здесь "звездочка" (*) служит кратким обозначением всех имен полей в таблице, указанной во фразе FROM. При этом порядок вывода полей соответствует порядку, в котором эти поля определялись при создании таблицы.

Еще один пример. Выдать основу всех блюд:

SELECT Основа FROM Блюда;

дает результат, показанный на рис. 2.1,б.

Судак190.80.0.187027001.110.30

а)б)в)
НазваниеСтатусАдрес

СЫТНЫЙрынокСытнинская, 3 ПОРТОСкооперативСадовая, 27 ШУШАРЫсовхозНовая, 17 ТУЛЬСКИЙуниверсамТульская, 3 УРОЖАЙкоопторгПесчаная, 19 ЛЕТОагрофирмаПулковское ш.,8 ОГУРЕЧИКфермаУкмерге, 15 КОРЮШКАкооперативНарвское ш., 64 Основа

Рис. 2.1. Примеры простой выборки



SQL-функции


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

COUNT - число значений в столбце, SUM - сумма значений в столбце, AVG - среднее значение в столбце, MAX - самое большое значение в столбце, MIN - самое малое значение в столбце.

Для функций SUM и AVG рассматриваемый столбец должен содержать числовые значения.

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

Аргументу всех функций, кроме COUNT(*), может предшествовать ключевое слово DISTINCT (различный), указывающее, что избыточные дублирующие значения должны быть исключены перед тем, как будет применяться функция. Специальная же функция COUNT(*) служит для подсчета всех без исключения строк в таблице (включая дубликаты).

2.4 | Содержание | 2.5.2



Вовлечение неопределенного значения (NULL-значения)


Как было рассказано в п.2.2.3, если при загрузке данных не введено значение в какое-либо поле таблицы, то СУБД поместит в него NULL-значение. Аналогичное значение можно ввести в поле таблицы, выполняя операцию изменения данных. Так, при отсутствии сведений о наличии у поставщиков судака и моркови в столбцы Цена и К_во соответствующих строк таблицы Поставки вводится NULL и там будет храниться код NULL-значения, а не 0, 0. или пробел. (Отметим, что в распечатке таблицы Поставки рис.1.1 в этих местах расположен пробел, установленный в СУБД для представления NULL-значения при выводе на печать).

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

Овощи
Мясо
Овощи
Рыба
Рыба
Мясо
Молоко
Молоко
...
Кофе
Основа
Кофе
Крупа
Молоко
Мясо
Овощи
Рыба
Фрукты
Яйца

Результат: ПР

Естественно, что для выявления продуктов, существующих в кладовой, следует дать запрос

SELECT DISTINCT ПР FROM Наличие WHERE К_во IS NOT NULL;

Использование условий

столбец IS NULL и столбец IS NOT NULL

вместо, например,

столбец = NULL и столбец <> NULL

связано с тем, что ничто - и даже само NULL-значение - не считается равным другому NULL-значению. (Несмотря на это, два неопределенных значения рассматриваются, однако, как дубликаты друг друга при исключении дубликатов, и предложение SELECT DISTINCT даст в результате не более одного NULL-значения.)

2.3.4 | Содержание | 2.4



Выборка с упорядочением


Синтаксис фразы упорядочения был дан в п. 2.1. Простейший вариант этой фразы - упорядочение строк результата по значению одного из столбцов с указанием порядка сортировки или без такого указания. (По умолчанию строки будут сортироваться в порядке возрастания значений в указанном столбце.)

Например, выдать перечень продуктов и содержание в них основных веществ в порядке убывания содержания белка

SELECT DISTINCT ПР FROM Наличие WHERE К_во IS NULL;
2
9

Результат:

SELECT Продукт, Белки, Жиры, Углев FROM Продукты ORDER BY Белки DESC;

ПродуктБелкиЖирыУглев . . .

При включении в список ORDER BY нескольких столбцов СУБД сортирует строки результата по значениям первого столбца списка пока не появится несколько строк с одинаковыми значениями данных в этом столбце. Такие строки сортируются по значениям следующего столбца из списка ORDER BY и т.д.

Например, выдать содержимое таблицы Блюда, отсортировав ее строки по видам блюд и основе:

Судак190.80.0.
Говядина189.124.0.
Творог167.90.13.
Яйца127.115.7.
Кофе127.36.9.
Мука106.13.732.

Результат:

SELECT * FROM Блюда ORDER BY В Основа;

БЛБлюдоВОснова Выход Труд . . . . . . . . .

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

Например, запрос

SELECT Продукт, ((Белки+Углев)*4.1+Жиры*9.3) FROM Продукты ORDER BY 2;

позволит получить список продуктов, показанный на рис.2.2,в – переупорядоченный по возрастанию значений калорийности список рис.2.2,а.

2.3.5 | Содержание | 2.5.1



Выборка вычисляемых значений


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

Например, если нужно получить значение калорийности всех продуктов, то можно учесть, что при окислении 1 г углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г жиров - 9.3 ккал, и выдать запрос:

SELECT Продукт, ((Белки+Углев)*4.1+Жиры*9.3) FROM Продукты;

результат которого приведен на рис. 2.2,а.

21Пудинг рисовыйГКрупа160.6
20Каша рисоваяГКрупа210.4
18СырникиГМолоко220.4
16ДраченаГЯйца180.4
28Крем творожныйДМолоко160.4
26Яблоки печеныеДФрукты160.3
7СметанаЗМолоко140.1
8ТворогЗМолоко140.2
2Салат мяснойЗМясо200.4
6Мясо с гарниромЗМясо250.3
1Салат летнийЗОвощи200.3

а)б)в)

Продукт

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

3.3.3 | Содержание | 3.3.5



Вставка единственной записи в таблицу


Добавить в таблицу Блюда блюдо:

Шашлык (БЛ - 34, Блюдо - Шашлык, В - Г, Основа - Мясо, Выход - 150)

при неизвестной пока трудоемкости приготовления этого блюда.

INSERT INTO Блюда (БЛ, Блюдо, В, Основа, Выход) VALUES (34, 'Шашлык', 'Г', 'Мясо', 150);

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

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

INSERT INTO Блюда (Основа, В, Блюдо, БЛ, Выход) VALUES ('Мясо', 'Г', 'Шашлык', 34, 150);

При известной трудоемкости приготовления шашлыка (например, 5 коп) сведения о нем можно ввести с помощью укороченного предложения:

INSERT INTO Блюда VALUES (34, 'Шашлык', 'Г', 'Мясо', 150, 5);

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

В предыдущих примерах проводилась модификация стержневой сущности, т.е. таблицы с первичным ключом БЛ (см.п.2.4 в литературе [2]). Почти все СУБД имеют механизмы для предотвращения ввода не уникального первичного ключа, например, ввода "Шашлыка" под номером, меньшим 34. А как быть с ассоциациями или другими таблицами, содержащими внешние ключи?

Пусть, например, потребовалось добавить в рецепт блюда Салат летний (БЛ = 1) немного (15 г) лука (ПР = 10), и мы воспользовались предложением

INSERT INTO Состав (БЛ, ПР, Вес) VALUES (1, 10, 15);

Подобно операции DELETE операция INSERT может нарушить непротиворечивость базы данных. Если не принять специальных мер, то СУБД не проверяет, имеется ли в таблице Блюда блюдо с первичным ключом БЛ = 1 и в таблице Продукты - продукт с первичным ключом ПР = 10. Отсутствие любого из этих значений породит противоречие: в базе появится ссылка на несуществующую запись. Проблемы, возникающие при использовании внешних ключей, подробно рассмотрены в литературе [2], а здесь отме-тим, что все "приличные" СУБД имеют механизмы для предотв-ращения ввода записей со значениями внешних ключей, отсутст-вующих среди значений соответствующих первичных ключей.

4.2 | Содержание | 4.3.2



Вставка множества записей


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

Для создания описания временной таблицы можно, например, воспользоваться предложением CREATE TABLE (см.п.5.2)

CREATE TABLE К_меню ( Вид CHAR (10), Блюдо CHAR (60), Калор_блюда INTEGER, Стоим_блюда REAL);

а для ее загрузки данными – предложение INSERT с вложенным подзапросами:

INSERT INTO К_меню SELECT Вид, Блюдо, INT(SUM(((Белки+Углев)*4.1+Жиры*9.3) * Вес/1000)), (SUM(Стоимость/К_во*Вес/1000) + MIN(Труд/100)) FROM Блюда, Вид_блюд, Состав, Продукты, Наличие WHERE Блюда.БЛ = Состав.БЛ AND Состав.ПР = Продукты.ПР AND Состав.ПР = Наличие.ПР AND Блюда.В = Вид_блюд.В AND БЛ NOT IN ( SELECT БЛ FROM Состав WHERE ПР IN ( SELECT ПР FROM Наличие WHERE К_во = 0)) GROUP BY Вид, Блюдо ORDER BY Вид, 3;

В этом запросе предложение SELECT выполняется так же, как обычно (см. описание запроса в п.3.6), но результат не выводится на экран, а копируется в таблицу К_меню. Теперь с этой копией можно работать как с обычной базовой таблицей (Блюда, Про-дукты,...), т.е. выбирать из нее даннные на экран или принтер, обновлять в ней данные и т.п. Никакая из этих операций не будет оказывать влияния на исходные данные (например, изменение в ней названия блюда Салат летний на Салат весенний не приведет к подобному изменению в таблице Блюда, где сохранится старое название). Так как это может привести к противоречиям, то подобные временные таблицы уничтожают после их использования. Поэтому программа, обслуживающая шеф-повара, должна исполнять предложение DROP TABLE К_меню после того, как будет закончено составление меню.

4.3.1 | Содержание | 4.3.3



Запросы, использующие EXISTS


Квантор EXISTS (существует) - понятие, заимствованное из формальной логики. В языке SQL предикат с квантором существования представляется выражением EXISTS (SELECT * FROM ...).

Такое выражение считается истинным только тогда, когда результат вычисления "SELECT * FROM ..." является непустым множеством, т.е. когда существует какая-либо запись в таблице, указанной во фразе FROM подзапроса, которая удовлетворяет условию WHERE подзапроса. (Практически этот подзапрос всегда будет коррелированным множеством.)

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

Говядина1928.1
Судак1523.
Масло8287.5
Майонез6464.7
Яйца1618.9
Сметана3011.4
Молоко605.1
Творог1575.
Морковь349.6
Лук459.2
Помидоры196.8
Зелень118.9
Рис3512.1
Мука3556.7
Яблоки479.7
Сахар4091.8
Кофе892.4
Продукт

Как уже отмечалось в п.3.3.1, при обработке полного запроса система выполняет прежде всего вложенный подзапрос. Этот подзапрос выдает множество номеров поставщиков, которые поставляют продукт с кодом ПР = 11, а именно множество (1, 5, 6, 8). Поэтому первоначальный запрос эквивалентен такому простому запросу:

SELECT Название, Статус FROM Поставщики WHERE ПС IN (1, 5, 6, 8);

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

SELECT Название, Статус FROM Поставщики WHERE ПС IN ( SELECT ПС FROM Поставки WHERE ПР IN ( SELECT ПР FROM Продукты WHERE Продукт = 'Помидоры' ));

В данном случае результатом самого внутреннего подзапроса является только одно значение (11). Как уже было показано выше, подзапрос следующего уровня в свою очередь дает в результате множество (1, 5, 6, 8). Последний, самый внешний SELECT, вычисляет приведенный выше окончательный результат. Вообще допускается любая глубина вложенности подзапросов.

Тот же результат можно получить с помощью соединения

SELECT Название, Статус FROM Поставщики, Поставки, Продукты WHERE Поставщики.ПС = Поставки.ПС AND Поставки.ПР = Продукты.ПР AND Продукт = 'Помидоры';

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

3.3.1 | Содержание | 3.3.3



Реализация операций реляционной алгебры предложением SELECT


С помощью предложения SELECT можно реализовать любую операцию реляционной алгебры [2].

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

SELECT * FROM Блюда WHER Основа = 'Молоко' AND Выход > 200;

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

SELECT DISTINCT Блюдо, Выход, Основа FROM Блюда;

Объединение двух таблиц содержит те строки, которые есть либо в первой, либо во второй, либо в обеих таблицах. Пример:

SELECT Блюдо, Основа, Выход FROM Блюда WHER Основа = 'Овощи' UNION SELECT Блюдо, Основа, Выход FROM Блюда WHER В = 'Г';

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

SELECT БЛ FROM Состав WHERE БЛ IN ( SELECT БЛ FROM Меню);

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

SELECT БЛ FROM Состав WHERE БЛ NOT IN ( SELECT БЛ FROM Меню);

Декартово произведение таблиц и различные виды соединений были подробно рассмотрены в п. 3.2.1-3.2.6.

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

3.4 | Содержание | 3.6



с возможностями предложения SELECT показало,


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

есть все составляющие их продукты; калорийность не превышает 400 ккал; стоимость не превышает 1.5 рубля, а результат надо упорядочить по возрастанию калорийности блюд в рамках их видов.
Для этого можно дать запрос, показанный на рис. 3.2, позволяющий получить искомый результат в виде таблицы
ГовядинаКалорий =1928.1
СудакКалорий =1523.
МаслоКалорий =8287.5
МайонезКалорий =6464.7
ЯйцаКалорий =1618.9
СметанаКалорий =3011.4
МолокоКалорий =605.1
ТворогКалорий =1575.
МорковьКалорий =349.6
Лук Калорий =459.2
ПомидорыКалорий =196.8
ЗеленьКалорий =118.9
Рис Калорий =3512.1
МукаКалорий =3556.7
ЯблокиКалорий =479.7
СахарКалорий =4091.8
КофеКалорий =892.4
Продукт

Рис. 2.2. Примеры запросов с вычисляемыми полями

Фраза SELECT может включать не только выражения, но и отдельные числовые или текстовые константы. Следует отметить, что текстовые константы должны заключаться в апострофы ('). На рис. 2.2,б приведен результат запроса:

SELECT Продукт, 'Калорий =', ((Белки+Углев)*4.1+Жиры *9.3) FROM Продукты;

А что произойдет, если какой-либо член выражения не определен, т.е. имеет значение NULL и каким образом появилось такое значение?

Если при загрузке строк таблицы в какой-либо из вводимых строк отсутствует значение для какого-либо столбца, то СУБД введет в такое поле NULL-значение. NULL-значение "придумано" для того, чтобы представить единым образом "неизвестные значения" для любых типов данных. Действительно, так как при вводе данных в столбец или их изменении СУБД запрещает ввод значений не соответствующих описанию данных этого столбца, то, например, нельзя использовать пробел для отсутствующего значения числа. Нельзя для этих целей использовать и ноль: нет месяца или дня недели равного нулю, да и для чисел ноль не может рассматриваться как неизвестное значение в одном месте и как известное - в другом. При выводе же NULL-значения на экран или печатающее устройство его код воспроизводится каким-либо специально заданным символом или набором символов: например, пробелом (если его нельзя перепутать с текстовым значением пробела) или сочетанием -0-.


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

Например, при выполнении запроса

SELECT ПР, Цена, К_во, (Цена * К_во) FROM Поставки;

и разных "настройках" СУБД могут быть получены разные результаты:

Зелень118.9
Помидоры196.8
Морковь349.6
Лук459.2
Яблоки479.7
Молоко605.1
Кофе892.4
Судак1523.
Творог1575.
Яйца1618.9
Говядина 1928.1
Сметана3011.4
Рис3512.1
Мука3556.7
Сахар4091.8
Майонез6464.7
Масло8287.5
ПРЦенаК_во(Цена*К_во)

Отметим, что ссылка на Поставки во вложенном подзапросе означает не то же самое, что ссылка на Поставки во внешнем запросе. В действительности, два имени Поставки обозначают различные значения. Чтобы этот факт стал явным, полезно использовать псевдонимы, например, X и Y:

SELECT DISTINCT X.ПС FROM Поставки X WHERE X.ПР IN ( SELECT Y.ПР FROM Поставки Y WHERE Y.ПС = 6 );

Здесь X и Y – произвольные псевдонимы таблицы Поставки, определяемые во фразе FROM и используемые как явные уточнители во фразах SELECT и WHERE. Напомним, что псевдонимы определены лишь в пределах одного запроса.

3.3.2 | Содержание | 3.3.4



Эквисоединение таблиц


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

9-0--0--0-
111.55075.
123.1030.
152.170340.
ПРЦенаК_во(Цена*К_во) 2.2.2 | Содержание | 2.3.1


О конструировании предложений модификации


Для тех, кто достаточно хорошо понял предложение SELECT, несложно овладеть конструированием предложений DELETE, INSERT и UPDATE. Но в процессе такого конструирования следует учитывать, что:

Если в WHERE фразе предложений DELETE и UPDATE используется вложенный подзапрос, то во фразе FROM этого подзапроса не должна упоминаться таблица, из которой удаляются (в которой обновляются) строки. Аналогично, в подзапросе предложения INSERT не должна упоминаться таблица, в которую загружаются данные.

Так, SQL отвергнет предложение

INSERT INTO Выбрано SELECT (33), Т, БЛ FROM Выбрано WHERE СМ = 17;

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

DELETE FROM Выбор;

INSERT INTO Выбор (СМ, Т, БЛ) SELECT (33), Т, БЛ FROM Выбрано WHERE СМ = 17;

INSERT INTO Выбрано SELECT СМ, Т, БЛ FROM Выбор;

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

4.4 | Содержание | 5.1



Декартово произведение таблиц


В литературе [2] показано, что соединения - это подмножества декартова произведения. Так как декартово произведение n таблиц - это таблица, содержащая все возможные строки r, такие, что r является сцеплением какой-либо строки из первой таблицы, строки из второй таблицы, ... и строки из n-й таблицы (а мы уже научились выделять с помощью SELECT любое подмножество реляционной таблицы), то осталось лишь выяснить, можно ли с помощью SELECT получить декартово произведение. Для получения декартова произведения нескольких таблиц надо указать во фразе FROM перечень перемножаемых таблиц, а во фразе SELECT – все их столбцы.

Так, для получения декартова произведения Вид_блюд и Трапезы надо выдать запрос

SELECT Вид_блюд.*, Трапезы.* FROM Вид_блюд, Трапезы;

Получим таблицу, содержащую 5 х 3 = 15 строк:

9-0--0-0.
111.55075.
123.1030.
152.170340.

ВВидТТрапеза

В другом примере, где перемножаются таблицы Меню, Трапезы, Вид_блюд, Блюда:

SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.* FROM Меню, Трапезы, Вид_блюд, Блюда;

образуется таблица (рис 3.1), содержащая 21 х 3 х 5 х 33 = 10395 строк.

Из первых 39 строк этой таблицы лишь две актуальных (отмечены "*"): в них совпадают номера блюд таблиц Меню и Блюда. В остальных – полная чепуха: к закускам отнесены супы и напитки, на завтрак предлагается незапланированный суп и т.д.

3.1 | Содержание | 3.2.2



Для чего нужны представления


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

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

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

5.4.3 | Содержание | 6.1



Естественное соединение таблиц


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

SELECT Т, В, БЛ, Трапеза, Вид, Блюдо, Основа, Выход, Труд FROM Меню, Трапезы, Вид_блюд, Блюда WHERE Меню.Т = Трапезы.Т AND Меню.В = Вид_блюд.В AND Меню.БЛ = Блюда.БЛ;

Реализация естественного соединения таблиц имеет вид

ЗЗакуска1Завтрак
ЗЗакуска2Обед
ЗЗакуска3Ужин
ССуп1Завтрак
ССуп2Обед
ССуп3Ужин
ГГорячее1Завтрак
ГГорячее2Обед
ГГорячее3Ужин
ДДесерт1Завтрак
ДДесерт2Обед
ДДесерт3Ужин
ННапиток1Завтрак
ННапиток2Обед
ННапиток3Ужин

ТВБЛТрапезаВидБлюдоОсноваВыходТруд ...

3.2.2 | Содержание | 3.2.4



Функции в подзапросе


Теперь, после знакомства с различными формулировками вложенных подзапросов и псевдонимами легче понять текст и алгоритм реализации запроса (п. 3.1) на получение тех поставщиков продуктов для Сырников, которые поставляют эти продукты за минимальную цену:

SELECT Продукт, Цена, Название, Статус FROM Продукты, Состав, Блюда, Поставки, Поставщики WHERE Продукты.ПР = Состав.ПР AND Состав.БЛ = Блюда.БЛ AND Поставки.ПР = Состав.ПР AND Поставки.ПС = Поставщики.ПС AND Блюдо = 'Сырники' AND Цена = ( SELECT MIN(Цена) FROM Поставки X WHERE X.ПР = Поставки.ПР );

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

На этом примере мы закончим знакомство с вложенными подзапросами, предложив попробовать свои силы в составлении ряда запросов, с помощью механизма таких подзапросов:

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

3.3.6 | Содержание | 3.4



Использование INSERT...SELECT для построения внешнего соединения


Рассмотренное в п.3.2.3 естественное соединение двух таблиц не включает тех строк какой-либо из них, для которых нет соответствующих строк в другой таблице. Например, если в таблицу Блюда были занесены под номером 34 сведения о Шашлыке, а рецепт его приготовления не был занесен в таблицу Рецепты, то при загрузке их естественного соединения в таблицу Временная:

CREATE TABLE Временная ( Вид CHAR (8), Блюдо CHAR (60), Рецепт CHAR (560));

INSERT INTO Временная SELECT Вид, Блюдо, Рецепт FROM Блюда, Рецепты, Вид_блюд WHERE Блюда.БЛ = Рецепты.БЛ AND Блюда.В = Вид_блюд.В;

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

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

INSERT INTO Временная SELECT Вид, Блюдо, Рецепт FROM Блюда, Рецепты, Вид_блюд WHERE Блюда.БЛ = Рецепты.БЛ AND Блюда.В = Вид_блюд.В;

INSERT INTO Временная SELECT Вид, Блюдо, “???” FROM Блюда, Вид_блюд WHERE Блюда.В = Вид_блюд.В AND БЛ NOT IN ( SELECT БЛ FROM Рецепты);

В результате будет создана базовая таблица

1З3ЗавтракЗакускаСалат витаминныйОвощи200.4
1З6ЗавтракЗакускаМясо с гарниромМясо250.3
1Г19ЗавтракГорячееОмлет с лукомЯйца200.5
3Г16УжинГорячееДраченаЯйца180.4
3Н30УжинНапитокКомпотФрукты200.2
3Н31УжинНапитокМолочный напитокМолоко200.2

Вид БлюдоРецепт . . .

где первые 33 строки соответствуют первому INSERT и представляют собой проекцию естественного соединения таблиц Блюда и Рецепты по кодам блюд (БЛ), включающую три столбца. Последняя строка результата соответствует второму INSERT и сохраняет информацию о блюде Шашлык, рецепт котого пока не введен в таблицу Рецепты.

Заметим, что для внешнего соединения нужны два отдельных INSERT...SELECT. Однако тот же результат можно получить и одним INSERT...SELECT, используя фразу UNION, объединяющую предложения SELECT из двух INSERT:

INSERT INTO Временная SELECT Вид, Блюдо, Рецепт FROM Блюда, Рецепты, Вид_блюд WHERE Блюда.БЛ = Рецепты.БЛ AND Блюда.В = Вид_блюд.В UNION SELECT Вид, Блюдо, “???” FROM Блюда, Вид_блюд WHERE Блюда.В = Вид_блюд.В AND БЛ NOT IN ( SELECT БЛ FROM Рецепты);

4.3.2 | Содержание | 4.4



Использование одной и той же таблицы во внешнем и вложенном подзапросе


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

ЗакускаСалат летнийПомидоры и яблоки нарезать...
Закуска Салат мясной Вареное охлажденное мясо, ...
Напиток Кофе черный Кофеварку или кастрюлю спо...
Напиток Кофе на молоке Сварить черный кофе, как ...
Горячее Шашлык???

Результат:

SELECT DISTINCT ПС FROM Поставки WHERE ПР IN ( SELECT ПР FROM Поставки WHERE ПС = 6);

ПС

1
3
5
6
8

Меню Трапезы Вид_блюд БлюдаТВБЛТТрапезаВВидБЛБлюдоВОсноваВыходТруд . . . . . . . . .

Рис. 3.1. Иллюстрация декартова произведения

Очевидно, что отбор актуальных строк обеспечивается вводом в запрос WHERE фразы, в которой устанавливается соответствие между:

кодами трапез (Т) в таблицах Меню и Трапезы (Меню.Т = Трапезы.Т), кодами видов блюд (В) в таблицах Меню и Вид_блюд (Меню.В = Вид_блюд.В), номерами блюд (БЛ) в таблицах Меню и Блюда (Меню.БЛ = Блюда.БЛ).

Такой скорректированный запрос

SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.* FROM Меню, Трапезы, Вид_блюд, Блюда WHERE Меню.Т = Трапезы.Т AND Меню.В = Вид_блюд.В AND Меню.БЛ = Блюда.БЛ;

позволит получить эквисоединение таблиц Меню, Трапезы, Вид_блюд и Блюда:

1З31ЗавтракЗЗакуска1Салат летнийЗОвощи200.3
1З31ЗавтракЗЗакуска2Салат мяснойЗМясо200.4
1З31ЗавтракЗЗакуска3Салат витаминныйЗОвощи200.4 *
1З31ЗавтракЗЗакуска12Суп молочныйСМолоко500.3
1З31ЗавтракЗЗакуска13БастурмаГМясо300.5
1З31ЗавтракЗЗакуска32Кофе черныйНКофе100.1
1З31ЗавтракЗЗакуска33Кофе на молокеНКофе200.2
1З61ЗавтракЗЗакуска1Салат летнийЗОвощи200.3
1З61ЗавтракЗЗакуска2Салат мяснойЗМясо200.4
1З61ЗавтракЗЗакуска3Салат витаминныйЗОвощи200.4
1З61ЗавтракЗЗакуска4Салат рыбныйЗРыба200.4
1З61ЗавтракЗЗакуска5Паштет из рыбыЗРыба120.5
1З61ЗавтракЗЗакуска6Мясо с гарниромЗМясо250.3 *

ТВБЛТТрапезаВВидБЛБлюдоВОсноваВыход

Труд

. . .

3.2.1 | Содержание | 3.2.3



Композиция таблиц


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

SELECT Трапеза, Вид, Блюдо, Основа, Выход, Труд FROM Меню, Трапезы, Вид_блюд, Блюда WHERE Меню.Т = Трапезы.Т AND Меню.В = Вид_блюд.В AND Меню.БЛ = Блюда.БЛ;

имеющую вид

1З31ЗавтракЗЗакуска3Салат витаминныйЗОвощи200.4
1З61ЗавтракЗЗакуска6Мясо с гарниромЗМясо250.3
1Г191ЗавтракГГорячее19Омлет с лукомГЯйца200.5
3Г163УжинГГорячее16ДраченаГЯйца180.4
3Н303УжинННапиток30КомпотНФрукты200.2
3Н313УжинННапиток31Молочный напитокНМолоко200.2

ТрапезаБлюдоВидОсноваВыходТруд . . .

3.2.3 | Содержание | 3.2.5



Коррелированные вложенные подзапросы


Выдать название и статус поставщиков продукта с номером 11.

SELECT Название, Статус FROM Поставщики WHERE 11 IN ( SELECT ПР FROM Поставки WHERE ПС = Поставщики.ПС );

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

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

( SELECT ПР FROM Поставки WHERE ПС = 1 );

получая в результате множество (9, 11, 12, 15). Теперь система может завершить обработку для поставщика с номером 1. Выборка значений Название и Статус для ПС=1 (СЫТНЫЙ и рынок) будет проведена тогда и только тогда, когда ПР=11 будет принадлежать этому множеству, что, очевидно, справедливо. Далее система будет повторять обработку такого рода для следующего поставщика и т.д. до тех пор, пока не будут рассмотрены все строки таблицы Поставщики.

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

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

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

ЗавтракСалат витаминныйЗакускаОвощи200.4
ЗавтракМясо с гарниромЗакускаМясо250.3
ЗавтракОмлет с лукомГорячееЯйца200.5
УжинДраченаГорячееЯйца180.4
УжинКомпотНапитокФрукты200.2
УжинМолочный напитокНапитокМолоко200.2

Результат:

SELECT DISTINCT X.ПР FROM Поставки X WHERE X.ПР NOT IN ( SELECT Y.ПР FROM Поставки Y WHERE Y.ПС <> X.ПС );

X.ПР

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

Отметим, что в этой формулировке должен быть использован по крайней мере один псевдоним - либо X, либо Y.

3.3.4 | Содержание | 3.3.6



О индексах и производительности


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

17

Значения столбца Строки, в которых встречается такое значение

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

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

Для построения индекса в SQL существует предложение CREATE INDEX (создать индекс), имеющее формат

CREATE [UNIQUE] INDEX имя_индекса ON базовая_таблица (столбец [[ASC] | DESC] [, столбец [[ASC] | DESC]] ...);


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

Например, индексы для столбцов БЛ и Основа таблицы Блюда создаются с помощью предложений

CREATE UNIQUE INDEX Блюда_БЛ ON Блюда (БЛ); CREATE INDEX Блюда_Основа ON Блюда (Основа);

а индекс для первичного ключа (столбцы БЛ и ПР) таблицы Состав - с помощью предложения

CREATE UNIQUE INDEX Состав_БЛ_ПР ON Состав (БЛ, ПР);

В больших (более 1000 строк) таблицах поиск индексированных значений выполняется на порядок быстрее, чем поиск неиндексированных, а в очень больших таблицах - на два-три порядка.

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

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

DROP INDEX имя_индекса;

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

5.2 | Содержание | 5.4.1


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


Затрагивая вопросы проектирования баз данных [2], мы выяснили, что базы данных - это множество взаимосвязанных сущностей или отношений (таблиц) в терминологии реляционных СУБД. При проектировании стремятся создавать таблицы, в каждой из которых содержалась бы информация об одном и только об одном типе сущностей. Это облегчает модификацию базы данных и поддержание ее целостности. Но такой подход тяжело усваивается начинающими проектантами, которые пытаются привязать проект к будущим приложениям и так организовать таблицы, чтобы в каждой из них хранилось все необходимое для реализации возможных запросов. Типичен вопрос: как же получить сведения о том, где купить продукты для приготовления того или иного блюда и определить его калорийность и стоимость, если нужные данные "рассыпаны" по семи различным таблицам? Не лучше ли иметь одну большую таблицу, содержащую все сведения базы данных ПАНСИОН ?

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

SELECT Продукт, Цена, Название, Статус FROM Продукты, Состав, Блюда, Поставки, Поставщики WHERE Продукты.ПР = Состав.ПР AND Состав.БЛ = Блюда.БЛ AND Поставки.ПР = Состав.ПР AND Поставки.ПС = Поставщики.ПС AND Блюдо = 'Сырники' AND Цена IS NOT NULL;

Кофе 32 33
Крупа 20 21
Молоко 7 8 12 18 22 24 28 31
Мясо 2 6 9 13 14
Овощи 1 3 17 23 15
Рыба 4 5 10 11
Фрукты 25 26 27 29 30
Яйца 16 19

ПродуктЦенаНазваниеСтатус

Он получен следующим образом: СУБД последовательно формирует строки декартова произведения таблиц, перечисленных во фразе FROM, проверяет, удовлетворяют ли данные сформированной строки условиям фразы WHERE, и если удовлетворяют, то включает в ответ на запрос те ее поля, которые перечислены во фразе SELECT.


Следует подчеркнуть, что в SELECT и WHERE ( во избежание двусмысленности) ссылки на все (*) или отдельные столбцы могут (а иногда и должны) уточняться именем соответствующей таблицы, например, Поставки.ПС, Поставщики.ПС, Меню.*, Состав.БЛ, Блюда.* и т.п.

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

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

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

SELECT Продукт, Цена, Название, Статус FROM Продукты, Состав, Блюда, Поставки, Поставщики WHERE Продукты.ПР = Состав.ПР AND Состав.БЛ = Блюда.БЛ AND Поставки.ПР = Состав.ПР AND Поставки.ПС = Поставщики.ПС AND Блюдо = 'Сырники' AND Цена = ( SELECT MIN(Цена) FROM Поставки X WHERE X.ПР = Поставки.ПР );

Результат запроса имеет вид

Яйца1.8ПОРТОСкооператив
Яйца2.КОРЮШКАкооператив
Сметана3.6ПОРТОСкооператив
Сметана2.2ОГУРЕЧИКферма
Творог1.ОГУРЕЧИКферма
Мука0.5УРОЖАЙкоопторг
Сахар0.94ТУЛЬСКИЙуниверсам
Сахар1.УРОЖАЙкоопторг
ПродуктЦенаНазваниеСтатус Здесь с помощью подзапроса, размещенного в трех последних строках запроса, описывается процесс определения минимальной цены каждого продукта для Сырников и поиск поставщика, предлагающего этот продукт за такую цену. Механизм реализации подзапросов будет подробно описан в п.3.3. Там же будет рассмотрено, как и для чего вводится псевдоним X для имени таблицы Поставки.

2.5.4 | Содержание | 3.2.1


Объединение (UNION)


В литературе [2] рассматривалась реляционная операция "Объединение", позволяющая получить отношение, состоящее из всех строк, входящих в одно или оба объединяемых отношения. Но при этом исходные отношения или их объединяемые проекции должны быть совместимыми по объединению. Для SQL это означает, что две таблицы можно объединять тогда и только тогда, когда:

они имеют одинаковое число столбцов, например, m; для всех i (i = 1, 2, ..., m) i-й столбец первой таблицы и i-й столбец второй таблицы имеют в точности одинаковый тип данных.

Например, выдать названия продуктов, в которых нет жиров, либо входящих в состав блюда с кодом БЛ = 1:

Яйца1.8ПОРТОСкооператив
Сахар0.94ТУЛЬСКИЙуниверсам
Мука0.5УРОЖАЙкоопторг
Сметана2.2ОГУРЕЧИКферма
Творог1.ОГУРЕЧИКферма

Результат:Продукт

SELECT Продукт FROM Продукты WHERE Жиры = 0 UNION SELECT Продукт FROM Соста WHERE БЛ = 1

Из этого простого примера видно, что избыточные дубликаты всегда исключаются из результата UNION. Поэтому, хотя в рассматриваемом примере Помидоры, Зелень и Яблоки выбираются обеими из двух составляющих предложения SELECT, в окончательном результате они появляются только один раз.

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

UNION SELECT Продукт FROM Продукты WHERE Ca < 250

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

WHERE Жиры = 0 OR Ca < 250

3.3.7 | Содержание | 3.5



Обновление единственной записи


Изменить название блюда с кодом БЛ=5 на Форшмак, увеличить его выход на 30 г и установить NULL-значение в столбец Труд.

UPDATE Блюда SET Блюдо = 'Форшмак', Выход = (Выход+30), Труд = NULL WHERE БЛ = 5;



Обновление множества записей


Утроить цену всех продуктов таблицы поставки (кроме цены кофе - ПР = 17).

UPDATE Поставки SET Цена = Цена * 3 WHERE ПР <> 17;



Обновление нескольких таблиц


Изменить номер продукта ПР = 13 на ПР = 20.

UPDATE Продукты UPDATE Состав SET ПР = 20 SET ПР = 20 WHERE ПР = 13; WHERE ПР = 13;

UPDATE Поставки UPDATE Наличие SET ПР = 20 SET ПР = 20 WHERE ПР = 13; WHERE ПР = 13;

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

4.3.3 | Содержание | 4.5



Обновление представлений


Рассмотренные в гл.4 операции DELETE, INSERT и UPDATE могут оперировать не только базовыми таблицами, но и представлениями. Однако, если из базовых таблиц можно удалять любые строки, обновлять значения любых их столбцов и вводить в такие таблицы новые строки, то этого нельзя сказать о представлениях, не все из которых являются обновляемыми.

Безусловно обновляемыми являются представления, полученные из единственной базовой таблицы простым исключением некоторых ее строк и (или) столбцов, обычно называемые "представление-подмножество строк и столбцов". Таким является представление Мясные_блюда, полученное из базовой таблицы Блюда исключением из нее столбца Труд и строк, не содержащих значение 'Мясо' в столбце Основа. Работая с ним, можно:

вставить (операция INSERT) новую строку, например, строку (34, 'Шашлык', 'Г', 150), фактически вставляя соответствующую строку (34, 'Шашлык', 'Г', 150, NULL) в лежащую в основе базовую таблицу Блюда;

удалить (операция DELETE) существующую строку из представления, например строку (13, 'Бастурма', 'Г', 300), фактически удаляя соответствующую строку (13, 'Бастурма', 'Г', 300, 5) из таблицы Блюда;

обновить (операция UPDATE) какое-либо поле в существующей строке, например увеличить массу порции Бефстроганова с 210 до 250 граммов, фактически осуществляя то же самое изменение в соответствующем поле таблицы Блюда.

Однако если бы представление Мясные_блюда имело вместо столбца Выход столбец Вых_труд, полученный путем суммирования значений столбцов Выход и Труд таблицы Блюда, то указанные выше операции INSERT и UPDATE были бы отвергнуты системой. Действительно, как распределить вводимое значение столбца Вых_труд (153 или 254) между значениями столбцов Выход и Труд базовой таблицы Блюда? Была бы отвергнута и операция удаления масла из состава бастурмы, если бы ее попытались выполнить путем удаления строки ('Бастурма', 'Масло', 5) в представлении Горячие_мясные_блюда. Встает множество вопросов: надо ли удалять из базовой таблицы Блюда строку, содержащую значение 'Бастурма' в столбце Блюдо?; надо ли удалять из базовой таблицы Продукты строку, содержащую значение 'Масло' в столбце Продукт?; надо ли удалять из базовой таблицы Состав все строки, содержащие значение 5 в столбце Вес?. Последний вопрос возник потому, что при конструировании представления Горячие_мяс-ные_блюда в него не была включена информация о связях между лежащими в его основе базовыми таблицами Блюда, Состав и Продукты, и у системы нет прямых путей для поиска той единственной строки таблицы Состав, которая должна быть удалена.


Таким образом, некоторые представления по своей природе обновляемы, в то время как другие таковыми не являются. Здесь следует обратить внимание на слова "по своей природе". Дело заключается не просто в том, что некоторая СУБД не способна поддерживать определенные обновления, в то время как другие СУБД могут это делать. Никакая СУБД не может непротиворечивым образом поддерживать без дополнительной помощи обновление такого представления как Горячие_мясные_блюда. "Без дополнительной помощи" означает здесь "без помощи какого-либо человека - пользователя".

Как было указано выше, к теоретически обновляемым представ-лениям относятся представления-подмножества строк и столбцов. Однако существуют некоторые представления, которые не являются представлениями-подмножествами строк и столбцов, но также теоретически обновляемы. Хотя известно, что такие есть и можно привести их примеры, но невозможно дать их формального определения. Неверным является такое формальное определение некоторых авторов - "нельзя обновлять соединение". Во-первых, в некоторых соединениях с успехом выполняется операция UPDATE, а, во-вторых, как было показано выше, не обновляемы и некоторые представления, не являющиеся соединениями. Кроме того, не все СУБД поддерживают обновление любых теоретически обновляемых представлений. Поэтому пользователь должен сам оценивать возможность использования операций DELETE, INSERT или UPDATE в созданном им представлении.

5.4.2 | Содержание | 5.4.4


Обновление с подзапросом


Установить равной нулю цену и К_во продуктов для поставщиков из Паневежиса и Резекне.

UPDATE Поставки SET Цена = 0, К_во = 0 WHERE ПС IN (SELECT ПС FROM Поставщики WHERE Город IN ('Паневежис', 'Резекне'));



Операции выборки из представлений


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

SELECT * FROM Мясные_блюда;

результат которого имеет вид

Майонез
Лук
Помидоры
Зелень
Яблоки
Сахар

БЛ Блюдо В Выход

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

CREATE VIEW Горячие_мясные_блюда AS SELECT Блюдо, Продукт, Вес FROM Мясные_блюда, Состав, Продукты WHERE Мясные_блюда.БЛ = Состав.БЛ AND Продукты.ПР = Состав.ПР AND В = 'Г';

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

SELECT Блюдо, Продукт, Вес FROM Горячие_мясные_блюда WHERE Блюдо IN ( SELECT Блюдо FROM Горячие_мясные_блюда WHERE Продукт = 'Помидоры')

и получить:

2 Салат мясной З 200
6 Мясо с гарниром З 250
9 Суп харчо С 500
13 Бастурма Г 300
14 Бефстроганов Г 210

Блюдо Продукт Вес

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

5.4.1 | Содержание | 5.4.3



Особенности и синтаксис предложений модификации


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

Предложение DELETE имеет формат

DELETE FROM базовая таблица | представление [WHERE фраза];

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

Предложение INSERT имеет один из следующих форматов:

INSERT INTO {базовая таблица | представление} [(столбец [,столбец] ...)] VALUES ({константа | переменная} [,{константа | переменная}] ...);

или

INSERT INTO {базовая таблица | представление} [(столбец [,столбец] ...)] подзапрос;

В первом формате в таблицу вставляется строка со значениями полей, указанными в перечне фразы VALUES (значения), причем i-е значение соответствует i-му столбцу в списке столбцов (столбцы, не указанные в списке, заполняются NULL-значениями). Если в списке VALUES фразы указаны все столбцы модифицируемой таблицы и порядок их перечисления соответствует порядку столбцов в описании таблицы, то список столбцов в фразе INTO можно опустить. Однако не советуем этого делать, так как при изменении описания таблицы (перестановка столбцов или изменение их числа) придется переписывать и INSERT предложение.

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


Предложение UPDATE также имеет два формата. Первый из них:

UPDATE (базовая таблица | представление} SET столбец = значение [, столбец = значение] ... [WHERE фраза]

где значение - это

столбец | выражение | константа | переменная

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

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

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

UPDATE {базовая таблица | представление} SET столбец = значение [, столбец = значение] ... FROM {базовая таблица | представление} [псевдоним], {базовая таблица | представление} [псевдоним] [,{базовая таблица | представление} [псевдоним]] ... [WHERE фраза]

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

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

3.6 | Содержание | 4.2


Простые вложенные подзапросы


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

Бастурма Говядина 180
Бастурма Помидоры 100
Бастурма Лук 40
Бастурма Зелень 20
Бастурма Масло 5

Результат:

SELECT Название, Статус FROM Поставщики WHERE ПС IN ( SELECT ПС FROM Поставки WHERE ПР = 11 );

НазваниеСтатус

СЫТНЫЙрынок
УРОЖАЙкоопторг
ЛЕТОагрофирма
КОРЮШКАкооператив

ВидБлюдо SELECT Вид, Блюдо, 'калорий -', (SUM(INT((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000)), (SUM(Стоимость/К_во*Вес/1000)+MIN(Труд/100)),’руб’ FROM Блюда, Вид_блюд, Состав, Продукты, Наличие WHERE Блюда.БЛ = Состав.БЛ AND Состав.ПР = Продукты.ПР AND Состав.ПР = Наличие.ПР AND Блюда.В = Вид_блюд.В AND БЛ NOT IN ( SELECT БЛ FROM Состав WHERE ПР IN ( SELECT ПР FROM Наличие WHERE К_во = 0)) GROUP BY Вид, Блюдо HAVING SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100)) < 1.5 AND SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400 ORDER BY Вид, 4;


Рис. 3.2. Пример сложного запроса
Такой результат, нестрого говоря, строился следующим образом.

FROM. Эта фраза инициирует создание в рабочей памяти таблицы, являющейся декартовым произведением таблиц Блюда, Вид_блюд, Состав, Продукты и Наличие. WHERE. Эта фраза нужна для преобразования полученного декартова произведения в естественное соединение и удаления из последнего строк с кодами блюд, не обеспеченных продуктами. Естественное соединение образуется путем вычеркивания строк, где не совпадают: код блюда из таблицы Блюда с кодом блюда из таблицы Состав, код продукта из таблицы Состав с кодом продукта из таблицы Продукты и т.д. Обеспеченность блюда всеми продуктами проверяется с помощью последовательности подзапросов. Внутренний подзапрос выдает перечень кодов продуктов, которых нет в кладовой пансионата. Следующий подзапрос выдает коды тех блюд, в состав которых должны входить "отсутствующие" продукты. И, наконец, из естественного соединения вычеркиваются строки с кодами полученных блюд (точнее оставляются строки "Где код блюда не принадлежит перечню кодов блюд, полученному в подзапросе". SELECT. Из полученного соединения удаляются столбцы, не используемые в выражениях SELECT или других фразах. Если в списке SELECT есть выражения (константы), то для хранения их значений формируются дополнительные столбцы и инициируются операции по их заполнению. В рассматриваемом примере будут сохранены столбцы Вид, Блюдо, Белки, Углев, Жиры, Вес, Стоимость, К_во и созданы дополнительные столбцы для формирования и хранения значений стоимости и калорийности составляющих каждого блюда, а также для хранения текстовых констант 'калорий -' и 'руб'. Обратите внимание на прием, использованный при суммировании стоимостей продуктов, входящих в состав блюда, и стоимости его приготовления (Труд): можно ли заменить MIN на MAX или AVG? GROUP BY. Отредактированное естественное соединение группируется по видам блюд и их названиям. Создаются группы горячих блюд, десертов и т.д., а внутри каждой группы создаются подгруппы строк со сведениями о продуктах, относящихся к конкретному блюду группы. SELECT. Каждая подгруппа строк, полученная на предыдущем шаге, преобразуется в единственную строку для результата. В нее заносится вид блюда (общий для всех подгрупп группы), название блюда (общее для всех строк подгруппы), две текстовых константы ('калорий -' и 'руб') и две суммы. Последние формируются путем суммирования тех значений дополнительных столбцов, которые принадлежат подгруппе. HAVING. Сформированные строки, не удовлетворяющие условиям фразы HAVING
SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100)) < 1.5 и SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400
исключаются из результата предыдущего шага. ORDER BY. Результат шага 6 упорядочивается в соответствии со списком фразы ORDER BY для получения окончательного результата. Сначала строки группируются по видам блюд (в алфавитном порядке), а затем – по значению элемента данных, указанного на четвертом месте фразы SELECT, т.е. по калорийности.
Конечно, рассмотренный запрос весьма сложен, но попробуйте написать на любом знакомом вам языке программу, реализующую те же действия, и оцените сложность ее написания и отладки.
3.5 | Содержание | 4.1

Системный каталог


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

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

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

SELRCT Tab_name,N_col,N_row,Tab_owner,Comments FROM SYSTABLES;

и получить результат, показанный на рис. 5.1,а.

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

SELECT Col_name, Type, Length, Comments FROM SYSCOLUMNS WHERE Tab_name = 'Блюда';

и получить результат, показанный на рис. 5.1,б.

а)

ГорячееПомидоры с лукомкалорий -244.60.44руб
ГорячееБефстрогановкалорий -321.30.53руб
ГорячееДраченакалорий -333.90.33руб
ГорячееКаша рисоваякалорий -339.20.27руб
ГорячееОмлет с лукомкалорий -354.90.36руб
ДесертЯблоки печеныекалорий -170.20.30руб
ДесертКрем творожныйкалорий -394.30.27руб
ЗакускаСалат летнийкалорий -155.50.32руб
ЗакускаСалат витаминныйкалорий -217.40.37руб
ЗакускаТворогкалорий -330.00.22руб
ЗакускаМясо с гарниромкалорий -378.70.62руб
НапитокКофе черныйкалорий -7.10.05руб
НапитокКомпоткалорий -74.40.14руб
НапитокКофе на молокекалорий -154.80.11руб
НапитокМолочный напитоккалорий -264.90.34руб
СупСуп молочныйкалорий -396.60.22руб

Tab_nameN_colN_rowTab_ownerComments ... ... ...

б)

SYS_TABLES 11 SYSTEM
SYS_COLUMNS 14 SYSTEM
Блюда 6 33 KIRILLOW Перечень блюд, известных шеф-повару
Поставки 4 37 GROMOW Данные о поставляемых продуктах
Вид_блюд 2 5 KIRILLOW Перечень видов блюд
Трапезы 2 3 GROMOW Перечень трапез в пансионате
Состав 3 148 KIRILLOW Состав блюд
Продукты 11 17 KIRILLOW Таблица продуктов

Col_nameTypeLengthComments
БЛ INTEGER 4 Код блюда
Блюдо TEXT 16 Название блюда
В TEXT 1 Код вида блюда (З, С, ...)
Основа TEXT 6 Основной продукт в блюде
Выход REAL 4 Масса порции готового блюда
Труд INTEGER 4 Стоимость приготовления блюда (коп)
Рис. 5.1. Результаты запросов по системным таблицам

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

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

Соединение таблиц с дополнительным условием


При формировании соединения создается рабочая таблица, к которой применимы все операции, рассмотренные в главе 2: отбор нужных строк соединения (WHERE фраза), упорядочение получаемого результата (ORDER BY фраза) и агрегатирование данных (SQL-функции и GROUP BY фраза).

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

SELECT Вид, Блюдо, Основа, Выход, 'Номер -', БЛ FROM Меню, Трапезы, Вид_блюд, Блюда WHERE Меню.Т = Трапезы.Т AND Меню.В = Вид_блюд.В AND Меню.БЛ = Блюда.БЛ AND Трапеза = ’Завтрак’;

Получим

ВидБлюдоОсноваВыход'Номер -'БЛ

В п.3.6 можно познакомиться с достаточно полным примером соединения таблиц с различными дополнительными фразами.

3.2.5 | Содержание | 3.2.7



Соединение таблицы со своей копией


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

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

Временную копию таблицы можно сформировать, указав имя псевдонима за именем таблицы во фразе FROM. Так, с помощью фразы

FROM Блюда X, Блюда Y, Блюда Z

будут сформированы три копии таблицы Блюда с именами X, Y и Z.

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

SELECT Блюдо, Копия.Блюдо, Основа FROM Блюда, Блюда Копия WHERE Основа = Копия.Основа AND Блюдо < Копия.Блюдо;

или двумя ее копиями (Первая и Вторая):

SELECT Первая.Блюдо, Вторая.Блюдо, Основа FROM Блюда Первая, Блюда Вторая WHERE Первая.Основа = Вторая.Основа AND Первая.Блюдо < Вторая.Блюдо;

Получим результат вида

ЗакускаСалат витаминныйОвощи200.Номер -3
ЗакускаМясо с гарниромМясо250.Номер -6
ГорячееОмлет с лукомЯйца200.Номер -19
ГорячееПудинг рисовыйКрупа160.Номер -21
НапитокМолочный напитокМолоко200.Номер -31
НапитокКофе черныйКофе100.Номер -32

Первая.БлюдоВторая.БлюдоОснова

3.2.6 | Содержание | 3.3.1



Создание и уничтожение базовых таблиц


Базовые таблицы описываются в SQL с помощью предложения CREATE TABLE (создать таблицу), синтаксис которого имеет небольшие различия в различных СУБД. Однако все они поддерживают следующую минимальную форму:

CREATE TABLE базовая_таблица (столбец тип_данных [NOT NULL] [,столбец тип_данных [NOT NULL]] ...);

где тип_данных должен принадлежать к одному из типов данных, поддерживаемых СУБД (например, одному из типов данных, перечисленных в п.1.2).

Так, описание таблицы Блюда может быть записано в виде

CREATE TABLE Блюда ( БЛ SMALLINT NOT NULL, Блюда CHAR (70) NOT NULL, В CHAR (1), Основа CHAR (10), Выход FLOAT, Труд SMALLINT );

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

Конструкция NOT NULL запрещает использование неопределенного значения, т.е. специального значения, которое вводится для представления "неизвестного значения" или "неприменимого значения". Например, строка поставки таблицы Поставки может содержать неопределенное значение в столбце Цена и (или) К_во (извесно, что поставщик поставляет указанный продукт, но на данный момент неизвестна цена этого продукта и (или) объем поставки).

Существующую базовую таблицу можно в любой момент уничтожить с помощью предложения DROP TABLE (уничтожить таблицу):

DROP TABLE базовая_таблица;

по которому удаляется описание таблицы, ее данные, связанные с ней представления и индексы, построенные для столбцов таблицы (см. п. 5.3).

В SQL существует также предложение ALTER TABLE (изменить таблицу), которое позволяет добавить справа к таблице новый столбец, т.е. модифицировать описание табицы. Так как без него "можно жить", а объем книги ограничен, то мы не будем здесь описывать это предложение.

5.1 | Содержание | 5.3



Создание и уничтожение представлений


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

Синтаксис предложения CREATE VIEW имеет вид

CREATE VIEW имя_представления [(столбец[,столбец] ...)] AS подзапрос [WITH CHECK OPTION];

где подзапрос, следующий за AS и являющийся определением данного представления, не исполняется, а просто сохраняется в каталоге;

необязательная фраза "WITH CHECK OPTION" (с проверкой) указывает, что для операций INSERT и UPDATE над этим пред-ставлением должна осуществляться проверка, обеспечивающая удовлетворение WHERE фразы подзапроса;

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

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

б) два или более столбцов подзапроса имеют одно и то же имя;

если же список отсутствует, то представление наследует имена столбцов из подзапроса.

Например, создадим представление Мясные_блюда

CREATE VIEW Мясные_блюда AS SELECT БЛ, Блюдо, В, Выход FROM Блюда WHERE Основа = 'Мясо';

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

Уничтожение ненужных представлений выполняется с помощью предложения DROP VIEW (уничтожить представление), имеющего следующий формат:

DROP VIEW представление;

5.3 | Содержание | 5.4.2



Тета-соединение таблиц


В базе данных ПАНСИОН трудно подобрать несложный пример, иллюстрирующий тета-соединение таблиц. Поэтому сконструируем такой надуманный запрос:

SELECT Вид_блюд.*, Трапезы.* FROM Вид_блюд, Трапезы WHERE Вид > Трапеза;

позволяющий выбрать из полученного в п.3.2.1 декартова произведения таблиц Вид_блюд и Трапезы лишь те строки, в которых значение трапезы "меньше" (по алфавиту) значения вида блюда:

Морковь с рисомПомидоры с лукомОвощи
Морковь с рисомСалат летнийОвощи
Морковь с рисомСалат витаминныйОвощи
Помидоры с лукомСалат витаминныйОвощи
Помидоры с лукомСалат летнийОвощи
Салат витаминныйСалат летнийОвощи
БастурмаБефстрогановМясо
БастурмаМясо с гарниромМясо
БефстрогановМясо с гарниромМясо

ВВидТТрапеза

3.2.4 | Содержание | 3.2.6



Удаление единственной записи


Удалить поставщика с ПС = 7.

DELETE FROM Поставщики WHERE ПС = 7;

Если таблица Поставки содержит в момент выполнения этого предложения какие-либо поставки для поставщика с ПС = 7, то такое удаление нарушит непротиворечивость базы данных. К сожалению нет операции удаления, одновременно воздействующей на несколько таблиц. Однако в некоторых СУБД реализованы механизмы поддержания целостности (см.п.2.5 в литературе [2]), позволяющие отменить некорректное удаление или каскадировать удаление на несколько таблиц.



Удаление множества записей


Удалить все поставки.

DELETE FROM Поставки;

Поставки - все еще известная таблица, но в ней теперь нет строк. Для уничтожения таблицы надо выполнить операцию DROP TABLE Поставки (см.п.5.2).

Удалить все мясные блюда.

DELETE FROM Блюда WHERE Основа = 'Мясо';



Удаление с вложенным подзапросом


Удалить все поставки для поставщика из Паневежиса.

DELETE FROM Поставки WHERE ПС IN (SELECT ПС FROM Поставщики WHERE Город = 'Паневежис');

4.1 | Содержание | 4.3.1



Виды вложенных подзапросов


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

Существуют простые и коррелированные вложенные подзапросы. Они включаются в WHERE (HAVING) фразу с помощью условий IN, EXISTS или одного из условий сравнения ( = | <> | < | <= | > | >= ). Простые вложенные подзапросы обрабатываютя системой "снизу вверх". Первым обрабатывается вложенный подзапрос самого нижнего уровня. Множество значений, полученное в результате его выполнения, используется при реализации подзапроса более высокого уровня и т.д.

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

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

3.2.7 | Содержание | 3.3.2



Вложенный подзапрос с оператором сравнения, отличным от IN


Выдать номера поставщиков, находящихся в том же городе, что и поставщик с номером 6.

ЗЗакуска1Завтрак
ССуп1Завтрак
ССуп2Обед
ННапиток1Завтрак

Результат:

SELECT ПС FROM Поставщики WHERE Город = ( SELECT Город FROM Поставщики WHERE ПС = 6 );

ПС

1
4
6

Результат:
SELECT Название FROM Поставщики WHERE EXISTS ( SELECT * FROM Поставки WHERE ПС = Поставщики.ПС AND ПР = 11 );

Название

Система последовательно выбирает строки таблицы Поставщики, выделяет из них значения столбцов Название и ПС, а затем проверяет, является ли истинным условие существования, т.е. су-ществует ли в таблице Поставки хотя бы одна строка со значением ПР=11 и значением ПС, равным значению ПС, выбранному из таблицы Поставщики. Если условие выполняется, то полученное значение столбца Название включается в результат.

Предположим, что первые значения полей Название и ПС равны, соответственно, 'СЫТНЫЙ' и 1. Так как в таблице Поставки есть строка с ПР=11 и ПС=1, то значение 'СЫТНЫЙ' должно быть включено в результат.

Хотя этот первый пример только показывает иной способ формулировки запроса для задачи, решаемой и другими путями (с помощью оператора IN или соединения), EXISTS представляет собой одну из наиболее важных возможностей SQL. Фактически любой запрос, который выражается через IN, может быть альтернативным образом сформулирован также с помощью EXISTS. Однако обратное высказывание несправедливо.

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

СЫТНЫЙ
УРОЖАЙ
КОРЮШКА
ЛЕТО

Результат:

SELECT Название, Статус FROM Поставщики WHERE NOT EXISTS ( SELECT * FROM Поставки WHERE ПС = Поставщики.ПС AND ПР = 11 );

НазваниеСтатус

3.3.5 | Содержание | 3.3.7



Безопасность и санкционирование доступа


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

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

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

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

Привилегии предоставляются с помощью предложения GRANT (предоставить), общий формат которого имеет вид

GRANT привилегии ON объект TO пользователи;

В нем "привилегии" - список, состоящий из одной или нескольких привилегий, разделенных запятыми, либо фраза ALL PRIVILEGES (все привилегии); "объект" - имя и, если надо, тип объекта (база данных, таблица, представление, индекс и т.п.); "пользователи" - список, включающий один или более идентификаторов санкционирования, разделенных запятыми, либо специальное ключевое слово PUBLIC (общедоступный).


К таблицам (представлениям) относятся привилегии SELECT, DELETE, INSERT и UPDATE [(столбцы)], позволяющие соответственно считывать (выполнять любые операции, в которых используется SELECT), удалять, добавлять или изменять строки указанной таблицы (изменение можно ограничить конкретными столбцами). Например, предложение

GRANT SELECT, UPDATE (Труд) ON Блюда TO cook;

позволяет пользователю, который представился системе идентификатором cook, использовать информацию из таблицы Блюда, но изменять в ней он может только значения столбца Труд.

Если пользователь USER_1 предоставил какие-либо привилегии другому пользователю USER_2, то он может впоследствии отменить все или некоторые из этих привилегий. Отмена осуществляется с помощью предложения REVOKE (отменить), общий формат которого очень похож на формат предложения GRANT:

REVOKE привилегии ON объект FROM пользователи;

Например, можно отобрать у пользователя cook право изменения значений столбца Труд:

REVOKE UPDATE (Труд) ON Блюда FROM cook;

5.4.4 | Содержание | 6.2


Обработка транзакций


В п.4.4.4 рассматривался пример, в котором требовалось изменить номер продукта ПР = 13 на ПР = 20 и для этого пришлось проводить последовательное изменение четырех таблиц:

UPDATE Продукты UPDATE Состав SET ПР = 20 SET ПР = 20 WHERE ПР = 13; WHERE ПР = 13;

UPDATE Поставки UPDATE Наличие SET ПР = 20 SET ПР = 20 WHERE ПР = 13; WHERE ПР = 13;

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

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

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

COMMIT (фиксировать), превращающее все предварительные обновления в окончательные ("зафиксированные");

ROLLBACK (откат), аннулирующее все предварительные обновления.


Таким образом, транзакцией можно назвать последовательность SQL-предложений, расположенных между "точками синхронизации", учреждаемых в начале выполнения программы и издании COMMIT или ROLLBACK и только в этих случаях. При этом следует иметь в виду, что возможен неявный COMMIT (существует режим AUTOCOMMIT, в котором система издает COMMIT после выполнения каждого SQL-предложения) и ROLLBACK (выполняемый при аварийном завершении программы).

Ясно теперь, что пользователь должен сам решать, включать ли механизм обработки транзакций и если включать, то где издавать COMMIT (ROLLEBACK), т.е. какие последовательности SQL-предложений являются транзакциями.

Теперь о проблемах, связанных с параллельным использованием базы данных множеством разнообразных пользователей.

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

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

6.1 | Содержание | 6.3


Прикладное программирование


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

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

Переменные включающего языка:

могут появляться в предложениях манипулирования данными языка SQL только во фразах SELECT и INTO предложения SELECT, фразе WHERE предложений SELECT, UPDATE и DELETE, фразе SET предложения UPDATE, фразе VALUES предложения INSERT и, наконец, в качестве элемента выражения во фразах SELECT, WHERE или SET, но не VALUES; должны иметь типы данных, совместимые с типами данных тех столбцов базы данных, с которыми они должны сравниваться, значения которых им должны быть присвоены или которым должны быть присвоены значения переменных; могут иметь имена, совпадающие с именами столбцов базы данных (система различает их по месторасположению в предложениях SQL или по специальному символу, например, ".", устанавливаемому перед именем переменной, когда надо использовать ее значение).

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


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

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

SELECT [[ALL] | DISTINCT]{ * | элемент_select [,элемент_select] ...} INTO переменная [[INDICATOR] индикаторная_переменная] [,переменная [[INDICATOR] индикаторная_переменная]] ... FROM базовая_таблица | представление [псевдоним] [,базовая_таблица | представление [псевдоним]] ... [WHERE фраза] [GROUP BY фраза [HAVING фраза]];

где элемент_select - это одна из следующих конструкций:

[таблица.]* | [таблица.]столбец | SQL_функция | переменная | (выражение) | системная_переменная

Очевидно, что это описание отличается от описания подзапроса (п.2.1) наличием фразы INTO и включением в список элементов_select переменных. Переменные могут также включаться в выражения и фразы WHERE и HAVING.

Приведем несколько примеров. Получить общий вес продуктов в кладовой пансионата и занести его в переменную Общий_вес:

SELECT SUM(К_во) INTO Общий_вес FROM Наличие;

Здесь определяется единственное значение (сумма данных в столб-це) и поэтому системная переменная SQLCODE устанавливается в нуль.

Однако в следующем примере

SELECT Продукт, К_во INTO Продукт, К_продукта FROM Наличие, Продукты WHERE Наличие.ПР = Продукты.ПР AND ПР IN ( SELECT ПР FROM Продукты WHERE Продукт = 'Икра черная');



где требовалось узнать количество черной икры в кладовой пансионата и занести название этого продукта и его количество в переменные Продукт и К_продукта, соответственно, переменная SQLCODE примет значение +100, так как в кладовой нет икры.

Наконец, в примере

SELECT Продукт, К_во INTO Продукт, К_продукта FROM Наличие, Продукты WHERE Наличие.ПР = Продукты.ПР;

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

В единичном SELECT можно ввести за каждой целевой переменной слово INDICATOR и имя индикаторной переменной. Значения индикаторных переменных не равны нулю только при нулевом значении SQLCODE и NULL-значениях элементов SELECT для соответствующих целевых переменных. Например, если в столбцах К_во и Стоимость продукта 9 хранится значение NULL, то после выполнения запроса

SELECT ПР, К_во, Стоимость INTO ПР INDICATOR Инд1, К_прод INDICATOR Инд2, Стоим INDICATOR Инд3 FROM Наличие WHERE ПР = 9;

будут получены следующие значения переменных: SQLCODE = 0, ПР = 9, Инд1 = 0, Инд2 = Инд3 = -1, а К_прод и Стоим имеют значение NULL.

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

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

UPDATE Поставки SET Цена = Цена + .Измен WHERE ПС IN (SELECT ПC FROM Поставщики WHERE Город = 'Ленинград');

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

DELETE FROM Блюда WHERE Основа = .Осн;

Добавить в таблицу Поставщики нового поставщика, атрибуты которого заданы соответствующими переменными ПС, Имя, Статус, Город, а Адрес и Телефон неизвестны:

INSERT INTO Поставщики (ПС, Название, Статус, Город) VALUES (.ПС, .Имя, .Статус, .Город);

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



Предложение

DECLARE имя_курсора CURSOR FOR подзапрос

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

Описанные с помощью DECLARE CURSOR множества используются рядом предложений SQL для удаления отмеченных строк (DELETE), их модификации (UPDATE) или присвоения значений перечисленных в SELECT столбцов переменным, список которых указывается в предложении FETCH (вызвать). Однако перед выполнением этих команд необходимо активизировать курсор, который в этот момент не должен быть открыт. Для этого используется предложение OPEN (OPEN имя_курсора).

Предложение FETCH используется для выборки той записи активного множества, на которую указывает курсор, для присвоения значений столбцов этой записи переменным, перечисленным во фразе INTO, и для перемещения курсора на следующую строку активного множества. При перемещении за последнюю строку переменная SQLCODE примет значение +100.

FETCH имя_курсора INTO переменная [[INDICATOR] индикаторная_переменная] {,переменная [[INDICATOR] индикаторная_переменная]} ...

Команду FETCH обычно помещают в некоторый цикл, размещая непосредственно за ней команду анализа SQLCODE. Это позволяет обнаружить переход от значения 0 на +100 и организовать выход из цикла.

Наконец, следует упомянуть еще два предложения, связанные с курсорами. Это предложение для дезактивации курсора (CLOSE имя_курсора) и предложение для уничтожения курсора (DROP CURSOR имя_курсора).

Мы уже отмечали, что ограниченный объем книги не позволяет подробнее обсудить и должным образом проиллюстрировать использование курсоров. Не затронуты также предложения COMMENT ON (ввести описание таблицы или столбца), CONNECT (открыть базу данных), DISCONNECT (закрыть базу данных), WHENEVER (организовать обработку индикатора ошибки SQL) и несколько предложений, связанных с управлением транзакциями и параллельным их исполнением.

6.2 | Содержание | Литература


Почему SQL?


Все языки манипулирования данными (ЯМД), созданные до появления реляционных баз данных и разработанные для многих систем управления базами данных (СУБД) персональных компьютеров, были ориентированы на операции с данными, представленными в виде логических записей файлов. Это требовало от пользователей детального знания организации хранения данных и достаточных усилий для указания не только того, какие данные нужны, но и того, где они размещены и как шаг за шагом получить их.

Рассматриваемый же ниже непроцедурный язык SQL (Structured Query Language - структуризованный язык запросов) ориентирован на операции с данными, представленными в виде логически взаимосвязанных совокупностей таблиц. Особенность предложений этого языка состоит в том, что они ориентированы в большей степени на конечный результат обработки данных, чем на процедуру этой обработки. SQL сам определяет, где находятся данные, какие индексы и даже наиболее эффективные последовательности операций следует использовать для их получения: не надо указывать эти детали в запросе к базе данных.

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

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


Появление теории реляционных баз данных и предложенного Коддом языка запросов "alpha", основанного на реляционном исчислении [2, 3], инициировало разработку ряда языков запросов, которые можно отнести к двум классам:

Алгебраические языки, позволяющие выражать запросы средствами специализированных операторов, применяемых к отношениям (JOIN - соединить, INTERSECT - пересечь, SUBTRACT - вычесть и т.д.). Языки исчисления предикатов представляют собой набор правил для записи выражения, определяющего новое отношение из заданной совокупности существующих отношений. Другими словами исчисление предикатов есть метод определения того отношения, которое нам желательно получить (как ответ на запроc) из отношений, уже имеющихся в базе данных.

Разработка, в основном, шла в отделениях фирмы IBM (языки ISBL, SQL, QBE) и университетах США (PIQUE, QUEL) [3]. Последний создавался для СУБД INGRES (Interactive Graphics and Retrieval System), которая была разработана в начале 70-х годов в Университете шт. Калифорния и сегодня входит в пятерку лучших профессиональных СУБД. Сегодня из всех этих языков полностью сохранились и развиваются QBE (Query-By-Example - запрос по образцу) и SQL, а из остальных взяты в расширение внутренних языков СУБД только наиболее интересные конструкции.

В начале 80-х годов SQL "победил" другие языки запросов и стал фактическим стандартом таких языков для профессиональных реляционных СУБД. В 1987 году он стал международным стандартом языка баз данных и начал внедряться во все распро-страненные СУБД персональных компьютеров. Почему же это произошло?

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

Борясь за покупателя, фирмы, производящие программное обеспечение, стали выпускать на рынок все более и более интеллектуальные и, следовательно, объемные программные комплексы. Приобретая (желая приобрести) такие комплексы, многие организации и отдельные пользователи часто не могли разместить их на собственных ЭВМ, однако не хотели и отказываться от нового сервиса. Для обмена информацией и ее обобществления были созданы сети ЭВМ, где обобществляемые программы и данные стали размещать на специальных обслуживающих устройствах - файловых серверах.



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

Для исключения указанных и некоторых других недостатков была предложена технология "Клиент-Сервер", по которой запросы пользовательских ЭВМ (Клиент) обрабатываются на специальных серверах баз данных (Сервер), а на ЭВМ возвращаются лишь результаты обработки запроса. При этом, естественно, нужен единый язык общения с Сервером и в качестве такого языка выбран SQL. Поэтому все современные версии профессиональных реляционных СУБД (DB2, Oracle, Ingres, Informix, Sybase, Progress, Rdb) и даже нереляционных СУБД (например, Adabas) используют технологию "Клиент-Сервер" и язык SQL. К тому же приходят разработчики СУБД персональных ЭВМ, многие из которых уже сегодня снабжены языком SQL.

Бытует мнение: Поскольку большая часть запросов формулируется на SQL, практически безразлично, что это за СУБД - был бы SQL.

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



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



В SQL используются следующие основные типы данных, форматы которых могут несколько различаться для разных СУБД:

INTEGER - целое число (обычно до 10 значащих цифр и знак); SMALLINT - "короткое целое" (обычно до 5 значащих цифр и знак); DECIMAL(p,q) - десятичное число, имеющее p цифр (0 < p < 16) и знак; с помощью q задается число цифр справа от десятичной точки (q < p, если q = 0, оно может быть опущено); FLOAT - вещественное число с 15 значащими цифрами и целочисленным порядком, определяемым типом СУБД; CHAR(n) - символьная строка фиксированной длины из n символов (0 < n < 256); VARCHAR(n) - символьная строка переменной длины, не превышающей n символов (n > 0 и разное в разных СУБД, но не меньше 4096); DATE - дата в формате, определяемом специальной командой (по умолчанию mm/dd/yy); поля даты могут содержать только реальные даты, начинающиеся за несколько тысячелетий до н.э. и ограниченные пятым-десятым тысячелетием н.э.; TIME - время в формате, определяемом специальной командой, (по умолчанию hh.mm.ss); DATETIME - комбинация даты и времени; MONEY - деньги в формате, определяющем символ денежной единицы ($, руб, ...) и его расположение (суффикс или префикс), точность дробной части и условие для показа денежного значения.

В некоторых СУБД еще существует тип данных LOGICAL, DOUBLE и ряд других. СУБД INGRES предоставляет пользователю возможность самостоятельного определения новых типов данных, например, плоскостные или пространственные координаты, единицы различных метрик, пяти- или шестидневные недели (рабочая неделя, где сразу после пятницы или субботы следует понедельник), дроби, графика, большие целые числа (что стало очень актуальным для российских банков) и т.п.

Ориентированный на работу с таблицами SQL не имеет достаточных средств для создания сложных прикладных программ. Поэтому в разных СУБД он либо используется вместе с языками программирования высокого уровня (например, такими как Си или Паскаль), либо включен в состав команд специально разработанного языка СУБД (язык систем dBASE, R:BASE и т.п.). Унификация полных языков современных профессиональных СУБД достигается за счет внедрения объектно-ориентированного языка четвертого поколения 4GL. Последний позволяет организовывать циклы, условные предложения, меню, экранные формы, сложные запросы к базам данных с интерфейсом, ориентированным как на алфавитно-цифровые терминалы, так и на оконный графический интерфейс (X-Windows, MS-Windows).

1.1 | Содержание | 1.3


Реляционная база данных


Реляционная база данных представляется пользователю как совокупность таблиц и ничего кроме таблиц. На рис.1.1 приведен пример реляционной базы данных ПАНСИОН. Этот простой пример используется для иллюстрации большинства вопросов, рассматриваемых в нашей книге. Поэтому советуем потратить немного времени, чтобы хорошо с ним разобраться*.

Кладовая пансионата периодически пополняется продуктами из списка, часть которого показана в таблице Продукты. Каждый продукт имеет кроме названия (столбец Продукт) уникальный номер этого продукта (столбец ПР). Химический состав продуктов приведен для 1 кг их съедобной части: основные пищевые вещества (белки, жиры и углеводы) даны в граммах, а минеральные вещества (калий, кальций, натрий) и витамины (B2, PP, C) - в миллиграммах.

В таблице Блюда представлены уникальные номера блюд (столбец БЛ), их названия, коды видов (см. таблицу Вид_блюд), основной продукт (столбец Основа), масса порции в граммах (столбец Выход) и приведенная стоимость в копейках приготовления одной порции (столбец Труд).

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

Таблица Состав связывает между собой таблицы Блюда и Продукты, оговаривая, какая масса (в граммах) того или иного продукта (столбец Вес) должна входить в состав одной порции блюда. Так, порция блюда с номером 12 (Суп молочный) должна состоять из 350 г продукта с номером 7 (Молоко), 35 г продукта с номером 13 (Рис), 5 г продукта с номером 3 (Масло) и 5 г продукта с номером 16 (Сахар).

Шеф-повар ежедневно получает от завхоза сведения о количестве в килограммах имеющихся продуктов и их текущей стоимости (столбцы К_во и Стоимость таблицы Наличие). Используя эти сведения он определяет по таблице Состав перечень тех блюд, которые можно приготовить из этих продуктов, а также калорийность и стоимость таких блюд. При этом стоимость блюда складывается из стоимости и массы продуктов, необходимых для приготовления одной его порции, а также из трудозатрат на ее приготовление (см. таблицу Блюда). Калорийность же определяется по массе и калорийности каждого из продуктов блюда. (Для получения значения калорийности продукта исходят из того, что при окислении 1 г углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г жиров - 9.3 ккал.)


ПОРТОСкооператив
ШУШАРЫсовхоз
ТУЛЬСКИЙуниверсам
ОГУРЕЧИКферма
БлюдаРецепты
БЛБлюдоВОснова Выход Труд
1Салат летнийЗОвощи200.3
2Салат мяснойЗМясо200.4
3Салат витаминныйЗОвощи200.4
4Салат рыбныйЗРыба200.4
5Паштет из рыбыЗРыба120.5
6Мясо с гарниромЗМясо250.3
7СметанаЗМолоко140.1
8ТворогЗМолоко140.2
9Суп харчоСМясо500.5
10Суп-пюре из рыбыСРыба500.6
11Уха из судакаСРыба500.5
12Суп молочныйСМолоко500.3
13БастурмаГМясо300.5
14БефстрогановГМясо210.6
15Судак по-польскиГРыба160.5
16ДраченаГЯйца180.4
17Морковь с рисомГОвощи260.3
18СырникиГМолоко220.4
19Омлет с лукомГЯйца200.5
20Каша рисоваяГКрупа210.4
21Пудинг рисовыйГКрупа160.6
22Вареники ленивыеГМолоко220.4
23Помидоры с лукомГОвощи260.4
24Суфле из творогаГМолоко280.6
25Рулет с яблокамиДФрукты200.5
26Яблоки печеныеДФрукты160.3
27Суфле яблочноеДФрукты220.6
28Крем творожныйДМолоко160.4
29"Утро"НФрукты200.5
30КомпотНФрукты200.2
31Молочный напитокНМолоко200.2
32Кофе черныйНКофе200.1
33Кофе на молокеНКофе200.2
БЛРецепт
1Помидоры ...
2Вареное ...
3Зелень ме...
4Вареные р...
5Филе суда...
6Мясо варе...
7Сметану п...
8Протертый ..
9Грудинку ...
10Филе суда...
11Судак очи...
12Промытый ...
13Мясо наре...
14Говядину ...
15Подготовл...
16Сырые яйц...
17Нарезать ...
18В протерт...
19К свежим ...
20Рис свари...
21Готовую р...
22В протерт...
23Спассеров...
24В протерт...
25Очистить ...
26Не прорез...
27Запеченны...
28Яйца разм...
29Очищенную ..
30Яблоки оч...
31Яблоки на...
32Кофеварку ..
33Сварить ч...
Поставщики

ПСНазваниеСтатусГородАдресТелефон
1СЫТНЫЙрынокЛенинградСытнинская, 32329916
2ПОРТОСкооперативРезекнеСадовая, 27317664
3ШУШАРЫсовхозПушкинНовая, 174705038
4ТУЛЬСКИЙуниверсамЛенинградТульская, 32710837
5УРОЖАЙкоопторгЛугаПесчаная, 19789000
6ЛЕТОагрофирмаЛенинградПулковское ш.,82939729
7ОГУРЕЧИКфермаПаневежисУкмерге, 15127331
8КОРЮШКАкооперативЙыхвиНарвское ш., 64432123
Состав Поставки
БЛПРВесБЛПРВесБЛПРВесБЛПРВес
111100911251673524880
115809133516615247100
1125912151614924540
14159315163524630
216510270179150241620
29401072501775024310
2113510320171325241410
21220101415173202515120
252010125171210251635
242011210017145251430
311551192018814025820
315551110201863025320
365011351814152615150
312201112218510261620
310151273501816152632
3165121335195120271550
4250123519745277150
411501216519102027580
444013118019315271635
493513111002013502732
452013104020775288100
412513122020157528520
5280133520161028620
5940141902035281615
53251475021137028310
512514620216302915150
618014101021320299200
611150143521520291615
643014125211615301570
6121014143228140301610
7612515210022630317150
71615159202214203115150
887515520221615311625
865015320225832178
81615151010231125033178
918015125231065331625
910301651202332033775
ПСПРЦенаК_во
19
1111.5050
1123.0010
1152.00170
213.60300
23
251.80100
263.6080
28
370.40200
39
3122.5020
3151.50200
42
442.0450
4130.88150
414
4160.94200
4174.5050
543.0050
59
5100.50130
511
5131.2040
5140.5070
5161.0050
6100.7090
611
612
714.2070
734.00250
762.20140
77
781.00150
82
852.0070
8111.00100
ПродуктыНаличие
ПР ПродуктБелкиЖирыУглевKCaNaB2PPC
1Говядина189.124.0.3150906001.528.0
2Судак190.80.0.187027001.110.30
3Масло60.825.90.2302207400.11.0
4Майонез31.670.26.48028000.0.0
5Яйца127.115.7.15305507104.41.90
6Сметана26.300.28.9508503201.1.2
7Молоко28.32.47.1460121015001.31.10
8Творог167.90.13.1120164014102.74.5
9Морковь13.1.70.20005102100.79.950
10Лук17.0.95.17503101800.22.100
11Помидоры6.0.42.2901404000.45.3250
12Зелень9.0.20.340275751.24.380
13Рис70.6.773.5402402600.416.0
14Мука106.13.732.17602401201.222.0
15Яблоки4.0.113.24801602600.33.130
16Сахар0.0.998.3020100.0.0
17Кофе127.36.9.97101801800.31.80
ПР К_воСтоим
1108429.84
200.00
373274.61
43997.46
561111.83
688206.60
721483.08
89282.80
900.00
107746.30
114651.70
121334.96
135451.14
149143.77
15117189.92
169896.14
1737166.50
Вид_блюдТрапезыМенюВыборВыбрано
ВВид
З Закуска
С Суп
Г Горячее
Д Десерт
Н Напиток
ТТрапеза
1 Завтрак
2 Обед
3 Ужин
Т В БЛ Т В БЛ Т В БЛ
1 З 3 2 З 1 3 З 6
1 З 6 2 З 6 3 З 8
1 Г 19 2 С 9 3 Г 20
1 Г 21 2 С 12 3 Г 16
1 Н 31 2 Г 14 3 Н 30
1 Н 32 2 Г 16 3 Н 31
2 Г 18
2 Д 26
2 Д 28
СМ Т В БЛ
2 1 З 3
2 1 Г 19
2 1 Н 31
2 2 З 1
2 2 С 12
2 2 Г 16
2 2 Д 26
2 3 З 8
2 3 Г 21
2 3 Н 32
СМ Т БЛ . . . Рис. 1.1. Основные таблицы базы данных ПАНСИОН

Учитывая примерную стоимость и необходимую калорийность дневного рациона отдыхающих, шеф-повар составляет меню на следующий день. В этом меню (таблица Меню) предлагается по несколько альтернативных блюд каждого вида (таблица Вид_блюд) и для каждой трапезы (таблица Трапезы). Перед завтраком каждый отдыхающий вводит в ЭВМ номер закрепленного за ним места в столовой пансионата (столбец СМ в таблице Выбор) и желаемый набор блюд для каждой из трапез следующего дня (в примере таблица заполнялась отдыхающим, сидящим на месте с номером 2). Таблицы Выбор объединяются по мере их создания в общую таблицу Выбрано, по которой определяют, сколько порций того или иного блюда надо приготовить для каждой трапезы.



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

Таблица Поставки связывает между собой таблицы Продукты и Поставщики, оговаривая, какое количество продукта (столбец К_во) и по какой цене поставил тот или иной поставщик. Отсутствие в строке цены и количества говорит о том, что поставщик ПС может поставлять продукт ПР, но в данный момент не осуществил такой поставки.

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



всякому столбцу таблицы присвоено имя, которое должно быть уникальным для этой таблицы; столбцы таблицы упорядочиваются слева направо, т.е. столбец 1, столбец 2, ..., столбец n. С математической точки зрения это утверждение некорректно, потому что в реляционной системе столбцы не упорядочены. Однако с точки зрения пользователя, порядок, в котором определены имена столбцов, становится порядком, в котором должны вводиться в них данные, если не предварять при вводе каждое значение именем соответствующего столбца (подробнее это описано в Приложении А литературы [2]); строки таблицы не упорядочены (их последовательность определяется лишь последовательностью ввода в таблицу); в поле на пересечении строки и столбца любой таблицы всегда имеется только одно значение данных и никогда не должно быть множества значений (правда, это "атомарное" значение может быть достаточно объемным, например, таким, как рецепт блюда); всем строкам таблицы соответствует одно и то же множество столбцов, хотя в определенных столбцах любая строка может содержать пустые значения (NULL-значения), т.е. может не иметь значений для этих столбцов; все строки таблицы обязательно отличаются друг от друга хотя бы единственным значением, что позволяет однозначно идентифицировать любую строку такой таблицы; при выполнении операций с таблицей ее строки и столбцы можно обрабатывать в любом порядке безотносительно к их информационному содержанию.



Почему же база данных, составленная из таких таблиц, называется реляционной? А потому, что отношение - relation - просто математический термин для обозначения неупорядоченной совокупности однотипных записей или таблиц определенного специфического вида, описанного выше. Таким образом, можно, например, сказать, что база данных ПАНСИОН состоит из одиннадцати отношений.

Реляционные системы берут свое начало в математической теории множеств. Они были предложены в конце 1968 года доктором Э.Ф.Коддом из фирмы IBM, который первым осознал, что можно использовать математику для придания надежной основы и строгости области управления базами данных.

Нечеткость многих терминов, используемых в сфере обработки данных, заставила Кодда отказаться от них и придумать новые или дать более точные определения существующим. Так, он не мог использовать широко распространенный термин "запись", который в различных ситуациях может означать экземпляр записи, либо тип записей, запись в стиле Кобола (которая допускает повторяющиеся группы) или плоскую запись (которая их не допускает), логическую запись или физическую запись, хранимую запись или виртуальную запись и т.д. Вместо этого он использовал термин "кортеж длины n" или просто "кортеж", которому дал точное определение. В литературе [2,3] можно подробно познакомиться с терминологией реляционных баз данных, а здесь мы будем использовать неформальные их эквиваленты:

1 1 3
1 1 21
2 2 16
2 2 26
3 1 6
32 3 30
Мы также принимаем, по определению, что "запись" означает "экземпляр записи", а "поле" означает "имя и тип поля".

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

Предисловие | Содержание | 1.2


Таблицы SQL


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

Базовые таблицы создаются с помощью предложения CREATE TABLE (создать таблицу), подробное описание которого приведено в главе 5. Здесь же приведем пример предложения для создания описания таблицы Блюда:

Рис. 1.2. База данных в восприятии пользователя

CREATE TABLE Блюда (БЛ SMALLINT, Блюдо CHAR (70), В CHAR (1), Основа CHAR (10), Выход FLOAT, Труд SMALLINT);

Предложение CREAT TABLE специфицирует имя базовой таблицы, которая должна быть создана, имена ее столбцов и типы данных для этих столбцов (а также, возможно, некоторую дополнительную информацию, не иллюстрируемую данным примером). CREAT TABLE - выполняемое предложение. Если его ввести с терминала, система тотчас построит таблицу Блюда, которая сначала будет пустой: она будет содержать только строку заголовков столбцов, но не будет еще содержать никаких строк с данными. Однако можно немедленно приступить к вставке таких строк данных, возможно, с помощью предложения INSERT и создать таблицу, аналогичную таблице Блюда рис.1.1.

Если теперь потребовалось узнать какие овощные блюда может приготовить повар пансионата, то можно набрать на терминале следующий текст запроса:

SELECT БЛ,Блюдо FROM Блюда WHERE Основа = 'Овощи';

и мгновенно получить на экране следующий результат его реализации:

таблицадля отношения,
строка или записьдля кортежа,
столбец или поледля атрибута.

БЛБлюдо

Для выполнения этого предложения SELECT (выбрать), подробное описание которого будет дано в главах 2 и 3, СУБД должна сначала сформировать пустую рабочую таблицу, состоящую из столбцов БЛ и Блюдо, тип данных которых должен совпадать с типом данных аналогичных столбцов базовой таблицы Блюда. Затем она должна выбрать из таблицы Блюда все строки, у которых в столбце Основа хранится слово Овощи, выделить из этих строк столбцы БЛ и Блюдо и загрузить укороченные строки в рабочую таблицу. Наконец, СУБД должна выполнить процедуры по организации вывода содержимого рабочей таблицы на экран терминала (при этом если в рабочей таблице содержится более 20-24 строк, она должна использовать процедуры постраничного вывода и т.п.). После выполнения запроса СУБД должна уничтожить рабочую таблицу.


Если, например, надо получить значение калорийности всех овощей, включенных в таблицу Продукты, то можно набрать на терминале запрос
SELECT Продукт, Белки, Жиры, Углев, ((Белки+Углев)*4.1+Жиры*9.3) FROM Продукты WHERE Продукт IN ('Морковь','Лук','Помидоры','Зелень');
и получить на экране следующий результат его реализации:
1Салат летний
3Салат витаминный
17Морковь с рисом
23Помидоры с луком

ПродуктБелкиЖирыУглев((Белки+Углев)*4.1+Жиры*9.3) В последнем столбце этой рабочей таблицы приведены данные о калорийности продуктов, отсутствующие в явном виде в базовой таблице Продукты. Эти данные вычислены по хранимым значениям основных питательных веществ продуктов, помещены в рабочую таблицу и будут существовать до момента смены изображения на экране. Однако если необходимо сохранить эти данные в какой-либо базовой таблице, то существует предложение (INSERT), позволяющее переписать содержимое рабочей таблицы в указанные столбцы базовой таблицы (реляционная операция присваивания).
Часто пользователя не устраивает как способ описания нужного набора выводимых строк, так и результат выполнения запроса, сформированного из данных одной таблицы. Ему хотелось бы уточнить выводимые (запрашиваемые) данные сведениями из других таблиц.
Например, в запросе на получение состава овощных блюд
SELECT БЛ,ПР,Вес FROM Состав WHERE БЛ IN (1,3,17,23);
пришлось перечислять номера этих блюд, так как в таблице Состав нет данных об основных продуктах блюда (они есть в таблице Блюда). Полученный состав овощных блюд (рис.1.3,а) оказался "слепым": в нем и блюда и продукты представлены номерами, а не именами. Удобнее и нагляднее (рис.1.3,б)
Морковь13.1.70.349.6
Лук17.0.95.459.2
Помидоры6.0.42.196.8
Зелень9.0.20.118.9

а)б)

БЛПРВесБлюдо
111100Салат летний
11580Салат летний
1125Салат летний
1415Салат летний
31155Салат витаминный
31555Салат витаминный
3650Салат витаминный
31220Салат витаминный
31015Салат витаминный
3165Салат витаминный
179150Морковь с рисом
17750Морковь с рисом
171325Морковь с рисом
17320Морковь с рисом
171210Морковь с рисом
17145Морковь с рисом
2311250Помидоры с луком
231065Помидоры с луком
23320Помидоры с луком

ПродуктВес Рис. 1.3. Состав овощных блюд базы данных ПАНСИОН
запрос сформированный по трем таблицам:
SELECT Блюдо, Продукт, Вес FROM Состав,Б люда, Продукты WHERE Состав.БЛ = Блюда.БЛ AND Состав.ПР = Продукты.ПР AND Основа = 'Овощи';
В нем для получения рабочей таблицы выполняется естественное соединение [2] таблиц Блюда, Продукты и Состав (условие соединения - равенство значений номеров блюд и значений номеров продуктов). Затем выделяются строки, у которых в столбце Основа хранится слово Овощи, и из этих строк - столбцы Блюдо, Продукт и Вес.
Если пользователи достаточно часто интересуются составом различных блюд, то для упрощения формирования запросов целесообразно создать представление.
Представление - это пустая именованная таблица, определяемая перечнем тех столбцов таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. Представление является как бы "окном" в одну или несколько базовых таблиц. Оно создается с помощью предложения CREATE VIEW (создать представление), подробное описание которого приведено в главе 5. Здесь же приведем пример предложения для создания представления Состав_блюд:
CREATE VIEW Состав_блюд AS SELECT Блюдо, Продукт, Вес FROM Состав,Блюда,Продукты WHERE Состав.БЛ = Блюда.БЛ AND Состав.ПР = Продукты.ПР;
Оно описывает пустую таблицу, в которую при реализации запроса будут загружаться данные из столбцов Блюдо, Продукт и Вес таблиц Блюда, Продукты и Состав, соответственно. Теперь для получения состава овощных блюд можно дать запрос
SELECT Блюдо,Продукт,Вес FROM Состав_блюд WHERE Основа = 'Овощи';
и получить на экране терминала данные, которые представлены на рис. 1.3,б. А для получения состава супа Харчо можно дать запрос
SELECT Блюдо, Продукт, Вес FROM Состав_блюд WHERE Блюдо = 'Суп харчо';


О целесообразности создания представлений будет рассказано ниже, а здесь лишь отметим, что они позволяют повысить уровень логической независимости данных, упростить их восприятие и "скрыть" от некоторых пользователей те или иные данные, например, данные о новых ценах на продукты первой необходимости или из какой рыбы приготавливается "Судак по-польски".
Наконец, еще об одних виртуальных таблицах - курсорах. Курсор - это пустая именованная таблица, определяемая перечнем тех столбцов базовых таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. В чем же различие между курсором и представлением?
Для пользователя представления почти не отличаются от базовых таблиц (есть лишь некоторые ограничения при выполнении различных операций манипулирования данными). Они могут использоваться как в интерактивном режиме, так и в прикладных программах. Курсоры же созданы для процедурной работы с таблицей в прикладных программах. Например, после объявления курсора
DECLARE Блюд_состав CURSOR FOR SELECT Блюдо,Продукт,Вес FROM Состав,Блюда,Продукты WHERE Состав.БЛ = Блюда.БЛ AND Состав.ПР = Продукты.ПР AND Блюдо = 'Суп харчо';
и его активизации (OPEN Блюд_состав) будет создана временная таблица с составом блюда "Суп харчо" и специальным указателем, определяющим в качестве текущей первую строку этой таблицы. С помощью предложения FETCH (выбрать), которое обычно исполняется в программном цикле, можно присвоить определенным переменным значения указанных столбцов этой строки. Одновременно курсор будет передвинут к следующей строке таблицы. После обработки в программе полученных значений переменных выполняется следующее предложение FETCH и т.д. до окончания перебора всех продуктов Харчо.
1.2 | Содержание | 2.1








Помидоры100
Яблоки80
Зелень5
Майонез15
Помидоры55
Яблоки55
Сметана50
Зелень20
Лук15
Сахар5
Морковь150
Молоко50
Рис25
Масло20
Зелень10
Мука5
Помидоры250
Лук65
Масло20