SQL лекции
SQL лекции
Реляционный способ доступа к данным основывается на операциях с группами записей. Для задания операций используются
средства языка структурированных запросов — SQL (Structured Query Language), поэтому реляционный способ доступа называют
также SQL-ориентированным.
Средства SQL применимы для выполнения операций с локальными и удаленными БД. Наиболее полно преимущества
реляционного способа доступа и языка SQL проявляются при работе с удаленными БД. Основным достоинством реляционного
способа доступа является небольшая загрузка сети, поскольку передаются только запросы и результат их выполнения.
Применительно к локальным БД использование реляционного способа доступа не дает существенного преимущества, но и в
этом случае с помощью SQL-запроса можно:
♦ формировать состав полей набора данных при выполнении приложения;
♦ включать в набор данных поля и записи из нескольких таблиц;
♦ отбирать записи по сложным критериям;
♦ сортировать набор данных по любому полю, в том числе неиндексированному;
♦ осуществлять поиск данных по частичному совпадению со значениями полей.
Язык SQL имеет несколько стандартов, из которых наиболее распространенными среди производителей программных
продуктов являются стандарты SQL-89 и SQL-92. Стандарт SQL-92, поддерживаемый Американским национальным институтом
стандартов (ANSI — American National Standards Institute) и Международной организацией по стандартизации (ISO —
International Standard Organization), также называют стандартом ANSI или ANSI/ISO. Наличие нескольких стандартов и
различная их интерпретация породили множество диалектов языка SQL, которые в большей или меньшей степени отличаются
друг от друга.
В языке SQL можно выделить следующие основные подмножества операторов:
♦ определения данных;
♦ обработки данных;
♦ управления привилегиями (доступом к данным);
♦ управления транзакциями.
Рассмотрим основные возможности, реализованные в используемой в Delphi версии языка SQL. Эта версия несколько отличается от
стандарта SQL-92, например, в ней нельзя работать с просмотрами и управлять привилегиями.
Набрать и выполнить в интерактивном режиме текст SQL-запроса позволяют инструментальные программы, поставляемые вместе с
Delphi 5, например такие, как Database Desktop, SQL Explorer и SQL Builder.
Проверка синтаксиса и отработка запроса, встроенного в приложение производятся на этапе выполнения приложения. При наличии
синтаксических ошибок в тексте SQL-запроса генерируется исключительная ситуация, интерпретировать которую порой непросто.
Для отладки SQL-запросов удобно использовать программы с развитым интерфейсом, например Database Desktop. После отладки
текст запроса вставляется в разрабатываемое приложение. При таком подходе значительно сокращается время создания запросов и
существенно уменьшается вероятность появления динамических ошибок.
В качестве результата выполнения SQL-запроса может возвращаться набор данных, который составляют отобранные с
его помощью записи. Этот набор данных называют результирующим.
Стоит заметить, что запрос по своей природе не обязательно упорядочивает выходные данные каким-либо
определенным образом. Одна и та же команда, выполненная над одними и теми же данными в различные моменты времени, в
результате выдает данные, упорядоченные по-разному. Обычно строки выдаются в том порядке, в котором они представлены
в таблице, но этот порядок может быть совершенно произвольным. Необязательно, что данные в результате выполнения
запроса будут представлены в том порядке, в котором они вводятся или хранятся. Можно упорядочить выходные данные
непосредственно с помощью SQL-команд, указав специальное предложение.
Если необходимо увидеть каждую колонку таблицы, существует упрощенный вариант сделать это. Можно
использовать символ "*" ("звездочка"), который заменяет полный список столбцов.
SELECT *
FROM Salespeople;
Результат выполнения этой команды тот же, что и для рассмотренной ранее. Мощность команды SELECT
заключается в ее свойстве извлекать из таблицы лишь определенную информацию. Надо отметить возможность просмотра
только указанных столбцов таблицы. Для этого достаточно пропустить столбцы, которые нет необходимости просматривать, в
части команды SELECT. Например,
SELECT sname, comm FROM Salespeople;
Реляционный оператор — это математический символ, который задает определенный тип сравнения между двумя
значениями. Уже известно как применяются равенства, такие как 2+3=5 или city = 'London'. Однако существуют и другие
операторы сравнения. Предположим, необходимо вычислить продавцов (Salespeople), комиссионные (commissions) которых
превышают заданное значение. В этом случае следует воспользоваться сравнением типа "больше или равно". SQL распознает
следующие операторы сравнения:
= Равно
> Больше, чем
< Меньше, чем
>= Больше или равно
<= Меньше или равно
<> Неравно
Эти операторы имеют стандартное значение для числовых величин. Их определение для символьных значений
зависит от используемого формата представления (ASCII или EBCDIC). SQL сравнивает символьные значения в терминах
соответствующих чисел, определенных в формате преобразования. Символьные значения, представляющие числа, например,
необязательно равны тому числу, которое они представляют.
Операторы сравнения можно применять для того, чтобы представить алфавитный порядок; например, 'а' < 'n'
означает, что 'а' предшествует 'n' в алфавитном порядке, но эта процедура ограничена параметрами формата преобразования.
Как в ASCII, так и в EBCDIC, сохранен алфавитный порядок предшествования символов, представленных в одном и том же
регистре. В ASCII все заглавные символы меньше, чем все строчные, значит 'Z' < 'а', а все цифры меньше, чем все символы,
значит 1 < 'Z'. В EBCDIC все наоборот. Для простоты рассмотрения, предположим, что используется формат ASCII. Если точно
неизвестно, с каким форматом идет работа или как работает формат, то следует обратиться к документации.
Значения, которые здесь сравниваются, называются скалярными значениями. Скалярные значения получаются из
скалярных выражений: 1 + 2 является скалярным выражением, которое дает скалярное значение 3. Скалярные значения могут
быть символами или числами, хотя только числа используются с арифметическими операторами, такими как + или *.
Предикаты обычно сравнивают скалярные значения, используя, операторы сравнения или специальные SQL-операторы, для
того, чтобы проверить, является ли результат сравнения истинным.
Предположим, необходимо увидеть всех покупателей (Customers) с рейтингом (rating) более 200. Поскольку 200 — это
скалярное значение, как и все значения столбца rating, для их сравнения можно использовать оператор отношения:
SELECT *
FROM Customers
WHERE rating > 200;
При необходимости увидеть всех покупателей, рейтинг (rating) которых больше или равен 200, следовало бы
использовать предикат:
Rating >= 200.
SQL распознает основные булевы операторы. Булевы выражения — это те выражения, относительно которых,
подобно предикатам, можно сказать, истинны они или ложны. Булевы операторы связывают одно или несколько значений
"истина/ложь" и в результате получают единственное значение "истина/ложь". Стандартные булевы операторы,
распознаваемые SQL, — это AND, OR, NOT. Существуют и другие, более сложные булевы операторы (как, например, "исклю-
чающее ИЛИ"), но их можно построить с помощью трех простых. Булева логика "истина/ложь" представляет собой полный
базис для работы цифрового компьютера. Поэтому фактически весь SQL (или какой-либо другой язык программирования)
можно свести к булевой логике. Далее перечислены булевы операторы и основные принципы их действия:
• AND берет два булевых выражения (в виде A AND В) в качестве аргументов и дает в результате истину, если они оба
истинны.
• OR два булевых выражения (в виде A OR В) в качестве аргументов и оценивает результат как истину, если хотя бы один из
них истинен.
• NOT берет единственное булево выражение (в виде NOT А) в качестве аргумента и изменяет его значение с истинного на
ложное или с ложного на истинное.
Используя предикаты с булевыми операторами, можно значительно увеличить их избирательную мощность.
Предположим, необходимо увидеть всех покупателей (customers) из San Jose, чей рейтинг (rating) превышает 200:
SELECT *
FROM Customers
WHERE city = 'San Jose' AND rating > 200;
При использовании OR , будут получены сведения обо всех тех покупателях (customers), которые либо проживают в
San Jose, либо имеют рейтинг (rating), превышающий 200.
SELECT *
FROM Customers
WHERE city = 'San Jose' OR rating > 200;
NОТ дает возможность получить отрицание (противоположное значение) булева выражения. Вот пример запроса с
использованием NOT:
SELECT*
FROM Customers
WHERE city ='San Jose'
OR NOT rating > 200;
Заметим, что оператор NOT должен предшествовать булеву выражению, значение которого он должен изменить, но не может
располагаться непосредственно перед оператором сравнения.
Можно получить другой результат по следующей команде:
SELECT *
FROM Customers
WHERE NOT (city = 'San Jose'
OR rating > 200);
SQL понимает круглые скобки следующим образом: все то, что расположено внутри круглых скобок, вычисляется прежде
всего и рассматривается как единственное выражение по отношению к тому, что расположено за пределами круглых скобок (это
соответствует стандартной интерпретации в математике). Другими словами, SQL извлекает каждую строку и определяет,
выполняется ли для нее условие city = 'San Jose' или rating > 200. Если одно из этих выражений истинно, то булево выражение,
расположенное в круглых скобках, тоже истинно. Однако, если булево выражение в круглых скобках истинно, предикат в целом ложен,
поскольку NOT превращает истину в ложь и наоборот.
IN полностью определяет множество, которому данное значение может принадлежать или не принадлежать. Если
нужно найти всех продавцов, расположенных либо в 'Barcelona', либо в 'London', основываясь только на том, что известно к
настоящему моменту, необходимо написать следующий запрос:
SELECT *
FROM Salespeople
WHERE city IN ('Barcelona', 'London');
Как видно из примера, IN определяет множество, элементы которого точно перечисляются в круглых скобках и
разделяются запятыми. Если в поле, имя которого указано слева от IN, есть одно из перечисленных в списке значений
(требуется точное совпадение), то предикат считается истинным. Если элементы множества имеют числовой, а не
символьный тип, то одиночные кавычки непосредственно слева и справа от значения необходимо опустить. Можно найти всех
покупателей, обслуживаемых продавцами 1001, 1007, 1004.
SELECT *
FROM Customers
WHERE snum IN (1001,1007,1004);
Оператор BETWEEN
Оператор BETWEEN сходен с IN. Вместо перечисления элементов множества, как это делается в IN, BETWEEN
задает границы, в которые должно попадать значение, чтобы предикат был истинным. Используется ключевое слово
BETWEEN, за которым следуют начальное значение, ключевое слово AND и конечное значение. Также как и IN, BETWEEN
чувствителен к порядку: первое значение в предложении должно быть первым в соответствии с алфавитным или числовым
порядком.
Следующий запрос позволит извлечь из таблицы Salespeople всех продавцов (salespeople), комиссионные которых имеют
величину в диапазоне .10 и .12:
SELECT
FROM Salespeople
WHERE comm BETWEEN .10 AND. 12;
Оператор BETWEEN является включающим, т.е. граничные значения (в данном примере это .10 и .12) делают
предикат истинным. SQL непосредственно не поддерживает исключающий BETWEEN. Необходимо сформулировать гра-
ничные значения так, чтобы включающая интерпретация была справедлива, либо сделать примерно следующую запись:
SELECT *
FROM Salespeople
WHERE (comm BETWEEN .10, AND.12) AND NOT comm IN (.10, .12);
Пусть эта запись и неуклюжа, но она показывает, как новые операторы можно комбинировать с булевыми операторами для
получения более сложных предикатов. Значит, IN и BETWEEN используются, как и операторы сравнения, для сопоставления
значений, одно из которых является множеством (для IN) или диапазоном (для BETWEEN).
Аналогично всем операторам сравнения, BETWEEN действует на символьных полях, представленных в двоичном
(ASCII) эквиваленте, т.е. для выборки можно воспользоваться алфавитным порядком. Следующий запрос выбирает всех
покупателей имена, которых попадают в заданный алфавитный диапазон:
SELECT *
FROM Customers
WHERE cname BETWEEN 'A' AND 'G';
Оператор LIKE
LIKE применим только к полям типа CHAR или VARCHAR, поскольку он используется для поиска подстрок. Другими
словами, он осуществляет просмотр строки для выяснения: входит ли заданная подстрока в указанное поле. С это же целью
используются шаблоны — специальные символы, которые могут обозначать все, что угодно. Существует два типа шаблонов,
используемых с LIKE:
• Символ "подчеркивание" ( _ ) заменяет один любой символ. Например, образцу 'b_t' соответствуют 'bat' или 'bit', но не
соответствует 'brat'.
• Символ "процент" (%) заменяет последовательность символов произвольной длины, в том числе и нулевой. Например,
образцу '%p%t' соответствуют 'put', 'posit', 'opt', но не 'spite'.
Можно найти покупателей, фамилии которых начинаются на 'G':
SELECT *
FROM Customers
WHERE cname LIKE 'G%';
LIKE может оказаться полезным при осуществлении поиска имени или другого значения, полное написание которого
неизвестно. Предположим, не совсем понятно, как правильно записывается фамилия одного из продавцов (salespeople): Peal
или Peel. Можно использовать ту часть, которая известна, и символы шаблона для нахождения всех возможных вариантов:
SELECT *
FROM Salespeople
WHERE sname LIKE 'P_I%';
Каждый символ подчеркивания в шаблоне представляет единственный символ, поэтому, например, имя Prettel не
вошло бы в состав выходных данных. Символ шаблона ( % ) в конце строки необходим в тех реализациях SQL, в которых
длина поля sname превосходит количество букв в имени. Символ ( % ) в шаблоне заменяет все пробелы. Все
вышеперечисленное не относится к полю sname типа VARCHAR.
Чтобы найти в строке символ подчеркивания или процента, в предикате LIKE любой символ можно определить как
Escape-символ. Он используется в предикате непосредственно перед символом процента или подчеркивания и означает, что
следующий за ним символ интерпретируется именно как обычный символ, а не как символ шаблона. Например, поиск символа
подчеркивания в столбце sname можно задать следующим образом:
SELECT *
FROM Salespeople
WHERE sname LIKE '%/_%'ESCAPE'/';
Для тех данных, которые хранятся в таблице в текущий момент времени, выходных данных нет, поскольку в именах
продавцов нет подчеркиваний. Предложение ESCAPE определяет '/' как Escape-символ, который используется в LIKE-строке,
за ним следуют символ процента, символ подчеркивания или сам символ '/', т.е. тот символ, поиск которого будет
осуществляться в столбце и который уже не интерпретируется как символ шаблона. Escape-символ может быть единственным
символом и применяться только к единственному символу, который следует за ним. В приведенном примере начальный и
конечный символы процента являются символами шаблона, только символ подчеркивания представляет собой символ как
таковой.
Escape-символ может использоваться и в своем собственном значении. Другими словами, если нужно найти в столбце
Escape-символ, то его необходимо ввести дважды. Первый раз он действует как обычный Escape-символ и означает:
«следующий символ надо понимать буквально так, как он указан», а второй раз указывает на то, что речь идет
непосредственно об Escape-символе. Далее представлен пример поиска строки '_/' в столбце sname:
SELECT *
FROM Salespeople
WHERE sname LIKE '%/_//%'ESCAPE'/';
Просматриваемая строка состоит из любой последовательности символов (%), за которыми следуют символ подчеркивания
(/_), Escape-символ (//) и любая последовательность заключительных символов (%).
Работа с NULL-значениями
Часто в таблице встречаются записи с незаданными значениями какого-либо полей, потому что значение поля
неизвестно или его просто нет. В таких случаях SQL позволяет указать в поле NULL-значение. Строго говоря, NULL-значение
вовсе не представлено в поле. Когда значение поля, есть, NULL это значит, что программа базы данных специальным образом
помечает поле, как не содержащее какого-либо значения для данной строки (записи). Дело обстоит не так в случае простого
приписывания полю значения "нуль" или "пробел", которые база данных трактует как любое другое значение. Поскольку NULL
не является значением как таковым, он не имеет типа данных. NULL может раздаться в поле любого типа. Тем не менее,
NULL, как NULL-значение, часто используется в SQL.
Предположим, появился покупатель, которому еще не назначен продавец. Чтобы констатировать этот факт. нужно
ввести значение NULL в поле snum, а реальное учение включить туда позже, когда данному покупателю будет назначен
продавец.
Оператор IS NULL
Поскольку NULL фиксирует пропущенные значения, результат любого сравнения при наличии NULL-значений
неизвестен. Когда NULL-значение сравнивается с любым значением, даже с NULL-значением, результат просто неизвестен.
Булево значение "неизвестно" ведет себя также, как "ложь" — строка на которой предикат принимает значение "неизвестно",
не включается в результат запроса — при одном важном исключении: NOT от лжи есть истина (NOT (false)=true), тогда как
NOT от неизвестного значения есть также неизвестное значение. Следовательно, такое выражение как "city = NULL" или "city
IN (NULL)” является неизвестным независимо от значения city.
Часто необходимо различать false и unknown — строки, содержащие значения, не удовлетворяющие предикату, и строки,
которые содержат NULL. Для этой цели SQL располагает специальным оператором IS, который используется с ключевым
словом NULL для локализации NULL-значения.
Для нахождения всех записей со значениями NULL в таблице Customers в столбце city следует ввести:
SELECT *
FROM Customers
WHERE city IS NULL;
Запросы могут обобщать не только группы значений, но и значения одного поля. Для этого применяются агрегатные
функции. Они дают единственное значение для целой группы строк таблицы. Ниже приводится список этих функций:
• COUNT определяет количество строк или значений поля, выбранных посредством запроса и не являющихся NULL-
значениями;
• SUM вычисляет арифметическую сумму всех выбранных значений данного поля;
• AVG вычисляет среднее значение для всех выбранных значений данного поля;
Предложение HAVING
Обращаясь к предыдущему примеру, можно предположить, что интересны только покупки, превышающие $3000.00.
Однако использовать агрегатные функции в предложении WHERE нельзя (если только не применяется подзапрос, который
будет объяснен позднее), поскольку предикаты оцениваются в терминах единственной строки, тогда как агрегатные функции
оцениваются в терминах групп строк.
Чтобы увидеть максимальную покупку, превышающую $3000.00, следует использовать предложение HAVING. Оно
определяет критерий, согласно которому определенные группы исключаются из числа выходных данных, так же, как
предложение WHERE делает это для отдельных строк. Команда выглядит так:
SELECT snum, odate, MAX (amt)
FROM Orders
GROUP BY snum, odate HAVING MAX (amt) > 3000.00;
Аргументы HAVING подчиняются тем же правилам, что и аргументы SELECT в команде, использующей GROUP BY, и
должны иметь единственное значение для каждой выходной группы.
HAVING может иметь только такие аргументы, у которых единственное значение для группы выходных данных. На
практике чаще всего применяются агрегатные функции, но можно осуществлять выбор полей и с помощью GROUP BY.
В версии языка SQL, определяемой ANSI, нельзя применять агрегатную функцию с агрегатом в качестве
аргумента.
SELECT*
FROM Orders
ORDER BY cnum DESC;
Внутри уже произведенного упорядочения по полю спит можно упорядочить таблицу и по другому столбцу, например,
amt:
SELECT *
FROM Orders
ORDER BY cnum DESC, amt DESC;
Так можно использовать ORDER BY одновременно для любого количества столбцов. Во всех случаях столбцы, по
которым выполняется сортировка, входят в число выбранных.
Упорядочение составных групп
ORDER BY может использоваться с GROUP BY для упорядочения групп. ORDER BY всегда выполняется последней.
Пример с добавлением предложения ORDER BY. До этого выходные данные были сгруппированы, но порядок групп был
произвольным; теперь группы выстроены в определенной последовательности:
SELECT snum, odate, MAX(amt)
FROM Orders
GROUP BY snum, odate ORDER BY snum;
Поскольку в команде не указан способ упорядочения, по умолчанию применяется возрастающий.
Упорядочение результата по номеру столбца
Вместо имен столбцов для указания полей, по которым упорядочиваются выходные данные, можно использовать
номера. Но, ссылаясь на них, следует иметь в виду, что это номера в определении выходных данных, а не столбцов в
таблице. Т.е. первое поле, имя которого указано в SELECT, является для предложения ORDER BY полем с номером 1,
независимо от его расположения в таблице. Например, можно применить следующую команду, чтобы увидеть определенные
поля таблицы Salespeople, упорядоченные по убыванию поля commission (comm):
SELECT sname, comm
FROM Salespeople
ORDER BY 2 DESC;
Столбцы, полученные с помощью функций агрегирования, константы или выражения в предложении запроса SELECT,
можно применить и с ORDER BY, если на них ссылаются по номеру. Например, чтобы подсчитать заявки (orders) для каждого
продавца (salespeople) и вывести результаты в убывающем порядке:
SELECT snum, COUNT (DISTINCT onum)
FROM Orders
GROUP BY snum ORDER BY 2 DESC;
В этом случае был использован номер столбца, но так как выходной столбец не имеет имени, саму функцию
агрегирования применять не понадобилось. В соответствии со стандартом ANSI SQL, следующий запрос не работает, хотя в
некоторых системах он воспринимается без проблем:
SELECT snum, COUNT (DISTINCT onum)
FROM Orders;
ORDER BY с NULL-значениями
Если в поле, которое используется для упорядочения выходных данных, существуют NULL-значения, то все они
следуют в конце или предшествуют всем остальным значениям этого поля. Конкретный вариант не оговаривается стандартом
ANSI, вопрос решается индивидуально для каждого программного продукта, и один из этих вариантов принимается.
Выполнение операции соединения двух копий одной таблицы. Соединение таблицы с ее же копией означает
следующее: любую строку таблицы (одну в каждый момент времени) можно комбинировать с ее копией и с любой другой
строкой этой же таблицы. Каждая такая комбинация оценивается в терминах предиката, как и в случае соединения
нескольких различных таблиц. Это позволяет легко конструировать определенные виды связей между различными записями
внутри единственной таблицы — например, осуществлять поиск пар строк с общим значением поля.
Соединение таблицы со своей копией можно представить себе следующим образом: реально таблица не копируется,
но SQL выполняет команду так, как будто бы делалось именно это. Другими словами, подобный тип соединения не
отличается от обычного соединения двух таблиц, за исключением того, что в данном случае они идентичны.
Алиасы
Синтаксис команды соединения таблицы с ее же копией тот же, что и для различных таблиц, с единственным
исключением. В рассматриваемом случае все имена столбцов повторяются независимо от использования имени таблицы в
качестве префикса. Чтобы сослаться на столбцы запроса, нужно иметь два различных имени для одной и той же таблицы.
Для этого надо определить временные имена, называемые переменными области определения, переменными
корреляции или просто алиасами. Они определяются в предложении запроса FROM. Для этого указывается имя таблицы,
ставится пробел, а затем указывается имя алиаса для данной таблицы.
Приведем пример поиска всех пар продавцов, имеющих одинаковый рейтинг:
SELECT first.cname, second.cname, first.rating
FROM Customers first, Customers second
WHERE first.rating = second.rating;
В приведенном примере команды SQL ведет себя так, как будто в операции соединения участвуют две таблицы,
называемые "first" (первая) и "second" (вторая). Обе они в действительности являются таблицей Customers, но алиасы по-
зволяют рассматривать ее как две независимые таблицы. Алиасы first и second были определены в предложении запроса
FROM непосредственно за именем таблицы. Алиасы применяются также в предложении SELECT, несмотря на то, "то они не
определены вплоть до предложения FROM. Это совершенно оправдано. SQL сначала примет какой-либо из таких алиасов на
веру, но затем отвергнет команду, если в предложении FROM запроса алиасы не определены. Время жизни алиаса зависит от
времени выполнения команды. После выполнения запроса используемые в нем алиасы теряют свои значения. Получив две
копии таблицы Customers для работы, SQL выполняет операцию JOIN, как для двух разных таблиц: выбирает очередную
строку из одного алиаса и соединяет ее с каждой строкой другого алиаса.
Исключение избыточности. Выходные данные включают каждую комбинацию значений дважды, причем во второй раз — в
обратном порядке. Это объясняется тем, что значение появляется один раз для каждого алиаса, а предикат является
симметричным. Следовательно, значение А в алиасе first выбирается в комбинации со значением В в алиасе second, и
значение А в алиасе second — в комбинации со значением В в алиасе first. Каждая запись присоединяется сама к себе в
выходных данных, например, Lie и Lie.
Простой способ исключить повторения — задать порядок для двух значений так, чтобы одно значение было меньше,
чем другое, или предшествовало в алфавитном порядке. Это делает предикат ассиметричным, и одни и те же значения не
извлекаются снова в обратном порядке, например:
SELECT flrst.cname, second.cname, first.rating
FROM Customers first, Customers second
WHERE first.rating = second.rating
AND first.cname < second.cname;
Если нужно включить в запрос соединение строки с ее копией, достаточно использовать <= вместо <.
Более сложные операции соединения
В запросе можно использовать любое количество алиасов для единственной таблицы, хотя применение более двух в
одном предложении SELECT нетипично. Предположим, продавцам (salespeople) еще не назначили покупателей (customers).
Политика кампании состоит в том, чтобы назначить всем продавцам по три покупателя, каждому из которых приписывается
одно из трех возможных значений рейтинга. Необходимо решить, как осуществить такое распределение, и использовать
следующие запросы для просмотра всех возможных комбинаций назначаемых покупателей .
SELECT a.cnum, b.cnum, c.cnum
FROM Customers a, Customers b, Customres с
WHERE a.rating = 100
AND b.rating = 200
AND c.rating = 300;
Этот запрос находит все возможные комбинации покупателей (customers) с тремя значениями рейтинга таким
образом, что в первом столбце расположены покупатели с рейтингом 100, во втором столбце — покупатели с рейтингом 200, в
третьем столбце — покупатели с рейтингом 300. Они повторены во всех возможных комбинациях. Это своего рода
группирование данных, которое нельзя выполнить средствами GROUP BY или ORDER BY, поскольку они сравнивают
значения только из одного столбца.
Каждый алиас или таблицы, имена которых упомянуты в предложении FROM запроса SELECT, использовать
необязательно. Иногда алиас или таблица запрашиваются таким образом, что на них ссылаются предикаты запроса.
Например, следующий запрос находит всех покупателей (customers), расположенных в городах, где действует
продавец Serres (snum 1002):
SELECT b.cnum, b.cnam
FROM Customers a, Customers b
WHERE a.snum = 1002 AND b.city = a.city;
Алиас а сделает предикат ложным, за исключением случаев, когда столбца snum равно 1002. Так алиас исключает всех покупателей,
кроме покупателей продавца Serres. Алиас b принимает значение "истина" для всех стр же значением города (city), что и текущее значение города
(city) в а; в выполнения запроса строка с алиасом b делает предикат истинным вся когда в поле city этой строки представлено то же значение, что
и в поле cil с алиасом а. Поиск строк алиаса b выполняется исключительно для q значений с алиасом а, из строк с алиасом b никакого реального
выбора д; выполняется. Покупатели (customers) продавца Serres расположены в одном и том же городе, значит выбор их из алиаса а не является
необходимым. Итак локализует строки покупателей (customers) Serres, Liu и Grass. Алиас b находит всех покупателей (customers), расположенных
в одном из городов (San Jose соответственно), включая, конечно, самих Liu и Grass.
Можно конструировать соединения (joins), которые содержат различные таблицы и алиасы единственной таблицы. Следующий запрос
соединяет Customers с ее копией для нахождения всех пар покупателей, обслуживаемых одним и тем же продавцом. В любой момент времени он
соединяет покупателя (customer) с таблицей Salespeople для того, чтобы определить имя продавца (salesperson):
SELECT sname, Salespeople.snum, first.cname, second.cname
FROM Customers first, Customers second, Salespeople
WHERE first.snum = second.snum
AND Salespeople.snum = first.snum
AND first.cnum < second.cnum;
SQL позволяет вкладывать запросы друг в друга. Обычно внутренний запрос генерирует значения, которые
тестируются на предмет истинности предиката. Предположим, известно имя, но мы не знаем значения поля snum для
продавца Monica. Необходимо извлечь все ее заказы из таблицы Orders. Перед вами один из способов решения этой
проблемы
SELECT *
FROM Orders WHERE snum =
(SELECT snum
FROM Salespeople
WHERE sname = 'Monika');
Чтобы оценить внешний (основной) запрос, SQL прежде всего должен оценить внутренний запрос (или подзапрос) в
предложении WHERE. Эта оценка осуществляется так, как если бы запрос был единственным: просматриваются все строки
таблицы Salespeople и выбираются все строки, для которых значение поля sname равно Motika, для таких строк выбираются
значения поля snum.
В результате выбранной оказывается единственная строка с snum = 1004. Однако вместо простого вывода этого значения
SQL подставляет его в предикат основного запроса вместо самого подзапроса, теперь предикат читается следующим
образом:
WHERE snum = 1004
Затем основной запрос выполняется как обычный, и его результат точно такой же. Подзапрос должен выбирать один и
только один столбец, а тип данных этого столбца должен соответствовать типу значения, указанному в предикате. Часто
выбранное поле и это значение имеют одно и то же имя (в данном случае, snum).
Если бы было известно значение персонального номера продавца (salesperson number) Monika, то можно было бы
указать:
WHERE snum = 1004
и тем самым освободиться от подзапроса, однако использование подзапроса делает процедуру более гибкой.
Вариант с подзапросом сработает и в случае изменения персонального номера продавца Monika. Простая замена имени
продавца
в подзапросе позволяет использовать его во множестве вариантов.
При использовании подзапросов, основанных на операторах отношения, нужно быть уверенным, что выходными
данными подзапроса является только одна строка. Если применяется подзапрос, не генерирующий никаких значений, то это
не является ошибкой, однако в настоящем случае и основной запрос не даст никаких выходных данных. Подзапросы, не
генерирующие никаких выходных данных (или NULL-выход), приводят к тому, что предикат оценивается не как истинный или
ложный, а как имеющий значение "неизвестно" (unknown). Предикат со значением "неизвестно" работает как и предикат со
значением "ложь": основной запрос не выбирает ни одной строки.
DISTINCT с подзапросами
В некоторых случаях можно использовать DISTINCT для гарантии получения единственного значения в результате
выполнения подзапроса. Предположим, нужно найти все заказы (orders), с которыми работает продавец, обслуживающий
покупателя Hoffman (cnum = 2001). Вот один из вариантов решения этой задачи:
SELECT *
FROM Orders
WHERE snum =
(SELECT DISTINCT snum
FROM Orders
WHERE cnum = 2001);
Подзапрос выясняет, что значение поля snum для продавца, обслуживающего Hoffman, равно 1001; следовательно,
основной запрос извлекает из таблицы Orders всех покупателей с тем же значением поля snum. Поскольку каждый покупатель
обслуживается только одним продавцом, каждая строка таблицы Orders с данным значением cnum имеет то же значение поля
snum. Однако, поскольку может быть любое количество таких строк, подзапрос может дать в результате множество значений
snum (возможно, одинаковых) для данного cnum. Если бы подзапрос возвращал более одного значения, получилась бы
ошибка в данных. Аргумент DISTINCT предотвращает такую ситуацию.
Альтернативный способ действия — сослаться в подзапросе на таблицу Customers, а не на таблицу Orders. Поскольку cnum —
первичный ключ таблицы Customer, есть гарантия, что в результате выполнения запроса будет получено единственное
значение. Однако, если пользователь имеет доступ к таблице Orders, а не к таблице Customers, остается вариант,
рассмотренный ранее. (SQL располагает механизмами определения того, кто и какие привилегии имеет при работе с
таблицами).
Надо помнить, что приведенный в предыдущем примере прием приемлем только тогда, когда есть уверенность, что в
двух различных полях содержатся одни и те же значения. Такая ситуация является скорее исключением, а не правилом для
реляционных баз данных.
EXISTS — оператор, генерирующий значение "истина" или "ложь", другими словами, булево выражение. Это значит, что его
можно применять отдельно в предикате или комбинировать с другими булевыми выражениями с помощью операторов AND, OR и NOT.
Используя подзапрос в качестве аргумента, этот оператор оценивает его как истинный, если он генерирует выходные данные, а в
противном случае как ложный. В отличие от прочих операторов и предикатов, он не может принимать значение unknown. Например, нужно
извлечь данные из таблицы Customers в том случае, если один (или более) покупатель из нее находится в San Jose:
SELECT cnum, cname, city
FROM Customers
WHERE EXISTS
(SELECT*
FROM Customers
WHERE city ='San Jose');
Внутренний запрос выбрал все данные обо всех покупателях из San Jose. Оператор EXISTS внешнего предиката отметил, что
подзапрос генерирует выходные данные, и поскольку выражение EXISTS является единственным в этом предикате, он принимает
значение "истинно". Здесь подзапрос (не являющийся связанным) выполняется только один раз для всего внешнего запроса и,
следовательно, имеет единственное значение для всех случаев. Поскольку EXISTS в данном примере делает предикат истинным или
ложным для всех строк сразу, применять его в таком стиле для извлечения специфической информации не стоит.
В приведенном примере EXISTS выбирает один столбец, в отличие от с звездочки, выбирающей все столбцы, что
отличается от рассмотренных ранее подзапросов, где извлекался единственный столбец. Однако несущественно, сколько
столбцов извлекает EXISTS, поскольку он вообще не применяет полученных значений, а лишь фиксирует наличие выходных данных
подзапроса.
Использование EXISTS со связанными подзапросами
При применении связанных подзапросов предложение EXISTS, как и другие
предикатные операторы, оценивается отдельно для каждой строки таблицы, на
которую есть ссылка во внешнем запросе. Это позволяет использовать EXISTS
как правильный предикат, генерирующий различные ответы для каждой строки
таблицы, на которую есть ссылка в основном запросе. Следовательно, при таком
способе применения EXIST информация из внутреннего запроса сохраняется,
если непосредственно не выводится. Например, можно сделать запрос на поиск
тех продавцов, которые имеют нескольких покупателей:
SELECT DISTINCTsnum
FROM Customers outer
WHERE EXISTS
(SELECT*
FROM Customers inner
WHERE inner.snum = outer.snum
AND inner.cnum <> outer.cnum);
Для каждой строки-кандидата внешнего запроса (представляющей рассматриваемого в настоящий момент покупателя),
внутренний запрос находит строки, имеющие соответствующее значение snum (имеет того же продавца), но не значение сnum
(соответствует другому покупателю). Если строка, удовлетворяющая подобному критерию, найдена во внутреннем запросе, это означает,
что различные покупатели обслуживаются данным продавцом (т.е. продавцом, обслуживающим покупателя, указанного в строке-
кандидате внешнего запроса). Следовательно, предикат EXISTS истинен для текущей строки, и номер поля продавца (snum) из
таблицы внешнего запроса включается в состав выходных данных. Если бы не был задан DISTINCT, каждый из таких продавцов
выбирался бы один раз для каждого покупателя, которого он обслуживает.
Комбинирование EXISTS и соединений
Иногда кроме номера требуется получить о каждом продавце больше информации. Это можно сделать соединением таблиц
Customers и Salespeople:
SELECT DISTINCT first.snum, snatne, first.city
FROM Salespeople first, Customers second
WHERE EXISTS
(SELECT*
FROM Customers third
WHERE second.snum = third.snum ;
AND second.cnum <> third.cnum)
AND first.snum = second.snum;
Внутренний запрос тот же, что и в предыдущем примере, изменены только алиасы. Внешний запрос является соединением
таблиц Salespeople и Customers. Новое предложение основного предиката (AND first.snum = second.snum) оценивается на том же
уровне, что и предложение EXISTS. Это функциональный предикат самого соединения, сравнивающий две таблицы из внешнего
запроса в терминах общего для них поля snum. Поскольку используется булев оператор AND, оба условия, сформулированные в
предикатах основного запроса, должны быть истинными для истинности этого предиката. Следовательно, результаты подзапроса
действуют в случаях, когда вторая часть запроса истинна и соединение выполняется. Комбинирование соединений и подзапросов
указанным способом является весьма эффективным методом обработки данных.
Использование NOT EXISTS
Из предыдущего примера ясно, что EXISTS можно комбинировать с булевыми операторами. С EXISTS легче всего применять —
и чаще всего применяется — оператор NOT. Один из способов поиска всех продавцов, имеющих только одного покупателя, — это
поставить NOT перед EXISTS в предыдущем примере:
SELECT DISTINCT snum
FROM Customers outer
WHERE NOT EXISTS
(SELECT*
FROM Customers inner
WHERE inner.snum = outer.snum
AND inner.cnum <> outer.cnum);
EXISTS и агрегаты
EXISTS не может использовать агрегатные функции в своем подзапросе. Это естественно. Если функция агрегирования
находит строки для работы с ними, то EXISTS принимает значение "истина", и ему безразлично реальное значение функции; если
функция не находит никаких строк, то значение EXISTS — "ложь". Попытка использовать функции агрегирования с EXISTS
подобным образом свидетельствует о том, что проблема не была правильно понята.
Подзапрос для предиката EXISTS тоже может иметь в своем составе один или несколько подзапросов любого типа. Эти
подзапросы, как и любые другие, входящие в их состав, могут использовать агрегатные функции, если только не существует каких-
либо иных причин, по которым это невозможно. Пример, иллюстрирующий такую ситуацию, приведен в следующем разделе.
В любом случае можно получить тот же результат проще — выбрать поле, которое используется в агрегатной функции,
вместо применения самой функции. Другими словами, предикат EXISTS (SELECT COUNT (DISTINCT sname) FROM salespeople)
эквивалентен предикату EXISTS (SELECT sname FROM Salespeople), причем первый из них тоже допустим.
Предикат с ALL принимает значение "истина", если каждое (every) значение, выбранное в процессе выполнения
подзапроса, удовлетворяет условию, заданному в предикате внешнею запроса. Если бы было нужно, чтобы в предыдущем
примере в состав выходных данных включались только те покупатели, рейтинг которых превышает рейтинг каждого
покупателя в Rome, то необходимо было бы ввести следующую команду для получения результата:
SELECT *
FROM Customers
WHERE rating > ALL
(SELECT rating
FROM Customers
WHERE city = 'Rome');
Это предложение проверяет значения рейтинга для всех покупателей в Риме, а затем находит тех покупателей,
рейтинг которых превышает рейтинг каждого покупателя из Рима. Следовательно, в состав выходных данных включаются
только те покупатели, рейтинг которых превышает 200.
EXISTS можно использовать. как в случае с ANY, для получения альтернативной формулировки того же запроса:
SELECT *
FROM Customers outer
WHERE NOT EXISTS
(SELECT *
FROM Customers inner
WHERE outer.rating <= inner.rating
AND inner.city = 'Rome');
Равенства и неравенства
ALL как правило, используется с неравенствами. а не с равенствами, поскольку значение "равно всем" которое должно
полечиться в результате выполнения подзапpoca. может быть получено в случае, если все результаты идентичны. Следующий
запрос выглядит так:
SELECT *
FROM Customers
WHERE rating = ALL
(SELECT rating
FROM Customers
WHERE city = 'San Jose');
Команда задана корректно, но в настоящем примере выгодные данные не будут получены. Единственный случай,
когда этот запрос продуцирует выходные данные, — значения рейтинга всех покупателей из San Jose одинаковы. Тогда запрос
можно сформулировать иначе:
SELECT *
FROM Customers
WHERE rating =
(SELECT DISTINCT rating
FROM Customers WHERE city = 'San Jose');
Основное различие заключается в признании последней команды ошибочной, если в результате выполнения подзапроса
будет получено множество значений; В ЭТОМ случае вариант с ALL просто не генерирует никаких выходных данных.
Следовательно, предыдущий запрос можно сформулировать, например, так:
SELECT *
FROM Customers
WHERE rating <> ALL
(SELECT rating
FROM Customers WHERE city = 'San
Jose');
Этот подзапрос выбирает все рейтинги, для которых в поле city указано значение San Jose. В результате получается
множество из двух значений: 200 и 300. Основной запрос затем выбирает все строки, в которых значение поля rating
отличается oт этих значений, т.е. все строки со значением рейтинга 100. Этот же запрос можно сформулировать с NOT IN:
SELECT *
FROM Customers
WHERE rating NOT IN
(SELECT rating
FROM Customers
WHERE city = 'San Jose');
Можно также использовать ANY:
SELECT*
FROM Customers
WHERE NOT rating = ANY
(SELECT rating
FROM Customers WHERE city = 'San Jose');
Выходные данные одинаковы для трех последних запросов.
Можно задать множество запросов одновременно и комбинировать их выходные данные с использованием предложения
UNION. UNION объединяет выходные данные двух или более SQL-запросов в единое множество строк и столбцов. Для того
чтобы получить сведения обо всех продавцах (salespeople) и покупателях (customers) Лондона в виде выходных данных одного
запроса, следует ввести:
SELECT snum,
FROM Salespeople
WHERE city ='London'.
UNION
Среди них есть повторяющаяся комбинация значений (1001 с London), поскольку в запросе SQL не требуется исключать
дубликаты (повторяющиеся значения). Однако, если применяется UNION для комбинации этого запроса с таким же запросом
для таблицы Salespeople, избыточная комбинация исключается:
SELECT snum, city
FROM Customers
UNION
SELECT snum, city
FROM Salespeople;
Можно добиться того же (в некоторых программных продуктах SQL), указав
UNION ALL вместо UNION:
SELECT snum, city FROM Customers
UNION ALL
SELECT snum, city
FROM Salespeople;
UNION
SELECT a.snum, sname, onum, 'Lowest on', odate
FROM Salespeople a. Orders b
WHERE a.snum = b-cnum
AND b.amt =
(SELECT MIN (amt) FROM Orders с WHERE c.odate = b.odate);
Использование UNION с ORDER BY
Предложение ORDER BY применяется и для упорядочения выходных данных объединения, так же как это делалось для
отдельных запросов. Можно пересмотреть последний пример, предположив необходимость упорядочения выходных данных
запроса:
SELECT a.snum, sname, onum, 'Highest on', odate
FROM Salespeople a, Orders b
WHERE a.snum = b.snum
AND b.amt =
(SELECT MAX (amt)
FROM Orders с
WHERE c.odate = b.odate)
UNION
SELECT a.snum, sname, onum, 'Lowest on', odate
FROM Salespeople a, Orders b
WHERE a.snum = b.snum
AND b.amt =
(SELECT MIN (amt)
FROM Orders с
WHERE c.odate = b.odate)
ORDER BY 3;
Поскольку способ упорядочения по возрастанию (ASC) для ORDER BY принят по умолчанию, он специально не
указывается. Можно упорядочить выходные данные в соответствии со значениями нескольких полей: для каждого из полей
независимо по возрастанию или убыванию (ASC или DESC), как это делалось для выходных данные одного запроса. Число 3 в
предложении ORDER BY задает номер столбца в упорядоченном списке предложения SELECT. Поскольку столбцы выходных
данных, полученных в результате выполнения объединения, являются непоименованными, на столбец можно сослаться
только по номеру, определяющему его место расположения среди столбцов выходных данных.
Внешнее соединение
Часто бывает полезна операция объединения ДВУХ запросов, в которой второй запрос выбирает строки, исключенные первым.
Обычно что приходится делать для исключения строк, не удовлетворяющие предикату при выполнении операции соединения
таблиц. Это называется внешним соединением. Предположим, у некоторых из покупателей еще нет продавцов. Можно
посмотреть имена и города всех покупателей с указанием имен их продавцов, не отбрасывая покупателей, еще не имеющих
продавцов. Можно получить желаемые сведения, сформировав объединение двух запросов, один из которых выполняет
объединение, а второй выбирает покупателей с NULL-значением в поле snum. Последний может вставлять пробелы в поле,
соответствующее полю sname первого запроса,
Для идентификации запроса, с помощью которого получена данная строка, можно вставлять строки текста в
выходные данные. Использование этой возможности во внешнем соединении позволяет применять предикаты для класси-
фикации выходных данных, а не для их исключения.
В запрос можно добавить комментарий или выражение в качестве дополнительного поля. Для этого необходимо
включить некоторый совместимый комментарий или выражение в соответствующую позицию списка имен полей предложения
SELECT для каждого запроса в операторе объединения. Сравнимость по объединению предотвращает ситуации, когда
дополнительное поле добавляется к одному из запросов, но не добавляется к другому. Вот пример запроса, который
добавляем строки к выбранным полям. Текст этих строк сообщает о наличии для данного продавца назначенного ему
покупателя из его же города ("MATCHED' - 'NO MATCH'):
SELECT a.snum, sname, a.city, 'MATCHED'
FROM Salespeople a, Customers b
WHERE a.city = b.clty
UNION
SELECT snum, sname. city,'NO MATCH '
FROM Salespeople
WHERE NOT city = ANY
(SELECT city
FROM Customers
ORDER BY 2 DESC;
Это неполное внешнее соединение, поскольку оно включает только не назначенные поля для одной из участвующих в
соединении таблиц. Полное внешнее соединение должно включать всех покупателей, которые как имеют, так и не имеют
продавцов в их городах.
Все строки в SQL вводятся при помощи команды обновления INSERT. В простейшем случае команда INSERT
имеет такой синтаксис:
INSERT INTO «имя таблицы»
VALUES (<значение>, <значение>...);
Например, для того чтобы ввести строку в таблицу Salespeople, можно использовать следующее предложение:
INSERT INTO Salespeople
VALUES (1001, 'Peel', ‘London’,.12);
Команды языка манипулирования данными не генерируют выходных данных, но программа должна уведомлять
пользователя о том, что данные добавлены. Имя таблицы (в данном случае Salespeople) должно быть определено
предварительно (до выполнения команды INSERT) с помощью команды CREATE TABLE, а каждое значение в списке значений
должно иметь тип данных, соответствующий типу данных столбца, в который это значение должно быть вставлено. Согласно
стандарту ANSI, эти значения не могут включать выражения. Отсюда следует, что значение 3 допустимо, а 1 + 2 —
недопустимо. Значения, конечно, вводятся в таблицу в порядке следования столбцов таким образом, что первое из значений,
указанных в списке VALUES команды INSERT, вводится автоматически в столбец с номером 1, второе — в столбец с номером
2
Вставка NULL-значений
Если нужно вставить NULL-значение. необходимо указать его как обычное значение. Предположим, значение поля city для
Ms.Peel еще неизвестно. В этом случае для нее можно вставить строку, указав значение NULL в столбце city, следующим
образом:
• Она должна иметь четыре столбца, соответствующих столбцам таблицы Salespeople в смысле типов данных: т.е. первый,
второй и т.д. столбцы каждой из таблиц должны иметь один и тот же тип (использования одних и тех же имен не требуется).
Основное правило, в соответствии с которым столбцы таблицы вставляются. заключается в соответствии столбцов таблицы
столбцам выходных данных запроса, в данном случае, таблице Salespeople целиком.
Londonslaff является здесь независимой таблицей, имеющей ряд значений, совпадающих со значениями таблицы Salespeople.
Если значения в таблице Salespeople изменить, то эти изменения не отразятся на значениях, хранящихся в таблице
Londonstaff. Поскольку и запрос, и команда INSERT могут указывать столбцы по имени, при желании можно переставить
выбираемые столбцы (указать имена в команде SELECT) или указать имена вставляемых столбцов в произвольном порядке
(указать имена в команде INSERT).
По команде UPDATE можно изменять некоторые или все значения в существующей строке. Эта команда содержит
предложение UPDATE, позволяющее указать имя таблицы, для которой выполняется операция, и SET предложение,
определяющее изменение, которое необходимо выполнить для определенного столбца (столбцов). Например, для того чтобы
изменить для всех покупателей рейтинг на 200, следует ввести:
UPDATE Customers SET rating = 200;
В предикате команды DELETE можно использовать подзапросы. Это дает возможность формулировать достаточно
сложные критерии удаления строк, что требует особой внимательности. Например, если необходимо закрыть лондонский
офис, можно использовать следующий запрос для исключения всех покупателей, назначенных продавцам в London:
DELETE
FROM Customers
WHERE snum = ANY
(SELECT snum
FROM Salespeople
WHERE city = 'London');
Внимание! Когда выполняется изменение в базе данных, которое влечет другие изменения, первое желание — это
выполнить сначала основное изменение, а затем — трассировку тех изменений, которые последуют в связи с первым. Этот
пример показывает, почему более эффективно выполнять работу в обратной последовательности, т.е. сначала осуществить
вторичные изменения. Если изменение значения поля city началось с выдачи новых значений (назначений) продавцам, то
выполнение трассировки всех их покупателей окажется делом более сложным. Поскольку реальные базы данных существенно
превосходят простые таблицы, которые рассматриваются здесь в качестве примера, при работе с ними могут возникнуть
серьезные проблемы. Вам может оказаться полезен механизм ссылочной целостности SQL, но он не всегда применим и
доступен.
Хотя нельзя сослаться в предложении FROM (подзапроса) на таблицу, из которой осуществляется удаление, в предикате
можно ссылаться на текущую .строку-кандидат из таблицы, т.е. на ту строку, которая в настоящее время проверяется в
основном предикате. Другими словами, можно использовать связанные подзапросы. Они отличаются от применяемых с
INSERT тем, что действительно базируются на строках-кандидатах из таблицы, на которую воздействует команда, а не на
запросе для некоторой другой таблицы.
DELETE FROM Salespeople
WHERE EXISTS
(SELECT*
FROM Customers
WHERE rating = 100
AND Salespeople.snum = Customers.anum);
Часть AND предиката внутренней) запроса ссылается на таблицу Salespeople. то означает, что целый подзапрос буде!
выполняться отдельно для каждой роки таблицы Salespeople, как и в случае других связанных подзапросов. Команда удаляет
всех продавцов, имеющих по крайней мере одного покупателя с рейтингом 100 из таблицы Salespeople, Для достижения этого
существуют и другие способы. Приведем один из них:
DELETE FROM Salespeople
WHERE 100 IN
(SELECT rating
FROM Customers
WHERE Salespeople.snum = Customers.snum);
Запрос находит все рейтинги покупателей каждого продавца и удаляет продавца, имеющего покупателя с рейтингом 100.
Можно применять также обычные связанные подзапросы, т.е. связанные с таблицей, на которую есть ссылка во внешнем
запросе (а не в самом предложении DELETE). Например, можно найти наименьший заказ за каждый день и удалить продавца,
которому такой заказ был адресован, с помощью следующей команды:
DELETE FROM Salespeople
WHERE snum IN
(SELECT snum
FROM Orders a
WHERE amt =
(SELECT MIN (amt)
FROM Orders b WHERE a.odate = b.odate));
Подзапрос в предикате DELETE использует связанный подзапрос. Этот внутренний запрос находит минимальный заказ на
каждую дату для каждой строки внешнею запроса. Если его величина совпадает с величиной заказа текущей строки, предикат
внешнего запроса принимает значение "истина". Это значит, что текущая строка содержит минимальную заявку на данную
дату. Поле snum для продавца, ответственного за эту заявку, извлекается и подставляется в основной предикат самой
команды DELETE, которая затем удаляет все строки с этим значением поля snum из таблицы Salespeople. (Поскольку snum —
это первичный ключ таблицы Salespeople, найдется единственная строка, подлежащая удалению в соответствии с этим
запросом. Однако, если окажется, что таких строк больше, все они будут удалены.)
UPDATE, как и DELETE, использует подзапросы внутри предиката. Можно применять связанные подзапросы в любой форме,
приемлемой для DELETE: связанными либо с таблицей, которую следует модифицировать, либо с таблицей, на которую есть
ссылка во внешнем запросе. Например, используя связанный подзапрос для таблицы, подлежащей обновлению, можно
повысить комиссионные для всех продавцов, которые обслуживают по крайней мере двух покупателей:
UPDATE Salespeople
SET comm = comm + .01
WHERE 2 <=
(SELECT COUNT (cnum)
FROM Customers
WHERE Customers.snum = Salespeople.snum);
Приведем модификацию последнего примера для раздела, в котором рассматривалась команда DELЕТE. Здесь
уменьшаются комиссионные для продавцов, лучивших минимальные заказы:
UPDATE Salespeople
SET comm = comm - .01
WHERE snum IN
(SELECT snum
FROM Orders a
WHERE amt=
(SELECT MIN (amt)
FROM Orders b
WHERE a.odata=b.odate));
6.1.Создание таблиц
Таблицы определяются с помощью команды CREATE TABLE, создающей пустую таблицу — таблицу, не имеющую строк.
Значения вводятся с помощью команды DML (языка манипулирования данными) INSERT. Команда CREATE TABLE определяет
имя таблицы и множество поименованных столбцов в указанном порядке. Для каждого столбца устанавливаются тип и размер.
Каждая таблица должна иметь, по крайней мере, один столбец. Синтаксис команды CREATE TABLE:
CREATE TABLE <имя таблицы>
(<имя столбца> <тип данных> [(<размер>)],
<имя столбца> <тип данных> [(<размер>)],...);
Типы данных существенно различаются в разных программных продуктах. Однако в целях совместимости со
стандартом, они, как минимум, поддерживают стандартные ANSI-типы. Они приводятся в приложении В.
Поскольку пробелы используются для разделения отдельных частей команд в SQL, их нельзя использовать как часть имени
таблицы (либо как часть какого-либо другого объекта, например, индекса). Символ подчеркивания наиболее часто
используется для разделения слов в именах таблиц.
Значение аргумента размера зависит от типа данных. Если он не указывается. то система установит значение
автоматически. Вероятно, это наиболее удачнее решение для числовых значений, поскольку в данном случае все поля
определенного типа имеют один и тот же размер, что позволяет впоследствии не заботиться о совместимости по
объединению. Использование аргумента размера с некоторыми числовыми типами является непростой задачей. Для
хранения больших чисел вы должны убедиться, что поле имеет достаточную длину.
Тип данных, для которого обязательно следует указывать размер, — это CHAR. В данном случае аргумент размера —
целое число, задающее максимальное число символов, которые могут содержаться в поле. Реальное количество символов в
поле может изменяться от нуля (если в поле содержится NULL-значение) до заданного максимального значения. По
умолчанию это 1, т.е. в поле может содержаться единственный символ.
Таблицы принадлежат пользователю, который их создал, а имена этих таблиц должны различаться, как и имена столбцов, в
пределах одной таблицы. Даже если различные таблицы принадлежат одному пользователю, они могут иметь одноименные
столбцы. В SQL предполагается, что имя пользователя может использоваться в качестве идентификатора.
Следующая команда позволяет создать таблицу Salespeople:
CREATE TABLE Salespeople
(snum integer,
sname char(10),
city char(10),
comm decimal);
Порядок столбцов в определении таблицы существенен, он определяет порядок, в котором задаются значения
элементов .строк. Определения столбцов не должны задаваться в отдельных строках, но они должны разделяться запятыми.
Индексы
Индекс (index) — это упорядоченный (в алфавитном или числовом порядке) список содержимого столбцов или группы
столбцов в таблице. Таблицы могут иметь большое количество строк и, поскольку строки задаются в любом произвольном
порядке, поиск их по значению какого-либо из полей может занять достаточно много времени. Индексы предназначены для
решения этой проблемы для объединения всех значений в группы из одной или нескольких строк, отличных друг от друга.
Индексы — средство SQL, созданное из коммерческих соображений. В связи с этим стандарт ANSI в настоящее
время практически их не поддерживает, но они весьма полезны и используются на практике.
Когда создается индекс по значениям какого-либо поля для базы данных, создается упорядоченный список значений
для этого поля. Предположим, таблица Customers имеет тысячи строк и нужно найти покупателя с номером 2999. Поскольку
строки не упорядочены, программа должна просмотреть всю таблицу, строку за строкой, и выбрать ту, в которой значение поля
спит равно 2999. Если бы по полю спит был организован индекс, программа могла бы сразу найти в нем значение 2999 и
получить информацию о том, как обнаружить нужную строку таблицы. Это может значительно улучшить выполнение запросов,
но управление индексами существенно замедляет время выполнения операций обновления (таких как INSERT и DELETE);
кроме того, сам индекс занимает место в памяти. Следовательно, перед созданием индексов следует тщательно
проанализировать ситуацию.
Индексы можно создавать по множеству полей. Если указано более одного поля для создания единственного индекса,
данные упорядочиваются по значениям первого поля, по которому осуществляется индексирование. Внутри получившихся
групп осуществляется упорядочение по значениям второго поля, для получившихся в результате групп осуществляется
упорядочение по значениям третьего поля и т.д. Если есть первое и последнее имена для двух разных полей таблицы, можно
создать индекс, который упорядочивает первое внутри последнего. Это можно сделать независимо от порядка строк в
таблице.
Синтаксис команды создания индекса обычно выглядит так:
CREATE INDEX <имя индекса> ON <имя таблицы> (<имя столбца> [,<имя столбца>]..);
Таблица должна быть уже создана и содержать столбцы, имена которых указаны в команде. Имя индекса, определенное в
команде, должно быть уникальным в базе данных, то есть оно не может использоваться для каких-либо других целей любым
пользователем базы данных. Будучи однажды созданным, индекс является невидимым для пользователя. SQL сам решит,
когда есть смысл воспользоваться индексом, и сделает это автоматически. Например, если часто используется таблица
Customers для поиска клиентов для конкретных продавцов по значениям поля snum, следует создать индекс по полю snum
таблицы Customers.
CREATE INDEX Clientgroup ON Customers(snum);
Теперь вы можете быстро найти клиентов для продавцов.
Уникальные индексы
Для индекса в предыдущем примере уникальность не нужна. Данный продавец может иметь любое количество
покупателей. Это становится неприемлемым, если ключевое слово UNIQUE используется перед ключевым словом INDEX.
Поле cnum, как первичный ключ, может быть первым кандидатом на создание уникального индекса:
CREATE UNIQUE INDEX Custid ON Customers(cnum);
Предыдущий пример помог выяснить, можно ли использовать поле cnum в качестве первичного ключа таблицы Customers.
Для базы данных можно выполнить тщательное планирование первичного и других ключей.
Удаление индексов
Основная причина именования индексов состоит в их удалении время от времени. Обычно пользователи не знают о
существовании индекса. SQL автоматически определяет его необходимость и создает его, если это нужно. При исключении
индекса (вы обязательно должны знать его имя) используется такой синтаксис:
DROP INDEX <имя индекса>;
Удаление индекса не изменяет содержимого поля (полей).
Команда ALTER TABLE (изменить таблицу), не являясь частью стандарта АNSI, широко применяется. Форма команды
достаточно прозрачна, хотя ее возможности изменяются в широких границах. Обычно она осуществляет добавление столбцов
в таблицу, иногда может удалять столбцы или изменять их размеры — осуществлять добавление и удаление ограничений.
Обычный синтаксис команды, предназначенной для добавления столбца в таблицу выглядит следующим образом:
ALTER TABLE <имя таблицы> ADD <имя столбца> <тип данных> <размер>;
По этой команде для существующих в таблице строк добавляется столбец, в который заносится NULL-значение.
Новый столбец становится последним столбцом в таблице. Допустимо добавление в нее нескольких столбцов с помощью
одной команды; в этом случае их определения разделяются запятой. Можно исключать столбцы или изменять их описания.
Часто изменение столбцов связано с изменением их размеров, добавлением или удалением ограничений. Система должна
предоставить пользователю средства контроля, позволяющие удостовериться, что введенные данные, хранящиеся в таблице к
моменту выполнения команды ALTER TABLE, удовлетворяют заданным в команде новым ограничениям. Для этого команда
отвергается (выполнение команды завершается аварийно). Однако, наилучший вариант — возможность двойного контроля
ситуации. Необходимо изучить соответствующие разделы документации по конкретной системе, прежде чем приступить к
выполнению этой операции. Из-за нестандартной природы команды ALTER TABLE следует постоянно обращаться к доку-
ментации по конкретной системе, прежде чем приступать к внесению каких-либо изменений в таблицы.
ALTER TABLE становится неоценимой, когда возникает потребность переопределить таблицу, но база данных должна
проектироваться так, чтобы, по возможности, избежать подобных ситуаций. Изменение структуры таблицы, используемой в
настоящее время, дело рискованное. Представления таблицы, которые создаются на основе данных, хранящихся в реальных
таблицах, могут не допустить выполнения этой команды; программы, использующие встроенный SQL, могут привести к
ошибочной ситуации в процессе выполнения этой команды, либо могут отвергать эту команду. Кроме того, в процесс
изменения таблицы могут оказаться вовлеченными все пользователи, имеющие дело с этой таблицей. По этой причине
следует стараться проектировать таблицы с учетом перспективы их использования, а необходимость выполнения команды
ALTER TABLE следует рассматривать как крайнюю меру.
Если система не поддерживает команду ALTER TABLE или если желательно избежать применения этой команды, можно
создать новую таблицу с необходимыми изменениями в ее определении, а затем использовать команду INSERT с запросом
SELECT * для передачи в новую таблицу существовавших ранее данных. Пользователи, имевшие доступ к старой таблице,
автоматически наследуют право доступа к новой таблице.
Исключение таблицы
Необходимо быть владельцем (создателем) таблицы, чтобы иметь возможность ее удалить. Чтобы не причинить ущерба
данным, хранящимся в базе данных, необходимо предварительно удалить все данные из таблицы, то есть сделать ее пустой,
а затем уже исключить таблицу из базы данных. Таблица, имеющая строки, не может быть удалена. Синтаксис команды,
осуществляющей удаление пустой таблицы (определения таблицы) из системы таков:
DROP TABLE <имя таблицы>;
После выполнения команды, имя таблицы больше не распознается как имя таблицы, команды не могут работать с
объектом, имя которого было указано в команде DROP. Перед выполнением команды следует удостовериться, что эта
таблица не содержит внешних ключей для какой-либо другой таблицы и что эти таблицы не используются для определения
представлений
Команда реально не является частью стандарта ANSI, но поддерживается и является полезной. Она весьма проста и не
имеет различий в толковании (как команда ALTER TABLE). ANSI не оговаривает способа удаления или отказа от определений
таблиц.
Ограничения в таблицах
Когда создается таблица (или когда она изменяется), можно определить ограничения на значения, которые вводятся в
поля, и SQL будет отвергать любое из них, если оно не соответствует определенному критерию. Два основных типа
ограничения — это ограничения на столбцы и на таблицу. Разница между ними состоит в том, что ограничения на столбцы
(column constraints) применимы к только к отдельным столбцам, а ограничения на таблицу (table constraints) применимы к
группам, состоящим из одного или более столбцов.
Объявление ограничений
Ограничения на столбец добавляются в конце определения столбца после указания типа данных и перед запятой.
Ограничения на таблицу размещаются в конце определения таблицы, после определения последнего столбца, перед
закрывающей круглой скобкой. Команда CREATE TABLE имеет следующий синтаксис, расширенный включением ограничений:
CREATE TABLE <имя таблицы>
(<имя столбца> <тип данных> <ограничения на столбец>, <имя столбца> <тип данных>
<ограничения на столбец> ... <ограничения на таблицу> (<имя столбца>
[,<имя столбца>...])...);
Поля, заданные в круглых скобках после ограничений таблицы, — это поля, на которые эти ограничения
распространяются. Ограничения на столбцы применяются к тем столбцам, за которыми они следуют.
Представления — это таблицы, содержимое которых берется или выводится из других таблиц. Они используются в
запросах и в предложениях DML так же, как и обычные таблицы, но они не содержат своих собственных данных.
Представления подобны окнам, через которые просматривается информация. реально хранимая (содержащаяся) в базовых
таблицах. В действительности же это запросы, выполняемые всякий раз, когда представление является объектом команды. В
этот момент выходные данные запроса и становятся содержимым представления.
Представление определяется с помощью команды CREATE VIEW, состоящей из ключевых слов CREATE VIEW
(создать представление), имени создаваемого представления и ключевого слова AS, после которого следует запрос.
Например:
CREATE VIEW Londonstaff
AS SELECT *
FROM Salespeople
WHERE city = 'London';
В результате выполнения этой команды можно стать владельцем представления, называемого Londonstaff. Как и
любую другую таблицу представления можно использовать: формулировать к нему запросы, выполнять обновление, вставку,
удаление и соединение с другими таблицами и представлениями.
Преимущество использования представления вместо базовой таблицы состоит в том, что оно обновляется
автоматически при изменении формирующих его таблиц. Содержимое представления не фиксируется, а повторно
вычисляется всякий раз, когда вы ссылаетесь на представление в команде. Если завтра добавится еще один продавец,
находящийся в Лондоне (city = 'London'), то он автоматически попадет в представление.
Представления значительно расширяют возможности управления данными.
Они предоставляют пользователям доступ не ко всей информации, хранящейся таблице, а только к ее части. Если нужно,
чтобы каждый продавец мог прогреть таблицу Salespeople, но не мог видеть значения комиссионных, можно создать
представление с помощью следующего предложения:
CREATE VIEW Salesown
AS SELECT snum,sname,city
FROM Salespeople;
Другими словами, это представление есть то же самое, что и таблица Salespeople, за исключением столбца comm,
имя которого не указано в запросе и, следовательно, не включено в представление.
Обновление представлений
Это представление можно модифицировать командами обновления DML, но модификации воздействуют не на само
представление, а только на лежащую в его основе таблицу:
UPDATE Salespeople
SET city = 'Palo Alto'
WHERE snum = 1004;
Действие команды аналогично выполнению этой же команды для таблицы Salespeople. Однако, если продавец
попытается изменить величину своих комиссионных с помощью команды UPDATE:
UPDATE Salesown
SET comm = .20
WHERE snum =1004;
то она будет отвергнута, поскольку в представлении нет поля comm. Важно заметить, что не все представления могут
обновляться.
Именование столбцов
До сих пор в качестве имен полей представлений использовались непосредственно имена полей таблицы, лежащей в
основе представления. На начальном этапе работы с представлениями это допустимо. Однако иногда требуется задать новые
имена для столбцов:
• Когда некоторые столбцы являются выходными столбцами и, следовательно, не поименованы.
• Когда два или более столбцов в соединении имеют одинаковые имена в соответствующих таблицах.
Имена, которые станут именами полей, даются в круглых скобках после имени таблицы. Это делать необязательно, если они
соответствуют именам полей таблицы, участвующей в запросе. Типы данных и размеры выводятся из полей запроса. Часто не
требуется задавать новые имена полей, но при необходимости это можно сделать для каждого поля в представлении.
Групповые представления
Групповые представления (grouped views) — это представления, которые содержат предложение GROUP BY или
базируются на других групповых представлениях.
Групповые представления могут быть прекрасным способом непрерывной обработки производной информации.
Предположим, что каждый день нужно отслеживать количество покупателей, имеющих заказы, количество продавцов,
получивших заказы, количество заказов, среднее количество заказов и общее количество поступивших заказов. Вместо того,
чтобы многократно конструировать сложный запрос, можно просто создать следующее представление:
CREATE VIEW Totalforday
AS SELECT odate, COUNT (DISTINCT cnum),
COUNT (DISTINCT snum), COUNT (onum), AVG(amt), SUM(amt) FROM Orders
GROUP BY odate;
Теперь можно получить всю необходимую информацию с помощью единственного запроса:
SELECT *
FROM Totalforday;
Запросы SQL могут быть достаточно сложными, и, следовательно, представления являются гибким и мощным
средством определения того, как будут использоваться данные. Они приносят пользу, переформатируя данные необходимым
образом и исключая многочисленные повторения.
Представления и соединения
Представления не обязательно выводятся из единственной базовой таблицы, они могут получать информацию из любого
количества базовых таблиц или других представлений. Например, можно определить представление, которое доказывает для
каждого заказа имена продавца и покупателя:
CREATE VIEW Nameorders
AS SELECT onum, amt, a.snum, sname, cname , FROM Orders a, Customers b.
Salespeople с
WHERE a.cnum = b.cnum
AND a.snum = c.snum;
Теперь можно выбрать все заказы покупателя или продавца, либо увидеть эту информацию для любого заказа. Например, для
того чтобы увидеть все заказы продавца, нужно ввести следующий запрос:
SELECT *
FROM Nameorders
WHERE sname = 'Rifkin';
Представления можно соединять с другими таблицами, как с базовыми, так и с представлениями; таким образом
можно увидеть все заказы продавца и ее комиссионные:
SELECT a.sname, cname, amt * comm
FROM Nameorders a, Salespeople b
WHERE a.sname в 'Axelrod' AND b.snum = a.snum;
Представления и подзапросы
Представления могут также использовать подзапросы, включая и связанные подзапросы. Допустим, что компания
платит вознаграждение продавцу, который имеет покупателя с наибольшим количеством заказов на заданную дату. Можно
получить информацию с помощью следующего представления:
CREATE VIEW Elitesalesforce
AS SELECT b.odate, a.snum, a.sname, FROM Salespeople a. Orders b
WHERE a.snum = b.snum
AND b.amt =
(SELECT MAX (amt)
FROM Orders с WHERE c.odate = b.odate);
Если вознаграждение получает только тот продавец, который имел наибольшее количество заказов по крайней мере 10 раз, то
можно получить эту информацию с помощью другого представления, основанного на первом:
CREATE VIEW Bonus
AS SELECT DISTINCT snum, sname
FROM Elitesalesforce a WHERE 10 <=
(SELECT-COUNT (*)
FROM Elitesalesforce b
WHERE a.snum = b.snum);
Извлечь из этой таблицы продавца, получившего вознаграждение, можно, введя следующий запрос: '
SELECT *
FROM Bonus;
Удаление представлений
Синтаксис исключения представления из базы данных сходен с синтаксисом
для исключения базовых таблиц:
DROP VIEW <имя представления>
Здесь нет необходимости сначала удалить все содержимое, как это требовалось для базовых таблиц, поскольку содержимое
представления никогда явно не определяется, а сохраняется в процессе выполнения отдельной команды. На базовые
таблицы, на основе которых определено представление, команда ник кого действия не оказывает. Надо помнить, что только
владелец имеет полномочия.
Обновление представлений
• Оно не должно использовать подзапросы (это ограничение ANSI, но в некоторых программных продуктах его не
придерживаются).
• Оно может быть определено на другом представлении, но это представление должно быть также обновляемым.
• Оно не может содержать константы, строки или выражения (например. comm * 100) в списке выбираемых выходных полей
• Для INSERT оно должно включать любые поля из лежащей в основе представления таблицы, которые имеют ограничение
NOT NULL, хотя в качестве значения по умолчанию может быть указано другое значение.
Другая проблема, связанная с использованием представлений, состоит в том, что можно вводить значения, которые
"поглощаются" лежащей в основе представления таблицей. Рассмотрим такое представление:
CREATE VIEW Highratings
AS SELECT cnum, rating
FROM Customers WHERE rating = 300;
Это обновляемое представление. Оно просто ограничивает доступ к таблице определенными строками и столбцами.
Предположим, что вставляется следующая строка:
INSERT INTO Highratings
VALUES (2018, 200);
Это правильная команда INSERT для данного представления. Строка будет вставлена через представление Highratings в
таблицу Customers. Однако в данном случае она не вставляется в представление, поскольку значение рейтинга не равно 300.
Здесь также возникает проблема. Приемлемым может явиться значение 200, но теперь строку из таблицы Customers не видно.
Пользователь не может быть уверен в правильности того, что он ввел, поскольку строка не представлена явно, он также не
может и удалить ее в любом случае.
Такую проблему можно решить, указав WITH CHECK OPTION в определении представления. Если использовать WITH
CHECK OPTION в определении представления Highrating:
CREATE VIEW Highratings
AS SELECT cnum, rating
FROM Customers WHERE rating = 300 WITH CHECK OPTION;
то рассмотренная выше вставка будет отвергнута.
WITH CHECK OPTION относится к типу средств "все или ничего". Это предложение вводится в определение представления, а
не в команду DML, значит либо будут контролироваться все команды обновления представления, либо ни одна. Обычно
контроль нужен, поэтому удобно иметь WITH CHECK OPTION в определении представления, если только нет особых причин
для того, чтобы разрешить ввод в таблицу, лежащую в основе представления, значений, которые в самом представлении не
содержатся.