Базы Данных - Сибилев, 2007
.pdf191
FROM J JOIN SPJ
WHERE Snum = ‘S1’;
Вариант 2
SELECT DISTINCT Jnam
FROM J JOIN SPJ ON Snum = ‘S1’
AND J.Jnum = SPJ.Jnum;
Вариант 3
SELECT Jnam
FROM J, (
SELECT DISTINCT Jnum
FROM SPJ
WHERE Snum = ‘S1’
) AS JS1
WHERE J.Jnum = JS1.Jnum;
Вариант 4
SELECT DISTINCT Jnam
FROM J, SPJ
WHERE Snum = ‘S1’
AND J.Jnum = SPJ.Jnum;
Возможны и другие варианты формулировок (см. п. 7.2.12).
Любой правильно сформулированный оператор выборки извлечёт из нашей БД таблицу, эквивалентную следующей:
Jnam
дисплей
процессор
Однако логика вариантов исполнения запросов различна.
Вариант 1 выглядит наиболее естественно. Формула оператора вы-
борки в точности следует логике словесной формулировки запроса в стиле
192
РА. Предложение FROM построит в рабочем буфере F-таблицу – естест-
венное соединение таблиц J и SPJ:
Jnum |
Jnam |
Ci |
Snum |
Pnum |
Qt |
J1 |
процессор |
Асино |
S4 |
P4 |
130 |
J1 |
процессор |
Асино |
S7 |
P6 |
2800 |
J1 |
процессор |
Асино |
S8 |
P1 |
400 |
J2 |
клавиатура |
Яя |
S7 |
P3 |
1000 |
|
|
|
|
|
|
J2 |
клавиатура |
Яя |
S7 |
P8 |
2500 |
J2 |
клавиатура |
Яя |
S8 |
P9 |
150 |
J3 |
дисплей |
Томск |
S1 |
P1 |
100 |
J3 |
дисплей |
Томск |
S1 |
P2 |
1000 |
J4 |
принтер |
Саяногорск |
S2 |
P9 |
200 |
J5 |
процессор |
Яя |
S1 |
P2 |
200 |
|
|
|
|
|
|
J6 |
клавиатура |
Томск |
S8 |
P1 |
2200 |
J6 |
клавиатура |
Томск |
S8 |
P3 |
100 |
J6 |
клавиатура |
Томск |
S8 |
P7 |
200 |
J7 |
мышь |
Лесото |
S2 |
P3 |
500 |
J8 |
джойстик |
Лондон |
S3 |
P4 |
150 |
J8 |
джойстик |
Лондон |
S5 |
P3 |
400 |
J9 |
винчестер |
Рио-де-Жанейро |
S3 |
P9 |
600 |
|
|
|
|
|
|
J9 |
винчестер |
Рио-де-Жанейро |
S6 |
P5 |
2800 |
|
|
|
|
|
|
J9 |
винчестер |
Рио-де-Жанейро |
S6 |
P8 |
300 |
Предложение WHERE выполнит селекцию F-таблицы по условию
Snum = ‘S1’ и создаст W-таблицу:
Jnum |
Jnam |
Ci |
Snum |
Pnum |
Qt |
J3 |
дисплей |
Томск |
S1 |
P1 |
100 |
J3 |
дисплей |
Томск |
S1 |
P2 |
1000 |
J5 |
процессор |
Яя |
S1 |
P2 |
200 |
Наконец, предложение SELECT со спецификатором DISTINCT соз-
даст требуемую проекцию W-таблицы на столбец Jnam. Спецификатор
DISTINCT необходим, т.к. исполняющая система не удаляет дубликаты строк автоматически.
Вариант 2, возможно, представляется менее наглядным, чем первый.
Формула оператора выборки соответствует следующей фразе: «Выполнить проекцию на атрибут Jnam соединения отношений J и SPJ по условию
Snum = ‘S1’ AND J.Jnum = SPJ.Jnum». В этом случае F-таблица эквива-
лентна W-таблице предыдущего варианта и предложение WHERE в опе-
раторе выборки не нужно.
193
Вариант 3, по-видимому, наименее нагляден. Он описывает сле-
дующую процедуру:
−создать отношение JS1 как проекцию на атрибут Jnum селекции отношения SPJ по условию Snum = ‘S1’;
−выполнить проекцию на атрибут Jnam селекции расширенного прямого произведения отношений J и JS1 по условию J.Jnum = JS1.Jnum.
Реализующий первый шаг подзапрос в предложении FROM произ-
ведёт неименованную временную таблицу. Для ссылок внутри включаю-
щего оператора SELECT ей будет присвоен псевдоним JS1.
JS1
Jnum |
J3
J5
В результате обработки предложения FROM на втором шаге будет произведена следующая F-таблица:
J.Jnum |
Jnam |
Ci |
JS1.Jnum |
J1 |
процессор |
Асино |
J3 |
J1 |
процессор |
Асино |
J5 |
J2 |
клавиатура |
Яя |
J3 |
J2 |
клавиатура |
Яя |
J5 |
J3 |
дисплей |
Томск |
J3 |
J3 |
дисплей |
Томск |
J5 |
J4 |
принтер |
Саяногорск |
J3 |
J4 |
принтер |
Саяногорск |
J5 |
J5 |
процессор |
Яя |
J3 |
J5 |
процессор |
Яя |
J5 |
J6 |
клавиатура |
Томск |
J3 |
J6 |
клавиатура |
Томск |
J5 |
J7 |
мышь |
Лесото |
J3 |
J7 |
мышь |
Лесото |
J5 |
J8 |
джойстик |
Лондон |
J3 |
J8 |
джойстик |
Лондон |
J5 |
J9 |
винчестер |
Рио-де-Жанейро |
J3 |
J9 |
винчестер |
Рио-де-Жанейро |
J5 |
Обрабатывая предложение WHERE, система удалит все строки F-
таблицы, в которых значения столбцов J.Jnum и JS1.Jnum различны. Будет получена следующая W-таблица:
J.Jnum |
Jnam |
Ci |
JS1.Jnum |
|
|
|
194 |
|
|
|
|
|
|
J3 |
дисплей |
Томск |
|
J3 |
J5 |
процессор |
Яя |
|
J5 |
Наконец, будет выполнена SQL-проекция этой таблицы на столбец
Jnam.
Вариант 4 в словесной формулировке таков: «Выполнить проекцию на атрибут Jnam селекции расширенного прямого произведения отноше-
ний J и SPJ по условию Snum = ‘S1’ AND J.Jnum = SPJ.Jnum». Он (единст-
венный из приведённых) соответствует стандарту SQL1. Мы не будем приводить здесь F- и W-таблицы, производимые в процессе обработки этого запроса. Читателю рекомендуется проделать это самостоятельно.
В заключение заметим, что на самом деле логика исполнения экви-
валентных операторов SELECT различна только в представлении авторов формулировок. Всякая реальная СУБД обязательно имеет подсистему оп-
тимизации запросов. Задачи этой подсистемы – приведение предложенной пользователем формулировки запроса к канонической форме и построение оптимального плана выполнения запроса. Поэтому эффективность самой процедуры выборки данных зависит не от варианта записи оператора
SELECT, а от того, насколько удачно сконструирован оптимизатор.
Выразительные возможности SQL в части формулирования запросов к данным мало отличаются от возможностей естественного языка. Форму-
лируя запрос, следует стремиться к ясности формулировки для человека,
нимало не заботясь о СУБД.
Пример 2. Получить имена всех поставщиков и наименования изде-
лий, для которых они поставляют детали.
Источником данных являются таблицы S, J и SPJ. Словесная форму-
лировка предполагает использование внешнего соединения. Один из воз-
можных планов выполнения запроса таков:
–получить естественное соединение отношений J и SPJ;
–получить левое внешнее соединение отношения S и результата пре-
дыдущего шага;
195
–выполнить проекцию результата предыдущего шага на атрибуты
Snam и Jnam.
Вот соответствующая этому плану формула оператора выборки:
SELECT DISTINCT Snam, Jnam
FROM S LEFT JOIN (J JOIN SPJ);
а вот результат его исполнения:
Snam |
Jnam |
Владимир |
клавиатура |
|
|
Владимир |
процессор |
|
|
Григорий |
винчестер |
|
|
Григорий |
джойстик |
|
|
Егор |
NULL |
Иван |
дисплей |
Иван |
процессор |
|
|
Константин |
джойстик |
|
|
Николай |
мышь |
|
|
Николай |
принтер |
Петр |
процессор |
|
|
Иван |
винчестер |
|
|
Сергей |
клавиатура |
|
|
Сергей |
процессор |
Эквивалентный результат произведёт оператор
SELECT DISTINCT Snam, Jnam
FROM (S LEFT JOIN SPJ) JOIN J;
Читатель, без сомнения, сможет предложить ещё ряд эквивалентных формул SQL2. Однако SQL1 не имеет столь компактных средств записи внешних соединений. Формула SQL1 для рассматриваемого запроса при-
ведена в п. 7.2.15
7.2.10 Предложение GROUP BY и агрегатные функции SQL
Оператор SELECT, включающий предложение GROUP BY, реали-
зует реляционную операцию подведения итогов [Ошибка! Источник ссылки не найден., п. 2.5.5]. Предложение GROUP BY определяет груп-
пы строк рабочего буфера (F- или W-таблицы), для которых должны быть вычислены значения агрегатных функций. После обработки предложения
196
строки рабочего буфера оказываются собранными в группы с одинаковы-
ми значениями столбцов, указанных после ключевого слова.
Например, если рабочий буфер содержит эквивалент таблицы SPJ, и
система приняла предложение
GROUP BY Snum, Jnum
то после его обработки в буфере будет содержаться следующая G-
таблица21:
Snum |
Pnum |
Jnum |
Qt |
S1 |
P1 |
J3 |
100 |
S1 |
P2 |
J3 |
1000 |
|
|
|
|
S2 |
P9 |
J4 |
200 |
S1 |
P2 |
J5 |
200 |
S7 |
P6 |
J1 |
2800 |
|
|
|
|
S3 |
P9 |
J9 |
600 |
S5 |
P3 |
J8 |
400 |
S6 |
P8 |
J9 |
300 |
S6 |
P5 |
J9 |
2800 |
|
|
|
|
S7 |
P8 |
J2 |
1000 |
S7 |
P3 |
J2 |
2500 |
|
|
|
|
S8 |
P7 |
J6 |
400 |
S8 |
P1 |
J6 |
150 |
S8 |
P3 |
J6 |
100 |
S8 |
P9 |
J2 |
200 |
S8 |
P1 |
J1 |
2200 |
|
|
|
|
S4 |
P4 |
J1 |
130 |
S2 |
P3 |
J7 |
500 |
S3 |
P4 |
J8 |
150 |
Замечание. При обработке все NULL-значения солбцов группирова-
ния считаются одинаковыми.
Для каждой группы строк, определённой предложением GROUP BY,
создаётся одна строка выходной таблицы запроса. Поэтому элементом це-
левого списка оператора выборки, содержащего это предложение, может быть либо столбец группирования, указанный после ключевого слова
GROUP BY, либо агрегатная функция, либо выражение, содержащее ссылки на столбцы группирования и агрегатные функции и принимающее единственное значение для группы строк.
21 Отдельные группы разделены горизонтальными линиями. Заметьте, что группы не упорядочены.
197
Замечание. Это требование выглядит логически неоправданным.
Следовало бы запретить использование в целевом списке запроса только тех столбцов, которые могут принимать различные значения в группе строк. Однако авторы стандарта не могли сформулировать такой запрет.
Он предполагает наличие средств объявления функциональных зависимо-
стей. Стандарты же не поддерживают как обязательное даже требование целостности сущности.
Стандарты SQL дают следующее определение синтаксиса агрегат-
ных функций:
агрегатная_функция ::= { COUNT(*) } |
{
{ COUNT | SUM | AVG | MAX | MIN }
( [DISTINCT | ALL] выражение )
}
Здесь выражение не может содержать агрегатные функции или подзапросы. На практике это, как правило, имя столбца источника данных. Однако стандарт допускает и более сложные выражения.
Если оператор выборки содержит предложение GROUP BY, то агрегатные функции могут использоваться в его целевом списке и в предикате предложения HAVING. При этом их значения вычисляются на
группах строк – по одному для каждой группы. В противном случае агрегатные функции можно использовать только в целевом списке и их значения вычисляются на всём множестве строк источника данных. Ни-
когда агрегатная функция не может встретиться в предикатах пред-
ложений WHERE или FROM иначе, как в подзапросе.
Функция COUNT(*) возвращает полное число строк в группе, вклю-
чая пустые и дубликаты.
При вычислении значений остальных пяти функций действуют сле-
дующие правила:
− перед вычислением значения функции все неопределённые значе-
198
ния аргумента исключаются;
− если определён параметр DISTINCT, то дубликаты значений аргу-
мента в подсчёте значения функции не участвуют, в противном случае ис-
пользуются все значения аргумента (параметр ALL определён по умолча-
нию);
− если аргумент пуст, т.е. содержит только NULL-значения, то функ-
ция COUNT возвращает значение 0 (ноль), а все остальные функции – значение NULL;
−функция COUNT всегда возвращает целое число; другие функции наследуют типы аргументов;
−функци SUM и AVG работают только с аргументами числовых ти-
пов;
− функции COUNT, MAX и MIN могут использоваться с аргумен-
тами любых типов.
Пример 1. Получить общее число зарегистрированных поставок.
Полагая, что каждой поставке соответствует одна и только одна строка в таблице SPJ, можем записать следующую формулу:
SELECT COUNT(*) AS Число_поставок
FROM SPJ;
Запрос возвратит следующую таблицу:
Число_поставок
19
В нашем частном случае это истина. Однако таблицы SQL – это не отношения. Представим себе, что не определён первичный ключ таблицы
SPJ. Тогда в ней могут оказаться и частично или полностью пустые строки,
и дубликаты строк, т.е. мусор, который нередко встречается в БД, создан-
ных дилетантами. Наш оператор подсчитает все строки. Поэтому, если мы не уверены в том, что таблица не содержит мусора, нам следует сформули-
ровать запрос так: «Подсчитать число строк таблицы SPJ, в которых все столбцы имеют определённые значения и наборы значений столбцов
199
Snum, Pnum, Jnum не повторяются». Попытайтесь самостоятельно записать соответствующий оператор. Он будет значительно сложнее приведённого выше.
Пример 2. Подсчитать средний объём поставки детали P1.
SELECT AVG(Qt)
FROM SPJ
WHERE Pnum = ‘P1’;
900
Пример 3. Для каждого поставщика подсчитать количество постав-
ляемых им видов деталей.
Это двусмысленная формулировка, но, скорее всего, именно так и будет сформулировано пожелание бухгалтера, для которого нужно создать программу подготовки отчётов. Что ему нужно на самом деле? Количество видов деталей, поставляемых каждым поставщиком, зарегистрированным в БД? Или только тем, кто реально выполнял поставки? Какие сведения о поставщике должны быть включены в выходную таблицу? Только номер?
Номер и имя? И т.п.
Пусть после уточнения требования пользователя оказались следую-
щими: «Для каждого существующего поставщика получить его номер и количество поставляемых им видов деталей». В текущем состоянии БД с некоторыми поставщиками могут быть не связаны какие-либо поставки.
Поэтому в качестве источника данных следует использовать левое внеш-
нее соединение таблиц S и SPJ.
SELECT S.Snum AS Поставщик,
COUNT(DISTINCT Pnum) AS Видов
FROM S LEFT JOIN SPJ
GROUP BY S.Snum;
Поставщик |
Видов |
|
|
S1 |
2 |
S7 |
3 |
200
S3 |
2 |
S9 |
0 |
S5 |
1 |
S6 |
2 |
S4 |
1 |
S8 |
4 |
S2 |
2 |
Замечание. В целевой список запроса не может быть включён, на-
пример, столбец Snam, так как он не входит в список столбцов группиро-
вания.
7.2.11 Предложение HAVING
Предложение HAVING определяет условие, которому должны удов-
летворять группы строк, созданные предложением GROUP BY. Предикат предложения HAVING может содержать только выражения для вычисле-
ния значений, принимающие единственное значение на группе строк.
Обычно используются агрегатные функции и ссылки на столбцы группи-
рования. В выходную таблицу запроса попадают только строки, соответст-
вующие группам, на которых предикат принял значение TRUE. Если
HAVING используется без GROUP BY, то группой строк, на которой оце-
нивается его предикат, является всё множество строк таблицы предыду-
щего уровня.
Пример 1. Получить значения номеров поставщиков, номеров дета-
лей и превосходящих 500 штук суммарных объёмов поставок этих деталей,
выполненных этими поставщиками.
SELECT Snum AS Поставщик,
Pnum AS Деталь,
SUM(Qt) AS Объём
FROM SPJ
GROUP BY Snum, Pnum
HAVING SUM(Qt) > 500;
Этот оператор произведёт следующую таблицу:
Поставщик Деталь Объём