SQL1
Агрегаты, построенные на скалярном выражении
До этого вы использовали агрегатные функции с одиночными полями как аргументами. Вы можете также использовать агрегатные функции с аргументами, которые состоят из скалярных выражений, включающих одно или более полей. (Если вы это делаете, DISTINCT не разрешается.)
Предположим, что таблица Заказов имеет ещё один столбец, который хранит предыдущий неуплаченный баланс (поле blnc) для каждого заказчика. Вы должны найти этот текущий баланс добавлением суммы приобретений к предыдущему балансу.
Вы можете найти наибольший неуплаченный баланс следующим образом:
SELECT MAX (blnc + (amt)) FROM Orders;
Для каждой строки таблицы этот запрос будет складывать blnc и amt для данного заказчика и выбирать самое большое значение, которое он найдёт. Конечно, пока заказчики могут иметь несколько заказов, их неуплаченный баланс оценивается отдельно для каждого заказа. Возможно, заказ с более поздней датой будет иметь самый большой неуплаченный баланс. Иначе старый баланс должен быть выбран, как в запросе выше.
Фактически имеется большое количество ситуаций в SQL, где можно использовать скалярные выражения с полями или вместо полей, как вы увидите в Главе 7.
ALL ВМЕСТО DISTINCT
Вместо DISTINCT вы можете указать ALL. Это будет иметь противоположный эффект, дублирование строк вывода сохранится. Так как это - тот самый случай, когда вы не указываете ни DISTINCT ни ALL, то ALL - по существу скорее пояснительный, а не действующий аргумент.
ANY И ALL ВМЕСТО EXISTS С NULL
Значения NULL также имеют некоторые проблемы с операторами вроде этих. Когда SQL сравнивает два значения в предикате, одно из которых пустое (NULL), то результат неизвестен (смотрите Главу 5). Неизвестный предикат подобен неверному и является причиной того, что строка не выбирается, но работать он будет иначе в некоторых похожих запросах, в зависимости от того, используют они ALL или ANY вместо EXISTS.
Рассмотрим наш предыдущий пример:
SELECT * FROM Customers WHERE rating > ANY (SELECT rating FROM Customers WHERE city = 'Rome');
и ещё один пример:
SELECT * FROM Customers outer WHERE EXISTS (SELECT * FROM Customers inner WHERE outer.rating > inner.rating AND inner.city = 'Rome');
В общем, эти два запроса будут вести себя одинаково. Но предположим, что появилось пустое (NULL) значение в столбце rating таблицы Заказчиков:
CNUM CNAME CITY RATING SNUM
2003 Liu SanJose NULL 1002
В варианте с ANY, где оценка Liu выбрана основным запросом, значение NULL делает предикат неизвестным, а строка Liu не выбирается для вывода. Однако в варианте с NOT EXISTS, когда эта строка выбрана основным запросом, значение NULL используется в предикате подзапроса, делая его неизвестным в каждом случае. Это означает что подзапрос не будет производить никаких значений и EXISTS будет неправилен. Это, естественно, делает оператор NOT EXISTS верным. Следовательно, строка Liu будет выбрана для вывода. Это основное расхождение, в отличие от других типов предикатов, где значение EXISTS, независимо от того, верно оно или нет, всегда неизвестно. Всё это является аргументом в пользу варианта формулировки с ANY. Мы не считаем, что значение NULL является выше, чем допустимое значение. Более того, результат будет тот же, если мы будем проверять для более низкого значения.
АВТОМАТИЧЕСКИЕ ВНЕШНИЕ ОБЪЕДИНЕНИЯ
В Главе 14 мы обсуждали внешнее объединение и показывали вам, как выполнять его, используя команду UNION. Некоторые программы баз данных имеют более непосредственный способ выполнения внешних объединений. В некоторых реализациях вводимый знак " + " после предиката может выводить строки, которые удовлетворяют условию, так же как и строки, которые ему не удовлетворяют. В условии предиката может содержаться поле, совпадающее для обеих таблиц, и NULL-значения будут вставлены там, где такого совпадения не будет найдено.
Например, предположим, вы хотите видеть ваших продавцов и соответствующих им заказчиков, не исключая тех продавцов, которым не назначено ни одного заказчика (хотя такого нет в наших типовых таблицах, но в действительности это возможно):
SELECT a.snum, sname, cname FROM Salespeople a, Customers b WHEREa.snum = b.snum(+);
Это является эквивалентом следующего объединения (UNION):
SELECT a.snum, sname, cname FROM Salespeople a, Customers b WHERE a.snum = b.snum
UNION
SELECT snum, sname, '_ _ _ _ _ _ _ _ _ _' FROM Salespeople WHERE snum NOT IN (SELECT snum FROM Customers);
Предполагается, что подчёркивания будут отображены NULL-значениями (см. команду FORMAT ранее в этом приложении, где описывалось отображение NULL-значениями).
BEGIN DECLARE SECTION (НАЧАТЬ РАЗДЕЛ ОБЪЯВЛЕНИЙ)
Синтаксис
EXEC SQL BEGIN DECLARE SECTION
EXEC SQL END DECLARE SECTION
Эта команда создает главный раздел программы для объявления в ней главных переменных, которые будут использоваться во вкладываемых операторах SQL. Переменная SQLCODE должна быть включена как одна из объявляемых переменных главного языка.
БОЛЬШЕ ПСЕВДОНИМОВ
Хотя объединение таблицы с собой это первая ситуация, когда ясна необходимость наличия псевдонимов, вы не ограничены в их использовании тем, чтобы только отличать копию одной таблицы от её оригинала. Вы можете использовать псевдонимы в любое время, когда вы хотите создать альтернативные имена для ваших таблиц в команде. Например, если ваши таблицы имеют очень длинные и сложные имена, вы могли бы определить простые односимвольные псевдонимы, типа a и b, и использовать их вместо имён таблиц в предложении SELECT и предикате. Они будут также использоваться с соотнесенными подзапросами (обсуждаемыми в Главе 11).
БУЛЕВЫ ОПЕРАЦИИ
Основные булевы операции также распознаются в SQL. Выражения Буля являются или верными/true, или неверными/false, подобно предикатам. Булевы операции связывают одно или более верных/неверных значений и производят единственное верное или неверное значение.
Стандартными булевыми операциями, распознаваемыми в SQL, являются AND, OR и NOT.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE rating > 200; | | ============================================= | | snum cname city rating snum | | ----- -------- -------- ----- ----- | | 2004 Crass Berlin 300 1002 | | 2008 Cirneros San Jose 300 1007 | =============================================
Рисунок 4.1 Использование операции "больше" (>)
Существуют другие, более сложные булевы операции (типа "исключающее ИЛИ"), но они могут быть сформированы из этих трёх простых операций - AND, OR, NOT.
Как вы можете понять, булева логика верно/неверно основана на цифровой компьютерной операции; и фактически весь SQL (или любой другой язык) может быть сведён до уровня булевой логики.
Булевы операции, и как они работают:
AND берет два булевых значения (в форме A AND B) как аргументы и оценивает, верны ли они оба.
OR берет два булевых значения (в форме A OR B) как аргументы и оценивает, верен ли один из них.
NOT берет одиночное булево значение (в форме NOT A) как аргумент и заменяет его значение с неверного на верное или с верного на неверное (инвертирует).
Связывая предикаты с булевыми операциями, вы можете значительно расширить их возможности. Предположим, вы хотите видеть всех заказчиков в San Jose, которые имеют оценку (рейтинг) выше 200:
SELECT * FROM Customers WHERE city = " San Jose' AND rating > 200;
Вывод для этого запроса показан на Рисунке 4.2. Имеется только один заказчик, который удовлетворяет этому условию.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE city = 'San Jose' | | AND rating > 200; | | ============================================= | | сnum cname city rating snum | | ------ -------- -------- ---- ----- | | 2008 Cirneros San Jose 300 1007 | =============================================
Рисунок 4.2 SELECT, использующий AND
Если же вы используете OR, вы получите всех заказчиков, которые находились в San Jose или (OR) которые имели оценку выше 200.
SELECT * FROM Customers WHERE city = " San Jose' OR rating > 200;
Вывод для этого запроса показан на Рисунке 4.3.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE city = 'San Jose' | | OR rating > 200; | | ============================================= | | сnum cname city rating snum | | ----- ------- -------- ----- ------ | | 2003 Liu San Jose 200 1002 | | 2004 Grass Berlin 300 1002 | | 2008 Cirneros San Jose 300 1007 | =============================================
Рисунок 4.3 SELECT, использующий OR
NOT может использоваться для инвертирования булевых значений.
Имеется пример запроса с NOT:
SELECT * FROM Customers WHERE city = " San Jose' OR NOT rating > 200;
Вывод этого запроса показан на Рисунке 4.4.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE city = 'San Jose' | | OR NOT rating > 200; | | ============================================= | | cnum cname city rating snum | | ------ -------- ------ ----- ----- | | 2001 Hoffman London 100 1001 | | 2002 Giovanni Rome 200 1003 | | 2003 Liu San Jose 200 1002 | | 2006 Clemens London 100 1001 | | 2008 Cirneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================
Рисунок 4.4 SELECT, использующий NOT
Все записи, за исключением Grass, были выбраны. Grass не был в San Jose, и его оценка была больше, чем 200, так что он потерпел неудачу при обеих проверках. В каждой из других строк встретился тот или другой, или оба критерия.
Обратите внимание, что операция NOT должна предшествовать булевой операции, чьё значение должно измениться, и не должна помещаться перед реляционной операцией. Например: неправильным вводом предиката оценки будет:
rating NOT > 200
Он выдаст другую отметку. А как SQL оценит следующее?
SELECT * FROM Customers WHERE NOT city = " San Jose' OR rating > 200;
NOT применяется здесь только к выражению city = 'SanJose', или к выражению rating > 200 тоже? Как уже было сказано, правильный ответ будет прежним: SQL может применять NOT с булевым выражением, которое идёт только сразу после него. Вы можете получить другой результат при команде:
SELECT * FROM Customers WHERE NOT(city = " San Jose' OR rating > 200);
Здесь SQL понимает круглые скобки как означающие, что всё внутри них будет вычисляться в первую очередь и обрабатываться как единое выражение с помощью всего, что снаружи них (это является стандартной интерпретацией, как в математике). Другими словами, SQL берет каждую строку и определяет, соответствует ли истине равенство city = 'San Jose' или равенство rating > 200.
Если любое условие верно, булево выражение внутри круглых скобок верно. Однако, если булево выражение внутри круглых скобок верно, предикат как единое целое неверен, потому что NOT преобразует верно в неверно и наоборот.
Вывод для этого запроса показан на Рисунке 4.5. Имеется намеренно усложнённый пример. Сможете ли вы проследить его логику (вывод показан на Рисунке 4.6)?
SELECT * FROM Orders WHERE NOT ((odate = 10/03/1990 AND snum >1002) OR amt > 2000.00);
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE NOT (city = 'San Jose' | | OR rating > 200); | | ============================================= | | cnum cname city rating snum | | ----- -------- ------- ----- ------ | | 2001 Hoffman London 100 1001 | | 2002 Giovanni Rome 200 1003 | | 2006 Clemens London 100 1001 | | 2007 Pereira Rome 100 1004 | =============================================
Рисунок 4.5 SELECT, использующий NOT и вводное предложение
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE NOT ((odate = 10/03/1990 AND snum > 1002) | | OR amt > 2000.00); | | =============================================== | | onum amt odate cnum snum | | ------ -------- ---------- ----- ----- | | 3003 767.19 10/03/1990 2001 1001 | | 3009 1713.23 10/04/1990 2002 1003 | | 3007 75.75 10/04/1990 2004 1002 | | 3010 1309.95 10/06/1990 2004 1002 | =================================================
Рисунок 4.6 Полный (комплексный) запрос
Несмотря на то что булевы операции по отдельности просты, они не так просты, когда комбинируются в комплексное выражение.
Способ оценки булева комплекса состоит в том, чтобы оценивать булевы выражения, наиболее глубоко вложенные в круглых скобках, объединять их в единое булево значение, а затем объединять его с вышележащими значениями.
Вот подробное объяснение того, как пример выше был вычислен. Наиболее глубоко вложенные булевы выражения, в предикате это odate = 10/03/1990 и snum > 1002, объединяются с помощью AND, формируя одно булево выражение, которое будет оценено как верное для всех строк, в которых встретились оба эти условия. Это составное булево выражение (которое мы будем называть булево номер 1, или B1, для краткости) объединяется с выражением (amt) > 2000.00 (B2) с помощью OR, формируя третье выражение (B3), которое является верным для данной строки, если или B1 или B2 верны для этой строки.
B3 полностью содержится в круглых скобках, которым предшествует NOT, формируя последнее булево выражение (B4), которое является условием предиката.
Таким образом, B4 - предикат запроса - будет верен всякий раз, когда B3 неправилен. B3 неправилен всегда, когда B1 и B2 оба неверны. B1 неправилен для строки, если дата строки заказа не 10/03/1990 или если значение snum не больше, чем 1002. B2 неправилен для всех строк, значение суммы приобретений которых не превышает 2000.00. Любая строка со значением выше 2000.00 сделает B2 верным; в результате B3 будет верен, а B4 - нет. Следовательно, все эти строки будут удалены из вывода.
Из оставшихся, строки, которые на 3 октября имеют snum > 1002 (такие как строки для onum 3001 на 3 октября с snum = 1007), делают B1 верным с помощью верного B3 и неверного предиката запроса. Они будут также удалены из вывода. Вывод показан для строк, которые оставлены.
ЧТО ДЕЛАЕТ ANSI?
Как мы уже сказали во Введении, стандарт SQL определяется с помощью кода ANSI (Американский Национальный Институт Стандартов). SQL не изобретался ANSI. Это, по существу, изобретение IBM. Но другие компании подхватили SQL сразу же. По крайней мере одна компания (Oracle) отвоевала у IBM право на рыночную продажу SQL-продуктов.
После того как появился ряд конкурирующих программ SQL на рынке, ANSI определил стандарт, к которому они должны быть приведены. (Определение таких стандартов и является функцией ANSI). Однако после этого появились некоторые проблемы. Возникли они, в результате стандартизации ANSI, в виде некоторых ограничений. Так как не всегда ANSI определяет то, что является наиболее полезным, то программы пытаются соответствовать стандарту ANSI, не позволяя ему ограничивать их слишком сильно. Это, в свою очередь, ведет к случайным несогласованностям. Программы Баз Данных обычно придают ANSI SQL дополнительные особенности и часто ослабляют многие ограничения. Поэтому распространённые разновидности ANSI будут также рассмотрены. Хотя мы, очевидно, не сможем рассмотреть каждое исключение или разновидность, удачные идеи имеют тенденцию к внедрению и использованию в различных программах, даже когда они не определены стандартом ANSI. ANSI это вид минимального стандарта, и вы можете делать больше, чем он позволяет, хотя и должны выполнять его указания при выполнении задач, которые он определяет.
ЧТО НЕ МОГУТ ДЕЛАТЬ ПРЕДСТАВЛЕНИЯ?
Имеется большое количество типов представлений (включая многие из наших примеров в этой главе), которые являются доступными только для чтения. Это означает, что их можно запрашивать, но они не могут подвергаться действиям команд модификации. (Мы будем рассматривать эту тему в Главе 21.) Имеются также некоторые виды запросов, которые недопустимы в определениях представлений.
Одиночное представление должно основываться на одиночном запросе.
ОБЪЕДИНИТЬ (UNION) и ОБЪЕДИНИТЬ ВСЁ (UNION ALL) не разрешаются.
УПОРЯДОЧИТЬ ПО (ORDER BY) никогда не используется в определении представлений.
Вывод запроса формирует содержание представления, которое напоминает базовую таблицу и является - по определению - неупорядоченным.
ЧТО СЛУЧИТСЯ, ЕСЛИ ВЫ ВЫПОЛНИТЕ КОМАНДУ МОДИФИКАЦИИ?
Давайте условимся, что все внешние ключи созданные в наших таблицах примеров, объявлены и предписаны с ограничениями внешнего ключа следующим образом:
CREATE TABLE Salespeople (snum integer NOT NULL PRIMARY KEY, sname char(10) NOT NULL, city char(10), comm decimal);
CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY, cname char(10) NOT NULL, city char(10), rating integer, snum integer, FOREIGN KEY (snum) REFERENCES Salespeople, UNIQUE (cnum, snum) ;
CREATE TABLE Orders (cnum integer NOT NULL PRIMARY KEY, amt decimal, odate date NOT NULL, cnum integer NOT NULL snum integer NOT NULL FOREIGN KEY (cnum, snum) REFERENCES CUSTOMERS (cnum, snum);
ЧТО ТАКОЕ АГРЕГАТНЫЕ ФУНКЦИИ?
Запросы могут производить обобщённое групповое значение полей точно так же, как и значение одного поля. Это делается с помощью агрегатных функций. Агрегатные функции производят одиночное значение для всей группы таблицы.
Вот список этих функций:
COUNTвыдаёт количество строк или не-NULL значений полей, которые выбрал запрос.
SUMвыдаёт арифметическую сумму всех выбранных значений данного поля.
AVGвыдаёт усреднение всех выбранных значений данного поля.
MAXвыдаёт наибольшее из всех выбранных значений данного поля.
MINвыдаёт наименьшее из всех выбранных значений данного поля.
ЧТО ТАКОЕ "ПОЛЬЗОВАТЕЛЬ"?
SQL обычно находится в компьютерных системах, которые имеют больше чем одного пользователя и, следовательно, должен различать их (ваше семейство PC может иметь любое число пользователей, но оно обычно не имеет способов, чтобы отличать одного пользователя от другого).
Обычно в такой системе каждый пользователь имеет некий вид кода проверки прав, который идентифицирует его или её (терминология изменяется). В начале сеанса с компьютером пользователь входит в систему (регистрируется), сообщая компьютеру, кто этот пользователь, идентифицируясь с помощью определенного ID (Идентификатора). Любое количество людей, использующих ID доступа, являются отдельными пользователями; и, аналогично, один человек может представлять большое количество пользователей (в разное время), используя различные идентификаторы доступа к SQL. Действия в большинстве сред SQL приведены к специальному Идентификатору доступа, который точно соответствует определённому пользователю. Таблица или другой объект принадлежит пользователю, который имеет над ним полную власть. Пользователь может или может не иметь привилегии на выполнение действия над объектом. Для наших целей мы договоримся, что любой пользователь имеет необходимые привилегии для выполнения любого действия, пока мы не возвратимся специально к обсуждению привилегий в Главе 22. Специальное значение USER (ПОЛЬЗОВАТЕЛЬ) может использоваться как аргумент в команде. Оно указывает на доступный Идентификатор пользователя, выдавшего команду.
ЧТО ТАКОЕ ПРЕДСТАВЛЕНИЕ?
Таблицы, с которыми вы имели дело до сих пор, назывались базовыми таблицами. Это таблицы, которые содержат данные. Однако имеется другой вид таблиц - представления. Представления это таблицы, чьё содержание выбирается или получается из других таблиц. Они работают в запросах и операторах DML точно так же, как и основные таблицы, но не содержат никаких собственных данных.
Представления подобны окнам, через которые вы просматриваете информацию (как она есть, или в другой форме, как вы потом увидите), которая реально хранится в базовой таблице. Представление это фактически запрос, который выполняется всякий раз, когда представление становится темой команды. Вывод запроса при этом в каждый момент становится содержанием представления.
ЧТО ТАКОЕ "РЕЛЯЦИОННАЯ БАЗА ДАННЫХ"?
Реляционная база данных это тело связанной информации, сохраняемой в двухмерных таблицах. Это напоминает адресную или телефонную книгу.
В любой книге имеется большое количество разделов, каждый из которых соответствует определённой особенности. Для каждой такой особенности может быть несколько независимых фрагментов данных, например: имя, телефонный номер и адрес.
Предположим, что вы должны сформатировать эту адресную книгу в виде таблицы со строками и столбцами. Каждая строка (называемая также записью) будет соответствовать определённой личности; каждый столбец будет содержать значение для каждого типа данных: имени, телефонного номера и адреса, представляемых в каждой строке.
Адресная книга могла бы выглядеть следующим образом:
| Имя | Телефон | Адрес |
Gerry Farish(415)365-8775127 Primrose Ave.,SF
Celia Brock(707)874-3553246 #3rd St.,Sonoma
Yves Grillet(762)976-3665778 Modernas,Barcelona
То, что вы получили, является основой реляционной базы данных, как и было определено в начале этого обсуждения, а именно - двухмерной (строка и столбец) таблицей информации.
Однако РБД редко состоят из одной таблицы. Такая таблица меньше, чем файловая система. Создав несколько таблиц взаимосвязанной информации, вы сможете выполнять более сложные и мощные операции с вашими данными. Мощность БД зависит от связи, которую вы можете создать между фрагментами информации, а не от самого фрагмента информации.
ЧТО ТАКОЕ - ВЛОЖЕНИЕ SQL?
Чтобы вложить SQL в другой язык, вы должны использовать пакет программ, который обеспечивал бы поддержку вложения SQL в этот язык и, конечно же, поддержку самого языка. Естественно, вы должны быть знакомы с языком, который вы используете.
В основном вы будете использовать команды SQL для работы в таблицах баз данных, передачи результатов вывода в программу и получение ввода из программы, в которую они вкладываются, обобщённо ссылаясь к главной программе (которая может или не может принимать их из диалога или посылать обратно в диалог пользователя и программы).
ЧТО ТАКОЕ ЗАПРОС?
Запрос это команда, которую вы даёте вашей программе базы данных и которая сообщает ей, что нужно вывести определённую информацию из таблиц в память. Эта информация обычно посылается непосредственно на экран компьютера или терминала, которым вы пользуетесь, хотя в большинстве случаев её можно также послать на принтер, сохранить в файле (как объект в памяти компьютера) или предоставить как вводную информацию для другой команды или процесса.
CLOSE CURSOR (ЗАКРЫТЬ КУРСОР)
Синтаксис
EXEC SQL CLOSE CURSOR ;
Эта команда указывает курсору закрыться, после чего ни одно значение не сможет быть выбрано из него до тех пор, пока он не будет снова открыт.
COMMIT (WORK) (ПОДТВЕРДИТЬ (ТРАНЗАКЦИИ))
Синтаксис
COMMIT WORK;
Эта команда оставляет неизменными все изменения, сделанных в базе данных, до тех пор, пока начавшаяся транзакция не закончится и не начнется новая транзакция.
CREATE INDEX (СОЗДАТЬ ИНДЕКС)
(*NONSTANDARD*) (НЕСТАНДАРТНАЯ)
Синтаксис
CREATE [UNIQUE] INDEX
ON ();
Эта команда создает эффективный маршрут с быстрым доступом для поиска строк, содержащих указанные столбцы. Если UNIQUE указана, таблица не сможет содержать дубликаты (двойники) значений в этих столбцах.
CREATE SYNONYM (*NONSTANDARD*) (СОЗДАТЬ СИНОНИМ) (*НЕСТАНДАРТНЫЙ*)
Синтаксис
CREATE IPUBLICl SYNONYM FOR
.;
Эта команда создает альтернативное (синоним) имя таблицы. Синоним принадлежит его создателю, а сама таблица - обычно другому пользователю. Используя синоним, его владелец может не ссылаться на таблицу её полным (с включением имени владельца) именем. Если PUBLIC указан, синоним принадлежит каталогу SYSTEM и, следовательно, доступен всем пользователям.
CREATE TABLE (СОЗДАТЬ ТАБЛИЦУ)
Синтаксис
CREATE TABLE
({ []
[ . . .]
[]} . , . . . , . .);
Команда создает таблицу в базе данных. Эта таблица будет принадлежать её создателю. Столбцы будут рассматриваться в поимённом порядке.
определяет тип данных, которые столбец будет содержать. Стандарт описывается в Приложении B; все прочие используемые типы данных обсуждались в Приложении C. Значение размера зависит от типа данных .
и налагают ограничения на значения, которые могут быть введены в столбце.
определяет значение (по умолчанию), которое будет вставлено автоматически, если никакого другого значения не указано для этой строки. (См. в Главе 17 подробности о самой команде CREATE TABLE и в Главах 18 И 19 - подробности об ограничениях и о ).
CREATE VIEW (СОЗДАТЬ ПРОСМОТР)
Синтаксис
CREATE VIEW
AS
[WITH CHECK OPTION];
Просмотр обрабатывается как любая таблица в командах SQL. Когда команда ссылается на имя таблицы , запрос выполняется, и его вывод соответствует содержанию таблицы, указанной в этой команде.
Некоторые просмотры могут модифицироваться, что означает, что команды модификации могут выполняться в этих просмотрах и передаваться в таблицу, на которую была ссылка в запросе . Если указано предложение WITH CHECK OPTION, эта модификация должны также удовлетворять условию предиката в запросе .
DECLARE CURSOR (ОБЪЯВИТЬ КУРСОР)
Синтаксис
EXEC SQL DECLARE CURSOR FOR
Эта команда связывает имя курсора с запросом . Когда курсор открыт (см. OPEN CURSOR ), запрос выполняется, и его результат может быть выбран (командой FETCH) для вывода. Если курсор - модифицируемый, таблица, на которую ссылается запрос , может получить изменение содержания с помощью операции модификации в курсоре (См. в Главе 25 о модифицируемых курсорах).
ДЕЙСТВИЕ ОГРАНИЧЕНИЙ
Как ограничения воздействуют на возможность и невозможность использования команды модификации DML? Для полей, определённых как внешние ключи, ответ довольно простой: любые значения, которые вы помещаете в эти поля командой INSERT или UPDATE, должны уже быть представлены в их родительских ключах. Вы можете помещать пустые (NULL) значения в эти поля, несмотря на то что значения NULL непозволительны в родительских ключах, если они имеют ограничение NOT NULL. Вы можете удалять (DELETE) любые строки с внешними ключами, не используя родительские ключи вообще.
Поскольку затронут вопрос об изменении значений родительского ключа, ответ, по определению ANSI, ещё проще, но, возможно, несколько более ограничен: любое значение родительского ключа, на который ссылаются с помощью значения внешнего ключа, не может быть удалено или изменено. Это означает, например, что вы не можете удалить заказчика из таблицы Заказчиков, пока он ещё имеет заказы в таблице Заказов. В зависимости от того, как вы используете эти таблицы, это может быть или желательно, или хлопотно. Однако это, конечно, лучше, чем иметь систему, которая позволит вам удалить заказчика с текущими заказами и оставить таблицу Заказов ссылающейся на несуществующих заказчиков. Смысл этой системы ограничения в том, что создатель таблицы Заказов, используя таблицу Заказчиков и таблицу Продавцов как родительские ключи, может наложить значительные ограничения на действия в этих таблицах. По этой причине вы не сможете использовать таблицу которой вы не распоряжаетесь (т.е. не вы её создавали и не вы являетесь её владельцем), пока владелец (создатель) этой таблицы специально не передаст вам на это право (что объясняется в Главе 22).
Имеются некоторые другие возможные действия изменения родительского ключа, которые не являются частью ANSI, но могут быть найдены в некоторых коммерческих программах. Если вы хотите изменить или удалить текущее ссылочное значение родительского ключа, имеются три возможности:
* Вы можете ограничить или запретить изменение (способом ANSI), обозначив, что изменения в родительском ключе ограничены.
* Вы можете сделать изменение в родительском ключе и тем самым сделать изменения во внешнем ключе автоматическим, что называется каскадным изменением.
* Вы можете сделать изменение в родительском ключе и установить внешний ключ в NULL автоматически (полагая, что NULL разрешены во внешнем ключе), что называется пустым изменением внешнего ключа.
Даже в пределах этих трёх категорий вы можете не захотеть обрабатывать все команды модификации таким способом. INSERT, конечно, к делу не относится. Она помещает новые значения родительского ключа в таблицу, так что ни одно из этих значений не может быть вызвано в данный момент. Однако вы можете захотеть позволить модификациям быть каскадными, но без удалений, и наоборот.
Лучшей может быть ситуация, которая позволит вам определять любую из трёх категорий, независимо от команд UPDATE и DELETE. Мы будем, следовательно, ссылаться на эффекты модификации (update effects) и эффекты удаления (delete effects), которые определяют, что случится, если вы выполните команды UPDATE или DELETE в родительском ключе. Эти эффекты, о которых мы говорили, называются: Ограниченные (RESTRICTED) изменения, Каскадируемые (CASCADES) изменения и Пустые (NULL) изменения.
Фактические возможности вашей системы должны строго соответствовать стандарту ANSI - это эффекты модификации и удаления, оба автоматически ограниченные - для более идеальной ситуации, описанной выше. В качестве иллюстрации мы покажем несколько примеров того, что вы можете делать с полным набором эффектов модификации и удаления. Конечно, эффекты модификации и удаления, являющиеся нестандартными средствами, испытывают недостаток в стандартном синтаксисе. Синтаксис, который мы используем здесь, прост в написании и будет служить в дальнейшем для иллюстрации функций этих эффектов.
Для полноты эксперимента позволим себе предположить, что вы имеете причину изменить поле snum таблицы Продавцов в случае, когда наша таблица Продавцов изменяет разделы. (Обычно изменение первичных ключей это не то, что мы рекомендуем делать практически. Просто это ещё один из доводов, чтобы иметь первичные ключи, которые не умеют делать ничего другого, кроме как действовать как первичные ключи: они не должны изменяться.)
Когда вы изменяете номер продавца, вы хотите, чтобы были сохранены все его заказчики. Однако, если этот продавец покидает свою фирму или компанию, вы можете не захотеть удалить его заказчиков при удалении его самого из БД. Взамен вы захотите убедиться, что заказчики назначены кому-нибудь ещё. Чтобы сделать это, вы должны указать UPDATE с Каскадируемым эффектом, и DELETE с Ограниченным эффектом.
CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY, cname char(10) NOT NULL, city char(10), rating integer, snum integer REFERENCES Salespeople, UPDATE OF Salespeople CASCADES, DELETE OF Salespeople RESTRICTED);
Если вы теперь попробуете удалить Peel из таблицы Продавцов, команда будет недопустима, пока вы не измените значение поля snum заказчиков Hoffman и Clemens для другого назначенного продавца. С другой стороны, вы можете изменить значение поля snum для Peel на 1009, и Hoffman и Clemens будут также автоматически изменены.
Третий эффект - Пустые (NULL) изменения. Бывает, что, когда продавцы оставляют компанию, их текущие заказы не передаются другому продавцу. С другой стороны, вы хотите отменить все заказы автоматически для заказчиков, чьи счета вы удалите. Изменив номера продавца или заказчика, можно просто передать их ему. Пример ниже показывает, как вы можете создать таблицу Заказов с использованием этих эффектов.
CREATE TABLE Orders (onum integer NOT NULL PRIMARY KEY, amt decimal, odate date NOT NULL cnum integer NOT NULL REFERENCES Customers snum integer REFERENCES Salespeople, UPDATE OF Customers CASCADES, DELETE OF Customers CASCADES, UPDATE OF Salespeople CASCADES, DELETE OF Salespeople NULLS);
Конечно, в команде DELETE с эффектом Пустого изменения в таблице Продавцов, ограничение NOT NULL должно быть удалено из поля snum.
DELETE (УДАЛИТЬ)
Синтаксис
DELETE FROM
{ [WHERE ]; }
| WHERE CURRENT OF
Если предложение WHERE отсутствует, ВСЕ строки таблицы удаляются. Если предложение WHERE использует предикат , строки, которые удовлетворяют условию этого предиката , удаляются. Если предложение WHERE имеет аргумент CURRENT OF (ТЕКУЩИЙ) в имени курсора , строка из таблицы , на которую в данный момент имеется ссылка с помощью имени курсора , будет удалена. Форма WHERE CURRENT может использоваться только во вложенном SQL и только с модифицируемыми курсорами.
DISTINCT С ПОДЗАПРОСАМИ
В некоторых случаях вы можете использовать DISTINCT чтобы вынудить подзапрос генерировать одиночное значение. Предположим что мы хотим найти все порядки кредитования для тех продавцов, которые обслуживают Hoffman'а (cnum = 2001).
Вот способ сделать это (вывод показан на Рисунке 10.2):
SELECT * FROM Orders WHERE snum = (SELECT DISTINCT snum FROM Orders WHERE cnum = 2001);
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE snum = | | (SELECT DISTINCT snum | | FROM Orders | | Where cnum = 2001); | | =============================================== | | onum amt odate cnum snum | | ----- --------- --------- ------ ------- | | 3003 767.19 10/03/1990 2001 1001 | | 3008 4723.00 10/05/1990 2006 1001 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================
Рисунок 10.2 Использование DISTINCT для получения одного значения из подзапроса
Подзапрос установил, что значение поля snum совпало с Hoffman - 1001, а затем основной запрос выделил все заказы с этим значением snum из таблицы Заказов (не разбирая, относятся они к Hoffman или нет). Так как каждый заказчик назначен продавцу, мы знаем, что каждая строка в таблице Заказов с данным значением cnum должна иметь такое же значение snum. Однако, поскольку там может быть любое число таких строк, подзапрос мог бы вывести много (хотя и идентичных) значений snum для данного поля cnum. Аргумент DISTINCT предотвращает это. Если наш подзапрос возвратит более одного значения, это будет указывать на ошибку в наших данных - хорошая вещь для знающих об этом.
Должен быть и альтернативный подход, чтобы ссылаться к таблице Заказчиков, а не к таблице Заказов в подзапросе. Так как поле cnum это первичный ключ таблицы Заказчиков, запрос, выбирающий его, должен выдать только одно значение. Это рационально, только если вы как пользователь имеете доступ к таблице Заказов, но не к таблице Заказчиков. В этом случае вы можете использовать решение, которое мы показали выше. (SQL имеет механизмы, которые определяют, кто имеет привилегии на выполнение действий в определённой таблице. Это будет объясняться в Главе 22.)
Пожалуйста, учтите, что методика, используемая в предшествующем примере, применима, только когда вы знаете, что два различных поля в таблице должны всегда совпадать, как в нашем случае. Эта ситуация не является типичной в реляционных базах данных (РБД), она является исключением из правил.
ДРУГИЕ ФУНКЦИИ
Эта функция может быть применена к любому типу данных.
ФУНКЦИЯЗНАЧЕНИЕ
NVL(,) |
NVL (NULL-значение) будет менять на каждое NULL значение, найденное в столбце . Если полученное значение не = NULL, NVL ничего не делает. |
ДРУГИЕ СПОСОБЫ БЛОКИРОВКИ ДАННЫХ
Некоторые реализации выполняют блокировку страницы вместо блокировки строки. Это может быть либо возможность вашего управления, либо нечто, заложенное уже в конструкцию системы.
Страница это блок накопления памяти, обычно равный 1024 байт. Страница может состоять из одной или более строк таблицы, возможно, сопровождаемых индексами и другой сопутствующей информацией, а может состоять даже из нескольких строк другой таблицы. Если вы блокируете страницы вместо строк, все данные в этих страницах будут блокированы точно так же, как и в индивидуальных строках, согласно уровням изоляции, описанным выше.
Основным преимуществом такого подхода является эффективность. Когда SQL не следит за блокированностью и разблокированностью строк индивидуально, он работает быстрее. С другой стороны, язык SQL был разработан так, чтобы максимизировать свои возможности, и произвольно блокирует строки, которые не обязательно было блокировать. Похожая возможность доступна в некоторых системах - это блокировка областей DBS. Области базы данных имеют тенденцию быть больше, чем страница, так что этот подход удовлетворяет и достоинству увеличения производительности и недостатку блокирования страниц. Вообще-то лучше отключать блокировку низкого уровня, если вам кажется, что появились значительные проблемы с эффективностью.
ДРУГИЕ ТИПЫ ПРИВИЛЕГИЙ
Вы, разумеется, хотите знать, кто же имеет право первым создать таблицу. Эта область привилегий не относится к ANSI, но не может игнорироваться. Все стандартные привилегии ANSI вытекают из этой привилегии - привилегии создателей таблиц, которые могут передавать привилегии объекта. Если все ваши пользователи будут создавать в системе базовые таблицы с разными размерами, это приведет к избыточности в них и к неэффективности системы.
Возникают и другие вопросы:
Кто имеет право изменять, удалять или ограничивать таблицы?
Должны ли права создания базовых таблиц отличаться от прав создания представлений?
Должен ли иметься суперпользователь - пользователь который отвечает за поддержание базы данных и, следовательно, имеющий наибольшие или все привилегии, которые не предоставляются индивидуально?
Пока ANSI не принимает в этом участия, а SQL используется в различных средах, мы не можем дать окончательный ответ на эти вопросы. Мы предлагаем рассмотреть здесь часть наиболее общих выводов.
Привилегии, которые не определяются в терминах специальных объектов данных, называются привилегиями системы или правами базы данных. На базисном уровне они будут, вероятно, включать в себя право создавать объекты данных, возможно, отличающиеся от базовых таблиц (обычно создаваемых несколькими пользователями) и представлений (обычно создаваемых большинством пользователей).
Привилегии системы для создания представлений должны дополнять, а не заменять привилегии объекта, которые ANSI требует от создателей представлений (описанных ранее в этой главе). Кроме того, в системе любого размера всегда имеются некоторые типы суперпользователей - пользователей, которые автоматически имеют большинство или все привилегии и которые могут передать свой статус суперпользователя кому-нибудь с помощью привилегии или группы привилегий. Администратор Базы Данных (DBA) является термином наиболее часто используемым для такого суперпользователя и для привилегий, которыми он обладает.
ДРУГОЕ ИСПОЛЬЗОВАНИЕ КАТАЛОГА
Конечно, вы можете выполнять более сложные запросы в системном каталоге. Объединения, например, могут быть очень удобны.
Эта команда позволит вам увидеть столбцы таблиц и базовые индексы, установленные для каждого (вывод показан на Рисунке 24.9):
SELECT a.tname, a.cname, iname, cposition FROM SYSTEMCOLUMNS a, SYSTEMINDEXES b WHERE a.tabowner = b. tabowner AND a.tname = b.tname AND a.cnumber = b.cnumber ORDER BY 3 DESC, 2;
Она показывает два индекса: один - для таблицы Заказчиков и один - для таблицы Продавцов. Последний из них это одностолбцовый индекс с именем salesno в поле snum; он был помещён первым из-за сортировки по убыванию (в обратном алфавитном порядке) в столбце iname. Другой индекс, custsale, используется продавцами, чтобы отыскивать своих заказчиков. Он основывается на комбинации полей snum и cnum внутри таблицы Заказчиков с полем snum, приходящим в индексе первым, как это и показано с помощью поля cposition.
=============== SQL Execution Log ================ | | | SELECT a.tname, a.cname, iname, cposition | | FROM SYSTEMCOLUMNS a, SYSTEMINDEXES b | | WHERE a.tabowner = b.tabowner | | AND a.tname = b.tname | | AND a.cnumber = b.cnumber | | ORDER BY 3 DESC, 2; | | | | ================================================= | | tname cname iname cposition | | ----------- ------- -------- ------------ | | Salespeople sname salesno 1 | | Customers cnum custsale 2 | | Customers snum custsale 1 | | | ===================================================
Рисунок 24.9 Столбцы и их индексы
Подзапросы также могут быть использованы. Имеется способ увидеть данные столбца только для столбцов из таблиц каталога:
SELECT * FROM SYSTEMCOLUMNS WHERE tname IN (SELECT tname FROM SYSTEMCATALOG);
Для упрощения мы не будем показывать вывод этой команды, которая состоит из одного входа для каждого столбца каждой таблицы каталога. Вы могли бы поместить этот запрос в представление, назвав его, например, SYSTEMTABCOLS, для представления SYSTEMTABLES.
ДРУГОЕ ИСПОЛЬЗОВАНИЕ ПЕРЕМЕННОЙ INDICATOR
Переменная indicator также может использоваться для просвоения NULL-значения. Просто добавьте её к имени главной переменной в команде UPDATE или INSERT тем же способом, что и в команде SELECT. Если переменная indicator имеет отрицательное значение, значение NULL будет помещено в поле.
Например, следующая команда помещает значения NULL в поля city и comm таблицы Продавцов всякий раз, когда переменные indicator i_a или i_b будут отрицательными; в противном случае она помещает туда значения главных переменных:
EXEC SQL INSERT INTO Salespeople VALUES (:Id_num, :salesperson, :loc:i_a, :comm:i_b);
Переменная indicator также используется, чтобы показывать отбрасываемую строку. Это произойдет, если вы вставляете значения символов SQL в главную переменную, которая недостаточно длинна, чтобы вместить все символы. Это особая проблема с нестандартным типами данных VARCHAR и LONG (смотри Приложение C). В этом случае переменная будет заполнена первыми символами строки, а последние символы будут потеряны. Если используется переменная indicator, в неё будет установлено положительное значение, указывающее на длину отбрасываемой части строки, позволяя таким образом узнать, сколько символов было потеряно. В этом случае вы можете проверить с помощью просмотра значение переменной indicator > 0 или < 0.
EXEC SQL (ВЫПОЛНИТЬ SQL)
Синтаксис
EXEC SQL
EXEC SQL используется, чтобы указывать начало всех команд SQL, вложенных в другой язык.
EXISTS И АГРЕГАТЫ
Одна вещь которую EXISTS не может сделать - использовать функцию агрегата в подзапросе. Это имеет значение. Если функция агрегата находит любые строки для операций с ними, EXISTS верен, невзирая на то, что это значение функции; если же агрегатная функция не находит никаких строк, EXISTS неправилен.
=============== SQL Execution Log ============ | | | SELECT DISTINCT snum | | FROM Salespeople outer | | WHERE NOT EXISTS | | (SELECT * | | FROM Customers inner | | WHERE inner.snum = outer.snum | | AND inner.cnum < > outer.cnum); | | ============================================= | | cnum | | ----- | | 1003 | | 1004 | | 1007 | =============================================
Рисунок 12.4 Использование EXISTS с NOT
Попытка использовать агрегаты с EXISTS таким способом, вероятно, покажет, что проблема неверно решалась от начала до конца. Конечно, подзапрос в предикате EXISTS может также использовать один или более из его собственных подзапросов. Они могут иметь любой из различных типов, с которыми мы уже знакомы (или познакомимся далее). Такие подзапросы и любые другие в них позволяют использовать агрегаты, если нет другой причины, по которой они не могут быть использованы. Следующий раздел приводит этому пример. В любом случае вы можете получить тот же самый результат более легко, выбрав поле, которое вы использовали в агрегатной функции, вместо использования самой этой функции. Другими словами, предикат
EXISTS (SELECT COUNT (DISTINCT sname) FROM Salespeople)
будет эквивалентен
EXISTS (SELECT sname FROM Salespeople)
который был показан выше.
FETCH (ВЫБОРКА/ИЗВЛЕЧЕНИЕ)
Синтаксис
EXEC SQL FETCH
INTO
FETCH принимает вывод из текущей строки запроса , вставляет её в список главных переменных , и перемещает курсор на следующую строку. Список может включать переменную indicator в качестве целевой переменной (См. Главу 25).
ФОРТРАН
SQL ТИПЭКВИВАЛЕНТ ФОРТРАНА
| CHAR | CHAR |
| INTEGER | INTEGER |
| REAL | REAL |
| DOUBLE PRECISION | DOUBLE PRECISION |
ФУНКЦИИ ДАТЫ И ВРЕМЕНИ
Эти функции выполняются только для допустимых значений даты или времени.
ФУНКЦИЯЗНАЧЕНИЕ
| DAY() |
Извлекает день месяца из даты. Подобные же функции существуют для MONTH (МЕСЯЦ), YEAR (ГОД), HOUR (ЧАС), SECOND (СЕКУНДА) и так далее. |
| WEEKDAY() | Извлекает день недели из даты. |
ФУНКЦИИ
Для SQL в стандарте ANSI вы можете применять агрегатные функции для столбцов или использовать их значения в скалярных выражениях, таких, например, как comm * 100. Имеется много других полезных функций, которые вы, вероятно, встречали на практике.
Имеется список некоторых общих функций SQL, отличающихся от стандартных агрегатов. Они могут использоваться в предложениях SELECT-запросов точно так же, как агрегатные функции, но эти функции выполняются для одиночных значений, а не для групповых. В следующем списке они классифицированы согласно типам данных, с которыми они работают. Если нет примечаний, то переменные в этом списке стандартизированы для любого выражения значений соответствующего типа, которые могут быть использованы в предложении SELECT.
ФУНКЦИОНАЛЬНЫЕ ЭЛЕМЕНТЫ
Следующая таблица показывает функциональные элементы команд SQL и их определения:
ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ
Предложение SELECT
Заключённое в круглые скобки предложение SELECT внутри другого условия, которое фактически оценивается отдельно для каждой строки-кандидата другого предложения.
| |
любое из следующих: + - / *
| | | |
|
ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ
USER |
[ | .]
|
|
NOT NULL | UNIQUE | CHECK () | PRIMARY KEY | REFERENCES [()]
UNIQUE () | CHECK () | PRIMARY KEY () | FOREIGN KEY () | REFERENCES [()]
ЗНАЧЕНИЕ ПО УМОЛЧАНИЮ =
Допустимый тип данных (См. в Приложении B
описание типов, обеспечиваемых ANSI, или в Приложении C - другие общие типы.)
Значение зависит от (См. Приложение B.)
(*нестандартный*)
.,..
.,..
{ [] } .,..
ГДЕ ПРИМЕНЯЮТСЯ ЗАПРОСЫ?
Запросы обычно рассматриваются как часть языка DML. Однако, так как запрос не меняет информацию в таблицах, а просто показывает её пользователю, мы будем рассматривать запросы как самостоятельную категорию среди команд DML, которые производят действия, а не просто показывают содержание базы данных (БД).
Любой запрос SQL имеет в своём составе одну команду. Структура этой команды обманчиво проста, потому что вы можете расширять её так, чтобы выполнить сложные оценки и обработку данных. Эта команда называется SELECT (ВЫБРАТЬ).
в будущем. Эта команда даёт
Синтаксис (стандартный)
GRANT ALL [PRIVILEGES]
| {SELECT
| INSERT
| DELETE
| UPDATE [()]
| REFERENCES [()l } . , . .
ON . , . .
TO PUBLIC | . , . .
[WITH GRANT OPTION];
Аргумент ALL (ВСЕ), с или без PRIVILEGES (ПРИВИЛЕГИИ), включает каждую привилегию в список привилегий. PUBLIC (ОБЩИЙ) включает всех существующих пользователей и всех созданных в будущем. Эта команда даёт возможность передать права для выполнения действий в таблице с указанным именем. REFERENCES позволяет дать права на использование столбцов в списке столбцов как родительский ключ для внешнего ключа. Другие привилегии состоят из права выполнять команды, для которых привилегии указаны их именами в таблице. UPDATE подобен REFERENCES и может накладывать ограничения на определенные столбцы. GRANT OPTION даёт возможность передавать эти привилегии другим пользователям.
Синтаксис (нестандартный)
GRANT DBA
| RESOURCE
| CONNECT ... .
TO . , . .
[IDENTIFIED BY> password>
CONNECT дает возможность передавать право на регистрацию и некоторые другие ограниченные права.
RESOURCE дает пользователю право создавать таблицы.
DBA дает возможность передавать почти все права.
IDENTIFIED BY используется вместе с CONNECT для создания или изменения пароля пользователя.
ГРУППОВЫЕ ПРЕДСТАВЛЕНИЯ
Групповые представления это представления, наподобие запроса Ratingcount в предыдущем примере, которые содержат предложение 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-запросы могут дать вам полный комплекс возможностей, так что представления обеспечивают вас чрезвычайно гибким и мощным инструментом чтобы определить точно, как ваши данные могут быть использованы.
Они могут также делать вашу работу более простой, переформатируя данные удобным для вас способом и исключив двойную работу.
ГРУППЫ ПРИВИЛЕГИЙ. ГРУППЫ ПОЛЬЗОВАТЕЛЕЙ.
Вы не обязаны ограничивать себя предоставлением единственной привилегии отдельному пользователю командой GRANT. Списки привилегий или пользователей, разделяемых запятыми, являются совершенно приемлемыми.
Stephen может предоставить и SELECT, и INSERT в таблице Заказов для Adrian:
GRANT SELECT, INSERT ON Orders TO Adrian;
или и для Adrian, и для Diane:
GRANT SELECT, INSERT ON Orders TO Adrian, Diane;
Когда привилегии и пользователи перечислены таким образом, весь список привилегий предоставляется всем указанным пользователям. В строгой ANSI интерпретации вы не можете предоставить привилегии во многих таблицах сразу одной командой, но в некоторых реализациях это ограничение может быть ослаблено, что позволяет указывать несколько таблиц, отделяя их запятыми, так чтобы весь список привилегий мог быть предоставлен для всех указанных таблиц.
ИДЕНТИФИКАЦИЯ СТРОК (ПЕРВИЧНЫЙ КЛЮЧ)
По этим и другим причинам вы должны иметь в вашей таблице столбец, который идентифицировал бы каждую строку уникально. Обычно этот столбец содержит номер, например: номер пациента, назначаемый каждому пациенту. Конечно, вы могли бы использовать имена пациентов, но возможно, что имеется несколько Mary Smith, и в этом случае вы не будете иметь другого способа отличить этих пациентов друг от друга.
Вот почему номера так необходимы. Такой уникальный столбец (или уникальная группа столбцов), используемый для идентификации каждой строки и хранения всех строк по отдельности, называется первичным ключом таблицы.
Первичные ключи таблицы - важный элемент в структуре базы данных. Они - основа вашей системы записи в файл; и, когда вы хотите найти определённую строку в таблице, вы ссылаетесь на этот первичный ключ. Кроме того, первичные ключи гарантируют, что ваши данные имеют определенную целостность. Если первичный ключ правильно используется и поддерживается, вы будете знать, что нет пустых строк таблицы и что каждая строка отличается от любой другой строки. Мы будем обсуждать ключи и далее, когда поговорим относительно справочной целостности в Главе 19.
ИМЕНА ТАБЛИЦ И СТОЛБЦОВ
Полное имя столбца таблицы фактически состоит из имени таблицы, сопровождаемого точкой, и затем имени столбца. Вот несколько примеров имён:
Salespeople.snum
Salespeople.city
Orders.odate
До этого вы могли опускать имена таблиц, потому что вы запрашивали единовременно только одну таблицу, а SQL достаточно интеллектуален, чтобы присвоить соответствующий префикс имени таблицы. Даже когда вы делаете запрос нескольких таблиц, вы ещё можете опускать имена таблиц, если все их столбцы имеют различные имена. Но так бывает не всегда. Например, мы имеем две типовые таблицы со столбцами, называемыми city.
Если мы должны связать эти столбцы (кратковременно), мы должны будем указать их с именами Salespeople.city или Customers.city, чтобы SQL мог их различать.
ИМЕНОВАНИЕ СТОЛБЦА ДЛЯ ВСТАВКИ (INSERT)
Вы можете также указывать столбцы, куда вы хотите вставить значение имени. Это позволяет вам вставлять имена в любом порядке.
Предположим, что вы берёте значения для таблицы Заказчиков из отчёта, выводимого на принтер, который помещает их в таком порядке: city, cname, cnum; и, для упрощения, вы хотите ввести значения в том же порядке:
INSERT INTO Customers (city, cnamе, cnum) VALUES ('London', 'Honman', 2001);
Обратите внимание, что столбцы rating и snum отсутствуют. Это значит, что в эти строки автоматически установлены значения по умолчанию. По умолчанию может быть введено значение NULL или другое значение, определяемое по умолчанию. Если ограничение запрещает использование значения NULL в данном столбце и в этот столбец не установлено значение по умолчанию, этот столбец должен быть обеспечен значением для любой команды INSERT, которая относится к таблице (смотри в Главе 18 информацию об ограничениях на значения NULL и "по умолчанию").
ИМЕНОВАНИЕ СТОЛБЦОВ
В нашем примере поля наших представлений имеют свои имена, полученные прямо из имён полей основной таблицы. Это удобно. Однако вам нужно снабдить ваши столбцы новыми именами:
когда некоторые столбцы являются выводимыми, и поэтому не имеющими имен.
когда два или более столбцов в объединении имеют те же имена, что в их базовой таблице.
Имена, которые могут стать именами полей, даются в круглых скобках () после имени таблицы. Они не будут запрошены, если совпадают с именами полей запрашиваемой таблицы. Тип данных и размер этих полей будут отличаться от запрашиваемых полей, которые "передаются" в них. Обычно вы не указываете новых имен полей, но если вы всё-таки сделали это, вы должны делать это для каждого поля в представлении.
ИНДЕКСЫ
Индекс это упорядоченный (буквенный или числовой) список столбцов или групп столбцов в таблице. Таблицы могут иметь большое количество строк, а, так как строки не находятся в каком-нибудь определенном порядке, их поиск по указанному значению может потребовать времени. Индексный адрес это и забота, и в то же время обеспечение способа объединения всех значений в группы из одной или больше строк, которые отличаются одна от другой.
В Главе 18 мы будем описывать более непосредственный способ, который заставит ваши значения быть уникальными. Но этот метод не существует в ранних версиях SQL. Так как уникальность часто необходима, индексы и использовались с этой целью. Индексы это средство SQL, которое породил сам рынок, а не ANSI. Поэтому сам по себе стандарт ANSI в настоящее время не поддерживает индексы, хотя они очень полезны и широко применяются.
Когда вы создаёте индекс в поле, ваша база данных (БД) запоминает соответствующий порядок всех значений этого поля в области памяти. Предположим, что наша таблица Заказчиков имеет тысячи входов, а вы хотите найти заказчика с номером=2999. Так как строки не упорядочены, ваша программа будет просматривать всю таблицу, строку за строкой, проверяя каждый раз значение поля cnum на равенство значению 2999. Однако, если бы имелся индекс в поле cnum, то программа могла бы выйти на номер 2999 прямо по индексу и дать информацию о том, как найти правильную строку таблицы.
Хотя индекс значительно улучшает эффективность запросов, использование индекса несколько замедляет операции модификации DML (такие как INSERT и DELETE), а сам индекс занимает память. Следовательно, каждый раз, когда вы создаёте таблицу, вы должны принять решение, индексировать её или нет.
Индексы могут состоять из нескольких полей. Если больше чем одно поле указывается для одного индекса, второе упорядочивается внутри первого, третье внутри второго, и так далее. Если вы имели первое и последнее имя в двух различных полях таблицы, вы могли бы создать индекс, который упорядочил бы предыдущее поле внутри последующего. Это может быть выполнено независимо от способа упорядочивали столбцов в таблице.
Синтаксис для создания индекса обычно следующий (помните, что это не ANSI-стандарт):
CREATE INDEX ON (column name [,column name]...);
Таблица, конечно, должна уже быть создана и должна содержать имя столбца. Имя индекса не может быть использовано для чего-то другого в БД (любым пользователем). Однажды созданный, индекс будет невидим пользователю. SQL сам решает, когда он необходим, чтобы ссылаться на него, и делает это автоматически. Если, например, таблица Заказчиков будет наиболее часто упоминаться в запросах продавцов к их собственной клиентуре, было бы правильно создать такой индекс в поле snum таблицы Заказчиков:
CREATE INDEX Clientgroup ON Customers (snum);
Теперь тот продавец, который имеет отношение к этой таблице, сможет найти собственную клиентуру очень быстро.
INSERT (ВСТАВИТЬ)
Синтаксис
INSERT INTO ()
VALUES () I ;
INSERT создает одну или больше новых строк в таблице . Если используется предложение VALUES, значения строк вставляются в таблицу . Если запрос указан, каждая строка вывода будет вставлена в таблицу . Если список столбцов отсутствует, все столбцы таблицы
ИНТЕРАКТИВНЫЙ И ВЛОЖЕННЫЙ SQL
Имеются два SQL: Интерактивный и Вложенный. Большей частью обе формы работают одинаково, но используются различно. Интерактивный SQL используется для функционирования непосредственно в базе данных, чтобы производить вывод для использования его заказчиком. В этом SQL - когда вы введёте команду, она сейчас же выполнится, и вы сможете сразу увидеть вывод (если он вообще получится).
Вложенный SQL состоит из команд SQL, помещённых внутри программ, которые обычно написаны на другом языке (типа КОБОЛа или ПАСКАЛя). Это делает такие программы более мощными и эффективным.
Однако, допуская эти языки, приходится иметь дело со структурой SQL и стилем управления данных, который требует некоторых расширений интерактивного SQL. Передача SQL-команд во вложенный SQL является пропускаемой ("passed off") для переменных или параметров, используемых программой, в которую они были вложены.
В этой книге мы будем представлять SQL в интерактивной форме. Это даст нам возможность обсуждать команды и их действия, не заботясь о том, как они связаны с помощью интерфейса с другими языками. Интерактивный SQL это форма, наиболее полезная для непрограммистов. Всё, что вы узнаете относительно интерактивного SQL, в основном применимо и к вложенной форме. Изменения, необходимые для использования вложенной формы, будут рассмотрены в последней главе этой книги.
INTERSECT И MINUS
Команда UNION, как вы уже видели в Главе 14, может объединить два запроса, объединив их вывод в один. Два других, обычно имеющихся способа объединения отдельных запросов, - это INTERSECT (Плюс) и MINUS (Минус). INTERSECT выводит только строки, произведённые обоими перекрестными запросами, в то время как MINUS выводит строки, которые производятся одним запросом, но не другим.
Следовательно, следующие два запроса
SELECT * FROM Salespeople WHERE city = 'London'
INTERSECT
SELECT * FROM Salespeople WHERE 'London' IN (SELECT city FROM Customers WHERE Customers.snum = Salespeople.snum);
выведут строки, произведённые обоими запросами, выдающими всех продавцов в Лондоне, которые имели по крайней мере одного заказчика, размещённого там также. С другой стороны, запрос
SELECT * FROM Salespeople WHERE city = 'London'
MINUS
SELECT * FROM Salespeople WHERE 'London' IN (SELECT sity FROM Customers WHERE Customers.snum = Salespeople.snum);
удалит строки, выбранные вторым запросом, из вывода первого, и, таким образом, будут выведены все продавцы в Лондоне, которые не имели там заказчиков.
MINUS иногда ещё называют DIFFERENCE (ОТЛИЧИЕ).
ИСПОЛЬЗОВАНИЕ АГРЕГАТНЫХ ФУНКЦИЙ В ПОДЗАПРОСАХ
Тип функций, который автоматически может производить одиночное значение для любого числа строк, конечно же - агрегатная функция.
Любой запрос, использующий одиночную функцию агрегата без предложения GROUP BY, будет выбирать одиночное значение для использования в основном предикате. Например, вы хотите увидеть все заказы, имеющие сумму выше средней на 4-е октября (вывод показан на Рисунке 10.3):
SELECT * FROM Orders WHERE amt > (SELECT AVG (amt) FROM Orders WHERE odate = 10/04/1990);
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE amt > | | (SELECT AVG (amt) | | FROM Orders | | WHERE odate = 01/04/1990); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ----- | | 3002 1900.10 10/03/1990 2007 1004 | | 3005 2345.45 10/03/1990 2003 1002 | | 3006 1098.19 10/03/1990 2008 1007 | | 3009 1713.23 10/04/1990 2002 1003 | | 3008 4723.00 10/05/1990 2006 1001 | | 3010 1309.95 10/06/1990 2004 1002 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================
Рисунок 10.3 Выбор всех сумм со значением выше среднего на 10/04/1990
Средняя сумма приобретений на 4 октября - 1788.98 (1713.23 + 75.75) делится пополам, что в целом равняется 894.49. Все строки со значением в поле amt выше этого являются выбранными. Имейте в виду, что сгруппированные агрегатные функции, которые являются агрегатными функциями, определёнными в терминах предложения GROUP BY, могут производить многочисленные значения. Они, следовательно, недопустимы в подзапросах такого характера. Даже если GROUP BY и HAVING используются таким способом, что только одна группа выводится с помощью подзапроса, команда будет отклонена в принципе. Вы должны использовать одиночную агрегатную функцию с предложением WHERE, что устранит нежелательные группы.
Например, следующий запрос который должен найти среднее значение комиссионных продавца в Лондоне,
SELECT AVG (comm) FROM Salespeople GROUP BY city HAVlNG city = "London";
не может использоваться в подзапросе! Во всяком случае, это не лучший способ формировать запрос.
Другим способом может быть
SELECT AVG (comm) FROM Salespeople WHERE city = "London";
ИСПОЛЬЗОВАНИЕ АРГУМЕНТОВ ALL И PUBLIC
SQL поддерживает два аргумента для команды GRANT, которые имеют специальное значение: ALL PRIVILEGES (ВСЕ ПРИВИЛЕГИИ), или просто ALL, и PUBLIC (ОБЩИЕ).
ALL используется вместо имён привилегий в команде GRANT, чтобы передать все привилегии в таблице.
Например, Diane может выдать Stephen весь набор привилегий в таблице Заказчиков с помощью такой команды:
GRANT REFERENCES ON Salespeople TO Diane;
(привилегии UPDATE и REFERENCES, естественно, применяются ко всем столбцам.)
А это другой способ высказать ту же мысль:
GRANT ALL ON Customers TO Stephen;
PUBLIC больше похож на тип аргумента - он захватывает всё (catch-all), - чем на пользовательскую привилегию.
Когда вы предоставляете привилегии для публикации, все пользователи автоматически их получают. Наиболее часто это применяется для привилегии SELECT в определённых базовых таблицах или представлениях, которые вы хотите сделать доступными для любого пользователя. Чтобы позволить любому пользователю видеть таблицу Заказов, вы, например, можете ввести следующее:
GRANT SELECT ON Orders TO PUBLIC;
Конечно, вы можете предоставить любые или все привилегии каждому, но это, очевидно, нежелательно. Все привилегии, за исключением SELECT, позволяют пользователю изменять (или, в случае REFERENCES, ограничивать) содержание таблицы. Разрешение всем пользователям изменять содержание ваших таблиц создаст проблемы.
Даже если вы имеете небольшую компанию и в ней работают все ваши текущие пользователи, способные выполнять команды модификации в данной таблице, было бы лучше предоставить привилегии каждому пользователю индивидуально, чем одни и те же привилегии для всех.
PUBLIC не ограничен в его передаче только текущим пользователям. Любой новый пользователь, добавляемый к вашей системе, автоматически получит все привилегии назначенные ранее всем, так что, если вы захотите ограничить доступ к таблице всем, сейчас или в будущем, лучше всего предоставить привилегии иные, нежели SELECT для индивидуальных пользователей.
ИСПОЛЬЗОВАНИЕ CHECK ДЛЯ ПРЕДОПРЕДЕЛЕНИЯ ДОПУСТИМОГО ВВОДИМОГО ЗНАЧЕНИЯ
Мы можем также использовать ограничение CHECK, чтобы защитить от ввода в поле определённых значений, и таким образом предотвратить ошибку. Например, предположим, что городами, в которых мы имеем офисы сбыта, являются Лондон, Барселона, Сан-Хосе и Нью-Йорк. Если вам известны все продавцы, работающие в каждом из этих офисов, нет необходимости разрешать ввод других значений. Если же нет, использование ограничения может предотвратить опечатки и другие ошибки.
CREATE TABLE Salespeople (snum integer NOT NULL UNIQUE, sname char(10) NOT NULL UNIQUE, city char(10) CHECK, (city IN ('London', 'New York', 'San Jose', 'Barselona')), comm decimal CHECK (comm < 1));
Конечно, если вы собираетесь сделать это, вы должны быть уверены, что ваша компания не открыла уже других новых офисов сбыта. Большинство программ баз данных поддерживают команду ALTER TABLE (см. Главу 17), которая позволяет изменять определение таблицы, даже когда она находится в использовании. Однако изменение или удаление ограничений не всегда возможно для этих команд, даже там, где это вроде бы поддерживается.
Если вы используете систему, которая не может удалять ограничения, вы должны будете создавать (CREATE) новую таблицу и передавать информацию из старой таблицы в неё всякий раз, когда вы хотите изменить ограничение. Конечно же, вы не захотите делать это часто и со временем вообще перестанете это делать. Создадим таблицу Заказов:
CREATE TABLE Orders (onum integer NOT NULL UNIQUE, amt decimal, odate date NOT NULL, cnum integer NOT NULL, snum integer NOT NULL);
Как мы уже говорили в Главе 2, тип DATЕ (ДАТА) широко поддерживается, но не является частью стандарта ANSI. Что же делать, если мы используем БД, которая, следуя ANSI, не распознаёт тип DATЕ? Если мы объявим поле odate с любым числовым типом, мы не сможем использовать слэш (/) или тире (-) в качестве разделителя. Так как печатаемые номера это символы ASCII, мы можем объявить тип поля date - CHAR. Основная проблема в том, что мы должны будем использовать одинарные кавычки всякий раз, когда ссылаемся на значение поля odate в запросе. Нет более простого решения этой проблемы там, где тип DATЕ стал таким популярным. В качестве иллюстрации, давайте объявим поле odate типом CHAR. Мы можем, как минимум, наложить на него наш формат с ограничением CHECK:
CREATE TABLE Orders (onum integer NOT NULL UNIQUE, amt decimal, odate char (10) NOT NULL CHECK (odate LIKE '--/--/----'), cnum NOT NULL, snum NOT NULL);
Кроме того, если вы хотите, вы можете наложить ограничения, гарантирующие, что введенные символы - числа и что они в пределах значений нашего диапазона.
ИСПОЛЬЗОВАНИЕ COUNT СО СТРОКАМИ, А НЕ ЗНАЧЕНИЯМИ
Чтобы подсчитать общее число строк в таблице, используйте функцию COUNT со звёздочкой вместо имени поля, как в следующем примере, вывод из которого показан на Рисунке 6.4:
SELECT COUNT (*) FROM Customers
COUNT со звёздочкой включает и NULL, и дубликаты; по этой причине DISTINCT не может быть использован. DISTINCT может производить более высокие числа, чем COUNT особого поля, который удаляет все
=============== SQL Execution Log ============ | | | SELECT COUNT (*) | | FROM Customers; | | ==============================================| | | | ------- | | 7 | | | | | ===============================================
Рисунок 6.4 Подсчет строк вместо значений
строки, имеющие избыточные или NULL-данные в этом поле. DISTINCT неприменим c COUNT (*), потому что он не имеет никакого действия в хорошо разработанной и поддерживаемой БД. В такой БД не должно быть ни таких строк, которые являлись бы полностью пустыми, ни дубликатов (первые не содержат никаких данных, а последние полностью избыточны). Если всё-таки имеются полностью пустые или избыточные строки, вы, вероятно, не захотите, чтобы COUNT скрыл от вас эту информацию.
ИСПОЛЬЗОВАНИЕ COUNT ВМЕСТО EXISTS
Подчеркнём, что все формулировки с ANY и ALL могут быть в точности выполнены с EXISTS, в то время как обратное будет неверно. Хотя в этом случае также верно и то, что подзапросы EXISTS и NOT EXISTS могут проколоть при выполнении тех же самых подзапросов с COUNT(*) в предложении SELECT подзапроса. Если больше чем ноль строк в выводе будет подсчитано, это эквивалентно EXISTS; в противном случае это работает так же, как NOT EXISTS. Следующее является этому примером (вывод показан на Рисунке 13.12):
SELECT * FROM Customers outer WHERE NOT EXISTS (SELECT * FROM Customers inner WHERE outer.rating < = inner.rating AND inner.city = 'Rome');
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers outer | | WHERE NOT EXISTS | | (SELECT * | | FROM Customers inner | | WHERE outer.rating <= inner.rating | | AND inner.city = 'Rome'); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ------ | | 2004 Grass Berlin 300 1002 | | 2008 Cisneros San Jose 300 1007 | =============================================
Рисунок 13.12 Использование EXISTS с соотнесённым подзапросом
Это должно также быть выполнено как
SELECT * FROM Customers outer WHERE 1 > (SELECT COUNT (*) FROM Customers inner WHERE outer.rating < = inner.rating AND inner.city = 'Rome');
Вывод к этому запросу показан на Рисунке 13.13. Теперь вы начинаете понимать, сколько способов имеется в SQL. Если это всё кажется несколько сложным на данном этапе, нет причин волноваться. Вы обучаетесь, чтобы использовать ту технику, которая больше отвечает вашим требованиям и наиболее понятна для вас. Начиная с этого места, мы хотим показать вам большое количество возможностей, чтобы вы могли найти ваш собственный стиль.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers outer | | WHERE 1 > | | (SELECT COUNT (*) | | FROM Customers inner | | WHERE outer.rating <= inner.rating | | AND inner.city = 'Rome'); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ------ | | 2004 Grass Berlin 300 1002 | | 2008 Cisneros San Jose 300 1007 | =============================================
Рисунок 13.13 Использование COUNT вместо EXISTS
ИСПОЛЬЗОВАНИЕ DISTINCT
Обратите внимание в вышеупомянутом примере, что DISTINCT, сопровождаемый именем поля, с которым он применяется, помещён в круглые скобки, но не сразу после SELECT, как раньше. Такого использования DISTINCT с COUNT, применяемого к индивидуальным столбцам, требует стандарт ANSI, но большое количество программ не предъявляют такого требования.
=============== SQL Execution Log ============ | | | SELECT COUNT (DISTINCT snum) | | FROM Orders; | | ==============================================| | | | ------- | | 5 | | | | | ===============================================
Рисунок 6.3 Подсчет значений поля
Вы можете выполнять несколько подсчётов (COUNT) в полях с помощью DISTINCT в одиночном запросе, что, как мы видели в Главе 3, не выполнялось, когда вы выбирали строки с помощью DISTINCT.
DISTINCT может использоваться таким образом с любой агрегатной функцией, но наиболее часто он используется с COUNT. С MAX и MIN это просто не будет иметь никакого эффекта, а SUM и AVG вы обычно применяете для включения повторяемых значений, так как они эффективнее общих и средних значений всех столбцов.
ИСПОЛЬЗОВАНИЕ EXISTS С СООТНЕСЁННЫМИ ПОДЗАПРОСАМИ
В соотнесённом подзапросе предложение EXISTS оценивается отдельно для каждой строки таблицы, имя которой указано во внешнем запросе, точно так же, как и другие операторы предиката, когда вы используете соотнесённый подзапрос. Это даёт возможность использовать EXISTS как верный предикат, который генерирует различные ответы для каждой строки таблицы, указанной в основном запросе. Следовательно, информация из внутреннего запроса будет сохранена, если выведена непосредственно, когда вы используете EXISTS таким способом. Например, мы можем вывести продавцов, которые имеют нескольких заказчиков (вывод для этого запроса показан на Рисунке 12.2):
SELECT DISTINCT snum FROM Customers outer WHERE EXISTS (SELECT * FROM Customers inner WHERE inner.snum = outer.snum AND inner.cnum < > outer.cnum);
=============== SQL Execution Log ============ | | | SELECT DISTINCT cnum | | FROM Customers outer | | WHERE EXISTS | | (SELECT * | | FROM Customers inner | | WHERE inner.snum = outer.snum | | AND inner.cnum < > outer.cnum); | | ============================================= | | cnum | | ----- | | 1001 | | 1002 | =============================================
Рисунок 12.2 Использование EXISTS с соотнесённым подзапросом
Для каждой строки-кандидата внешнего запроса (представляющей заказчика, проверяемого в настоящее время) внутренний запрос находит строки, которые совпадают со значением поля snum (которое имел продавец), но не со значением поля cnum (соответствующего другим заказчикам). Если любые такие строки найдены внутренним запросом, это означает, что имеются два разных заказчика, обслуживаемых текущим продавцом (то есть продавцом заказчика в текущей строке-кандидате из внешнего запроса). Предикат EXISTS поэтому верен для текущей строки, и номер продавца поля (snum) таблицы, указанной во внешнем запросе, будет выведен. Если DISTINCT не был указан, каждый из этих продавцов будет выбран один раз для каждого заказчика, которому он назначен.
ИСПОЛЬЗОВАНИЕ IN ИЛИ EXISTS ВМЕСТО ANY
Мы можем также использовать оператор IN для создания запроса, аналогичного предыдущему:
SELECT * FROM Salespeople WHERE city IN (SELECT city FROM Customers);
Этот запрос даст вывод, показанный на Рисунке 13.2.
Однако оператор ANY может использовать другие реляционные операторы, помимо равно (=), и, таким образом, делать сравнения, которые превосходят возможности IN. Например, мы могли бы найти всех продавцов с их заказчиками, которые следуют в алфавитном порядке (вывод показан на Рисунке 13.3).
SELECT * FROM Salespeople WHERE sname < ANY (SELECT cname FROM Customers);
=============== SQL Execution Log ============ | SELECT * | | FROM Salespeople | | WHERE city IN | | (SELECT city | | FROM Customers); | | ============================================= | | cnum cname city comm | | ----- -------- ---- -------- | | 1001 Peel London 0.12 | | 1002 Serres San Jose 0.13 | | 1004 Motika London 0.11 | =============================================
Рисунок 13.2 Использование IN в качестве альтернативы ANY
=============== SQL Execution Log ============ | SELECT * | | FROM Salespeople | | WHERE sname < ANY | | (SELECT cname | | FROM Customers); | | ============================================= | | cnum cname city comm | | ----- -------- ---- -------- | | 1001 Peel London 0.12 | | 1004 Motika London 0.11 | | 1003 Axelrod New York 0.10 | =============================================
Рисунок 13.3 Использование оператора ANY с операцией "меньше" (<)
продавцов для их заказчиков, которые упорядочены в алфавитном порядке (вывод показан на Рисунке 13.3).
SELECT * FROM Salespeople WHERE sname < ANY (SELECT cname FROM Customers);
Все строки были выбраны для Serres и Rifkin, потому что нет других заказчиков, чьи имена следовали бы за ними в алфавитном порядке. Обратите внимание, что это является основным эквивалентом следующему запросу с EXISTS, вывод которого показан на Рисунке 13.4:
SELECT * FROM Salespeople outer WHERE EXISTS (SELECT * FROM Customers inner WHERE outer.sname < inner.cname);
=============== SQL Execution Log ============ | SELECT * | | FROM Salespeople outer | | WHERE EXISTS | | (SELECT * | | FROM Customers inner | | WHERE outer.sname < inner.cname); | | ============================================= | | cnum cname city comm | | ----- -------- ---- -------- | | 1001 Peel London 0.12 | | 1004 Motika London 0.11 | | 1003 Axelrod New York 0.10 | =============================================
Рисунок 13. 4 Использование EXISTS как альтернативы оператору ANY
Любой запрос, который может быть сформулирован с ANY (или, как мы увидим, с ALL), мог бы быть сформулирован также с EXISTS, хотя обратное будет неверно. Строго говоря, вариант с EXISTS не абсолютно идентичен вариантам с ANY или с ALL из-за различия в обработке пустых (NULL) значений (что будет обсуждаться позже в этой главе). Тем не менее, с технической точки зрения, вы могли бы делать это без ANY и ALL, если проявите находчивость в использовании EXISTS (и IS NULL).
Большинство пользователей, однако, находят ANY и ALL более удобными в использовании, чем EXISTS, который требует соотнесённых подзапросов. Кроме того, в зависимости от реализации, ANY и ALL могут, по крайней мере в теории, быть более эффективными, чем EXISTS. Подзапросы ANY или ALL могут выполняться один раз и иметь вывод, используемый, чтобы определять предикат для каждой строки основного запроса. EXISTS, с другой стороны, берёт соотнесенный подзапрос, который требует, чтобы весь подзапрос повторно выполнялся для каждой строки основного запроса. SQL пытается найти наиболее эффективный способ выполнения любой команды и может попробовать преобразовать менее эффективную формулу запроса в более эффективную (но вы не можете всегда рассчитывать на получение самой эффективной формулировки).
Основная причина для формулировки EXISTS как альтернативы ANY и ALL в том, что ANY и ALL могут быть несколько неоднозначны из-за способа использования этого термина в английском языке, как вы это скоро увидите. С приходом понимания различия способов формулирования данного запроса, вы сможете поработать над процедурами, которые сейчас кажутся вам трудными или неудобными.
ИСПОЛЬЗОВАНИЕ NOT EXISTS
Предыдущий пример показал, что EXISTS может работать в комбинации с булевыми операторами. Конечно, самым простым способом (и, вероятно, чаще всего используемым с EXISTS) является оператор NOT. Один из способов, которым мы могли бы найти всех продавцов только с одним заказчиком, будет состоять в том, чтобы инвертировать наш предыдущий пример. (Вывод для этого запроса показан на Рисунке 12.4:)
SELECT DISTINCT snum FROM Customers outer WHERE NOT EXISTS (SELECT * FROM Customers inner WHERE inner.snum = outer.snum AND inner.cnum < > outer.cnum);
ИСПОЛЬЗОВАНИЕ NOT СО СПЕЦИАЛЬНЫМИ ОПЕРАТОРАМИ
Операнды могут непосредственно предшествовать булеву NOT.
Это противоположно реляционным операциям, когда оператор NOT должен идти перед вводимым выражением. Например, если мы хотим устранить NULL из нашего вывода, мы будем использовать NOT, чтобы изменить на противоположное значение предиката:
SELECT * FROM Customers WHERE city NOT NULL;
При отсутствии значений NULL (как в нашем случае), будет выведена вся таблица Заказчиков. Аналогично можно ввести следующее
SELECT * FROM Customers WHERE NOT city IS NULL;
что также приемлемо. Мы можем также использовать NOT с IN:
SELECT * FROM Salespeople WHERE city NOT IN ('London', 'San Jose');
А вот другой способ подобного же выражения:
SELECT * FROM Salespeople WHERE NOT city IN ('London', ' San Jose');
Вывод для этого запроса показан на Рисунке 5.9.
Таким же способом вы можете использовать NOT BETWEEN и NOT LIKE.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salespeople | | WHERE сity NOT IN ('London', 'San Jose';) | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1003 Rifkin Barcelona 0.15 | | 1007 Axelrod New York 0.10 | | | ===============================================
Рисунок 5.9 Использование NOT с IN
ИСПОЛЬЗОВАНИЕ ОГРАНИЧЕНИЙ ДЛЯ ИСКЛЮЧЕНИЯ ПУСТЫХ (NULL) ЗНАЧЕНИЙ
Вы можете использовать команду CREATE TABLE, чтобы предотвратить появление в поле пустых (NULL) указателей с помощью ограничения NOT NULL. Это ограничение накладывается только для разнообразных столбцов. Вы можете вспомнить, что NULL это специальное обозначение, которое отмечает поле как пустое. NULL может быть полезен в тех случаях, когда вы хотите быть от этого гарантированы.
Очевидно, что первичные ключи никогда не должны быть пустыми, поскольку это будет нарушать их функциональные возможности. Кроме того, такие поля как имена требуют в большинстве случаев определённых значений. Например, вы, вероятно, захотите иметь имя для каждого заказчика в таблице Заказчиков. Если вы поместите ключевые слова NOT NULL сразу после типа данных (включая размер) столбца, любая попытка поместить значение NULL в это поле будет отклонена. В противном случае SQL принимает, что NULL разрешен.
Например, давайте улучшим наше определение таблицы Продавцов, не позволяя помещать NULL-значения в столбцы snum или sname:
CREATE TABLE Salespeople (Snum integer NOT, Sname char (10) NOT, city char (10), comm decimal);
Важно помнить, что любому столбцу с ограничением NOT NULL должно быть установлено значение в каждом предложении INSERT, воздействующем на таблицу. При отсутствии NULL, SQL может не иметь значений для установки в эти столбцы, если, конечно, значение по умолчанию, описанное ранее в этой главе, уже не было назначено. Если ваша система поддерживает использование ALTER TABLE, чтобы добавлять новые столбцы к уже существующей таблице, вы можете, вероятно, помещать ограничение столбцов типа NOT NULL для этих новых столбцов. Однако, если вы предписываете новому столбцу значение NOT NULL, текущая таблица должна быть пустой.
ИСПОЛЬЗОВАНИЕ ПЕРЕМЕННОЙ INDICATOR ДЛЯ ЭМУЛЯЦИИ NULL-ЗНАЧЕНИЙ SQL
Другая возможность состоит в том, чтобы обрабатывать переменную indicator, связывая её с каждой переменной главного языка, специальным способом, эмулирующим поведение NULL-значений SQL. Всякий раз, когда вы используете одно из этих значений в вашей программе, например, в предложении if ... then, вы можете сначала проверить связанную переменную indicator: равно ли её значение NULL. Если это так, то вы обрабатываете переменную по-другому.
Например, если NULL-значение было извлечено из поля city для главной переменной city, которая связана с переменной indicator i_city, вы должны установить значение city, равное последовательности пробелов. Это будет необходимо, только если вы будете распечатывать его на принтере; его значение не должно отличаться от логики вашей программы. Естественно, i_city автоматически устанавливается в отрицательное значение.
Предположим, что вы имели следующую конструкцию в вашей программе:
If sity = 'London' then comm: = comm + .01 else comm: = comm - .01
Любое значение, вводимое в переменную city, будет равно "London" или не будет равно. Следовательно, в каждом случае значение комиссионных будет либо увеличено, либо уменьшено. Однако эквивалентные команды в SQL выполняются по разному:
EXEC SQL UPDATE Salespeople SET comm = comm + .01 WHERE sity = 'London';
и
EXEC SQL UPDATE Salespeople SET comm = comm .01; WHERE sity < > 'London';
(Вариант на ПАСКАЛе работает только с единственным значением, в то время как вариант на SQL работает со всеми таблицами.) Если значение city в варианте на SQL будет равно значению NULL, оба предиката будут неизвестны, и значение comm, следовательно, не будет изменено в любом случае. Вы можете использовать переменную indicator, чтобы сделать поведение вашего главного языка не противоречащим этому, с помощью создания условия, которое исключает NULL значения:
If i_city > = O then begin If city = 'London' then comm: = comm + .01 else comm: = comm - .01; end; {begin and end нужны здесь только для понимания}
ИСПОЛЬЗОВАНИЕ ПЕРЕМЕННЫХ ОСНОВНОГО ЯЗЫКА В SQL
Основной способ, которым SQL и части базового языка ваших программ будут связываться друг с другом - значения переменных. Естественно, что разные языки распознают различные типы данных для переменных. ANSI определяет эквиваленты SQL для четыре базовых языков: ПЛ/1, Паскаль, КОБОЛ и ФОРТРАН; всё это подробно описано в Приложении B. Эквиваленты для других языков определяет проектировщик.
Имейте в виду, что такие типы как DATE не распознаются ANSI, и, следовательно, никакие эквивалентные типы данных для базовых языков не существуют в стандарте ANSI. Более сложные типы данных базового языка, такие как матрицы, не имеют эквивалентов в SQL. Вы можете использовать переменные из главной программы во вложенных операторах SQL везде, где вы будете использовать выражения значений. (SQL, используемый в этой главе, будет пониматься как вложенный SQL до тех пор, пока это не будет оговорено особо.)
Текущим значением переменной может быть значение, используемое в команде. Главные переменные должны:
быть объявленными в SQL DECLARE SESSION (РАЗДЕЛ ОБЪЯВЛЕНИЙ), который будет описан далее;
иметь совместимый тип данных с их функциями в команде SQL (например, числовой тип, если он вставляется в числовое поле);
быть присвоенными значению во время их использования в команде SQL, если команда SQL самостоятельно не может сделать назначение;
предшествовать двоеточию (:), когда они упоминаются в команде SQL.
Так как главные переменные отличаются от имён столбцов SQL наличием у них двоеточия, вы можете использовать переменные с теми же самыми именами, что и ваши столбцы, если это, конечно, нужно.
Предположим, что у вас есть четыре переменные с именами id_num, salesperson, loc и comm. Они содержат значения, которые вы хотите вставить в таблицу Продавцов. Вы могли бы вложить следующую команду SQL в вашу программу:
EXEC SQL INSERT INTO Salespeople VALUES (:id_num, :salesperson, :loc, :comm)
Текущие значения этих переменных будут помещены в таблицу. Как видите, переменная comm имеет то же самое имя, что и столбец, в который это значение вкладывается. Обратите внимание, что точка с запятой в конце команды отсутствует. Это потому, что соответствующее завершение для вложенной команды SQL зависит от языка, для которого делается вложение.
Для Паскаля и PL/1 это будет точка с запятой, для КОБОЛА - слово END-EXEC, а для ФОРТРАНА не будет никакого завершения. В других языках это зависит от реализации, и поэтому мы договоримся, что будем использовать точку с запятой (в этой книге) всегда, чтобы не противоречить интерактивному SQL и Паскалю. Паскаль завершает вложенный SQL и собственные команды одинаково: точкой с запятой. Способ сделать команду полностью такой, как описано выше, состоит в том, чтобы включать её в цикл и повторять её с различными значениями переменных, как показано в следующем примере:
while not end-ot-file (input) do begin readln (id_num, salesperson, loc, comm); EXEC SOL INSERT INTO Salespeople VALUES (:id_num, :salesperson, :loc, :comm); end;
Фрагмент программы на ПАСКАЛЕ определяет цикл, который будет считывать значения из файла, сохранять их в четырёх именованных переменных, сохранять значения этих переменных в таблице Продавцов, а затем считывать следующие четыре значения, повторяя этот процесс до тех пор, пока весь входной файл не будет прочитан. Считается, что каждый набор значений завершается возвратом каретки (для не знакомых с Паскалем: функция readln считывает вводимую информацию и переходит на следующую строку источника этой информации). Это дает вам простой способ передать данные из текстового файла в реляционную структуру.
Конечно, вы можете сначала обработать данные любыми возможными способами на вашем главном языке, например, для исключения всех комиссионных ниже значения .12
while not end-ot-file (input) do begin readln (id_num, salesperson, loc, comm); if comm > = .12 then EXEC SQL INSERT INTO Salespeople VALUES (:id_num, :salesperson, :loc, :comm); end;
Только строки, которые выполнят условие comm >= .12, будут вставлены в вывод. Это показывает, что можно использовать и циклы, и условия как нормальные для главного языка.
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ, КОТОРЫЕ ВЫДАЮТ МНОГО СТРОК С ПОМОЩЬЮ ОПЕРАТОРА IN
Вы можете использовать подзапросы, которые производят любое число строк, если вы применяете специальный оператор IN (операторы BETWEEN, LIKE и IS NULL не могут использоваться с подзапросами). Как вы помните, IN определяет набор значений, одно из которых должно совпадать с другим термином уравнения предиката в заказе, чтобы предикат был верным.
Когда вы используете IN с подзапросом, SQL просто формирует этот набор из вывода подзапроса. Мы можем, следовательно, использовать IN чтобы выполнить такой подзапрос, который не будет работать с реляционным оператором, и найти все атрибуты таблицы Заказов для продавца в Лондоне (вывод показан на Рисунке 10.4):
SELECT * FROM Orders WHERE snum IN (SELECT snum FROM Salespeople WHERE city = "LONDON");
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE snum IN | | (SELECT snum | | FROM Salespeople | | WHERE city = 'London'); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3003 767.19 10/03/1990 2001 1001 | | 3002 1900.10 10/03/1990 2007 1004 | | 3006 1098.19 10/03/1990 2008 1007 | | 3008 4723.00 10/05/1990 2006 1001 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================
Рисунок 10.4 Использование подзапроса с IN
В ситуации, подобной этой, подзапрос проще для понимания пользователем и проще для выполнения компьютером, чем если бы вы использовали объединение:
SELECT onum, amt, odate, cnum, Orders.snum FROM Orders, Salespeople WHERE Orders.snum = Salespeople.snum AND Salespeople.city = "London";
Хотя это и произведёт тот же самый вывод, что в примере с подзапросом, SQL должен будет просмотреть каждую возможную комбинацию строк из двух таблиц и проверить их снова по составному предикату. Проще и эффективнее извлекать из таблицы Продавцов значения поля snum, где city = "London", а затем искать эти значения в таблице Заказов, как это делается в варианте с подзапросом. Внутренний запрос даёт нам snums=1001 и snum=1004. Внешний запрос затем даёт нам строки из таблицы Заказов, где эти поля snum найдены.
Строго говоря, то, быстрее или нет работает вариант подзапроса, практически зависит от реализации - в какой программе вы это используете. Часть вашей программы, называемая оптимизатор, пытается найти наиболее эффективный способ выполнения ваших запросов. Хороший оптимизатор в любом случае преобразует вариант объединения в подзапрос, но нет достаточно простого способа, чтобы выяснить, выполнено это или нет. Лучше сохранить ваши запросы в памяти, нежели полагаться полностью на оптимизатор.
Конечно, вы можете также использовать оператор IN, даже когда вы уверены, что подзапрос произведет одиночное значение. В любой ситуации, где вы можете использовать реляционный оператор сравнения (=), вы можете использовать IN. В отличие от реляционных операторов, IN не может заставить команду потерпеть неудачу, если больше чем одно значение выбрано подзапросом. Это может быть или преимуществом или недостатком. Вы не увидите непосредственно вывода из подзапросов, если вы полагаете, что подзапрос собирается произвести только одно значение, а он производит несколько. Вы не сможете объяснить различия в выводе основного запроса. Например, рассмотрим команду, которая похожа на предыдущую:
SELECT onum, amt, odate FROM Orders WHERE snum = (SELECT snum FROM Orders WHERE cnum = 2001);
Вы можете устранить потребность в DISTINCT, используя IN вместо (=):
SELECT onum, amt, odate FROM Orders WHERE snum IN (SELECT snum FROM Orders WHERE cnum = 2001);
Что случится, если есть ошибка и один из заказов был аккредитован различным продавцам? Версия, использующая IN, будет выдавать вам все заказы для обоих продавцов. Нет никакого очевидного способа наблюдения за ошибкой, и поэтому сгенерированные отчеты или решения, сделанные на основе этого запроса, не будут содержать ошибки. Вариант, использующий (=), просто потерпит неудачу. Это, по крайней мере, позволило вам узнать, что имеется такая проблема. Вы должны затем выполнять поиск неисправности, выполнив этот подзапрос отдельно и наблюдая значения, которые он производит. В принципе, если вы знаете, что подзапрос должен (по логике) вывести только одно значение, вы должны использовать =.
IN является подходящим, если запрос может ограниченно производить одно или более значений, независимо от того, ожидаете вы их или нет. Предположим, мы хотим знать комиссионные всех продавцов, обслуживающих заказчиков в Лондоне:
SELECT comm FROM Salespeople WHERE snum IN (SELECT snum FROM Customers WHERE city = "London");
Выводимыми для этого запроса, показанного в Рисунке 10.5, являются значения комиссионных продавца Peel (snum = 1001), который имеет обоих заказчиков в Лондоне. Но это только для данного случая. Нет никакой причины, чтобы некоторые заказчики в Лондоне не могли быть назначены кому-то ещё. Следовательно, IN - это наиболее логичная форма для использования в запросе.
=============== SQL Execution Log ============== | | | SELECT comm | | FROM Salespeople | | WHERE snum IN | | (SELECT snum | | FROM Customers | | WHERE city = 'London'); | | =============================================== | | comm | | ------- | | 0.12 | | | | | ================================================
Рисунок 10.5 Использование IN с подзапросом для вывода одного значения
Между прочим, префикс таблицы для поля city в предыдущем примере не обязателен, несмотря на возможную неоднозначность между полями city таблицы Заказчика и таблицы Продавцов. SQL всегда ищет первое поле в таблице, обозначенной в предложении FROM текущего подзапроса. Если поле с данным именем там не найдено, проверяются внешние запросы. В вышеупомянутом примере, "city" в предложении WHERE означает, что имеется ссылка на Customer.city (поле city таблицы Заказчиков). Так как таблица Заказчиков указана в предложении FROM текущего запроса, SQL предполагает что это правильно. Это предположение может быть отменено полным именем таблицы или префиксом псевдонима, о которых мы поговорим позже, когда будем говорить о соотнесенных подзапросах. Если возможен беспорядок, конечно же, лучше всего использовать префиксы.
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С DELETE
Вы можете также использовать подзапросы в предикате команды DELETE. Это даст вам возможность определять некоторые довольно сложные критерии, чтобы установить, какие строки будут удаляться, что важно, так как вы, конечно же, не захотите по неосторожности удалить нужную строку. Например, если мы закрыли наше ведомство в Лондоне, мы могли бы использовать следующий запрос чтобы удалить всех заказчиков, назначенных продавцам в Лондоне:
DELETE FROM Customers WHERE snum = ANY (SELECT snum FROM Salespeople WHERE city = 'London');
Эта команда удалит из таблицы Заказчиков строки Hoffman и Clemens (назначенных для Peel), и Pereira (назначенного для Motika). Конечно, вы захотите удостовериться, правильно ли сформирована эта операция, прежде чем удалить или изменить строки Peel и Motika.
Это важно. Обычно, когда мы делаем модификацию в базе данных, которая повлечет другие модификации, наше первое желание - сделать сначала основное действие, а затем проследить другие, вторичные. Этот пример, покажет, почему более эффективно делать наоборот, выполнив сначала вторичные действия.
Если, например, вы решили изменить значения полей city ваших продавцов везде, где они переназначены, вы должны рассмотреть всех этих заказчиков более сложным способом.
Так как реальные БД имеют тенденцию разрастаться до значительно больших размеров, чем наши небольшие типовые таблицы, это может стать серьезной проблемой. SQL может предоставить некоторую помощь в этой области, используя механизм справочной целостности (обсуждённый в Главе 19), но это не всегда доступно и не всегда применимо.
Хотя вы не можете обращаться к таблице, из которой вы будете удалять строки, в предложении FROM подзапроса, вы можете в предикате сослаться на текущую строку-кандидат этой таблицы, которая является строкой, которая проверяется в основном предикате. Другими словами, вы можете использовать соотнесённые подзапросы. Они отличаются от тех соотнесённых подзапросов, которые вы могли использовать с INSERT, в котором они фактически базировались на строках-кандидатах таблицы, задействованной в команде, а не на запросе другой таблицы.
DELETE FROM Salespeople WHERE EXISTS (SELECT * FROM Customers WHERE rating = 100 AND Salespeople.snum = Customers.snum);
Обратите внимание, что часть AND предиката внутреннего запроса обращается к таблице Продавцов. Это означает, что весь подзапрос будет выполняться отдельно для каждой строки таблицы Продавцов, так же, как это выполнялось с другими соотнесенными подзапросами. Эта команда удалит всех продавцов, которые имели по меньшей мере одного заказчика с оценкой 100 в таблице Продавцов. Конечно же, имеется другой способ сделать то же:
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 WHERE amt = (SELECT MIN (amt) FROM Orders b WHERE a.odate = b.odate));
Подзапрос в предикате DELETE принимает соотнесённый подзапрос. Этот внутренний запрос находит минимальный заказ суммы приобретений для даты каждой строки внешнего запроса. Если эта сумма - такая же, как и сумма текущей строки, предикат внешнего запроса верен, что означает, что текущая строка имеет наименьший заказ для этой даты. Поле snum продавца, ответственного за этот заказ, извлекается и передается в основной предикат команды DELETE, которая затем удаляет все строки с этим значением поля snum из таблицы Продавцов (так как snum это первичный ключ таблицы Продавцов, то, естественно, там должна иметься только одна удаляемая строка для значения поля snum, выведенного с помощью подзапроса. Если имеется больше одной строки, все они будут удалены.) Поле snum = 1007, которое будет удалено, имеет наименьшее значение на 3 октября; поле snum = 1002, наименьшее на 4 октября; поле snum = 1001, наименьшее в заказах на 5 октября (эта команда кажется довольно грубой, особенно когда она удаляет Peel создавшего единственный заказ на 5 октября, но зато это хорошая иллюстрация).
Если вы хотите сохранить Peel, вы могли бы добавить другой подзапрос, который сделал бы это:
DELETE FROM Salespeople WHERE snum IN (SELECT snum FROM Orders a WHERE amt = (SELECT MIN (amt) FROM Orders b WHERE a.odate = b.odate) AND 1 < (SELECT COUNT onum FROM Orders b WHERE a.odate = b.odate));
Теперь для дня, в котором был создан только один заказ, будет произведен счёт = 1 во втором соотнесённом подзапросе. Это сделает предикат внешнего запроса неправильным, и поля snum, следовательно, не будут переданы в основной предикат.
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С INSERT
INSERT это самый простой случай. Вы уже видели, как вставлять результаты запроса в таблицу. Вы можете использовать подзапросы внутри любого запроса, который генерирует значения для команды INSERT, тем же самым способом, которым вы делали это для других запросов - т.е. внутри предиката или предложения HAVING.
Предположим, что имеется таблица SJpeople, столбцы которой совпадают со столбцами нашей таблицы Продавцов. Вы уже видели, как заполнять таблицу, подобную этой, заказчиками в городе, например, в San Jose:
INSERT INTO SJpeople SELECT * FROM Salespeople WHERE city = 'San Jose';
Теперь мы можем использовать подзапрос, чтобы добавить в таблицу SJpeople всех продавцов, которые имеют заказчиков в San Jose, независимо от того, находятся ли там продавцы или нет:
INSERT INTO SJpeople SELECT * FROM Salespeople WHERE snum = ANY (SELECT snum FROM Customers WHERE city = 'San Jose');
Оба запроса в этой команде функционируют так же, как если бы они не являлись частью выражения INSERT. Подзапрос находит все строки для заказчиков в San Jose и формирует набор значений snum. Внешний запрос выбирает строки из таблицы Salespeople, где эти значения snum найдены. В этом примере, строки для продавцов Rifkin и Serres, которые назначены заказчикам в San Jose - Liu и Cisneros, будут вставлены в таблицу SJpeople.
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С UPDATE
UPDATE использует подзапросы тем же самым способом, что и DELETE, внутри этого необязательного предиката. Вы можете использовать соотнесённые подзапросы в форме, пригодной для использования с DELETE - связанной или с модифицируемой таблицей, или с таблицей вызываемой во внешнем запросе. Например, с помощью соотнесённого подзапроса к таблице, которая будет модифицироваться, вы можете увеличить комиссионные всех продавцов которые были назначены по крайней мере двум заказчикам:
UPDATE Salespeople SET comm = comm + .01 WHERE 2 < = (SELECT COUNT (cnum) FROM Customers WHERE Customers.snum = Salespeople.snum);
Теперь продавцы Peel и Serres, имеющие нескольких заказчиков, получат повышение своих комиссионных. Имеется разновидность последнего примера из предыдущего раздела с DELETE. Он уменьшает комиссионные продавцов которые оформили наименьшие заказы, но не стирает их в таблице:
UPDATE Salespeople SET comm = comm - .01 WHERE snum IN (SELECT snum FROM Orders a WHERE amt = (SELECT MIN (amt) FROM Orders b WHERE a.odat = b.odate));
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ, СОЗДАННЫХ ВО ВНЕШНИХ ЗАПРОСАХ ТАБЛИЦ
Запрещение на обращение к таблице, которая модифицируется командой INSERT, не предохранит вас от использования подзапросов, которые обращаются к таблице, используемой в предложении FROM внешней команды SELECT. Таблица, из которой вы выбираете значения, чтобы произвести их для INSERT, не будет задействована командой, и вы сможете обращаться к этой таблице любым способом, которым вы обычно это делали, но только если эта таблица указана в автономном запросе. Предположим, что имеется таблица Samecity, в которой мы запомним продавцов с заказчиками в их городах. Мы можем заполнить таблицу используя, соотнесённый подзапрос:
INSERT INTO (Samecity SELECT * FROM (Salespeople outer WHERE city IN (SELECT city FROM Customers inner WHERE inner.snum = outer.snum);
Ни таблица Samecity, ни таблица Продавцов не должны быть использованы во внешних или внутренних запросах INSERT. В качестве другого примера предположим, что у вас установлена премия для продавца, имеющего самый большой заказ, на каждый день. Вы следите за этим в таблице с именем Bonus, которая содержит поле snum продавцов, поле odate и поле amt. Вы должны заполнить эту таблицу информацией, которая хранится в таблице Заказов, используя следующую команду:
INSERT INTO Bonus SELECT snum, odate, amt FROM Orders a WHERE amt = (SELECT MAX (amt) FROM Orders b WHERE a.odate = b.odate);
Даже если эта команда имеет подзапрос, который базируется на той же самой таблице, что и внешний запрос, он не обращается к таблице Bonus, на которую воздействует команда. Что для нас абсолютно приемлемо. Логика запроса, естественно, должна просматривать таблицу Заказов и находить для каждой строки максимальную сумму заказа для данной даты. Если эта величина - такая же, как у текущей строки, текущая строка является наибольшим заказом для этой даты, и данные вставляются в таблицу Bonus.
ИСПОЛЬЗОВАНИЕ ПРЕДСТАВЛЕНИЙ ДЛЯ ФИЛЬТРАЦИИ ПРИВИЛЕГИЙ
Вы можете сделать действия привилегий более точными, используя представления. Всякий раз, когда вы передаёте привилегию в базовой таблице пользователю, она автоматически распространяется на все строки, а при использовании возможных исключений UPDATE и REFERENCES - на все столбцы таблицы.
Создавая представление, которое ссылается на основную таблицу и затем переносит привилегию на представление, а не на таблицу, вы можете ограничивать эти привилегии любыми выражениями в запросе, содержащемся в представлении. Это значительно улучшает базисные возможности команды GRANT.
ИСПОЛЬЗОВАНИЕ ПРЕДСТАВЛЕНИЙ В ТАБЛИЦАХ КАТАЛОГА
Поскольку SYSTEMCATALOG это таблица, вы можете использовать её в представлении. Фактически можно считать, что имеется такое представление с именем SYSTEMTABLES.
Это представление SYSTEMCATALOG содержит только те таблицы, которые входят в системный каталог; это обычно таблицы базы данных, типа таблицы Продавцов, которые показаны в SYSTEMCATALOG, но не в SYSTEMTABLES.
Давайте предположим, что только таблицы каталога являются собственностью пользователя SYSTEM. Если вы захотите, вы можете определить другое представление, которое специально исключало бы таблицы каталога из вывода:
CREATE VIEW Datatables AS SELECT * FROM SYSTEMCATALOG WHERE owner < > 'SYSTEM';
РАЗРЕШИТЬ ПОЛЬЗОВАТЕЛЯМ ВИДЕТЬ (ТОЛЬКО) ИХ СОБСТВЕННЫЕ ОБЪЕКТЫ
Кроме того, возможно другое использование представлений каталога. Предположим, вам нужно, чтобы каждый пользователь был способен сделать запрос каталога для получения информации только из таблиц, которыми он владеет. Поскольку значение USER в команде SQL постоянно для ID доступа пользователя, выдающего команду, оно может всегда быть использовано, чтобы давать доступ пользователям только к их собственным таблицам.
Вы можете, для начала, создать следующее представление:
CREATE VIEW Owntables AS SELECT * FROM SYSTEMCATALOG WHERE Owner = USER;
Теперь вы можете предоставить всем пользователям доступ к этому представлению:
GRANT SELECT ON Owntables TO PUBLIC;
Каждый пользователь теперь способен выбирать (SELECT) только те строки из SYSTEMCATALOG, владельцем которых он сам является.
ПРЕДСТАВЛЕНИЕ SYSTEMCOLUMNS
Одно небольшое добавление к этому позволит каждому пользователю просматривать таблицу SYSTEMCOLUMNS для столбцов из его собственных таблиц.
Сначала давайте рассмотрим ту часть таблицы SYSTEMCOLUMNS, которая описывает наши типовые таблицы (другими словами, исключим сам каталог):
tname cname datatype cnumber tabowner ----------- ----- -------- ------- --------
Salespeople snum integer 1 Diane
Salespeople sname char 2 Diane
Salespeople city char 3 Diane
Salespeople comm decimal 4 Diane
Customers cnum integer 1 Claire
Customers cname char 2 Claire
Customers city char 3 Claire
Customers rating integer 4 Claire
Customers snum integer 5 Claire
Orders onum integer 1 Diane
Orders odate date 2 Diane
Orders amt decimal 3 Diane
Orders cnum integer 4 Diane
Orders snum integer 5 Diane
Как видите, каждая строка этой таблицы показывает столбец таблицы в БД. Все столбцы данной таблицы должны иметь разные имена, так же как каждая таблица должна иметь данного пользователя, и, наконец, все комбинации пользователей, таблиц и имён столбцов должны различаться между собой.
Следовательно, табличные столбцы: tname (имя таблицы), tabowner (владелец таблицы) и cname (имя столбца) вместе составляют первичный ключ этой таблицы. Столбец datatype (тип данных) говорит сам за себя. Столбец cnumber (номер столбца) указывает на местоположение этого столбца внутри таблицы. Для упрощения мы опустили параметры длины столбца, точности и масштаба.
Для справки показана строка из SYSTFMCATALOG, которая ссылается на эту таблицу:
tname owner numcolumns type CO ------------- ------ ----------- ----- ---
SYSTEMCOLUMNS System 8 B
Некоторые SQL-реализации будут обеспечивать вас б́ольшим количеством данных, чем показано в этих столбцах, но показанное является основой любых реализаций.
Для иллюстрации процедуры, предложенной в начале этого раздела, имеется способ, позволяющий каждому пользователю видеть информацию SYSTEMCOLUMNS только в принадлежащих ему таблицах:
CREATE VIEW Owncolumns AS SELECT * FROM SYSTEMCOLUMNS WHERE tabowner = USER;
GRANT SELECT ON Owncolumns TO PUBLIC;
ИСПОЛЬЗОВАНИЕ СООТНЕСЁННЫХ ПОДЗАПРОСОВ ДЛЯ ПОИСКА ОШИБОК
Иногда полезно выполнять запросы, которые разработаны специально так, чтобы находить ошибки. Это всегда возможно при появлении дефектной информации, которую можно ввести в вашу БД, и, если она введена, бывает трудно её выявить. Следующий запрос не должен производить никакого вывода. Он просматривает таблицу Заказов, чтобы увидеть, совпадают ли поля snum и cnum в каждой строке таблицы Заказчиков, и выводит каждую строку, где этого совпадения нет. Другими словами, запрос выясняет, тот ли продавец кредитовал каждую продажу (он принимает поле cnum как первичный ключ таблицы Заказчиков, который не будет иметь никаких двойных значений в этой таблице).
SELECT * FROM Orders main WHERE NOT snum = (SELECT snum FROM Customers WHERE cnum = main.cnum);
При использовании механизма справочной целостности (обсуждённого в Главе 19), вы можете быть гарантированы от некоторых ошибок такого вида. Этот механизм не всегда доступен, хотя его использование желательно во всех случаях, причем поиск ошибки запроса, описанный выше, может быть ещё полезнее.
ИСПОЛЬЗОВАНИЕ SQLCODE ДЛЯ УПРАВЛЕНИЯ ЦИКЛАМИ
Теперь мы можем усовершенствовать наш предыдущий пример для выхода из цикла автоматически, при условии что курсор пуст, все строки выбраны или произошла ошибка:
Look_at_more: = lhe; EXEC SQL OPEN CURSOR Londonsales; while Look_at_more and SQLCODE = O do begin EXEC SQL FETCH London$ales INTO :id_num, :Salesperson, :loc, :comm; writeln (id_num, Salesperson, loc, comm); writeln ('Do you want to see more data? (Y/N)'); readln (response); If response = 'N' then Look_at_more: = Fabe; end; EXEC SQL CLOSE CURSOR Londonsales;
ИСПОЛЬЗОВАНИЕ СТРОК И ВЫРАЖЕНИЙ С UNION
Иногда вы можете вставлять константы и выражения в предложения SELECT, используемые с UNION. Это не следует строго указаниям ANSI, но это полезная и необычно используемая возможность. Константы и выражения, используемые вами, должны соответствовать совместимым стандартам, как мы говорили ранее. Эта свойство полезно, например, чтобы устанавливать комментарии, указывающие, какой запрос вывел данную строку. Предположим, что вы должны сделать отчёт о том, какие продавцы выполняют наибольшие и наименьшие заказы по датам. Мы можем объединить два запроса, вставив туда текст, чтобы различать вывод каждого из них.
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 c 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 c WHERE c.odate = b.odate);
Вывод этой команды показан на Рисунке 14.4. Мы должны были добавить дополнительный пробел в строку 'Lowest on', чтобы сделать её совпадающей по длине со строкой 'Highest on'. Обратите внимание, что Peel выбран при наличии и самого высокого, и самого низкого (фактически он единственный) заказов на 5 октября. Так как вставляемые строки двух этих запросов различны, строки не будут устранены как дубликаты.
=============== SQL Execution Log ============ | | | AND b.amt = | | (SELECT min (amt) | | FROM Orders c | | WHERE c.odate = b.odate); | | ============================================= | | | | ----- ------- ------ ---------- ----------- | | 1001 Peel 3008 Highest on 10/05/1990 | | 1001 Peel 3008 Lowest on 10/05/1990 | | 1001 Peel 3011 Highest on 10/06/1990 | | 1002 Serres 3005 Highest on 10/03/1990 | | 1002 Serres 3007 Lowest on 10/04/1990 | | 1002 Serres 3010 Lowest on 10/06/1990 | | 1003 Axelrod 3009 Highest on 10/04/1990 | | 1007 Rifkin 3001 Lowest on 10/03/1990 | ===============================================
Рисунок 14.4 Выбор наивысших и наинизших заказов, определяемых с помощью строк
ИСПОЛЬЗОВАНИЕ UNION С ORDER BY
До сих пор мы не оговаривали, что данные нескольких запросов будут выводиться в каком-то особом порядке. Мы просто показывали вывод сначала из одного запроса, а затем из другого. Конечно, вы не можете полагаться на вывод, приходящий в произвольном порядке. Мы как раз сделаем так, чтобы этот способ выполнения примеров был более простым. Вы можете использовать предложение ORDER BY, чтобы упорядочить вывод из объединения, точно так же, как это делается в индивидуальных запросах. Давайте пересмотрим наш последний пример, чтобы упорядочить имена с помощью их порядковых номеров. Это может внести противоречие, такое как повторение имени Peel в последней команде, как вы сможете увидеть из вывода показанного в Рисунке 14.5.
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 c WHERE c.odate = b.odate)
UNION
SELECT a.snum, sname, onum, 'Lowest on', odat FROM Salespeople a, Orders b WHEREa.snum = b.snu AND b.amt = (SELECT MIN (amt) FROM Orders c WHERE c.odate = b.odate)
ORDER BY 3;
=============== SQL Execution Log ============ | (SELECT min (amt) | | FROM Orders c | | WHERE c.odate = b.odate) | | ORDER BY 3; | | ============================================= | | | | ----- ------- ------ ---------- ----------- | | 1007 Rifkin 3001 Lowest on 10/03/1990 | | 1002 Serres 3005 Highest on 10/03/1990 | | 1002 Serres 3007 Lowest on 10/04/1990 | | 1001 Peel 3008 Highest on 10/05/1990 | | 1001 Peel 3008 Lowest on 10/05/1990 | | 1003 Axelrod 3009 Highest on 10/04/1990 | | 1002 Serres 3010 Lowest on 10/06/1990 | | 1001 Peel 3011 Highest on 10/06/1990 | ===============================================
Рисунок 14.5 Формирование объединения с использованием ORDER BY
Пока ORDER BY используется по умолчанию, мы не должны его указывать. Мы можем упорядочить наш вывод с помощью нескольких полей, одно внутри другого, и указать ASC или DESC для каждого, точно так же, как мы делали это для одиночных запросов. Заметьте, что номер 3 в предложении ORDER BY указывает, какой столбец из предложения SELECT будет упорядочен. Так как столбцы объединения это столбцы вывода, они не имеют имён, и, следовательно, должны определяться по номеру. Этот номер указывает их место среди других столбцов вывода. (Смотрите Главу 7, обсуждающую столбцы вывода.)
ИСПОЛЬЗОВАНИЕ ВЫРАЖЕНИЙ ДЛЯ МОДИФИКАЦИИ
Вы можете использовать скалярные выражения в предложении SET команды UPDATE, включив его в выражение поля, которое будет изменено. В этом их отличие от предложения VALUES команды INSERT, в котором выражения не могут использоваться; это свойство скалярных выражений - весьма полезная особенность. Предположим, что вы решили удвоить комиссионные всем вашим продавцам. Вы можете использовать следующее выражение:
UPDATE Salespeople SET comm = comm * 2;
Всякий раз, когда вы обращаетесь к указанному значению столбца в предложении SET, произведённое значение может получится из текущей строки; прежде в ней будут сделаны какие-то изменения с помощью команды UPDATE. Естественно, вы можете скомбинировать эти особенности и сказать "удвоить комиссию всем продавцам в Лондоне" таким предложением:
UPDATE Salespeople SET comm = comm * 2 WHERE city = 'London';
ИСПОЛЬЗОВАНИЕ ВЫРАЖЕНИЙ В ПОДЗАПРОСАХ
Вы можете использовать выражение, основанное на столбце, а не просто сам столбец, в предложении SELECT подзапроса. Это может быть выполнено или с помощью реляционных операторов, или с IN. Например, следующий запрос использует реляционный оператор = (вывод показан на Рисунке 10.6):
SELECT * FROM Customers WHERE cnum = (SELECT snum + 1000 FROM Salespeople WHERE sname = Serres);
Он находит всех заказчиков, чьё значение поля cnum, равное 1000, выше поля snum Serres. Мы предполагаем, что столбец sname не имеет никаких двойных значений (это может быть предписано или UNIQUE INDEX, обсуждаемым в Главе 17, или ограничением UNIQUE, обсуждаемым в Главе 18); иначе
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE cnum = | | (SELECT snum + 1000 | | WHERE Salespeople | | WHERE sname = 'Serres' | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ----- | | 2002 Giovanni Rome 200 1003 | =============================================
Рисунок 10.6 Использование подзапроса с выражением
подзапрос может произвести несколько значений. Когда поля snum и сnum не имеют такого простого функционального значения как, например, первичный ключ, что не всегда хорошо, запрос типа вышеупомянутого невероятно полезен.
ИЗМЕНЕНИЕ ТАБЛИЦЫ, ПОСЛЕ ТОГО КАК ОНА БЫЛА СОЗДАНА
Команда ALTER TABLE не является частью стандарта ANSI, но это широко распространённая и довольно содержательная форма, хотя её возможности несколько ограничены. Она используется для того, чтобы изменить определение существующей таблицы. Обычно она добавляет столбцы к таблице. Иногда она может удалять столбцы или изменять их размеры, а также, в некоторых программах, добавлять или удалять ограничения (рассмотренные в Главе 18). Типичный синтаксис добавления столбца к таблице:
ALTER TABLE
ADD ;
Столбец будет добавлен со значениями NULL для всех строк таблицы. Новый столбец станет последним по порядку столбцом таблицы. Вообще-то можно добавить сразу несколько новых столбцов, отделив их запятыми, в одной команде. Имеется возможность удалять или изменять столбцы. Наиболее частым изменением столбца может быть просто увеличение его размера или добавление (удаление) ограничения.
Ваша система должна убедиться, что любые изменения не противоречат существующим данным: например, при попытке добавить ограничение к столбцу, который уже имел значение, при нарушении которого ограничение будет отклонено. Лучше всего дважды проверить это. Как минимум - посмотрите документацию вашей системы чтобы убедиться, гарантирует ли она, что именно это было причиной. Из-за нестандартного характера команды ALTER TABLE вам всё равно необходимо посмотреть тот раздел вашей системной документации, где говорится об особых случаях.
ALTER TABLE не действует, когда таблица должна быть переопределена, но вы должны разрабатывать вашу БД по возможности так, чтобы не слишком ей в этом доверяться. Изменять структуры таблицы, когда она уже в использовании, опасно! Просмотрите внимательно таблицы, которые, являясь вторичными таблицами с извлеченными данными из другой таблицы (см. Главу 20), не долго будут работать правильно, а программы, использующие вложенный SQL (Глава 25), начнут работать неправильно или не всегда правильно. Кроме того, изменение может стереть всех пользователей, имеющих разрешение на обращение к таблице.
По этим причинам вы должны разрабатывать ваши таблицы так, чтобы использовать ALTER TABLE только в крайнем случае. Если ваша система не поддерживает ALTER TABLE, или если вы хотите избежать её использования, вы можете просто создать новую таблицу, с необходимыми изменениями при создании, и использовать команду INSERT с SELECT * запросом чтобы переписать в нее данные из старой таблицы. Пользователям, которым был предоставлен доступ к старой таблице (см. Главу 22), должен быть предоставлен доступ к новой таблице.
ИЗМЕНЕНИЕ ЗНАЧЕНИЙ ПОЛЯ
Теперь, когда вы уже можете вводить и удалять строки таблицы, вы должны узнать, как изменять некоторые или все значения в существующей строке. Это выполняется командой UPDATE. Эта команда содержит предложение UPDATE, в котором указано имя используемой таблицы, и предложение SET, которое указывает на изменение, выполняемое для определенного столбца. Например, чтобы изменить оценки всех заказчиков на 200, вы можете ввести
UPDATE Customers SET rating = 200;
ИЗВЛЕЧЕНИЕ ЗНАЧЕНИЙ ПЕРЕМЕННЫХ
Кроме помещения значений переменных в таблицы с помощью команды SQL, вы можете использовать SQL для получения значений этих переменных.
Один из способов сделать это - с помощью разновидности команды SELECT, которая содержит предложение INTO. Давайте вернемся к нашему предыдущему примеру и переместим строку Peel из таблицы Продавцов в наши переменные главного языка.
EXEC SQL SELECT snum, sname, city, comm INTO :id_num, :salesperson, :loc, :comm FROM Salespeople WHERE snum = 1001;
Выбранные значения помещаются в переменные с упорядоченными именами, указанными в предложении INTO. Разумеется, переменные с именами, указанными в предложении INTO, должны иметь соответствующий тип, чтобы принять эти значения, и должна быть своя переменная для каждого выбранного столбца. Если не учитывать присутствие предложения INTO, то этот запрос похож на любой другой. Однако предложение INTO добавляет значительное ограничение к запросу. Запрос должен извлекать не более одной строки. Если он извлекает много строк, все они не могут быть вставлены одновременно в одну и ту же переменную. Команда, естественно, потерпит неудачу.
По этой причине, SELECT INTO должна использоваться только при следующих условиях:
когда вы используете предикат, проверяющий значения, которые, как вы знаете, могут быть уникальным, как в этом примере. Значения, которые, как вы знаете, могут быть уникальными, это те значения, которые имеют принудительное ограничение уникальности или уникальный индекс, как это говорилось в Главах 17 и 18;
когда вы используете одну или более агрегатных функций и не используете GROUP BY;
когда вы используете SELECT DISTINCT во внешнем ключе с предикатом, ссылающимся на единственное значение родительского ключа (обеспечивая вашей системе предписание справочной целостности), как в следующем примере:
EXEC SQL SELECT DISTINCT snum INTO :salesnum FROM Customers WHERE snum = (SELECT snum FROM Salespeople WHERE sname = 'Motika');
Предполагалось что Salespeople.sname и Salespeople.snum это, соответственно, уникальный и первичный ключи этой таблицы, а Customers.snum - внешний ключ, ссылающийся на Salespeople.snum, и вы предполагали, что этот запрос произведёт единственную строку.
Имеются другие случаи, когда вы знаете, что запрос должен произвести единственную строку вывода, но они мало известны, и в большинстве случаев вы основываетесь на том, что ваши данные имеют целостность, которая не может быть предписана с помощью ограничений. Не полагайтесь на это! Вы создаёте программу, которая, вероятно, будет использоваться в течение некоторого времени, и лучше всего проиграть её, чтобы иметь гарантированное отсутствие в будущем возможных отказов. Во всяком случае, нет необходимости группировать запросы, которые производят одиночные строки, поскольку SELECT INTO используется только для удобства.
Как вы увидите, можно использовать запросы, выводящие многочисленные строки, с помощью курсора.
ЭКВИВАЛЕНТНЫЕ ТИПЫ ДАННЫХ В ДРУГИХ ЯЗЫКАХ
Когда используется вложение SQL в другие языки, значения, используемые и произведённые командами SQL, обычно сохраняются в переменных главного языка (см. Главу 25). Эти переменные должны иметь тип данных, совместимый со значениями SQL, которые они будут получать.
В дополнениях, которые не являются частью официального SQL-стандарта, ANSI обеспечивает поддержку при использовании вложения SQL в четыре языка: Паскаль, PL/I, КОБОЛ, и ФОРТРАН. Между прочим, он включает определение эквивалентов SQL для данных типов переменных, используемых в этих языках.
Эквиваленты типов данных четырёх языков, определенные ANSI:
ЭЛЕМЕНТЫ SQL
Этот раздел определяет элементы команд SQL.
Они разделены на две категории: Основные элементы языка и Функциональные элементы языка.
Основные элементы - это создаваемые блоки языка; когда SQL исследует команду, то он сначала оценивает каждый символ в тексте команды в терминах этих элементов. Разделитель отделяет одну часть команды от другой; всё, что находится между разделителями , обрабатывается как модуль. Основываясь на этом разделении, SQL интерпретирует команду.
Функциональные элементы - это разнообразные элементы, отличающиеся от ключевых слов, которые могут интерпретироваться как модули. Это части команды, отделяемые с помощью разделителей , имеющие специальное значение в SQL. Некоторые из них являются специальными для определенных команд и будут описаны вместе с этими командами позже в этом приложении.
Перечисленные здесь являются общими элементами для всех описываемых команд.
Функциональные элементы могут определяться в терминах друг друга или даже в собственных терминах. Например, предикат , наш последний и наиболее сложный случай, содержит предикат внутри собственного определения. Это потому, что предикат , использующий AND или OR, может содержать любое число предикатов , которые могут работать автономно. Мы представляли вам предикат в отдельной секции в этом приложении из-за разнообразия и сложности этого функционального элемента языка. Он будет постоянно присутствовать при обсуждении других функциональных частей команд.
БАЗОВЫЕ ЭЛЕМЕНТЫ ЯЗЫКА
ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ
| |
--
пробел
определяемый реализацией конец символьной строки
[{ | не должен быть длиннее 18-ти символов.
ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ -
%
любое из следующих: , ( ) <> . : = + " - | <> >= <= или
[любой печатаемый текст в одинарных кавычках] Примечание: В , две последовательных одинарных кавычки ( ' ' ) интерпретируются как одна ( ' ).
окончание/терминатор, зависящее от главного языка. (*только вложенный SQL*)
КАК ANY, ALL И EXIST ПОСТУПАЮТ С ОТСУТСТВУЮЩИМИ И НЕИЗВЕСТНЫМИ ДАННЫМИ?
Как было сказано, имеются некоторые различия между EXISTS и операторами, представленными в этой главе, в том, как они обрабатывают оператор NULL. ANY и ALL также отличаются друг от друга тем, как они реагируют, если подзапрос не произвел никаких значений, чтобы использовать их в сравнении. Эти различия могут привести к непредвиденным результатам в ваших запросах, если вы не будете их учитывать.
КАК ANY МОЖЕТ СТАТЬ НЕОДНОЗНАЧНЫМ?
Как говорилось выше, ANY не полностью однозначен. Если мы создаём запрос, чтобы выбрать заказчиков, которые имеют больший рейтинг, чем любой заказчик в Риме, мы можем получить вывод, который несколько отличается от того, что мы ожидали (как показано в Рисунке 13.5):
SELECT * FROM Customers WHERE rating > ANY (SELECT rating FROM Customers WHERE city = Rome);
В английском языке способ, которым мы обычно склонны интерпретировать оценку "больше, чем любой (где city = Rome) ", должен вам сообщить, что это значение оценки должно быть выше, чем значение оценки в каждом случае, где значение city = Rome. Однако это не так в случае с ANY, используемом в SQL. ANY оценивает как true, если подзапрос находит любое значение, которое делает условие верным.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE rating > ANY | | (SELECT rating | | FROM Customers | | WHERE city = 'Rome'); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ------ | | 2002 Giovanni Rome 200 1003 | | 2003 Liu San Jose 200 1002 | | 2004 Grass Berlin 300 1002 | | 2008 Cisneros San Jose 300 1007 | =============================================
Рисунок 13.5 Как операция "больше"(>) интерпретируется ANY
Если мы оценим ANY способом, использующим грамматику английского языка, то только заказчики с оценкой 300 будут превышать Giovanni, который находится в Риме и имеет оценку 200. Однако подзапрос ANY также находит Pereira в Риме с оценкой 100. Так как все заказчики с оценкой 200 были выше этой, они будут выбраны, даже если имелся другой заказчик из Рима (Giovanni), чья оценка не была выше (фактически то, что один из выбранных заказчиков также находится в Риме, несущественно). Так как подзапрос произвел по крайней мере одно значение, которое сделает предикат верным в отношении этих строк, строки были выбраны. Чтобы дать другой пример, предположим, что мы должны были выбирать все заказы на сумму, которая была больше, чем по крайней мере один из заказов на 6-е октября:
SELECT * FROM Orders WHERE amt > ANY ( SELECT amt FROM Orders WHERE odate = 10/06/1990);
Вывод для этого запроса показан на Рисунке 13.6.
Даже если самая высокая сумма приобретений в таблице (9891.88) имелась на 6-е октября, предыдущая строка имеет более высокое значение суммы, чем другая строка на 6-е октября, которая имела значение суммы = 1309.95. Имея реляционную операцию ">=" вместо просто " > ", эта строка будет также выбрана, потому что она равна самой себе.
Конечно, вы можете использовать ANY с другой SQL-техникой, например, с техникой объединения.
Этот запрос будет находить все заказы со значением суммы меньше, чем значение любой суммы для заказчика в San Jose (вывод показан на Рисунке 13.7):
SELECT * FROM Orders WHERE amt < ANY (SELECT amt FROM Orders A, Customers b WHERE a.cnum = b.cnum AND b.city = " San Jose');
Даже если наименьший заказ в таблице был для заказчика из San Jose, то был второй наибольший; следовательно, почти все строки будут выбраны. Простой запомните, что < ANY это значение, меньшее, чем наибольшее выбранное значение, а > ANY - значение, большее, чем наименьшее выбранное значение.
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE amt > ANY | | (SELECT amt | | FROM Orders | | WHERE odate = 10/06/1990); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3002 1900.10 10/03/1990 2007 1004 | | 3005 5160.45 10/03/1990 2003 1002 | | 3009 1713.23 10/04/1990 2002 1003 | | 3008 4723.00 10/05/1990 2006 1001 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================
Рисунок 13.6 Выбрано значение, большее чем любое (ANY) на 6-е октября
=============== SQL Execution Log ============== | | | WHERE amt > ANY | | (SELECT amt | | FROM Orders a, Customers b | | WHERE a.cnum = b.cnum | | AND b.city = 'San Jose'); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3001 18.69 10/03/1990 2008 1007 | | 3003 767.10 10/03/1990 2001 1001 | | 3002 1900.10 10/03/1990 2007 1004 | | 3006 1098.10 10/03/1990 2008 1007 | | 3009 1713.23 10/04/1990 2002 1003 | | 3007 75.10 10/04/1990 2004 1002 | | 3008 4723.00 10/05/1990 2006 1001 | | 3010 1309.88 10/06/1990 2004 1002 | ================================================
Рисунок 13.7 Использование ANY с объединением
Фактически вышеуказанные команды весьма похожи на следующие (вывод показан на Рисунке 13.8):
SELECT * FROM Orders WHERE amt < (SELECT MAX amt FROM Orders A, Customers b WHERE a.cnum = b.cnum AND b.city = " San Jose');
=============== SQL Execution Log ============== | | | WHERE amt < | | (SELECT MAX (amt) | | FROM Orders a, Customers b | | WHERE a.cnum = b.cnum | | AND b.city = 'San Jose'); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3002 1900.10 10/03/1990 2007 1004 | | 3005 5160.45 10/03/1990 2003 1002 | | 3009 1713.23 10/04/1990 2002 1003 | | 3008 4723.00 10/05/1990 2006 1001 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================
Рисунок 13.8 Использование агрегатной функции вместо ANY
КАК БАЗА ДАННЫХ РАСПРЕДЕЛЕНА МЕЖДУ ПОЛЬЗОВАТЕЛЯМИ?
Таблицы и другие объекты данных сохраняются в БД и находятся там связанными с определёнными пользователями, которые ими владеют. В некотором смысле вы могли бы сказать, что они сохраняются в "именной области пользователя", хотя это никак не отражает их физического расположения, но зато, как и большинство вещей в SQL, является строгой логической конструкцией.
Однако на самом деле объекты данных сохраняются, в физическом смысле; и количество памяти, которое может использоваться определенным объектом или пользователем в данное время, имеют свой предел.
В конце концов, никакой компьютер не имеет прямого доступа к бесконечному числу аппаратных средств (диску, ленте или внутренней памяти) для хранения данных. Кроме того, эффективность SQL расширится, если логическая структура данных будет отображаться неким физическим способом, при котором эти команды получат преимущество.
В больших SQL-системах, БД будет разделена на области, так называемые Области Базы Данных или Разделы. Это области сохраняемой информации размещены так, чтобы информация внутри них находилась близко друг к другу для выполнения команд; то есть программа не должна искать где-то далеко информацию, сгруппированную в отдельной области базы данных. Хотя её физические возможности зависят от аппаратного оборудования, целесообразно чтобы команда работала в этих областях внутри самой SQL.
Системы, которые используют области БД (в дальнейшем называемых - DBS (Data Base Spaces)), которые дают возможность, с помощью команд SQL, обрабатывать эти области как объекты.
DBS создаются командами CREATE DBSPACE (СОЗДАТЬ DBS), ACQUIRE DBSPACE (ПОЛУЧИТЬ DBS) или CREATE TABLE SPACE (СОЗДАТЬ ТАБЛИЧНУЮ ОБЛАСТЬ), в зависимости от используемой реализации.
Одна DBS может вмещать любое число пользователей, и отдельный пользователь может иметь доступ к многим DBS.
Привилегия создавать таблицы, хотя и может быть передана по всей базе данных, часто передается в конкретной DBS.
Мы можем создать DBS с именем Sampletables следующей командой:
CREATE DBSPACE Sampletables ( pctindex 10, pctfree 25);
Параметр pctindex определяет, какой процент DBS должен быть оставлен, чтобы сохранять в нём индексы таблиц. Pctfree это процент DBS, который оставлен, чтобы позволить таблицам расширять размеры их строк (ALTER TABLE может добавлять столбцы или увеличивать размер столбцов, делая каждую строку длиннее. Это расширяет памяти, отводимой для этого).
Имеются другие параметры, которые вы также можете определять и которые меняются от программы к программе. Большинство программ автоматически будут обеспечивать значения по умолчанию, поэтому вы можете создавать DBS, не определяя эти параметры.
DBS может иметь определенное ограничение размера, или ей может быть позволено расти неограниченно вместе с таблицами.
Если DBS создана, пользователям предоставляются права создавать в неё объекты.
Вы можете, например, предоставить Diane право создать таблицу Sampletables с помощью следующей команды:
GRANT RESOURCE ON Sampletables TO Diane;
Это даст вам возможность более конкретно определять место хранения данных. Первая DBS, назначаемая данному пользователю, обычно та, где все объекты этого пользователя создаются по умолчанию. Пользователи, имеющие доступ к многочисленным DBS, могут определить, где они хотят разместить определённый объект.
При разделении вашей БД на DBSы вы должны иметь в виду типы операций, которые вы будете часто выполнять.
Таблицы, которые, как вам уже известно, будут часто объединяться или которые имеют одну таблицу, ссылающуюся на другую во внешнем ключе, должны находиться вместе в одной DBS.
Например, вы могли бы сообщить при назначении типовых таблиц, что таблица Заказов будет часто объединяться с одной или обеими из двух других таблиц, так как таблица Заказов использует значения из обеих этих таблиц. При прочих равных условиях эти три таблицы должны входить в ту же самую область DBS, независимо от того, кто их владелец. Возможное присутствие ограничения внешнего ключа в таблице Заказов просто приведет к более строгому совместному использованию области DBS.
КАК ДЕЛАЕТСЯ ВЛОЖЕНИЕ SQL?
Команды SQL помещаются в исходный текст главной программы; им предшествует фраза EXEC SQL (EXECute SQL). Далее устанавливаются некоторые команды, которые являются специальными для вложенной формы SQL и которые будут рассмотрены в этой главе.
Строго говоря, стандарт ANSI не поддерживает вложенный SQL как таковой. Он поддерживает понятие, называемое "модуль", которое, более точно, является вызываемым набором процедур SQL, а не вложением в другой язык. Официальное определение синтаксиса вложения SQL будет включать расширение официального синтаксиса каждого языка, в который может вкладываться SQL, что весьма долгая и неблагодарна работа, которой ANSI избегает. Однако ANSI обеспечивает четыре приложения (не являющиеся частью стандарта), которые определяют синтаксис вложения SQL для четырех языков: КОБОЛ, ПАСКАЛЬ, ФОРТРАН и ПЛ/1.
Язык C так же широко поддерживается, как и другие языки. Когда вы вставляете команды SQL в текст программы, написанной на другом языке, вы должны выполнить прекомпиляцию, прежде чем вы окончательно её скомпилируете.
Программа, называемая прекомпилятором (или препроцессором), будет просматривать текст вашей программы и преобразовывать команды SQL в форму, удобную для использования базовым языком.
Затем вы используете обычный транслятор, чтобы преобразовывать программу из исходного текста в исполняемый код.
Согласно подходу к модульному языку, определённому ANSI, основная программа вызывает процедуры SQL. Процедуры выбирают параметры из главной программы и возвращают уже обработанные значения обратно в основную программу. Модуль может содержать любое число процедур, каждая из которых состоит из одиночной команды SQL. Идея в том, чтобы процедуры могли работать тем же самым способом, что и процедуры на языке, в который они были вложены (хотя модуль ещё должен идентифицировать базовый язык из-за различий в типах данных различных языков).
Реализации могут удовлетворить стандарту, выполнив вложение SQL таким способом, как если бы модули уже были точно определены. Для этой цели прекомпилятор будет создавать модуль, называемый модулем доступа. Только один модуль, содержащий любое число процедур SQL, может существовать для данной программы. Размещение операторов SQL непосредственно в главном коде происходит более просто и более практично, чем непосредственно создание самих модулей.
Каждая из программ, использующих вложение SQL, связана с ID доступа во время её выполнения. ID доступа, связанный с программой, должен иметь все привилегии, чтобы выполнять операции SQL в программе. Вообще-то вложенная программа SQL регистрируется в БД, так же как и пользователь, выполняющий программу. Более подробно это определяет проектировщик, но, вероятно, было бы неплохо включить в вашу программу команду CONNECT или ей подобную.
КАК ИСПОЛЬЗОВАТЬ АГРЕГАТНЫЕ ФУНКЦИИ?
Агрегатные функции используются, подобно именам полей в предложении SELECT-запроса, но с одним исключением: они берут имена полей как аргументы.
Только числовые поля могут использоваться с SUM и AVG.
С функциями COUNT, MAX и MIN могут использоваться и числовые, и символьные поля.
При использовании с символьными полями, MAX и MIN будут транслировать их в эквивалент ASCII, который должен сообщать, что MIN будет означать первое, а MAX последнее значение в алфавитном порядке (алфавитное упорядочивание обсуждается более подробно в Главе 4).
Чтобы найти SUM всех наших покупок в таблице Заказов, мы можем ввести следующий запрос, с выводом на Рисунке 6.1:
SELECT SUM ((amt)) FROM Orders;
=============== SQL Execution Log ============ | | | SELECT SUM (amt) | | FROM Orders; | | ==============================================| | | | ------- | | 26658.4 | | | | | ===============================================
Рисунок 6.1 Определение суммы
Это, конечно, отличается от выбора поля, при котором возвращается одиночное значение, независимо от того, сколько строк находится в таблице. Из-за этого агрегатные функции и поля не могут выбираться одновременно, если не будет использовано предложение GROUP BY (описанное далее).
Нахождение усреднённой суммы - похожая операция (вывод следующего запроса показан на Рисунке 6.2):
SELECT AVG (amt) FROM Orders;
=============== SQL Execution Log ============ | | | SELECT AVG (amt) | | FROM Orders; | | ==============================================| | | | ------- | | 2665.84 | | | | | ===============================================
Рисунок 6.2 Выбор средней суммы
КАК МОЖНО ПРЕДСТАВИТЬ ПОЛЯ В КАЧЕСТВЕ ВНЕШНИХ КЛЮЧЕЙ
Вы используете ограничение FOREIGN KEY в команде CREATE TABLE (или ALTER TABLE), содержащей поле, которое вы хотите объявить внешним ключом. Вы даёте имя родительскому, ключу на которое вы будете ссылаться внутри ограничения FOREIGN KEY. Помещение этого ограничения в команду - такое же, что и для других ограничений, обсуждённых в предыдущей главе.
Подобно большинству ограничений, оно может быть ограничением таблицы или столбца, в форме таблицы, позволяющей использовать многочисленные поля как один внешний ключ.
КАК РАБОТАЕТ EXISTS?
EXISTS это оператор, который производит верное или неверное значение, другими словами, булево выражение (см. в Главе 4 обзор этого термина).
Это означает, что он может работать автономно в предикате или в комбинации с другими выражениями, использующими булевы операторы AND, OR и NOT. Он берет подзапрос как аргумент и оценивает его как верный, если тот производит любой вывод, или как неверный, если тот не делает этого. Этим он отличается от других операторов предиката, в которых он не может быть неизвестным.
Например, мы можем решить, извлекать ли нам некоторые данные из таблицы Заказчиков, если, и только если, один или более заказчиков в этой таблице находятся в San Jose (вывод для этого запроса показан на Рисунке 12.1):
SELECT cnum, cname, city FROM Customers WHERE EXISTS (SELECT * FROM Customers WHERE city = " San Jose');
Внутренний запрос выбирает все данные для всех заказчиков в San Jose. Оператор EXISTS во внешнем предикате отмечает, что некоторый вывод был произведён подзапросом, и, поскольку выражение EXISTS было полным предикатом, делает предикат верным. Подзапрос (не соотнесённый) был выполнен только один раз для всего внешнего запроса, и, следовательно, имеет одно значение во всех случаях. Поэтому EXISTS, когда используется этим способом, делает предикат верным или неверным для всех строк сразу, что не так уж полезно для извлечения определенной информации.
=============== SQL Execution Log ============ | | | SELECT snum, sname, city | | FROM Customers | | WHERE EXISTS | | (SELECT * | | FROM Customers | | WHERE city = 'San Jose'); | | ============================================= | | cnum cname city | | ----- -------- ---- | | 2001 Hoffman London | | 2002 Giovanni Rome | | 2003 Liu San Jose | | 2004 Grass Berlin | | 2006 Clemens London | | 2008 Cisneros San Jose | | 2007 Pereira Rome | =============================================
Рисунок 12.1 Использование оператора EXISTS
КАК РАБОТАЕТ ПОДЗАПРОС?
С помощью SQL вы можете вкладывать запросы друга в друга. Обычно внутренний запрос генерирует значение, которое проверяется в предикате внешнего запроса, определяющего, верно оно или нет. Например, предположим, что мы знаем имя продавца: Motika, но не знаем значение его поля snum и хотим извлечь все заказы из таблицы Заказов. Вот способ сделать это (вывод показан на Рис. 10.1 ):
SELECT * FROM Orders WHERE snum = (SELECT snum FROM Salespeople WHERE sname = 'Motika');
Чтобы оценить внешний (основной) запрос, SQL сначала должен оценить внутренний запрос (или подзапрос) внутри предложения WHERE. Он делает это так, как и должен делать запрос, имеющий единственную цель - отыскать через таблицу Продавцов все строки, где поле sname равно значению Motika, а затем извлечь значения поля snum этих строк.
Единственной найденной строкой, естественно, будет snum = 1004. Однако SQL не просто выдает это значение, а помещает его в предикат основного запроса вместо самого подзапроса, так чтобы предикат прочитал, что
WHERE snum = 1004
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | WHERE snum = | | (SELECT snum | | FROM Salespeople | | WHERE sname = 'Motika'); | |=================================================| | onum amt odate cnum snum | | ----- ------- ---------- ----- ----- | | 3002 1900.10 10/03/1990 2007 1004 | | | =================================================
Рисунок 10.1 Использование подзапроса
Основной запрос затем выполняется как обычно с вышеупомянутыми результатами. Разумеется, подзапрос должен выбрать один, и только один, столбец, а тип данных этого столбца должен совпадать с тем значением, с которым он будет сравниваться в предикате.
Часто, как показано выше, выбранное поле и его значение будут иметь одинаковые имена (в данном случае snum), но это не обязательно. Конечно, если бы мы уже знали номер продавца Motika, мы могли бы просто напечатать WHERE snum = 1004 и работать далее с подзапросом в целом, но это было бы не так универсально. Этот же запрос будет продолжать работать, даже если номер Motika изменился, а с помощью простого изменения имени в подзапросе вы можете использовать его для чего угодно.
КАК РАБОТАЕТ СООТНЕСЕННЫЙ ПОДЗАПРОС?
В вышеупомянутом примере, "внутренний" (inner) и "внешний" (outer) это псевдонимы, подробно обсуждённые в Главе 9. Мы выбрали эти имена для большей ясности; они отсылают к значениям внутренних и внешних запросов, соответственно. Так как значение в поле cnum внешнего запроса меняется, внутренний запрос должен выполняться отдельно для каждой строки внешнего запроса. Строка внешнего запроса, для которого внутренний запрос каждый раз будет выполнен, называется текущей строкой-кандидатом.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers outer | | WHERE 10/03/1990 IN | | (SELECT odate | | FROM Orders inner | | WHERE outer.cnum = inner.cnum); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ----- | | 2001 Hoffman London 100 1001 | | 2003 Liu San Jose 200 1002 | | 2008 Cisneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================
Рисунок 11.1 Использование соотнесённого подзапроса
Следовательно, процедура оценки, выполняемой соотнесённым подзапросом:
Выбрать строку из таблицы, именованной во внешнем запросе. Это будет текущая строка-кандидат.
Сохранить значения из этой строки-кандидата в псевдониме с именем в предложении FROM внешнего запроса.
Выполнить подзапрос. Везде, где псевдоним, заданный для внешнего запроса, найден (в данном случае "внешний"), использовать значение текущей строки-кандидата. Использование значения из строки-кандидата внешнего запроса в подзапросе называется внешней ссылкой.
Оценить предикат внешнего запроса на основе результатов подзапроса, выполняемого в шаге 3. Он определяет, выбирается ли строка-кандидат для вывода.
Повторить процедуру для следующей строки-кандидата таблицы, и так далее, пока все строки таблицы не будут проверены.
В вышеупомянутом примере SQL осуществляет следующую процедуру:
Выбирает строку Hoffman из таблицы Заказчиков.
Сохраняет эту строку как текущую строку-кандидат под псевдонимом "внешний".
Затем он выполняет подзапрос. Подзапрос просматривает всю таблицу Заказов, чтобы найти строки, где значение поля cnum - такое же, как значение outer.cnum, которое в настоящее время равно 2001 - поле cnum строки Hoffman.
Затем он извлекает поле odate из каждой строки таблицы Заказов, для которой это верно, и формирует набор значений поля odate.
Получив набор всех значений поля odate для поля cnum = 2001, он проверяет предикат основного запроса, чтобы увидеть, имеется ли значение на 3 октября в этом наборе.
Если это так (а это так), то он выбирает строку Hoffman для вывода её из основного запроса.
Он повторяет всю процедуру, используя строку Giovanni как строку-кандидат, а затем сохраняет повторно, пока каждая строка таблицы Заказчиков не будет проверена.
Как видите, вычисления, которые SQL выполняет с помощью этих простых инструкций, довольно сложны. Конечно, вы могли бы решить ту же самую проблему, используя объединение следующего вида (вывод для этого запроса показан на Рисунке 11.2):
SELECT * FROM Customers first, Orders second WHERE first.cnum = second.cnum AND second.odate = 10/03/1990;
Обратите внимание, что Cisneros был выбран дважды, по одному разу для каждого заказа, который он имел для данной даты. Мы могли бы устранить это, используя SELECT DISTINCT вместо просто SELECT. Но это не обязательно в варианте подзапроса. Оператор IN, используемый в варианте подзапроса, не делает никакого различия между значениями, которые выбираются подзапросом один раз, и значениями, которые выбираются неоднократно. Следовательно, DISTINCT не обязателен.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers first, Orders second | | WHERE first.cnum = second.cnum | | (SELECT COUNT (*) | | FROM Customers | | WHERE snum = main.snum; | | ============================================= | | cnum cname | | ----- -------- | | 1001 Peel | | 1002 Serres | =============================================
Рисунок 11.2 Использование объединения вместо соотнесенного подзапроса
Предположим, что мы хотим видеть имена и номера всех продавцов, которые имеют более одного заказчика. Следующий запрос выполнит это для вас (вывод показан на Рисунке 11.3):
SELECT snum, sname FROM Salespeople main WHERE 1 < ( SELECT COUNT (*) FROM Customers WHERE snum = main.snum );
Обратите внимание, что предложение FROM подзапроса в этом примере не использует псевдоним. При отсутствии имени таблицы или префикса псевдонима, SQL может для начала принять, что любое поле выводится из таблицы с именем, указанным в предложении FROM текущего запроса. Если поле с этим именем отсутствует (в нашем случае - snum ) в той таблице, SQL будет проверять внешние запросы. Именно поэтому префикс имени таблицы обычно необходим в соотнесенных подзапросах для отмены этого предположения.
Псевдонимы также часто запрашиваются, чтобы дать возможность ссылаться к той же самой таблице во внутреннем и внешнем запросе без какой-либо неоднозначности.
=============== SQL Execution Log ============ | | | SELECT snum sname | | FROM Salespeople main | | WHERE 1 < | | AND second.odate = 10/03/1990; | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ----- | | 2001 Hoffman London 100 1001 | | 2003 Liu San Jose 200 1002 | | 2008 Cisneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================
Рисунок 11.3 Нахождение продавцов с несколькими заказчиками
КАК РАБОТАЕТ SQL?
SQL это язык, ориентированный специально на реляционные базы данных (РБД). Он выполняет большую работу, которую вы должны были бы делать, если бы использовали универсальный язык программирования, например C. Чтобы сформировать РБД на C, вам необходимо было бы начать с нуля. Вы должны были бы определить объект, называемый таблицей, которая могла бы увеличиваться, чтобы иметь любое число строк, а затем создавать постепенно процедуры для вставки и извлечения значений.
Если бы вы захотели найти некоторые конкретные строки, вам необходимо было бы выполнить по шагам процедуру, подобную следующей:
Рассмотреть строку таблицы.Выполнить проверку: является ли эта строка одной из строк, которая вам нужна.
Если это так, сохранить её где-нибудь, пока вся таблица не будет проверена.Проверить, имеются ли другие строки в таблице.
Если имеются, возвратиться на шаг 1.Если строк больше нет, вывести все значения, сохранённые в шаге 3.
(Конечно, это не фактический набор C-команд, а только логика шагов, которые должны были бы быть включены в реальную программу.) SQL сделает всё это за вас. Команды в SQL могут работать со всеми группами таблиц как с единым объектом и могут обрабатывать любое количество информации, извлечённой или полученной из них в виде единого модуля.
КАК СФОРМИРОВАТЬ СООТНЕСЁННЫЙ ПОДЗАПРОС?
Когда вы используете подзапросы в SQL, вы можете обратиться к внутреннему запросу таблицы в предложении внешнего запроса FROM, сформировав соотнесённый подзапрос. Когда вы делаете это, подзапрос выполняется неоднократно, по одному разу для каждой строки таблицы основного запроса. Соотнесённый подзапрос - одно из большого количества тонких понятий в SQL. Если вы сумеете овладеть им, вы найдёте, что он очень мощен, поскольку может выполнять сложные функции с помощью очень лаконичных указаний.
Например, вот способ найти всех заказчиков в Заказах на 3-е октября (вывод показан на Рисунке 11.1):
SELECT * FROM Customers outer WHERE 10/03/1990 IN (SELECT odate FROM Orders inner WHERE outer.cnum = inner.cnum);
КАК СПРАВОЧНАЯ ЦЕЛОСТНОСТЬ ОГРАНИЧИВАЕТ ЗНАЧЕНИЯ РОДИТЕЛЬСКОГО КЛЮЧА?
Поддержание справочной целостности требует некоторых ограничений на значения, которые могут быть представлены в полях, объявленных как внешний ключ и родительский ключ. Родительский ключ должен быть структурирован, чтобы гарантировать, что каждое значение внешнего ключа будет соответствовать одной указанной строке. Это означает, что он (ключ) должен быть уникальным и не должен содержать никаких пустых значений (NULL). Этого недостаточно для родительского ключа в случае выполнения такого требования, как при объявлении внешнего ключа. SQL должен быть уверен, что двойные значения или пустые значения (NULL) не были введены в родительский ключ. Следовательно, вы должны убедиться, что все поля, которые используются как родительские ключи, имеют или ограничение PRIMARY KEY, или ограничение UNIQUE, наподобие ограничения NOT NULL.
КАК SQL ОБЩАЕТСЯ СРАЗУ С НЕСКОЛЬКИМИ ПОЛЬЗОВАТЕЛЯМИ?
SQL часто работает в многопользовательской среде - в среде, где сразу много пользователей одновременно могут выполнять действия в базе данных. Это создает потенциальную возможность конфликта между различными выполняемыми действиями. Например, предположим, что вы выполняете команду в таблице Продавцов:
UPDATE Salespeople SET comm = comm * 2 WHERE sname LIKE 'R%';
и в это же время Diane вводит такой запрос:
SELECT city, AVG (comm) FROM Salespeople GROUP BY city;
Может ли усреднённое значение (AVG) Diane отразить изменения, которые вы делаете в таблице? Не важно, будет это сделано или нет, а важно, чтобы были отражены все или ни одно из значений комиссионных (comm), для которых выполнялись изменения. Любой промежуточный результат является случайным или непредсказуемым для заказа, в котором значения были изменены физически. Вывод запроса не должен быть случайным и непредсказуемым.
Посмотрим на это с другой стороны. Предположим, что вы находите ошибку и откатываете все ваши модификации уже после того, как Diane получила их результаты в виде вывода. В этом случае Diane получит ряд усреднённых значений, основанных на тех изменениях, которые были позже отменены, не зная, что её информации неточна.
Обработка одновременных транзакций называется параллелизмом или совпадением и имеет несколько возможных проблем, которые могут при этом возникать.
Вот примеры:
Модификация может быть сделана без учета другой модификации. Например, продавец должен сделать запрос в таблице инвентаризации, чтобы найти десять фрагментов пунктов торговцев акциями и упорядочить шесть из них для заказчика. Прежде чем это изменение было сделано, другой продавец делает запрос к таблице и упорядочивает семь из тех же фрагментов для своего заказчика.
КАК ВЫПОЛНИТЬ ОБЪЕДИНЕНИЕ ТАБЛИЦЫ С СОБОЙ?
Для объединения таблицы с собой вы можете сделать каждую строку таблицы одновременно и комбинацией её с собой, и комбинацией с каждой другой строкой таблицы. Вы затем оцениваете каждую комбинацию в терминах предиката так же, как в объединениях нескольких таблиц. Это позволит легко создавать определенные виды связей между различными позициями внутри одной таблицы с помощью обнаружения, например, пар строк со значением поля.
Вы можете представить объединение таблицы с собой как объединение двух копий одной и той же таблицы. Таблица на самом деле не копируется, но SQL выполняет команду так, как если бы это было сделано. Другими словами: это такое же объединение, как и любое другое объединение между двум таблицами, за исключением того, что в данном случае обе таблицы идентичны.
КАКОЕ ПРЕДСТАВЛЕНИЕ ЯВЛЯЕТСЯ МОДИФИЦИРУЕМЫМИ?
Вот некоторые примеры модифицируемых представлений и представлений только_чтение.
CREATE VIEW Dateorders (odate, ocount) AS SELECT odate, COUNT (*) FROM Orders GROUP BY odate;
Это представление только_чтение из-за присутствия в нём агрегатной функции и GROUP BY.
CREATE VIEW Londoncust AS SELECT * FROM Customers WHERE city = 'London';
А это - модифицируемое представление.
CREATE VIEW SJsales (name, number, percentage) AS SELECT sname, snum, comm * 100 FROM Salespeople WHERE city = 'SanJose';
Это - представление только_чтение из-за выражения "comm * 100". При этом, однако, возможны переупорядочивание и переименование полей. Некоторые программы будут позволять удаление в этом представлении или в заказах столбцов snum и sname.
CREATE VIEW Salesonthird AS SELECT * FROM Salespeople WHERE snum IN (SELECT snum FROM Orders WHERE odate = 10/03/1990);
Это - представление только_чтение в ANSI из-за присутствия в нём подзапроса. В некоторых программах, это может быть приемлемо.
CREATE VIEW Someorders AS SELECT snum, onum, cnum FROM Orders WHERE odate IN (10/03/1990,10/05/1990);
Это - модифицируемое представление.
КАТАЛОГ СИСТЕМЫ
Чтобы функционировать как БД SQL, ваша компьютерная система должна следить за многими вещами: таблицами, представлениями, индексами, синонимами, привилегиями, пользователями и т.д. Имеются различные способы делать это, но ясно, что наиболее логичный, эффективный и согласованный способ делать это в реляционной среде состоит в том, чтобы сохранять эту информацию в таблицах. Это даёт возможность компьютеру размещать информацию и управлять ею, используя те же самые процедуры, которые он использует чтобы размещать и управлять данными, которые он хранит для вас.
Хотя это - вопрос конкретной программы, а не стандарта ANSI, большинство БД SQL используют набор SQL-таблиц, хранящих служебную информацию, для своих внутренних потребностей. Этот набор называется в различных публикациях системный каталог, словарь данных или просто системные таблицы (Термин "словарь данных" может также относится к общему архиву данных, включая информацию о физических параметрах БД, - данных, которые хранятся вне SQL. Следовательно, имеются программы баз данных, которые имеют и системный каталог, и словарь данных.)
Таблицы системного каталога напоминают обычные SQL-таблицы: те же строки и столбцы данных. Например, одна таблица каталога обычно содержит информацию о таблицах, существующих в БД, по одной строке на каждую таблицу базы данных; другая содержит информацию о различных столбцах таблиц, по одной строке на столбец, и так далее.
Таблицы каталога создаются и заполняются с помощью самой БД и идентифицируются с помощью специальных имён, таких, например, как SYSTEM.
База данных создает эти таблицы и модифицирует их автоматически; таблицы каталога не могут быть непосредственно подвергнуты действию команды модификации. Если это случится, это значительно запутает всю систему и сделает её неработоспособной. Однако в большинстве систем каталог может быть запрошен пользователем. Это очень полезно, потому что это даёт возможность узнать кое-что о БД, которую вы используете. Конечно, не вся информация всегда доступна всем пользователям. Подобно другим таблицам, доступ к каталогу ограничен для пользователей без соответствующих привилегий. Так как каталог принадлежит самой системе, имеется некоторая неясность относительно того, кто имеет привилегии и кто может предоставить привилегии в этом каталоге. Обычно привилегии каталога предоставляет суперпользователь, например, администратор системы, зарегистрированный как SYSTEM или DBA. Кроме того, некоторые привилегии могут предоставляться пользователям автоматически.
КОБОЛ
SQL ТИПЭКВИВАЛЕНТ КОБОЛА
| CHAR () | PIC X () |
| INTEGER | PIC S () USAGE COMPUTTATIONAL |
| NUMERIC | PIC S (< nines with embedded V >) DISPLAY SING LEADING SEPERATE |
КОГДА ПОДЗАПРОС ВОЗВРАЩАЕТСЯ ПУСТЫМ?
Одно существенное различие между ALL и ANY - способ действия в ситуации, когда подзапрос не возвращает никаких значений. В принципе всякий раз, когда допустимый подзапрос не в состоянии сделать вывод, ALL автоматически правилен, а ANY автоматически неправилен. Это означает, что следующий запрос
SELECT * FROM Customers WHERE rating > ANY (SELECT rating FROM Customers WHERE city = Boston);
не произведет никакого вывода, в то время как запрос
SELECT FROM Customers WHERE rating > ALL (SELECT rating FROM Customers WHERE city = 'Boston');
выведет всю таблицу Заказчиков. Когда нет никаких заказчиков в Boston, естественно, ни одно из этих сравнений не имеет значения.
КОГДА СДЕЛАННЫЕ ИЗМЕНЕНИЯ СТАНОВЯТСЯ ПОСТОЯННЫМИ ?
Визуально среда базы данных это картина, которая постоянно отображает для существующих пользователей постоянно вводимые и изменяемые данные, предполагая, что, если система правильно разработана, она будет функционировать без сбоев. Однако реально, благодаря человеческим или компьютерным сбоям, ошибки время от времени случаются, и поэтому хорошие компьютерные программы стали применять способы отмены действий, вызвавших такие ошибки.
Команда SQL, которая воздействует на содержание или структуру БД - например, команда модификации DML или команда DROP TABLE, - не обязательно будет необратимой. Вы можете определить после окончания её действия, останутся ли изменения, сделанные данной командой или группой команд, постоянными в базе данных, или они будут полностью проигнорированы. С этой целью команды обрабатываются группами, называемыми транзакциями. Транзакция начинается всякий раз, когда вы начинаете сеанс с SQL. Все команды, которые вы введёте, будут частью этой транзакции, пока вы не завершите их вводом команды COMMIT WORK или команды ROLLBACK WORK. COMMIT может сделать все изменения постоянными с помощью транзакции, а ROLLBACK может откатить их обратно или отменить. Новая транзакция начинается после каждой команды COMMIT или ROLLBACK. Этот процесс известен как диалоговая обработка запросов или транзакция.
Вот синтаксис, чтобы оставить все ваши изменения постоянными во время регистрации или во время последнего COMMIT или ROLLBACK:
COMMIT WORK;
Синтаксис отмены изменения:
ROLLBACK WORK;
В большинстве реализаций вы можете установить параметр, называемый AUTOCOMMIT. Он будет автоматически запоминать все действия, которые будут выполняться. Действия, которые приведут к ошибке, всегда будут автоматически "прокручены" обратно. Если это предусмотрено в вашей системе, для фиксации всех ваших действий вы можете использовать эту возможность с помощью команды типа:
SET AUTOCOMMIT ON;
Вы можете вернуться к обычной диалоговой обработке запросов с помощью такой команды:
SET AUTOCOMMIT OFF;
Имеется возможность установки AUTOCOMMIT, которую система выполнит автоматически при регистрации. Если сеанс пользователя завершается аварийно - например, произошел сбой системы или выполнена перезагрузка пользователя, - то текущая транзакция выполнит автоматический откат изменений. Это - одна из причин, по которой вы можете управлять выполнением вашей диалоговой обработки запросов, разделив ваши команды на большое количество различных транзакций.
Одиночная транзакция не должна содержать много несвязанных команд; фактически она может состоять из единственной команды. Транзакции, которые включают всю группу несвязанных изменений, не оставляют вам фактически никакого выбора - сохранить или отклонить целую группу, если вы хотите отменить только одно определенное изменение.
Хорошее правило, которому надо следовать: делать ваши транзакции состоящими из одной команды или нескольких тесно связанных команд.
Например, предположим, вы хотите удалить продавца Motika из базы данных. Прежде чем вы удалите его из таблицы Продавцов, вы сначала должны сделать что-нибудь с его заказами и его заказчиками. (Если используются ограничения внешнего ключа, и ваша система, следуя ANSI, ограничивает изменение родительского ключа, у вас не будет выбора: делать или не делать этого. Это будет сделано обязательно.) Одно из логических решений будет состоять в том, чтобы установить поле snum в его заказах в NULL, вследствие чего ни один продавец не получит комиссионные в этих заказах, пока комиссионные не будут предоставлены заказчикам для Peel. Затем вы можете удалить их из таблицы Продавцов:
UPDATE Orders SET snum = NULL WHERE snum = 1004;
UPDATE Cudomers SET snum = 1001 WHERE snum = 1004;
DELETE FROM Salespeople WHERE snum = 1004;
Если у вас проблема с удалением Motika (возможно, имеется другой внешний ключ, ссылающийся на него, о котором вы не знали и который не учитывали), вы могли бы отменить все изменения, которые вы сделали, до тех пор пока проблема не будет определена и решена. Более того, это должна быть группа команд, чтобы обрабатывать её как одиночную транзакцию. Вы можете предусмотреть это с помощью команды COMMIT и завершить её с помощью команды COMMIT или ROLLBACK.
КОГДА ВЫ МОЖЕТЕ ДЕЛАТЬ ОБЪЕДИНЕНИЕ ЗАПРОСОВ?
Когда два (или более) запроса подвергаются объединению, их столбцы вывода должны быть совместимы для объединения. Это означает, что каждый запрос должен указывать одинаковое количество столбцов и в том же порядке и каждый должен иметь тип, совместимый с каждым. Значение совместимости типов меняется. ANSI следит за этим очень строго, и поэтому числовые поля должны иметь одинаковый числовой тип и размер, хотя некоторые имена, используемые ANSI для этих типов, являются синонимами. (Смотрите в Приложении B подробности о числовых типах ANSI.) Кроме того, символьные поля должны иметь одинаковое количество символов (значение предназначенного числа - не обязательно такое же, как используемое число).
Хорошо, что некоторые SQL-программы обладают большей гибкостью, чем это определяется ANSI. Типы, не определённые ANSI, такие как DATA и BINARY, обычно должны совпадать с другими столбцами такого же нестандартного типа.
Длина строки также может стать проблемой. Большинство программ разрешают поля переменной длины, но они не обязательно будут использоваться с UNION. С другой стороны, некоторые программы (и ANSI тоже) требуют, чтобы символьные поля были точно одинаковой длины. В этих вопросах вы должны проконсультироваться с документацией вашей собственной программы.
Другое ограничение на совместимость: пустые значения (NULL) запрещены в любом столбце объединения, причем эти значения необходимо запретить и для всех соответствующих столбцов в других запросах объединения. Пустые значения (NULL) запрещены ограничением NOT NULL, которое будет обсуждаться в
Главе 18. Кроме того, вы не можете использовать UNION в подзапросах, а также не можете использовать агрегатные функции в предложении SELECT-запроса в объединении. (Большинство программ пренебрегают этими ограничениями.)
КОМАНДА CREATE VIEW
Вы создаёте представление командой CREATE VIEW. Она состоит из слов CREATE VIEW (СОЗДАТЬ ПРЕДСТАВЛЕНИЕ), имени представления, которое нужно создать, сл́ова AS (КАК) и запроса, как в следующем примере:
CREATE VIEW Londonstaff AS SELECT * FROM Salespeople WHERE city = 'London';
Теперь у вас есть представление Londonstaff. Вы можете использовать это представление точно так же, как и любую другую таблицу. Может быть выполнен запрос, модификация, вставка в, удаление из и соединение с другими таблицами и представлениями.
Давайте сделаем запрос представления (вывод показан на Рисунке 20.1):
SELECT * FROM Londonstaff;
=============== SQL Execution Log ============ | | | SELECT * | | FROM Londonstaff; | | | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.1200 | | 1004 Motika London 0.1100 | | | ===============================================
Рисунок 20.1 Представление Londonstaff
Когда вы приказываете SQL выбрать (SELECT) все строки (*) из представления, он выполняет запрос, содержащий в определении Loncfonstaff, и возвращает всё из его вывода. Имея предикат в запросе представления, можно вывести только те строки представления, которые будут удовлетворять этому предикату. Вы можете вспомнить, что в Главе 15 вы имели таблицу Londonstaff, в которую вы вставляли это же самое содержимое (конечно, мы понимаем, что таблица не слишком велика. Если это так, вы должны будете выбрать другое имя для вашего представления).
Преимущество использования представления, по сравнению с основной таблицей, в том, что представление будет модифицировано автоматически всякий раз, когда изменится таблица, лежащая в его основе.
Содержание представления не фиксировано и переназначается каждый раз, когда вы ссылаетесь на представление в команде. Если вы добавите завтра другого живущего в Лондоне продавца, он автоматически появится в представлении.
Представления значительно расширяют управление вашими данными. Это превосходный способ дать публичный доступ к некоторой, но не всей, информации в таблице. Если вы хотите, чтобы ваш продавец был показан в таблице Продавцов, но при этом не были показаны комиссионные других продавцов, вы могли бы создать представление с использованием следующего оператора (вывод показан на Рисунке 20.2):
CREATE VIEW Salesown AS SELECT snum, sname, city FROM Salespeople:
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salesown; | | | | ==============================================| | snum sname city | | ------ ---------- ----------- | | 1001 Peel London | | 1002 Serres San Jose | | 1004 Motika London | | 1007 Rifkin Barcelona | | 1003 Axelrod New York | ===============================================
Рисунок 20.2 Представление Salesown
Другими словами, это представление - такое же, как для таблицы Продавцов, за исключением того что поле comm не упоминалось в запросе и, следовательно, не было включено в представление.
КОМАНДА FORMAT
Как мы подчеркивали в Главе 7, процесс вывода выполняемого в стандарте SQL, имеет ограничения. Хотя большинство реализаций включают SQL в пакеты, имеющие другие средства для управления этой функцией, некоторые реализации также используют команду типа FORMAT внутри SQL чтобы навязывать выводу запроса определённые формы структуры или ограничения.
Среди возможных функций команды FORMAT существуют такие:
определение ширины столбцов (при печати)
определение представления NULL-значений
обеспечение (новых) заголовков для столбцов
обеспечение заголовков внизу или вверху страниц, выводимых на печать
навязывает присвоение или изменение форматам полей, содержащих значения даты, времени или денежной суммы
вычисляет общие и промежуточные суммы, не исключая возможности обобщения поля, как это делает, например, SUM
(Альтернативным подходом к этой проблеме в некоторых программах является предложение COMPUTE.)
Команда FORMAT может вводиться сразу перед или сразу после запроса, к которому она применяется, в зависимости от реализации. Одна команда FORMAT обычно может применяться только к одному запросу, хотя любое число команд FORMAT может применяться к одному и тому же запросу.
Вот некоторые примеры команды FORMAT:
FORMAT NULL '_ _ _ _ _ _ _'; FORMAT BTITLE 'Orders Grouped by Salesperson'; FORMAT EXCLUDE (2, 3);
Первая команда NULL представляется в виде ' _ _ _ _ _ _ _ ' при выводе на печать; вторая вставляет заголовок 'Orders Grouped by Salesperson' в нижнюю часть каждой страницы; третья исключает второй и третий столбцы из вывода предыдущего запроса. Вы могли бы использовать последнюю, если выбираете конкретные столбцы, чтобы использовать их в предложении ORDER BY в вашем выводе. Так как указанные функции команды FORMAT могут выполняться по разному, все варианты их использования не могут быть здесь показаны.
Имеются другие команды, которые могут использоваться для выполнения тех же функций. Команда SET подобна команде FORMAT; она является вариантом или дополнением к команде, которая применяется во всех запросах текущего сеанса пользователя, а не просто в одиночном запросе. В следующей реализации, команда FORMAT начинается ключевым словом COLUMN следующим образом:
COLUMN odate FORMAT dd-mon-yy;
что форсирует формат типа 10-Oct-90 в поле даты, использующемся в выводе запроса на печать. Предложение COMPUTE, упомянутое ранее, вставляется в запрос, следующим образом:
SELECT odate, amt FROM Orders WHERE snum = 1001 COMPUTE SUM (amt);
Оно выводит все заказы продавца Peel с датой и суммой приобретения по каждой дате, а в конце - общую сумму приобретений. Другая реализация выводит промежуточные суммы приобретений, используя COMPUTE в качестве команды. Сначала, она определяет разбивку
BREAK ON odate;
вывода вышеупомянутого запроса на страницы, сгруппировав по датам, поэтому все значения odate в каждой группе - одинаковые. Затем вы можете ввести следующее предложение:
COMPUTE SUM OF amt ON odate;
Столбец в предложении ON предварительно должен быть использован в команде BREAK.
КОМАНДА GRANT
Предположим, что пользователь Diane имеет таблицу Заказчиков и хочет разрешить пользователю Adrian выполнить запрос к ней. Diane должна в этом случае ввести следующую команду:
GRANT INSERT ON Salespeople TO Diane;
Теперь Adrian может выполнить запросы к таблице Заказчиков. Без этой привилегии он может только выбирать значения, но не может выполнить любое действие, которые влияло бы на значения в таблице Заказчиков (включая использование таблицы Заказчиков в качестве родительской таблицы внешнего ключа, что ограничивает изменения, которые можно выполнять со значением в таблице Заказчиков).
Когда SQL получает команду GRANT, он проверяет привилегии пользователя, подавшего эту команду, чтобы определить, допустима ли команда GRANT.
Adrian самостоятельно не может выдать эту команду. Он также не может предоставить право SELECT другому пользователю: таблица еще принадлежит Diane (далее мы покажем, как Diane может дать право Adrian предоставлять SELECT другим пользователям).
Синтаксис - тот же самый, что и для предоставления других привилегий. Если Adrian - владелец таблицы Продавцов, то он может позволить Diane вводить в неё строки с помощью следующего предложения:
GRANT INSERT ON Salespeople TO Diane;
Теперь Diane имеет право помещать нового продавца в таблицу.
КОМАНДА SELECT
В самой простой форме команда SELECT просто инструктирует БД, чтобы извлечь информацию из таблицы. Например, вы могли бы вывести таблицу Продавцов, напечатав следующее:
SELECT snum, sname, city, comm FROM Salespeople;
Вывод для этого запроса показан на Рисунке 3.1.
=============== SQL Execution Log ============ | | | SELECT snum, sname, city, comm | | FROM Salespeople; | | | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.12 | | 1002 Serres San Jose 0.13 | | 1004 Motika London 0.11 | | 1007 Rifkin Barcelona 0.15 | | 1003 Axelrod New York 0.10 | ===============================================
Рисунок 3.1 Команда SELECT
Другими словами, эта команда просто выводит все данные из таблицы. Большинство программ будут также давать заголовки столбца, как выше, а некоторые позволяют определить детальное форматирование вывода, но это уже вне стандартной спецификации.
Вот объяснение каждой части этой команды:
SELECT
Ключевое слово, которое сообщает базе данных, что эта команда - запрос. Все запросы начинаются этим словом с последующим пробелом.
snum, sname
Это список столбцов из таблицы, которые выбираются запросом. Любые столбцы, не перечисленные здесь, не будут включены в вывод команды. Это, конечно, не значит, что они будут удалены или их информация будет стёрта из таблиц, ведь запрос не воздействует на информацию в таблицах; он только показывает данные.
FROM Salespeople
FROM - ключевое слово, подобное SELECT, которое должно быть представлено в каждом запросе. Оно сопровождается пробелом и именем таблицы, используемой в качестве источника информации. В данном случае это таблица Продавцов (Salespeople).
;
Точка с запятой используется во всех интерактивных командах SQL, чтобы сообщать базе данных, что команда записана и готова к выполнению. В некоторых системах индикатором конца команды является обратный слэш (\) в строке.
Естественно, запрос такого характера не обязательно будет упорядочивать вывод любым указанным способом. Та же самая команда, выполненная с теми же самыми данными, но в другое время, не сможет вывести тот же самый заказ. Обычно строки обнаруживаются в том порядке, в котором они найдены в таблице, поскольку, как мы установили в предыдущей главе, этот порядок произволен. Это не обязательно будет тот порядок, в котором данные вводились или сохранялись. Вы можете упорядочивать вывод непосредственно командами SQL с помощью специального предложения. Позже мы покажем, как это делается. А сейчас просто запомните, что, в отсутствие явного упорядочивания, в вашем выводе нет никакого определенного порядка.
Использование возврата каретки (клавиша ENTER) является произвольным. Мы должны точно установить, как удобнее составить запрос - в несколько строк или в одну строку - следующим образом:
SELECT snum, sname, city, comm FROM Salespeople;
С тех пор как SQL использует точку с запятой, чтобы указывать конец команды, большинство программ SQL обрабатывают возврат каретки (через нажатие Возврат или клавиши ENTER ) как пробел. Хорошая идея - использовать возвраты каретки и выравнивание, как мы делали ранее, чтобы сделать ваши команды более лёгкими для чтения и более понятными.
КОМАНДА СОЗДАНИЯ ТАБЛИЦЫ
Таблицы создаются командой CREATE TABLE. Эта команда создает пустую таблицу - таблицу без строк. Значения вводятся с помощью DML-команды INSERT (См. Главу 15). Команда CREATE TABLE определяет имя таблицы и описание набора имён столбцов, указанных в определенном порядке. Она также определяет типы данных и размеры столбцов. Каждая таблица должна иметь по крайней мере один столбец.
Синтаксис команды CREATE TABLE:
CREATE TABLE table-name ( [(size)], [(size)] ...);
Как сказано в Главе 2, типы данных значительно меняются от программы к программе. Для совместимости со стандартом, все они должны по крайней мере поддерживать стандарт типа ANSI. Он описан в Приложении B.
Так как пробелы используются для разделения частей команды SQL, они не могут быть частью имени таблицы (или любого другого объекта, такого как индекс).
Знак подчеркивания ( _ ) обычно используется для разделения слов в именах таблиц.
Значение аргумента размера зависит от типа данных. Если вы его не указываете, ваша система сама будет назначать значение автоматически. Для числовых значений, это - лучший выход, потому что в этом случае все ваши поля такого типа получат один и тот же размер, что освобождает вас от проблем их совместимости (см. Главу 14). Кроме того, использование аргумента размера с некоторыми числовым наборами - не совсем простой вопрос. Если вам нужно хранить большие числа, вам, несомненно, понадобятся гарантии, что поля достаточно велики, чтобы вместить их.
Тип данных, для которого вы, в основном, должны назначать размер, это CHAR.
Аргумент размера это целое число, определяющее максимальное число символов, которые может вместить поле. Фактически число символов поля может быть от нуля (если поле - NULL) до этого числа. По умолчанию аргумент размера = 1, что означает, что поле может содержать только одну букву. Это, конечно, не совсем то, что вам нужно.
Таблицы принадлежат пользователю, который их создал, и имена всех таблиц, принадлежащих данному пользователю, должны отличаться друга от друга, как и имена всех столбцов внутри данной таблицы. Отдельные таблицы могут использовать одинаковые имена столбцов, даже если они принадлежат одному и тому же пользователю. Пример этого - столбец city в таблице Заказчиков и в таблице Продавцов. Пользователи, не являющиеся владельцами таблиц, могут обращаться к этим таблицам с помощью имени владельца этих таблиц, сопровождаемого точкой; например, таблица Employees, созданная Smith, будет называться Smith.Employees, когда она упоминается каким-то другим пользователем (мы понимаем, что Smith это идентификатор (ID). ID, сообщаемый пользователем (ваш ID - это ваше имя в SQL. Этот вывод обсуждался в Главе 2 и будет продолжен в Главе 22).
Эта команда создаст таблицу Продавцов:
CREATE TABLE Saleepeople (snum integer, sname char (10), city char (10), comm declmal);
Порядок столбцов в таблице определяется порядком, в котором они указаны. Имена столбца не должны разделяться при переносе строки (что делается для удобочитаемости) и отделяются запятыми.
КОМАНДА UPDATE ДЛЯ НЕСКОЛЬКИХ СТОЛБЦОВ
Однако вы не обязаны ограничивать себя модифицированием единственного столбца с помощью команды UPDATE. Предложение SET может назначать любое число столбцов, отделяемых запятыми. Все указанные назначения могут быть сделаны для любой табличной строки, но только для одной в каждый момент времени. Предположим, что продавец Motika ушел на пенсию и мы хотим переназначить его номер новому продавцу:
UPDATE Salespeople SET sname = 'Gibson', city = 'Boston', comm = .10 WHERE snum = 1004;
Эта команда передаст новому продавцу Gibson, всех текущих заказчиков бывшего продавца Motika и заказы в том виде, в котором они были скомпонованы для Motika, с помощью поля snum. Вы не можете, однако, модифицировать сразу несколько таблиц в одной команде, отчасти потому, что вы не можете использовать префиксы таблиц со столбцами, изменёнными предложением SET. Другими словами, вы не можете сказать "SET Salespeople.sname = Gibson" в команде UPDATE, вы можете сказать только так: "SET sname = Gibson".
КОМАНДЫ МОДИФИКАЦИИ ЯЗЫКА DML
Значения могут быть помещены и удалены из полей тремя командами языка DML (Язык Манипулирования Данными):
INSERT (ВСТАВИТЬ), UPDATE (МОДИФИЦИРОВАТЬ), DELETE (УДАЛИТЬ).
Не удивляйтесь, все они упоминались ранее в SQL как команды модификации.
КОМБИНАЦИЯ ИЗ EXISTS И ОБЪЕДИНЕНИЯ
Однако для нас может быть полезнее вывести больше информации об этих продавцах, а не только их номера. Мы можем сделать это, объединив таблицу Заказчиков с таблицей Продавцов (вывод для запроса показан на Рисунке 12.3):
SELECT DISTINCT first.snum, sname, 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;
=============== SQL Execution Log ============ | | | SELECT DISTINCT first.snum, sname, 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; | | ============================================= | | cnum cname city | | ----- -------- ---- | | 1001 Peel London | | 1002 Serres San Jose | =============================================
Рисунок 12.3 Комбинация EXISTS с объединением
Внутренний запрос здесь, как и в предыдущем варианте, фактически сообщает, что псевдоним был изменён. Внешний запрос это объединение таблицы Продавцов с таблицей Заказчиков, наподобие того, что мы видели прежде. Новое предложение основного предиката (AND first.snum = second.snum), естественно, оценивается на том же самом уровне, что и предложение EXISTS. Это функциональный предикат самого объединения, сравнивающий две таблицы из внешнего запроса в терминах поля snum, которое является для них общим. Из-за булева оператора AND, оба условия основного предиката должны быть верны в заказе для верного предиката. Следовательно, результаты подзапроса имеют смысл только в тех случаях, когда вторая часть запроса верна, а объединение - выполнимо. Таким образом, комбинация объединения и подзапроса может стать очень мощным способом обработки данных.
КОМБИНИРОВАНИЕ ПРЕДИКАТОВ ПРЕДСТАВЛЕНИЙ И ОСНОВНЫХ ЗАПРОСОВ В ПРЕДСТАВЛЕНИЯХ
Когда вы делаете запрос представления, вы, собственно, выполняете запрос. Основной способ для SQL обойти это - объединить предикаты двух запросов в один. Давайте посмотрим ещё раз на наше представление Londonstaff:
CREATE VIEW Londonstaff AS SELECT * FROM Salespeople WHERE city = 'London';
Если мы выполняем следующий запрос в этом представлении
SELECT * FROM Londonstaff WHERE comm > .12;
он будет такой же, как если бы мы выполнили следующее в таблице Продавцов:
SELECT * FROM Salespeople WHERE city = 'London' AND comm > .12;
Это прекрасно, за исключением того что появляется возможная проблема с представлением. Имеется возможность комбинации из двух полностью допустимых предикатов и получения предиката, который не будет работать. Например, предположим, что мы создаем (CREATE) следующее представление:
CREATE VIEW Ratingcount (rating, number) AS SELECT rating, COUNT (*) FROM Customers GROUP BY rating;
Это даёт нам число заказчиков, которое мы имеем для каждого уровня оценки (rating). Вы можете затем сделать запрос этого представления, чтобы выяснить, имеется ли какая-нибудь оценка в настоящее время, назначенная для трёх заказчиков:
SELECT * FROM Ratingcount WHERE number = 3;
Посмотрим, что случится если мы скомбинируем два предиката:
SELECT rating, COUNT (*) FROM Customers WHERE COUNT (*) = 3 GROUP BY rating;
Это недопустимый запрос. Агрегатные функции, такие как COUNT (СЧЕТ), не могут использоваться в предикате. Правильным способом при формировании вышеупомянутого запроса, конечно же, будет следующий:
SELECT rating, COUNT (*) FROM Customers GROUP BY rating; HAVING COUNT (*) = 3;
Но SQL может не выполнить превращения. Может ли равноценный запрос вместо запроса Ratingcount потерпеть неудачу? Да может! Это неоднозначная область SQL, где методика использования представлений может дать хорошие результаты. Самое лучшее, что можно сделать в случае, когда об этом ничего не сказано в вашей системной документации, это попытаться разобраться.
Если команда допустима, вы можете использовать представления, чтобы установить некоторые ограничения SQL в синтаксисе запроса.
КОММЕНТАРИЙ В СОДЕРЖАНИИ КАТАЛОГА
Большинство версий SQL, позволяют помещать комментарии (ремарки) в специальные столбцы пояснений таблиц каталогов SYSTEMCATALOG и SYSTEMCOLUMNS, что удобно, так как эти таблицы не всегда могут объяснить свое содержание. Для простоты мы пока исключали этот столбец из наших иллюстраций. Можно использовать команду COMMENT ON со строкой текста, чтобы пояснить любую строку в одной из этих таблиц. Состояние TABLE - для комментирования в SYSTEMCATALOG, а текст COLUMN - для SYSTEMCOLUMNS. Например:
COMMENT ON TABLE Chris.Orders
IS 'Current Customer Orders';
Текст будет помещен в столбец пояснений SYSTEMCATALOG. Обычно максимальная длина таких пояснений - 254 символа.
Сам комментарий указывается для конкретной строки, один с tname=Orders, а другой owner=Chris. Мы увидим этот комментарий в строке таблицы Заказов в SYSTEMCATALOG:
SELECT tname, remarks FROM SYSTEMCATALOG WHERE tname = 'Orders' AND owner = 'Chris';
Вывод для этого запроса показан на Рисунке 24.2.
SYSTEMCOLUMNS работает точно так же. Сначала мы создаём комментарий
COMMENT ON COLUMN Orders.onum IS 'Order #';
затем выбираем эту строку из SYSTEMCOLUMNS:
SELECT cnumber, datatype, cname, remarks FROM SYSTEMCOLUMNS WHERE tname = 'Orders' AND tabowner = 'Chris' AND cname = onum;
Вывод для этого запроса показан на Рисунке 24.3.
Чтобы изменить комментарий, вы можете просто ввести новую команду COMMENT ON для той же строки. Новый комментарий будет записан поверх старого. Если вы хотите удалить комментарий, напишите поверх него пустой комментарий:
COMMENT ON COLUMN Orders.onum IS '';
и этот пустой комментарий затрёт предыдущий.
=============== SQL Execution Log =============== | | | SELECT tname, remarks | | FROM SYSTEMCATALOG | | WHERE tname = 'Orders' | | AND owner = 'Chris' | | ; | | ================================================ | | tname remarks | | ------------- ----------------------- | | Orders Current Customers Orders | | | ==================================================
Рисунок 24.2 Комментарий в SYSTEMCATALOG
=============== SQL Execution Log =============== | | | SELECT cnumber, datatype, cname, remarks | | FROM SYSTEMCOLUMNS | | WHERE tname = 'Orders' | | AND tabowner = 'Chris' | | AND cname = 'onum' | | ; | | ================================================ | | cnumber datatype cname remarks | | ---------- --------- ------ ------------ | | 1 integer onum Orders # | | | ==================================================
Рисунок 24.3 Комментарий в SYSTEMCOLUMNS
КОМПЛЕКСНЫЕ ОБЪЕДИНЕНИЯ
Вы можете использовать любое количество псевдонимов для одной таблицы в запросе, хотя использование более двух в данном предложении SELECT * будет излишним. Предположим, что вы ещё не назначили ваших заказчиков вашему продавцу. Компания должна назначить каждому продавцу первоначально трёх заказчиков, по одному для каждого рейтингового значения. Вы лично можете решить, какого заказчика какому продавцу назначить, но следующий запрос вы используете, чтобы увидеть все возможные комбинации заказчиков, которые вы можете назначать (вывод показан на Рисунке 9.3):
SELECT a.cnum, b.cnum, c.cnum FROM Customers a, Customers b, Customers c WHERE a.rating = 100 AND b.rating = 200 AND c.rating = 300;
=============== SQL Execution Log ============== | | | AND c.rating = 300; | | =============================================== | | cnum cnum cnum | | ----- ------ ------ | | 2001 2002 2004 | | 2001 2002 2008 | | 2001 2003 2004 | | 2001 2003 2008 | | 2006 2002 2004 | | 2006 2002 2008 | | 2006 2003 2004 | | 2006 2003 2008 | | 2007 2002 2004 | | 2007 2002 2008 | | 2007 2003 2004 | | 2007 2003 2008 | =================================================
Рисунок 9.3 Комбинация пользователей с различными значениями рейтинга
Как видите, этот запрос находит все комбинации заказчиков с тремя значениями оценки, поэтому первый столбец состоит из заказчиков с оценкой 100, второй - с 200, и последний - с оценкой 300. Они повторяются во всех возможных комбинациях. Это сортировка с группировкой, которая не может быть выполнена с GROUP BY или ORDER BY, поскольку они сравнивают значения только в одном столбце вывода.
Вы должны также понимать, что не всегда обязательно использовать в предложении SELECT каждый псевдоним или таблицу, которые упомянуты в предложении FROM запроса. Иногда предложение или таблица становятся запрашиваемыми исключительно потому, что они могут вызываться в предикате запроса. Например, следующий запрос находит всех заказчиков, размещённых в городах, где продавец Serres (snum 1002) имеет заказчиков (вывод показан на Рисунке 9.4):
SELECT b.cnum, b. cname FROM Customers a, Customers b WHERE a.snum = 1002 AND b.city = a.city;
=============== SQL Execution Log ============ | | | SELECT b.cnum, b.cname | | FROM Customers a, Customers b | | WHERE a.snum = 1002 | | AND b.city = a.city; | | ==============================================| | cnum cname | | ------ --------- | | 2003 Liu | | 2008 Cisneros | | 2004 Grass | =============================================
Рисунок 9.4 Нахождение заказчиков в городах относящихся к Serres
Псевдоним a будет делать предикат неверным, за исключением случая, когда его значение столбца snum = 1002. Таким образом, псевдоним опускает всё, кроме заказчиков продавца Serres. Псевдоним b будет верным для всех строк с тем же самым значением города, что и текущее значение города для a; в ходе запроса строка псевдонима b будет верна один раз, когда значение города представлено в a. Нахождение этих строк псевдонима b - единственная цель псевдонима a, поэтому мы не выбираем все столбцы подряд. Как вы можете видеть, собственные заказчики Serres выбираются при нахождении их в том же самом городе, что и он сам, поэтому выбор их из псевдонима a не обязателен. Короче говоря, псевдоним находит строки заказчиков Serres, Liu и Grass. Псевдоним b находит всех заказчиков, размещенных в любом из их городов (San Jose и Berlin, соответственно), включая, конечно, самих Liu и Grass.
Вы можете также создать объединение, которое включает и различные таблицы, и псевдонимы одиночной таблицы. Следующий запрос объединяет таблицу Пользователей с собой, чтобы найти все пары заказчиков, обслуживаемых одним продавцом. В то же самое время этот запрос объединяет заказчика с таблицей Продавцов с именем этого продавца (вывод показан на Рисунке 9.5):
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 Execution Log ================== | | | SELECT cname, 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; | | ====================================================| | cname snum cname cname | | ------ ------ -------- -------- | | Serres 1002 Liu Grass | | Peel 1001 Hoffman Clemens | =====================================================
Рисунок 9.5 Объединение таблицы с собой и с другой таблицей
КТО МОЖЕТ СОЗДАВАТЬ ПРЕДСТАВЛЕНИЯ?
Чтобы создавать представление, вы должны иметь привилегию SELECT во всех таблицах, на которые вы ссылаетесь в представлении. Если это представление модифицируемое, любая привилегия INSERT, UPDATE и DELETE, которые вы имеете в базовой таблице, будут автоматически передаваться представлению.
Если у вас отсутствуют привилегии на модификацию в базовых таблицах, вы не сможете иметь их и в представлениях, которые вы создали, даже если сами эти представления - модифицируемые.
Так как внешние ключи не используются в представлениях, привилегия REFERENCES никогда не используется при создании представлений. Все эти ограничения определяются ANSI.
Нестандартные привилегии системы (обсуждаемые позже в этой главе) также могут быть включены.
В последующих разделах мы предположим, что создатели представлений, которые мы обсуждаем, имеют частные или соответствующие привилегии во всех базовых таблицах.
ОГРАНИЧЕНИЕ ПРИВИЛЕГИИ SELECT ДЛЯ ОПРЕДЕЛЁННЫХ СТОЛБЦОВ
Предположим, вы хотите дать пользователю Claire способность видеть только столбцы snum и sname таблицы Продавцов. Вы можете сделать это, поместив имена этих столбцов в представление
CREATE VIEW Clairesview AS SELECT snum, sname FROM Salespeople;
и предоставить Claire привилегию SELECT в представлении, а не в самой таблице Продавцов:
GRANT SELECT On Clairesview to Claire;
Вы можете создать привилегии специально для столбцов, наподобие использования других привилегий, но для команды INSERT это будет означать вставку значений по умолчанию, а для команды DELETE ограничение столбца не будет иметь значения.
Привилегии REFERENCES и UPDATE, конечно, могут сделать столбцы специализированными, не прибегая к представлению.
ОГРАНИЧЕНИЕ ПРИВИЛЕГИЙ ДЛЯ ОПРЕДЕЛЁННЫХ СТРОК
Обычно более полезный способ фильтровать привилегии с представлениями - это использовать представление, чтобы привилегия относилась только к определенным строкам. Вы делаете это, естественно, используя предикат в представлении, который определит, какие строки являются включенными.
Чтобы предоставить пользователю Adrian привилегию UPDATE в таблице Заказчиков для всех заказчиков, размещенных в Лондоне, вы можете создать такое представление:
CREATE VIEW Londoncust AS SELECT * FROM Customers WHERE city = 'London' WITH CHECK OPTION;
Затем вы должны передать привилегию UPDATE в этой таблице для Adrian:
GRANT UPDATE ON Londoncust TO Adrian;
В этом отличие привилегии для определённых строк от привилегии UPDATE для определённых столбцов, которая распространена на все столбцы таблицы Заказчиков, но не на строки, среди которых строки со значением поля city, иным, чем London, не будут учитываться. Предложение WITH CHECK OPTION предохраняет Adrian от замены значения поля city на любое значение кроме London.
ПРЕДОСТАВЛЕНИЕ ДОСТУПА ТОЛЬКО К ИЗВЛЕЧЁННЫМ ДАННЫМ
Другая возможность состоит в том, чтобы предлагать пользователям доступ к уже извлечённым данным, а не к фактическим значениям в таблице. Агрегатные функции могут быть весьма удобными в применении такого способа. Вы можете создавать представление, которое выдаёт подсчёт, среднее и общее количество заказов на каждый день заказов:
CREATE VIEW Datetotals AS SELECT odate, COUNT (*), SUM (amt), AVG (amt) FROM Orders GROUP BY odate;
Теперь вы передаёте пользователю Diane привилегию SELECT в представлении Datetotals:
GRANT SELECT ON Datetotals TO Diane;
ИСПОЛЬЗОВАНИЕ ПРЕДСТАВЛЕНИЙ В КАЧЕСТВЕ АЛЬТЕРНАТИВЫ ОГРАНИЧЕНИЯМ
Одной из последних прикладных программ из серии, описанной в Главе 18, является использование представлений с WITH CHECK OPTION как альтернативы ограничениям.
Предположим, вы хотели бы удостовериться, что все значения поля city в таблице Продавцов находятся в одном из городов, где ваша компания в настоящее время имеет ведомство. Вы можете установить ограничение CHECK непосредственно на столбец city, но позже может стать трудно его изменить, если ваша компания, например, откроет там другие офисы. В качестве альтернативы можно создать представление, которое исключает неправильные значения city:
CREATE VIEW Curcities AS SELECT * FROM Salespeople WHERE city IN ('London', 'Rome', 'San Jose', 'Berlin') WITH CHECK OPTION;
Теперь, вместо того чтобы предоставить пользователям привилегии модифицирования в таблице Продавцов, вы можете предоставить их в представлении Curcities. Преимущество такого подхода в том, что, если вам нужно сделать изменение, вы можете удалить это представление, создать новое и предоставить в этом новом представлении привилегии пользователям, что проще, чем изменять ограничения. Недостатком является то, что владелец таблицы Продавцов также должен использовать это представление, если он не хочет чтобы его собственные команды были отклонены. С другой стороны, этот подход позволяет владельцу таблицы и любым другим получить привилегии модификации в самой таблице, а не в представлении, чтобы делать исключения для ограничений.
Это часто бывает желательно, но невыполнимо, если вы используете ограничения в базовой таблице. К сожалению, эти исключения нельзя будет увидеть в представлении. Если вы выберите этот подход, вам захочется создать второе представление, содержащее только исключения:
CREATE VIEW Othercities AS SELECT * FROM Salespeople WHERE city NOT IN ('London', 'Rome', 'San Jose', 'Berlin') WITH CHECK OPTION;
Вы должны выбрать для передачи пользователям только привилегию SELECT в этом представлении, чтобы они могли видеть исключенные строки, но не могли помещать недопустимые значения city в базовую таблицу. Фактически пользователи могли бы сделать запрос обоих представлений в объединении и увидеть все строки сразу.
КУРСОР
Одно из сильных качеств SQL - способность функционировать на всех строках таблицы, чтобы найти определенное условие как блок "запись", не зная сколько таких строк там может быть. Если десять строк удовлетворяют предикату, то запрос может вывести все десять строк. Если десять миллионов строк определены, все десять миллионов строк будут выведены. Это несколько затруднительно, когда вы попробуете связать это с другими языками. Как вы сможете назначать вывод запроса для переменных, когда не знаете, насколько велик будет вывод? Решение состоит в том, чтобы использовать то, что называется курсором. Вы, вероятно, знакомы с курсором - мигающей черточкой, которая отмечает вашу позицию на экране компьютера. Вы можете рассматривать SQL-курсор как устройство, которое, аналогично этому, отмечает ваше место в выводе запроса, хотя аналогия не полная.
Курсор это вид переменной, которая связана с запросом. Значением этой переменной может быть каждая строка, которая выводится при запросе. Подобно главным переменным, курсоры должны быть объявлены, прежде чем они будут использованы. Это делается командой DECLARE CURSOR следующим образом:
EXEC SQL DECLARE CURSOR Londonsales FOR SELECT * FROM Salespeople WHERE city = 'London';
Запрос не выполнится немедленно; он только определяется.
Курсор немного напоминает представление, в котором курсор содержит запрос, а содержание курсора напоминает любой вывод запроса каждый раз, когда курсор становится открытым. Однако, в отличие от базовых таблиц или представлений, строки курсора упорядочены: имеются первая, вторая ... и последняя строка курсора. Этот порядок может быть произвольным, с явным управлением с помощью предложения ORDER BY в запросе, или же по умолчанию следовать какому-то упорядочиванию, определяемому инструментально определяемой схемой. Когда вы ищете точку в вашей программе, в которой вы хотите выполнить запрос, вы открываете курсор с помощью следующей команды:
EXEC SQL OPEN CURSOR Londonsales;
Значения в курсоре могут быть получены, когда вы выполняете именно эту команду, но не предыдущую команду DECLARE и не последующую команду FETСH. Затем вы используете команду FETCH, чтобы извлечь вывод из этого запроса, по одной строке в каждый момент времени.
EXEC SQL FETCH Londonsales INTO :id_num, :salesperson, :loc, :comm;
Это выражение переместит значения из первой выбранной строки в переменные. Другая команда FETCH выводит следующий набор значений. Идея состоит в том, чтобы поместить команду FETCH внутрь цикла так, чтобы, выбрав строку, вы могли, переместив набор значений из этой строки в переменные, возвращаться обратно в цикл, чтобы переместить следующий набор значений в те же самые переменные.
Например, возможно, вам нужно, чтобы вывод выдавался по одной строке, спрашивая каждый раз у пользователя, хочет ли он продолжить, чтобы увидеть следующую строку
Look_at_more:= True; EXEC SQL OPEN CURSOR Londonsales; while Look_at_more do begin EXEC SQL FETCH Londonsales INTO :id_num, :Salesperson, :loc, :comm; writeln (id_num, Salesperson, loc, comm); writeln ('Do you want to see more data? (Y/N)'); readln (response); it response = 'N' then Look_at_more: = False end; EXEC SQL CLOSE CURSOR Londonsales;
В Паскале знак : = означает "является назначенным значением из", в то время как = ещё имеет обычное значение "равно". Функция writeln записывает её вывод, а затем переходит к новой строке. Одиночные кавычки вокруг символьных значений во втором writeln и в предложении if ... then обычны для Паскаля, что случается при дубликатах в SQL.
В результате выполнения этого фрагмента, булева переменная с именем Look_at _more должна быть установлена в состояние верно, открыт курсор, и запущен цикл. Внутри цикла строка выбирается из курсора и выводится на экран. У пользователя спрашивают, хочет ли он видеть следующую строку. Пока он не ответил N (Нет), цикл повторяется, и следующая строка значений будет выбрана.
Хотя переменные Look_at_more и ответ должны быть объявлены как булева переменная и символьная (char) переменная, соответственно, в разделе объявлений переменных в Паскаля, они не должны быть включены в раздел объявлений SQL, потому что они не используются в командах SQL.
Как видите, двоеточия перед именами переменных не используются для не-SQL операторов. Также обратите внимание, что имеется оператор CLOSE CURSOR, соответствующий оператору OPEN CURSOR. Он, как вы поняли, освобождает курсор значений, поэтому запрос нужно будет выполнить повторно с оператором OPEN CURSOR, прежде чем перейти в выбору следующих значений. Это не обязательно для тех строк, которые были выбраны запросом после закрытия курсора, хотя это и обычная процедура. Пока курсор закрыт, SQL не следит за тем, какие строки выбраны. Если вы открываете курсор снова, запрос повторно выполняется с этой точки, и вы начинаете всё сначала.
Этот пример не обеспечивает автоматический выхода из цикла, когда все строки уже будут выбраны. Когда у FETCH нет больше строк которые надо извлекать, он просто не меняет значений в переменных предложения INTO. Следовательно, если данные исчерпались, эти переменные будут неоднократно выводиться с идентичными значениями до тех пор, пока пользователь не завершит цикл, введя ответ N.
КВАЛИФИЦИРОВАННЫЙ ВЫБОР ПРИ ИСПОЛЬЗОВАНИИ ПРЕДЛОЖЕНИЙ
Таблица имеет тенденцию становиться очень большой, поскольку с течением времени всё большее и большее количество строк в неё добавляется. Поскольку обычно только определённые строки интересуют вас в данное время, SQL дает возможность устанавливать критерии, чтобы определить, какие строки будут выбраны для вывода.
WHERE - предложение команды SELECT, которое позволяет устанавливать предикаты, условие которых может быть или верным (true), или неверным (false) для любой строки таблицы. Команда извлекает только те строки из таблицы, для которых такое утверждение верно. Например, предположим, вы хотите видеть имена и комиссионные всех продавцов в Лондоне. Вы можете ввести такую команду:
SELECT sname, city FROM Salespeople; WHERE city = "LONDON";
Когда предложение WHERE предоставлено, программа базы данных просматривает всю таблицу построчно и исследует каждую строку, чтобы определить, верно ли утверждение. Следовательно, для записи Peel программа рассмотрит текущее значение столбца city, определит, что оно равно "London", и включит эту строку в вывод. Запись для Serres не будет включена, и так далее. Вывод для вышеупомянутого запроса показан на Рисунке 3.6.
=============== SQL Execution Log ============ | | | SELECT sname, city | | FROM Salespeople | | WHERE city = 'London' | | ============================================= | | sname city | | ------- ---------- | | Peel London | | Motika London | =============================================
Рисунок 3.6 SELECT с предложением WHERE
Давайте попробуем пример с числовым полем в предложении WHERE. Поле rating таблицы Заказчиков предназначено для того, чтобы разделять заказчиков на группы, основанные на некоторых критериях, которые могут быть получены в итоге через этот номер. Возможно это - форма оценки кредита или оценки, основанной на объёме предыдущих приобретений. Такие числовые коды могут быть полезны в реляционных базах данных как способ подведения итогов сложной информации. Мы можем выбрать всех заказчиков с рейтингом 100 следующим образом:
SELECT * FROM Customers WHERE rating = 100;
Одиночные кавычки не используются здесь, потому что оценка это числовое поле. Результаты запроса показаны на Рисунке 3. 7.
Предложение WHERE совместимо с предыдущим материалом в этой главе. Другими словами, вы можете использовать номера столбцов, устранять дубликаты или переупорядочивать столбцы в команде SELECT, которая использует WHERE. Однако вы можете изменять порядок столбцов для имён только в предложении SELECT, но не в предложении WHERE.
============ SQL Execution Log ============== | | | SELECT * | | FROM Customers | | WHERE rating = 100; | | ============================================= | | сnum cname city rating snum | | ------ -------- ------ ---- ------ | | 2001 Hoffman London 100 1001 | | 2006 Clemens London 100 1001 | | 2007 Pereira Rome 100 1001 | =============================================
Рисунок 3.7 SELECT с числовым полем в предикате
МАТЕМАТИЧЕСКИЕ ФУНКЦИИ
Эти функции применяются для работы с числами.
ФУНКЦИЯЗНАЧЕНИЕ
| ABX(X) | Абсолютное значение X (преобразование отрицательного или положительного значения в положительное). |
| CEIL (X) | X является десятичным значением, которое будет округляться сверху. |
| FLOOR (X) | X является десятичным значением, которое будет округляться снизу. |
| GREATEST(X,Y) | Возвращает большее из двух значений. |
| LEAST(X,Y) | Возвращает меньшее из двух значений. |
| MOD(X,Y) | Возвращает остаток от деления X на Y. |
| POWER(X,Y) | Возвращает значение X в степени Y. |
| ROUND(X,Y) | Цикл от X до десятичного Y. Если Y отсутствует, цикл до целого числа. |
| SING(X) | Возвращает минус если X < 0, или плюс если X > 0. |
| SQRT (X) | Возвращает квадратный корень из X. |
МНОГОСТОЛБЦОВЫЕ ВНЕШНИЕ КЛЮЧИ
В реальности внешний ключ не обязательно состоит только из одного поля. Подобно первичному ключу, внешний ключ может иметь любое число полей, которые все обрабатываются как единый модуль. Внешний ключ и родительский ключ, на который он ссылается, конечно же, должны иметь одинаковый номер и тип поля и находиться в одинаковом заказе. Внешние ключи, состоящие из одного поля, - те, что мы использовали в наших типовых таблицах, - наиболее распространённые.
Чтобы сохранить простоту нашего обсуждения, мы будем часто говорить о внешнем ключе как об одиночном столбце. Это не случайно. Если это не отметить, любой скажет о поле, которое является внешним ключом, что это также относится и к группе полей, которые являются внешними ключами.
МОДИФИЦИРОВАНИЕ КУРСОРОВ
Курсоры могут также быть использованы, чтобы выбирать группу строк из таблицы, которые могут быть затем модифицированы или удалены одна за другой. Это дает вам возможность, обходить некоторые ограничения предикатов, используемых в командах UPDATE и DELETE. Вы можете ссылаться на таблицу, задействованную в предикате запроса курсора или любом из его подзапросов, которые вы не можете выполнить в предикатах самих этих команд. Как подчёркнуто в Главе 16, стандарт SQL отклоняет попытку удалить всех пользователей с рейтингом ниже среднего, в следующей форме:
EXEC SQL DELETE FROM Customers WHERE rating < (SELECT AVG (rating) FROM Customers);
Однако вы можете получить тот же эффект, используя запрос для выбора соответствующих строк, запомнив их в курсоре и выполнив DELETE с использованием курсора. Сначала вы должны объявить курсор:
EXEC SQL DECLARE Belowavg CURSOR FOR SELECT * FROM Customers WHERE rating < (SELECT AVG (rating) FROM Customers);
Затем вы должны создать цикл, чтобы удалить всех заказчиков, выбранных курсором:
EXEC SQL WHENEVER SQLERROR GOTO Error_handler; EXEC SQL OPEN CURSOR Belowavg; while not SOLCODE = 100 do begin EXEC SOL FETCH Belowavg INTO :a, :b, :c, :d, :e; EXEC SOL DELETE FROM Customers WHERE CURRENT OF Belowavg; end; EXEC SOL CLOSE CURSOR Belowavg;
Предложение WHERE CURRENT OF означает, что DELETE применяется к строке, которая в настоящее время выбрана курсором. Здесь подразумевается, что и курсор, и команда DELETE ссылаются на одну и ту же таблицу и, следовательно, что запрос в курсоре - это не объединение. Курсор должен также быть модифицируемым. Являясь модифицируемым, курсор должен удовлетворять тем же условиям, что и представления (см. Главу 21).
Кроме того, ORDER BY и UNION, которые не разрешены в представлениях, в курсорах разрешаются, но предохраняют курсор от модифицируемости. Обратите внимание в вышеупомянутом примере, что мы должны выбирать строки из курсора в набор переменных, даже если мы не собирались использовать эти переменные. Этого требует синтаксис команды FETCH.
UPDATE работает так же.
Вы можете увеличить значение комиссионных всем продавцам, которые имеют заказчиков с оценкой = 300, следующим способом. Сначала вы объявляете курсор:
EXEC SOL DECLARE CURSOR High_Cust AS SELECT * FROM Salespeople WHERE snum IN (SELECT snum FROM Customers WHERE rating = 300);
Затем вы выполняете модификации в цикле:
EXEC SQL OPEN CURSOR High_cust; while SQLCODE = 0 do begin EXEC SOL FETCH High_cust INTO :id_num, :salesperson, :loc, :comm; EXEC SQL UPDATE Salespeople SET comm = comm + .01 WHERE CURRENT OF High_cust; end; EXEC SQL CLOSE CURSOR High_cust;
Обратите внимание, что некоторые реализации требуют, чтобы вы указывали в определении курсора, что курсор будет использоваться для выполнения команды UPDATE на определенных столбцах. Это делается с помощью заключительной фразы определения курсора - FOR UPDATE . Для объявления курсора High_cust таким способом, чтобы вы могли модифицировать командой UPDATE столбец comm, вы должны ввести следующее предложение:
EXEC SQL DECLARE CURSOR High_Cust AS SELECT * FROM Salespeople WHERE snum IN (SELECT snum FROM Customers WHERE rating = 300) FOR UPDATE OF comm;
Это обеспечит вас определенной защитой от случайных модификаций, которые могут разрушить весь порядок в базе данных.
МОДИФИЦИРОВАНИЕ NULL-ЗНАЧЕНИЙ
Предложение SET это не предикат. Оно может вводить пустые NULL-значения так же, как оно вводило значения, не используя какого-то специального синтаксиса (такого, например, как IS NULL). Так что, если вы хотите установить все оценки заказчиков в Лондоне в NULL, вы можете ввести следующее предложение:
UPDATE customers SET rating = NULL WHERE city = 'London';
что обнулит все оценки заказчиков в Лондоне.
МОДИФИЦИРОВАНИЕ ПРЕДСТАВЛЕНИЙ
Представление может теперь изменяться командами модификации DML, но модификация не будет воздействовать на само представление. Команды будут на самом деле перенаправлены в базовую таблицу:
UPDATE Salesown SET city = 'Palo Alto' WHERE snum = 1004;
Его действие идентично выполнению той же команды в таблице Продавцов. Однако, если значение комиссионных продавца будет обработано командой UPDATE
UPDATE Salesown SET comm = .20 WHERE snum = 1004;
она будет отвергнута, так как поле comm отсутствует в представлении Salesown. Это важное замечание, показывающее, что не все представления могут быть модифицированы. Мы будем исследовать проблемы модификации представлений в Главе 21.
МОДИФИЦИРОВАНИЕ ПРЕДСТАВЛЕНИЯ
Один из наиболее трудных и неоднозначных аспектов представлений - непосредственное их использование с командами модификации DML. Как упомянуто в предыдущей главе, эти команды фактически воздействуют на значения в базовой таблице представления. Это является некоторым противоречием.
Представление состоит из результатов запроса, и, когда вы модифицируете представление, вы модифицируете набор результатов запроса. Но модификация не должна воздействовать на запрос; она должна воздействовать на значения в таблице, к которой был сделан запрос, и таким образом изменять вывод запроса. Это не простой вопрос. Следующий оператор будет создавать представление, показанное на Рисунке 21.1:
CREATE VIEW Citymatch (custcity, salescity) AS SELECT DISTINCT a.city, b.city FROM Customers a, Salespeople b WHERE a.snum = b.snum;
Это представление показывает все совпадения заказчиков с их продавцами так, что имеется по крайней мере один заказчик в городе_заказчика, обслуживаемый продавцом в городе_продавца.
Например, одна строка этой таблицы - London London - показывает, что имеется по крайней мере один заказчик в Лондоне, обслуживаемый продавцом в Лондоне. Эта строка может быть произведена при совпадении Hoffmanа с его продавцом Peel, причем если оба они из Лондона.
=============== SQL Execution Log ============== | | | SELECT * | | FROM Citymatch; | | =============================================== | | custcity salescity | | --------- --------- | | Berlin San Jose | | London London | | Rome London | | Rome New York | | San Jose Barselona | | San Jose San Jose | | | ================================================
Рисунок 21.1 Представление совпадения по городам
Однако то же самое значение будет произведено при совпадении Clemens из Лондона с его продавцом, который также оказался с именем Peel. Пока отличающиеся комбинации городов выбирались конкретно, только одна строка из этих значений была произведена.
Даже если вы не получите выбора, используя отличия, вы всё ещё будете в том же самом положении, потому что вы будете тогда иметь две строки в представлении с идентичными значениями, то есть с обоими столбцами, равными " Lоndon London ". Эти две строки представления будут отличаться друг от друга, так что вы пока не сможете сообщить, какая строка представления исходила из каких значений базовых таблиц (имейте в виду, что запросы, не использующие предложение ORDER BY, производят вывод в произвольном порядке).
Это относится также и к запросам, используемым внутри представлений, которые не могут использовать ORDER BY. Таким образом, заказ из двух строк не может быть использован для их отличий. Это означает, что мы будем снова обращаться к выводу строк, которые не могут быть точно связаны с указанными строками запрашиваемой таблицы.
Что, если вы пробуете удалить строку " London London " из представления? Означало бы это удаление Hoffman из таблицы Заказчиков, удаление Clemens из той же таблицы, или удаление их обоих? Должен ли SQL также удалить Peel из таблицы Продавцов? На эти вопросы невозможно ответить точно, поэтому удаления не разрешены в представлениях такого типа. Представление Citymatch это пример представления только_для_чтения: оно может быть только запрошено, но не изменено.
МОДИФИЦИРОВАНИЕ ТОЛЬКО ОПРЕДЕЛЕННЫХ СТРОК
Конечно, вы не всегда захотите указывать все строки таблицы для изменения единственного значения, так что UPDATE, наподобие DELETE, может использовать предикаты. Вот как, например, можно выполнить изменение, одинаковое для всех заказчиков продавца Peel (имеющего snum=1001):
UPDATE Customers SET rating = 200 WHERE snum = 1001;
МОДИФИЦИРУЕМЫЕ ПРЕДСТАВЛЕНИЯ И ПРЕДСТАВЛЕНИЯ ТОЛЬКО_ДЛЯ_ЧТЕНИЯ.
Одно из этих ограничений - то, что модифицируемые представления фактически подобны окнам в базовых таблицах. Они показывают кое-что, но не обязательно всё, из содержимого таблицы. Они могут ограничивать определенные строки (использованием предикатов) или специально именованные столбцы (с исключениями), но они представляют значения непосредственно и не выводят информацию с использованием составных функций и выражений.
Они также не сравнивают строки таблиц друг с другом (как в объединениях и подзапросах или как с DISTINCT).
Различия между модифицируемыми представлениями и представлениями только_чтение не случайны.
Цели для которых вы их используете, часто различны. Модифицируемые представления в основном используются точно так же, как и базовые таблицы. Фактически пользователи не могут даже осознать, является ли объект, который они запрашивают, базовой таблицей или представлением. Это превосходный механизм защиты для скрытия частей таблицы, которые являются конфиденциальными или не относятся к потребностям данного пользователя. (В Главе 22 мы покажем вам, как разрешить пользователям обращаться к представлению, а не к базовой таблице).
Представления только_чтение, с другой стороны, позволяют вам получать и переформатировать данные более рационально. Они дают вам библиотеку сложных запросов, которые вы можете выполнить и повторить снова, сохраняя полученную вами информацию до последней минуты.
Кроме того, результаты этих запросов в таблицах, которые могут затем использоваться в запросах самостоятельно (например, в объединениях), имеют преимущество над просто выполнением запросов.
Представления только_чтение могут также иметь прикладные программы защиты. Например, вы можете захотеть, чтобы некоторые пользователи видели агрегатные данные, такие как усредненное значение комиссионных продавца, а не представление индивидуальных значений комиссионных.
НЕ ДЕЛАЙТЕ ВЛОЖЕННЫХ АГРЕГАТОВ
В строгой интерпретации ANSI SQL вы не можете использовать агрегат агрегата. Предположим, что вы хотите выяснить, в какой день имелась наибольшая сумма продаж. Если вы попробуете сделать так,
SELECT odate, MAX (SUM (amt)) FROM Orders GROUP BY odate;
то ваша команда будет, вероятно, отклонена. (Некоторые реализации не предписывают этого ограничения, что выгодно, потому что вложенные агрегаты могут быть очень полезны, даже если они и несколько проблематичны.) В вышеупомянутой команде, например, SUM должен применяться к каждой группе поля odate, а MAX - ко всем группам, производящим одиночное значение для всех групп. Однако предложение GROUP BY подразумевает, что должна иметься одна строка вывода для каждой группы поля odate.
НЕ УКАЗЫВАТЬ СПИСОК СТОЛБЦОВ ПЕРВИЧНЫХ КЛЮЧЕЙ
Используя ограничение FOREIGN KEY таблицы или столбца, вы можете не указывать список столбцов родительского ключа, если родительский ключ имеет ограничение PRIMARY KEY. Естественно, в случае ключей со многими полями, порядок столбцов во внешних и первичных ключах должен совпадать, и, в любом случае, принцип совместимости между двум ключами всё ещё применим. Например, если мы поместили ограничение PRIMARY KEY в поле snum таблицы Продавцов, мы могли бы использовать его как внешний ключ в таблице Заказчиков (подобно предыдущему примеру) в этой команде:
CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY, cname char(10), city char(10), snum integer REFERENCES Salespeople);
Это средство встраивалось в язык, чтобы поощрять вас использовать первичные ключи в качестве родительских ключей.
НЕ ВСТАВЛЯЙТЕ ДУБЛИКАТЫ СТРОК
Последовательность команд в предшествующем разделе может быть критичной. Продавец Serres находится в San Jose и, следовательно, будет вставлен с помощью первой команды. Вторая команда попытается вставить его снова, поскольку он имеет ещё одного заказчика в San Jose. Если имеются любые ограничения в таблице SJpeople, которые вынуждают её иметь уникальные значения, эта вторая вставка потерпит неудачу (как и должно быть).
Дублирующие строки это плохо. (См. в Главе 18 подробности об ограничениях.) Было бы лучше, если бы вы могли как-то выяснить, что эти значения уже были вставлены в таблицу, прежде чем попытаетесь сделать это снова, с помощью добавления другого подзапроса (использующего операторы типа EXISTS, IN, < > ALL и так далее) к предикату.
К сожалению, чтобы сделать эту работу, вы должны будете сослаться на саму таблицу SJpeople в предложении FROM этого нового подзапроса, а, как мы говорили ранее, вы не можете ссылаться на таблицу, которая задействована (целиком) в любом подзапросе команды модификации. В случае с INSERT это будет также препятствовать соотнесённым подзапросам, основанным на таблице, в которую вы вставляете значения. Это имеет значение, потому что, с помощью INSERT, вы создаете новую строку в таблице. "Текущая строка" не будет существовать до тех пор, пока INSERT не закончит её обрабатывать.
Некоторые общие нестандартные средства SQL
Имеется ряд особенностей языка SQL, которые пока не определены как часть стандарта ANSI или стандарта ISO (Международная Организация По Стандартизации), и являются общими для многочисленных реализаций, так как они были получены для практического использования. Конечно, эти особенности меняются от программы к программе, и их обсуждение предназначено только для того, чтобы показать некоторые общие подходы к ним.
ОБЪЕДИНЕНИЕ БОЛЕЕ ДВУХ ТАБЛИЦ
Вы можете также создавать запросы, объединяющие более двух таблиц. Предположим, что мы хотим найти все заказы заказчиков, не находящихся в тех городах, где находятся их продавцы. Для этого необходимо связать все три наши типовые таблицы (вывод показан на Рисунке 8.4):
SELECT onum, cname, Orders.cnum, Orders.snum FROM Salespeople, Customers, Orders WHERE Customers.city < > Salespeople.city AND Orders.cnum = Customers.cnum AND Orders.snum = Salespeople.snum;
=============== SQL Execution Log ============== | | | SELECT onum, cname, Orders.cnum, Orders.snum | | FROM Salespeople, Customers, Orders | | WHERE Customers.city < > Salespeople.city | | AND Orders.cnum = Customers.cnum | | AND Orders.snum = Salespeople.snum; | | =============================================== | | onum cname cnum snum | | ------ ------- ----- ----- | | 3001 Cisneros 2008 1007 | | 3002 Pereira 2007 1004 | | 3006 Cisneros 2008 1007 | | 3009 Giovanni 2002 1003 | | 3007 Grass 2004 1002 | | 3010 Grass 2004 1002 | ===============================================
Рисунок 8.4 Объединение трёх таблиц
Хотя эта команда выглядит скорее как комплексная, вы можете следовать за логикой, просто проверяя, что заказчики не размещены в тех городах, где размещены их продавцы (совпадение двух snum полей), и что перечисленные заказы выполнены с помощью этих заказчиков (совпадение заказов с полями cnum и snum в таблице Заказов).
ОБЪЕДИНЕНИЕ НЕСКОЛЬКИХ ЗАПРОСОВ В ОДИН
Вы можете поместить несколько запросов вместе и объединить их вывод, используя предложение UNION. Предложение UNION объединяет вывод двух или более SQL-запросов в единый набор строк и столбцов. Например, чтобы получить всех продавцов и заказчиков, размещённых в Лондоне, и вывести их как единое целое, вы могли бы ввести:
SELECT snum, sname FROM Salespeople WHERE city = 'London'
UNION
SELECT cnum, cname FROM Customers WHERE city = 'London';
и получить вывод, показанный в Рисунке 14.1.
Как видите, столбцы, выбранные двумя командами, выведены так, как если бы это была одна команда. Заголовки столбца исключены, потому что ни один из столбцов, выведённых объединением, не был извлечён непосредственно из одной таблицы. Следовательно, все эти столбцы вывода не имеют никаких имён (смотрите Главу 7, в которой обсуждается вывод столбцов).
Кроме того, обратите внимание, что только последний запрос заканчивается точкой с запятой.
Отсутствие точки с запятой дает понять SQL, что имеется ещё один или более запросов.
=============== SQL Execution Log ============ | | | SELECT snum, sname | | FROM Salespeople | | WHERE city = 'London' | | UNION | | SELECT cnum, cname | | FROM Customers | | WHERE city = 'London'; | | ============================================= | | | | ----- -------- | | 1001 Peel | | 1004 Motika | | 2001 Hoffman | | 2006 Climens | | | =============================================
Рисунок 14.1 Формирование объединения из двух запросов
ОБЪЕДИНЕНИЕ ТАБЛИЦ ЧЕРЕЗ СПРАВОЧНУЮ ЦЕЛОСТНОСТЬ
Эта особенность часто используется просто для эксплуатации связей, встроенных в БД. В предыдущем примере мы установили связь между двумя таблицами в объединении. Это прекрасно. Но эти таблицы уже были соединены через snum-поле. Эта связь называется состоянием справочной целостности, как мы уже говорили в Главе 1. Используя объединение, можно извлекать данные в терминах этой связи.
Например, чтобы показать имена всех заказчиков, соответствующих продавцам, которые их обслуживают, мы будем использовать такой запрос:
SELECT Customers.cname, Salespeople.sname FROM Customers, Salespeople WHERE Salespeople.snum = Customers.snum;
Вывод этого запроса показан на Рисунке 8.2.
Это пример объединения, в котором столбцы используются для определения предиката запроса, и в этом случае snum-столбцы из обеих таблиц удалены из вывода. И это прекрасно. Вывод показывает, какие заказчики каким продавцом обслуживаются; значения поля snum, которые устанавливают связь, отсутствуют. Однако, если вы введёте их в вывод, то вы должны или удостовериться, что вывод понятен сам по себе, или должны обеспечить комментарий данных при выводе.
=============== SQL Execution Log ============ | SELECT Customers.cname, Salespeople.sname, | | FROM Salespeople, Customers | | WHERE Salespeople.snum = Customers.snum | | ============================================= | | cname sname | | ------- -------- | | Hoffman Peel | | Giovanni Axelrod | | Liu Serres | | Grass Serres | | Clemens Peel | | Cisneros Rifkin | | Pereira Motika | =============================================
Рисунок 8.2 Объединение продавцов с их заказчикам
ОБЪЕДИНЕНИЕ ТАБЛИЦ ПО РАВЕНСТВУ ЗНАЧЕНИЙ В СТОЛБЦАХ И ДРУГИЕ ВИДЫ ОБЪЕДИНЕНИЙ
Объединения, которые используют предикаты, основанные на равенствах, называются объединениями по равенству. Все наши примеры в этой главе до настоящего времени относились именно к этой категории, потому что все условия в предложениях WHERE базировались на математических выражениях, использующих знак равенства (=). Строки 'city = 'London' и 'Salespeople.snum = Orders.snum ' - примеры таких типов равенств, найденных в предикатах.
Объединения по равенству это, вероятно, наиболее общий вид объединения, но имеются и другие. Вы можете использовать практически любую реляционную операцию в объединении. Здесь дан пример другого вида объединения (вывод показан на Рисунке 8.3):
SELECT sname, cname FROM Salespeople, Customers WHERE sname < cname AND rating < 200;
=============== SQL Execution Log ============ | SELECT sname, cname | | FROM Salespeople, Customers | | WHERE sname < cname | | AND rating < 200; | | ============================================= | | sname cname | | -------- ------- | | Peel Pereira | | Motika Pereira | | Axelrod Hoffman | | Axelrod Clemens | | Axelrod Pereira | | | =============================================
Рисунок 8.3 Объединение, основанное на неравенстве
Эта команда не часто бывает полезна. Она воспроизводит все комбинации имени продавца и имени заказчика так, что первый предшествует последнему в алфавитном порядке, а последний имеет оценку, меньше чем 200. Обычно вы не создаёте сложных связей, подобно этой, и по этой причине вы, вероятно, будете строить наиболее общие объединения по равенству, но вы должны хорошо знать и другие возможности.
ОБЪЕДИНЕНИЕ ТАБЛИЦ
Одна из наиболее важных особенностей запросов SQL - их способность определять связи между многочисленными таблицами и выводить информацию из них, в терминах этих связей, всю внутри одной команды. Этот вид операции называется объединением, которое является одним из видов операций в реляционных базах данных. Как установлено в Главе 1, главное в реляционном подходе это связи, которые можно создавать между позициями данных в таблицах. Используя объединения, мы непосредственно связываем информацию с любым числом таблиц и таким образом способны создавать связи между сравнимыми фрагментами данных. При объединении, таблицы, представленные списком в предложении FROM, отделяются запятыми. Предикат запроса может ссылаться к любому столбцу любой связанной таблицы и, следовательно, может использоваться для связи между ими. Обычно предикат сравнивает значения в столбцах различных таблиц, чтобы определить, удовлетворяет ли WHERE установленному условию.
ОБЪЯВЛЕНИЕ ОГРАНИЧЕНИЙ
Вы вставляете ограничение столбца в конец имени столбца после типа данных и перед запятой. Ограничение таблицы помещается в конец имени таблицы после последнего имени столбца, но перед заключительной круглой скобкой. Далее показан синтаксис для команды CREATE TABLE, расширенной для включения в неё ограничения:
CREATE TABLE ( , ... ( [, ])...);
(Для краткости мы опустили аргумент размера, который иногда используется с типом данных.) Поля данных в круглых скобках после ограничения таблицы это поля, к которым применено данное ограничение. Ограничение столбца, естественно, применяется к тем столбцам, после чьих имен оно следует. Остальная часть этой глава будет описывать различные типы ограничений и их использование.
ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ
Все переменные, на которые имеется ссылка в предложениях SQL, должны сначала быть объявлены в SQL DECLARE SECTION (РАЗДЕЛЕ ОБЪЯВЛЕНИЙ), использующем обычный синтаксис главного языка. Вы можете иметь любое число таких разделов в программе, и они могут размещаться где-нибудь в коде перед используемой переменной, подчиняясь ограничениям, определённым в соответствии с главным языком.
Раздел объявлений должен начинаться и кончаться вложенными командами SQL: BEGIN DECLARE SECTION (Начало Раздела Объявлений) и END DECLARE SECTION (Конец Раздела Объявлений), которым предшествует, как обычно, EXEC SQL (Выполнить SQL).
Чтобы объявить переменные, используемые в предыдущем примере, вы можете ввести следующее:
EXEC SQL BEGIN DECLARE SECTION; Var id-num: integer; Salesperson: packed array (1 . .10) ot char; loc: packed array (1. .10) ot char; comm: real; EXEC SQL END DECLARE SECTION;
Для не знакомых с ПАСКАЛем: Var это заголовок, который предшествует ряду объявляемых переменных и упакованным (или распакованным) массивам, являющимися серией фиксированных переменных значений, различаемых с помощью номеров (например, третий символ loc будет loc (3)). Использование точки с запятой после каждой переменной указывает на то, что это - Паскаль, а не SQL.
ОДНО ИМЯ ДЛЯ КАЖДОГО
Если вы планируете иметь таблицу Заказчиков, используемую большим числом пользователей, лучше всего, чтобы они обращались к ней с помощью одного и того же имени. Это даст вам возможность, например, использовать это имя в вашем внутреннем общении без ограничений. Чтобы создать единое имя для всех пользователей, вы создаёте общий синоним.
Например, если все пользователи будут вызывать таблицу Заказчиков с именем Customers, вы можете ввести
CREATE PUBLIC SYNONYM Customers FOR Customers;
Мы пронимаем, что таблица Заказчиков, это ваша собственность, поэтому никакого префикса имени пользователя в этой команде не указывается. В основном общие синонимы создаются владельцами объектов или привилегированными пользователями типа DBA. Пользователям, кроме того, должны ещё быть предоставлены привилегии в таблице Заказчиков, чтобы они могли иметь к ней доступ.
Даже если имя является общим, сама таблица общей не является. Общие синонимы становятся собственными с помощью команды PUBLIC, а не с помощью их создателей.
ОГРАНИЧЕНИЕ ПЕРВИЧНЫХ КЛЮЧЕЙ
До этого мы воспринимали первичные ключи исключительно как логические понятия. Хоть мы и знаем, что такое первичный ключ и как он должен использоваться в любой таблице, мы не в курсе, "знает" ли об этом SQL. Поэтому мы использовали ограничение UNIQUE или уникальные индексы в первичных ключах, чтобы предписывать им уникальность. В более ранних версиях языка SQL это было необходимо и могло выполняться данным способом. Однако теперь SQL поддерживает первичные ключи непосредственно ограничением Первичный Ключ (PRIMARE KEY). Это ограничение может быть доступным или недоступным в вашей системе. PRIMARY KEY может ограничивать таблицы или их столбцы. Это ограничение работает так же, как и ограничение UNIQUE, за исключением случая, когда только один первичный ключ (для любого числа столбцов) может быть определен для данной таблицы. Имеется также различие между первичными ключами и уникальностью столбцов в способе их использования с внешними ключами, о которых будет рассказано в Главе 19. Синтаксис и определение их уникальности - те же, что и для ограничения UNIQUE. Первичные ключи не могут позволить значений NULL. Это означает, что, подобно полям в ограничении UNIQUE, любое поле, используемое в ограничении PRIMARY KEY, должно уже быть объявлено NOT NULL. Имеется улучшенный вариант создания нашей таблицы Продавцов:
CREATE TABLE Salestotal (snum integer NOT NULL PRIMARY KEY, sname char(10) NOT NULL UNIQUE, city char(10), comm decimal);
Как видите, уникальность (UNIQUE) полей может быть объявлена для той же самой таблицы. Лучше всего помещать ограничение PRIMARY KEY в поле (или в поля), которое будет образовывать ваш уникальный идентификатор строки, и сохранять ограничение UNIQUE для полей, которые должны быть уникальными логически (такие как номера телефона или поле sname), а не для идентификации строк.
ОГРАНИЧЕНИЕ ПРИВИЛЕГИЙ НА ОПРЕДЕЛЕННЫЕ СТОЛБЦЫ
Все привилегии объекта используют один и тот же синтаксис, кроме команд UPDATE и REFERENCES, в которых не обязательно указывать имена столбцов.
Привилегию UPDATE можно предоставлять наподобие других привилегий:
GRANT UPDATE ON Salespeople TO Diane;
Эта команда позволит Diane изменять значения в любом или во всех столбцах таблицы Продавцов. Однако, если Adrian хочет ограничить Diane в изменении, например, комиссионных, он может ввести:
GRANT UPDATE (comm) ON Salespeople TO Diane;
Другими словами, он просто должен указать конкретный столбец, к которому привилегия UPDATE должна быть применена, в круглых скобках после имени таблицы. Имена нескольких столбцов таблицы могут указываться в любом порядке, отделяемые запятыми:
GRANT UPDATE (city, comm) ON Salespeople TO Diane;
REFERENCES следует тому же самому правилу. Когда вы предоставите привилегию REFERENCES другому пользователю, он сможет создавать внешние ключи, ссылающиеся на столбцы вашей таблицы как на родительские ключи. Подобно UPDATE, для привилегии REFERENCES может быть указан список из одного или более столбцов, для которых ограничена эта привилегия. Например, Diane может предоставить Stephen право использовать таблицу Заказчиков как таблицу родительского ключа с помощью такой команды:
GRANT REFERENCES (cname, cnum) ON Customers TO Stephen;
Эта команда дает Stephen право использовать столбцы cnum и cname в качестве родительских ключей по отношению к любым внешним ключам в его таблицах. Stephen может контролировать то, как это будет выполнено. Он может определить (cname, cnum) или, в нашем случае, (cnum, cname), как двухстолбцовый родительский ключ, совпадающий с помощью внешнего ключа с двумя столбцами в одной из его собственных таблиц. Или он может создать раздельные внешние ключи, чтобы ссылаться на поля индивидуально, обеспечив тем самым, чтобы Diane имела принудительное присвоение родительского ключа (см. Главу 19).
Не имея ограничений на количество внешних ключей, он должен базироваться на этих родительских ключах, а родительские ключи различных внешних ключей разрешены для перекрытия (overlap).
Как и в случае с привилегией UPDATE, вы можете исключить список столбцов и таким образом позволить всем без исключения столбцам быть используемыми в качестве родительских ключей.
Adrian может предоставить Diane право сделать это следующей командой:
GRANT REFERENCES ON Salespeople TO Diane;
Естественно, привилегия будет пригодна для использования только в столбцах, которые имеют ограничения, требуемые для родительских ключей.
ОГРАНИЧЕНИЕ ТАБЛИЦ
Когда вы создаёте таблицу (или, когда вы её изменяете), вы можете указывать ограничения на значения, которые могут быть введены в поля. Если вы это сделали, SQL будет отклонять любые значения, нарушающие критерии, которые вы определили.
Есть два основных типа ограничений: ограничение столбца и ограничение таблицы. Различие между ними в том, что ограничение столбца применяется только к отдельным столбцам, в то время как ограничение таблицы применяется к группам из одного и более столбцов.
ОГРАНИЧЕНИЕ ВНЕШНЕГО КЛЮЧА/FOREIGN KEY
SQL поддерживает справочную целостность с ограничением FOREIGN KEY. Хотя ограничение FOREIGN KEY это новая особенность в SQL, оно ещё не обеспечивает его универсальности. Кроме того, некоторые его реализации более сложны, чем другие. Эта функция должна ограничивать значения, которые вы можете ввести в вашу БД, чтобы заставить внешний ключ и родительский ключ соответствовать принципу справочной целостности.
Одно из действий ограничения Внешнего Ключа - отбрасывание значений для полей, ограниченных как внешний ключ, который ещё не представлен в родительском ключе. Это ограничение также воздействует на вашу способность изменять или удалять значения родительского ключа (мы будем обсуждать это позже в этой главе).
ОГРАНИЧЕНИЯ ПОДЗАПРОСОВ КОМАНД DML
Невозможность обратиться к таблице, задействованной в любом подзапросе, из команды модификации (UPDATE) исключает целые категории возможных действий. Например, вы не можете просто выполнить такую операцию как удаление всех заказчиков с оценками ниже средней. Вероятно, лучше всего вы могли бы сначала (Шаг 1.) выполнить запрос, получающий среднюю величину, а затем (Шаг 2.) удалить все строки с оценкой ниже этой величины.
Шаг 1.
SELECT AVG (rating) FROM Customers;
Вывод = 200.
Шаг 2.
DELETE FROM Customers WHERE rating < 200;
ОГРАНИЧЕНИЯ ВНЕШНЕГО КЛЮЧА
Внешний ключ, в частности, может содержать только те значения, которые фактически представлены в родительском ключе, или пустые (NULL). Попытка ввести другие значения в этот ключ будет отклонена.
Вы можете объявить внешний ключ как NOT NULL, но это не обязательно и, в большинстве случаев, нежелательно. Например, предположим, что вы вводите заказчика, не зная заранее, к какому продавцу он будет назначен. Лучший выход в этой ситуации - использовать значение NOT NULL, которое должно быть изменено позже на конкретное значение.
OPEN CURSOR (ОТКРЫТЬ КУРСОР)
Синтаксис
EXEC SQL OPEN CURSOR
OPEN CURSOR выполняет запрос, связанный с курсором . Вывод может теперь извлекать по одной строке для каждой команды FETCH.
ОПЕРАТОР BETWEEN
Оператор BETWEEN похож на оператор IN. Но, в отличие от определения по числам из набора, как это делает IN, BETWEEN определяет диапазон, значения которого должны уменьшаться, что делает предикат верным. Вы должны ввести ключевое слово BETWEEN с начальным значением, ключевое AND и конечное значение. В отличие от IN, BETWEEN чувствителен к порядку, и первое значение в предложении должно быть последним по алфавитному или числовому порядку. (Обратите внимание, что, в отличие от английского языка, SQL не говорит, что "значение находится (между) BETWEEN значением и значением", а просто "значение BETWEEN значение значение". Это применимо и к оператору LIKE). Следующий пример будет извлекать из таблицы Продавцов всех продавцов с комиссионными между .10 и .12 (вывод показан на Рисунке 5.4):
SELECT * FROM Salespeople WHERE comm BETWEEN .10 AND .12;
Для оператора BETWEEN, значение, совпадающее с любым из двух значений границы (в данном случае это .10 и .12 ), заставляет предикат быть верным.
=============== SQL Execution Log ============ | SELECT * | | FROM Salespeople | | WHERE comm BETWEEN .10 AND .12; | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.12 | | 1004 Motika London 0.11 | | 1003 Axelrod New York 0.10 | ===============================================
Рисунок 5.4 SELECT с BETWEEN
SQL не делает непосредственной поддержки невключения граничных значений BETWEEN. Вы должны или определить ваши граничные значения так, чтобы включающая интерпретация была приемлема, или сделать что-нибудь типа этого:
SELECT * FROM Salespeople WHERE (comm BETWEEN .10, AND .12) AND NOT comm IN (.10, .12);
Вывод для этого запроса показан на Рисунке 5.5.
По общему признанию, это немного неуклюже, но зато показывает, как эти новые операторы могут комбинироваться с булевыми операциями, чтобы производить более сложные предикаты.
В основном вы используете IN и BETWEEN так же, как вы использовали реляционные операции при сравнении значений, которые берутся либо из набора (для IN), либо из диапазона (для BETWEEN).
Также, подобно реляционным операциям, BETWEEN может работать с символьными полями в терминах эквивалентов ASCII. Это означает, что вы можете использовать BETWEEN для выборки ряда значений из упорядоченных по алфавиту значений.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salespeople | | WHERE (comm BETWEEN .10 AND .12) | | AND NOT comm IN (.10 .12); | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1004 Motika London 0.11 | | | ===============================================
Рисунок 5.5 Сделать BETWEEN с невключением
Этот запрос выбирает всех заказчиков, чьи имена попали в определенный алфавитный диапазон:
SELECT * FROM Customers WHERE cname BETWEEN 'A' AND 'G';
Вывод для этого запроса показан на Рисунке 5.6.
Обратите внимание, что Grass и Giovanni отсутствуют даже при включенном BETWEEN. Это происходит из-за того, что BETWEEN сравнивает строки неравной длины. Строка 'G' короче, чем строка Giovanni, поэтому BETWEEN выводит 'G' с пробелами. Пробелы предшествуют символам в алфавитном порядке (в большинстве реализаций), поэтому Giovanni не выбирается. То же самое происходит и с Grass. Важно помнить это, когда вы используете BETWEEN для извлечения значений из алфавитных диапазонов. Обычно вы указываете диапазон с помощью символа начала диапазона и символа конца (вместо которого можно просто поставить z).
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE cname BETWEEN 'A' AND 'G'; | | ============================================= | | cnum cname city rating snum | | ------ -------- ------ ---- ------ | | 2006 Clemens London 100 1001 | | 2008 Cisneros San Jose 300 1007 | | | =============================================
Рисунок 5.6 Использование BETWEEN в алфавитных порядках
ОПЕРАТОР IN
Оператор IN определяет набор значений, в который данное значение может или может не быть включено. В соответствии с нашей учебной базой данных, на которой вы обучаетесь по настоящее время, если вы хотите найти всех продавцов, которые находятся в Barcelona или в London, вы должны использовать следующий запрос (вывод показан на Рисунке 5.1):
SELECT * FROM Salespeople WHERE city = 'Barcelona' OR city = 'London';
Имеется и более простой способ получить ту же информацию:
SELECT * FROM Salespeople WHERE city IN ('Barcelona', 'London');
Вывод для этого запроса показан на Рисунке 5.2.
Как видите, IN определяет набор значений с помощью имён членов набора, заключённых в круглые скобки и разделённых запятыми. Он затем проверяет различные значения указанного поля, пытаясь найти совпадение со значениями из набора. Если это случается, то предикат верен. Когда набор содержит числовые значения, а не символы, одиночные кавычки опускаются. Давайте найдём всех заказчиков, относящихся к продавцам, имеющих значения snum = 1001, 1007, и 1004. Вывод для следующего запроса показан на Рисунке 5.3:
SELECT * FROM Customers WHERE snum IN (1001, 1007, 1004);
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salespeople | | WHERE city = 'Barcelona' | | OR city = 'London'; | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.12 | | 1004 Motika London 0.11 | | 1007 Rifkin Barcelona 0.15 | | | ===============================================
Рисунок 5.1 Нахождение продавцов в Барселоне и Лондоне
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salespeople | | WHERE city IN ('Barcelona', 'London'; | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.12 | | 1004 Motika London 0.11 | | 1007 Rifkin Barcelona 0.15 | | | ===============================================
Рисунок 5.2 SELECT использует IN
=============== SQL Execution Log ============ | SELECT * | | FROM Customers | | WHERE snum IN ( 1001, 1007, 1004 ); | | ============================================= | | snum cname city rating snum | | ------ -------- ------ ---- ------ | | 2001 Hoffman London 100 1001 | | 2006 Clemens London 100 1001 | | 2008 Cisneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================
Рисунок 5.3 SELECT использует IN с номерами
ОПЕРАТОР IS NULL
Так как NULL указывает на отсутствие значения, вы не можете знать, каков будет результат любого сравнения с использованием NULL. Когда NULL сравнивается с любым значением, даже с другим таким же NULL, результат будет ни true, ни false, он неизвестен/undefined. Неизвестный булев вообще ведёт себя так же, как неверная строка, которая, произведя неизвестное значение в предикате, не будет выбрана запросом. Имейте в виду, что, в то время как NOT (неверное) равняется верно, NOT (неизвестное) равняется неизвестно.
Следовательно, выражение типа 'city = NULL' или 'city IN (NULL)' будет неизвестно в любом случае.
Часто вы должны отличать неверно и неизвестно между строками, содержащими значения столбцов, которые не соответствуют условию предиката и которые содержат NULL в столбцах. По этой причине SQL предоставляет специальный оператор IS, который используется с ключевым словом NULL, для размещения значения NULL.
Найдём все записи в нашей таблице Заказчиков с NULL-значениями в столбце city:
SELECT * FROM Customers WHERE city IS NULL;
Здесь не будет никакого вывода, потому что мы не имеем никаких значений NULL в наших типовых таблицах. Значения NULL очень важны, и мы вернёмся к ним позже.
ОПЕРАТОР LIKE
LIKE применим только к полям типа CHAR или VARCHAR, с которыми он используется для поиска подстрок. Т.е. он ищет поле символа, чтобы увидеть, совпадает ли с условием часть его строки. В качестве условия он использует групповые символы-шаблоны (wildсards) - специальные символы, которые могут соответствовать чему-нибудь.
Имеются два типа шаблонов, используемых с LIKE:
символ подчёркивания ( _ ) замещает любой одиночный символ. Например, 'b_t' будет соответствовать словам 'bat' или 'bit', но не будет соответствовать 'brat'.
знак процента (%) замещает последовательность любого количества символов (включая символы нуля). Например '%p%t' будет соответствовать словам 'put', 'posit', или 'opt, но не 'spite'.
Давайте найдём всех заказчиков, чьи имена начинаются с G (вывод показан на Рисунке 5.7):
SELECT FROM Customers WHERE cname LIKE 'G%';
=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE cname LIKE 'G'; | | ============================================= | | cnum cname city rating snum | | ------ -------- ------ ---- ------ | | 2002 Giovanni Rome 200 1003 | | 2004 Grass Berlin 300 1002 | | | =============================================
Рисунок 5.7 SELECT использует LIKE с %
LIKE может быть удобен, если вы ищете имя или другое значение и если вы не помните, как они точно пишутся. Предположим, что вы не уверены, как записано по буквам имя одного из ваших продавцов - Peal или Peel. Вы можете просто использовать ту часть, которую вы знаете, и групповые символы, чтобы находить все возможные совпадения (вывод этого запроса показан на Рисунке 5.8):
SELECT * FROM Salespeople WHERE sname LIKE 'P _ _ l %';
Группа символов подчёркивания, каждый из которых представляет один символ, добавит только два символа к уже существующим 'P' и 'l' , поэтому имя наподобие Prettel не может быть показано. Групповой символ ' % ' в конце строки необходим в большинстве реализаций, если длина поля sname больше, чем число символов в имени Peel (потому что некоторые другие значения sname длиннее, чем четыре символа). В этом варианте значение поля sname, фактически сохраняемое как имя Peel, сопровождается рядом пробелов. Следовательно, символ 'l' не будет считаться концом строки. Групповой символ ' % ' просто соответствует этим пробелам. Это не обязательно, если поле sname имеет тип VARCHAR.
=============== SQL Execution Log ============ | | | SELECT * | | FROM Salespeople | | WHERE sname LIKE 'P__l'; | | ==============================================| | snum sname city comm | | ------ ---------- ----------- ------- | | 1001 Peel London 0.12 | | | ===============================================
Рисунок 5.8 SELECT использует LIKE с подчёркиванием (_)
А что вы будете делать, если вам нужно искать символ процента или символ подчёркивания в строке? В LIKE-предикате вы можете определить любой одиночный символ как символ ESC. Символ ESC используется сразу перед процентом или подчёркиванием в предикате и означает, что процент или подчёркивание будет интерпретироваться как символ, а не как групповой символ-шаблон. Например, мы могли бы найти наш sname-столбец, где присутствует подчёркивание, следующим образом:
SELECT * FROM Salespeople WHERE sname LIKE '%/_%' ESCAPE '/';
С этими данными не будет никакого вывода, потому что мы не включили никакого подчёркивания в имя нашего продавца. Ключевое слово ESCAPE определяет '/ ' как ESC-символ. ESC-символ, используемый в LIKE-строке, сопровождается знаком процента, знаком подчёркивания или знаком ESCAPE, который будет искаться в столбце, а не обрабатываться как шаблон.
Символ ESC должен быть одиночным символом и применяется только к одиночному символу сразу после него.
В примере выше, символ процента начала и символ процента окончания обрабатываются как групповые символы; только подчёркивание представлено как сам символ.
Как упомянуто выше, символ ESC может также использоваться самостоятельно. Другими словами, если вы будете искать столбец с символом ESC, вы просто вводите его дважды. Символ ESC "берёт следующий символ буквально как символ" и, во-вторых, символ ESC самостоятелен.
Вот предыдущий пример, который пересмотрен, чтобы найти местонахождение строки '_/' в sname-столбце:
SELECT * FROM Salespeople WHERE sname LIKE '%/_//%' ESCAPE'/';
Снова не будет никакого вывода с такими данными. Строка сравнивается с содержанием любой последовательности символов (%), сопровождаемых символом подчёркивания ( /_ ), символом ESC ( // ) и любой последовательностью символов в конце строки (%).
ОПИСАНИЕ ОГРАНИЧЕНИЙ ТАБЛИЦЫ
Имеется несколько атрибутов таких определений, о которых нужно поговорить. Причина, по которой мы решили сделать поля cnum и snum в таблице Заказов единым внешним ключом, это гарантия того, что для каждого заказчика, содержащегося в заказах, продавец, кредитующий этот заказ - тот же, что и указанный в таблице Заказчиков.
Чтобы создать такой внешний ключ, мы должны были бы поместить ограничение таблицы UNIQUE в два поля таблицы Заказчиков, даже если оно необязательно для самой этой таблицы. Пока поле cnum в этой таблица имеет ограничение PRIMARY KEY, оно будет уникально в любом случае, и, следовательно, невозможно получить ещё одну комбинацию поля cnum с каким-то другим полем.
Создание внешнего ключа таким способом поддерживает целостность БД (даже если при этом вам будет запрещено внутреннее прерывание по ошибке) и кредитование любого продавца, отличного от того, который назначен именно этому заказчику.
С точки зрения поддержания целостности БД, внутренние прерывания (или исключения), конечно же, нежелательны. Если вы их допускаете и, в то же время, хотите поддерживать целостность вашей БД, вы можете объявить поля snum и cnum в таблице Заказов независимыми внешними ключами этих полей в таблице Продавцов и таблице Заказчиков, соответственно.
Фактически не обязательно использовать поля snum в таблице Заказов, как это делали мы,
хотя это полезно было сделать для разнообразия. Поле cnum, связывая каждый заказ заказчиков в таблице Заказчиков и в таблице Заказов, должно всегда быть общим, чтобы находить правильное поле snum для данного заказа (не разрешая никаких исключений). Это означает, что мы записываем фрагмент информации - какой заказчик назначен к какому продавцу - дважды, и нужно будет выполнять дополнительную работу чтобы удостовериться, что обе версии согласуются.
Если мы не имеем ограничения внешнего ключа, как сказано выше, эта ситуация будет особенно проблематична, потому что каждый порядок нужно будет проверять вручную (вместе с запросом), чтобы удостовериться, что именно соответствующий продавец кредитовал каждую соответствующую продажу. Наличие такого типа информационной избыточности в вашей БД называется деморализация (denormalization), что нежелательно в идеальной реляционной базе данных, хотя практически и может быть разрешено. Деморализация может заставить некоторые запросы выполняться быстрее, поскольку запрос в одной таблице выполняется всегда значительно быстрее, чем в объединении.
ОПИСАНИЕ SELECT
В общем случае команда SELECT начинается с ключевого слова SELECT, сопровождаемого пробелом. После этого должен следовать список имён столбцов, которые вы хотите видеть, отделяемых запятыми. Если вы хотите видеть все столбцы таблицы, вы можете заменить этот список звездочкой (*). Ключевое слово FROM, следующее далее, сопровождается пробелом и именем таблицы, запрос к которой делается. В конце должна использоваться точка с запятой (;) для окончания запроса и указания на то, что команда готова к выполнению.
ОПРЕДЕЛЕНИЕ МОДИФИЦИРУЕМОСТИ ПРЕДСТАВЛЕНИЯ
Если команды модификации могут выполняться в представлении, представление, как уже говорилось, будет модифицируемым; в противном случае оно предназначено только для чтения при запросе. Не противореча этой терминологии, мы будем использовать выражение "модифицировать представление" (updating a view), что означает возможность выполнения в представление любой из трёх команд модификации DML (Вставить, Изменить и Удалить), которые могут изменять значения.
Как вы определите, является ли представление модифицируемым? В теории базы данных, это - пока обсуждаемая тема. Основной принцип таков: модифицируемое представление это представление, в котором команда модификации может выполниться, чтобы изменить одну, и только одну, строку основной таблицы в каждый момент времени, не воздействуя на любые другие строки любой таблицы. Использование этого принципа на практике, однако, затруднено.
Кроме того, некоторые представления, которые теоретически являются модифицируемыми, на самом деле не являются модифицируемыми в SQL.
Вот критерии, по которым определяют, является ли в SQL-представление модифицируемым, или нет:
Оно должно выводиться из одной, и только из одной, базовой таблицы.
Оно должно содержать первичный ключ этой таблицы (это технически не предписывается стандартом ANSI, но было бы неплохо придерживаться этого).
Оно не должно иметь никаких полей, которые являлись бы агрегатными функциями.
Оно не должно содержать DISTINCT в своем определении.
Оно не должно использовать GROUP BY или HAVING в своем определении.
Оно не должно использовать подзапросы (это ANSI-ограничение, которое не предписано для некоторых реализаций).
Оно может быть использовано в другом представлении, но это представление должно также быть модифицируемым.
Оно не должно использовать константы, строки или выражения для значений (например: comm * 100) среди выбранных полей вывода.
Для INSERT оно должно содержать любые поля основной таблицы, которые имеют ограничение NOT NULL, если другое ограничение по умолчанию не определено.
ОСТАЛЬНОЕ СОДЕРЖИМОЕ КАТАЛОГА
Здесь показаны определения остальных ваших системных таблиц с типовым запросом для каждого:
ОТМЕНА ПРИВИЛЕГИЙ
Также как ANSI предоставляет команду CREATE TABLE, чтобы создать таблицу, а DROP TABLE - чтобы от нее избавиться, так и команда GRANT позволяет вам давать привилегии пользователям, не предоставляя способа отобрать их обратно.
Удаление привилегии выполняется командой REVOKE, фактически - стандартному средству с достаточно понятной формой записи. Синтаксис команды REVOKE похож на GRANT, но имеет обратный смысл.
Чтобы удалить привилегию INSERT для Adrian в таблице Заказов, вы можете ввести
REVOKE INSERT ON Orders FROM Adrian;
Использование списков привилегий и пользователей здесь допустимы, как и в случае с GRANT, так что вы можете ввести следующую команду:
REVOKE INSERT, DELETE ON Customers FROM Adrian, Stephen;
Однако здесь имеется некоторая неясность. Кто имеет право отменять привилегии? Когда пользователь с правом передавать привилегии другим, теряет это право, пользователи, которым он предоставил эти привилегии, также их потеряют?
Так как это нестандартная особенность, нет никаких авторитетных ответов на эти вопросы, но наиболее общий подход таков:
Привилегии отменяются пользователем, который их предоставил, и отмена будет каскадироваться, то есть она будет автоматически распространяться на всех пользователей, получивших от него эту привилегию.
ОТСЛЕЖИВАНИЕ ДЕЙСТВИЙ
Ваша SQL-реализация достаточна хороша, если она доступна многим пользователям, чтобы обеспечивать им некий способ слежения за действиями, выполняемыми в базе данных.
Имеются две основные формы, чтобы реализовать это: Journaling (Протоколирование) и Auditing (Ревизия).
Эти формы отличаются по назначению.
Journaling применяется с целью защиты ваших данных при разрушении вашей системы. Сначала вы используете реализационно зависимую процедуру, чтобы архивировать текущее содержание вашей базы данных, поэтому копия её содержания где-нибудь будет сохранена. Затем вы просматриваете протокол изменений, сделанных в базе данных. Он сохраняется в некоторой области памяти, но не в главной памяти базы данных, а желательно на отдельном устройстве, и содержит список всех команд, которые произвели изменения в структуре или в содержании базы данных.
Если у вас вдруг появились проблемы и текущее содержание вашей базы данных оказалось нарушенным, вы можете повторно выполнить все изменения, зарегистрированные в протоколе на резервной копии вашей базы данных, и снова привести вашу базу данных в состояние, которое было до момента последней записи в протокол. Типичной командой для начала протоколирования будет следующая:
SET JOURNAL ON;
Auditing используется с целью защиты. Она следит за тем, кто и какие действия выполнял в базе данных, и сохраняет эту информацию в таблице, доступной только очень немногим привилегированным пользователям. Конечно, вы редко будете прибегать к процедуре ревизии, потому что очень скоро она займет много памяти и вам будет сложно работать в вашей БД. Но вы можете устанавливать ревизию для определённых пользователей, определённых действий или определённых объектов данных. Имеется такая форма команды AUDIT:
AUDIT INSERT ON Salespeople BY Diane;
Или предложение ON, или предложение BY могут быть исключены, устанавливая ревизию либо всех объектов, либо всех пользователей, соответственно. Применение AUDIT ALL вместо AUDIT INSERT приведет к отслеживанию всех действий Diane в таблице Продавцов.
ПАРАМЕТРЫ DISTINCT
DISTINCT может указываться только один раз в данном предложении SELECT. Если предложение выбирает несколько полей,
=============== SQL Execution Log =========== | | | SELECT DISTINCT snum | | FROM Orders; | | | | ============================================= | | snum | | ------- | | 1001 | | 1002 | | 1003 | | 1004 | | 1007 | =============================================
Рисунок 3.5 SELECT без дублирования
DISTINCT опускает строки, где все выбранные поля идентичны. Строки, в которых некоторые значения одинаковы, а некоторые - различны, будут сохранены. DISTINCT фактически приводит к показу всей строки вывода, не указывая полей (за исключением случав, когда он используется внутри агрегатных функций, как описано в Главе 6), так что нет никакого смысла его повторять.
ПАСКАЛЬ
SQL ТИПЭКВИВАЛЕНТ ПАСКАЛЯ
| INTEGER | INTEGER |
| REAL | REAL |
| CHAR () | PACKED ARRAY [1..] OF CHAR |
ПЕРЕИМЕНОВАНИЕ С ТЕМ ЖЕ САМЫМ ИМЕНЕМ
Префикс (прозвище) пользователя это фактически часть имени любой таблицы. Всякий раз, когда вы не указываете ваше имя пользователя вместе с именем вашей собственной таблицы, SQL сам заполняет для вас это место. Следовательно, два одинаковых имени таблицы, но связанные с различными владельцами, становятся неидентичными и не приводят к какому-нибудь беспорядку (по крайней мере в SQL). Это означает, что два пользователя могут создать две полностью не связанные таблицы с одинаковыми именами, но это также будет означать, что один пользователь может создать представление, основанное на имени другого пользователя, стоящем после имени таблицы. Это иногда делается, когда представление рассматривается как сама таблица - например, если представление просто использует CHECK OPTION как заменитель ограничения CHECK в базовой таблице (смотрите подробности в Главе 22).
Вы можете также создавать ваши собственные синонимы, имена которых будут такими же, что и первоначальные имена таблиц.
Например, Adrian может определить Customers как свой синоним для таблицы Diane.Customers:
CREATE SYNONYM Customers FOR Diane.Customers;
С точки зрения SQL теперь имеются два разных имени одной таблицы: Diane.Customers и Adrian.Customers. Однако каждый из этих пользователей может обращаться к этой таблице просто Customers. SQL, как говорилось выше, сам добавит недостающие имена пользователей.
ПЕРЕИМЕНОВАНИЕ ТАБЛИЦ
Каждый раз, когда вы ссылаетесь в команде на базовую таблицу или представление, не являющиеся вашей собственностью, вы должны установить в команде префикс имени владельца так, чтобы SQL знал, где искать. Так как это со временем становится неудобным, большинство реализаций SQL позволяют вам создавать синонимы для таблиц (что не является стандартом ANSI). Синоним это альтернативное имя, наподобие прозвища.
Когда вы создаёте синоним, вы становитесь его собственником, так что нет никакой необходимости, чтобы он предшествовал другому пользовательскому идентификатору доступа (имени пользователя).
Если вы имеете по крайней мере одну привилегию в одном или более столбцах таблицы, вы можете создать для них синоним. (Некоторое отношение к этому может иметь специальная привилегия для создания синонимов.)
Adrian может создать синоним с именем Clients для таблицы с именем Diane.Customers с помощью команды CREATE SYNONYM:
CREATE SYNONYM Clients FOR Diane.Customers;
Теперь Adrian может использовать таблицу с именем Clients в команде точно так же, как её использует Diane.Customers. Синоним Clients это собственность, используемая исключительно Adrian.
ПЕРЕМЕННАЯ INDICATOR
Пустые (NULL) значения это специальные маркеры, определяемые самим SQL. Они не могут помещаться в главные переменные. Попытка вставить NULL-значения в главную переменную будет некорректна, так как главные языки не поддерживают NULL-значений в SQL по определению. Хотя результат при попытке вставить NULL-значение в главную переменную определяет проектировщик, этот результат не должен противоречить теории БД и поэтому обязан выдавать ошибку - код SQLCODE в виде отрицательного числа - и вызывать подпрограмму управления ошибкой. Естественно, вам нужно этого избежать. Поэтому вы можете заменить NULL-значения на допустимые значения, не приводящие к разрушению вашей программы. Даже если программа и не разрушится, значения в главных переменных станут неправильными, потому что они не могут иметь NULL-значений.
Альтернативным методом, предоставляемым для этой ситуации, является функция переменной indicator (указатель). Переменная indicator, объявленная в разделе объявлений SQL, напоминает другие переменные. Она может иметь тип главного языка, который соответствует числовому типу в SQL. Всякий раз, когда вы выполняете операцию, которая должна поместить NULL-значение в переменную главного языка, вы должны использовать переменную indicator для надежности.
Вы помещаете переменную indicator в команду SQL непосредственно после переменной главного языка, которую вы хотите защитить, без каких-либо пробелов или запятых, хотя вы и можете, при желании, вставить слово INDICATOR. Переменной indicator в команде изначально присваивается значение 0. Однако, если производится значение NULL, переменная indicator становится равной отрицательному числу. Вы можете проверить значение переменной indicator, чтобы узнать, было ли найдено значение NULL.
Давайте предположим, что поля city и comm таблицы Продавцов не имеют ограничения NOT NULL, и что мы объявили в разделе объявлений SQL две ПАСКАЛевские переменные целого типа, i_a и i_b. (Нет ничего такого в разделе объявлений, что могло бы представить их как переменные indicator. Они станут переменными indicator, когда будут использоваться как переменные indicator.)
Имеется одна возможность:
EXEC SQL OPEN CURSOR High_cust; while SQLCODE = O do begin EXEC SQL FETCH High_cust INTO :id_num, :salesperson, :loc:i_a, :commINDlCATOR:i_b; If i_a > = O and i_b > = O then {no NULLs produced} EXEC SQL UPDATE Salespeople SET comm = comm + .01 WHERE CURRENT OF Hlgh_cust; else {one or both NULL} begin If i_a < O then writeln ('salesperson ', id_num, ' has no city'); If i_b < O then writeln ('salesperson ', id_num, ' has no commission'); end; {else} end; {while} EXEC SQL CLOSE CURSOR High_cust;
Как видите, мы включили, ключевое слово INDICATOR в одном случае и исключили его в другом случае, чтобы показать, что эффект будет одинаковым в любом случае. Каждая строка будет выбрана, но команда UPDATE выполнится, только если NULL-значения не будут обнаружены. Если будут обнаружены NULL-значения, выполнится ещё одна часть программы, которая распечатает предупреждающее сообщение - где было найдено каждое NULL-значение.
Обратите внимание: переменные indicator должны проверяться в главном языке, как указывалось выше, а не в предложении WHERE команды SQL. Последнее в принципе не запрещено, но результат часто бывает непредсказуем.
ПЕРЕУПОРЯДОЧИВАНИЕ СТОЛБЦА
Даже если столбцы таблицы, по определению, упорядочены, это не означает, что вы будете восстанавливать их в том же порядке. Конечно, звёздочка (*) покажет все столбцы в их естественном порядке, но если вы укажете столбцы отдельно, вы можете получить их в том порядке, в котором хотите. Давайте рассмотрим таблицу Заказов, содержащую дату приобретения (odate), номер продавца (snum), номер заказа (onum) и суммы приобретения (amt):
SELECT odate, snum, onum, amt FROM Orders;
Вывод этого запроса показан на Рисунке 3.3.
============= SQL Execution Log ============== | | | SELECT odate, snum, onum, amt | | FROM Orders; | | | | ------------------------------------------------| | odate snum onum amt | | ----------- ------- ------ --------- | | 10/03/1990 1007 3001 18.69 | | 10/03/1990 1001 3003 767.19 | | 10/03/1990 1004 3002 1900.10 | | 10/03/1990 1002 3005 5160.45 | | 10/03/1990 1007 3006 1098.16 | | 10/04/1990 1003 3009 1713.23 | | 10/04/1990 1002 3007 75.75 | | 10/05/1990 1001 3008 4723.00 | | 10/06/1990 1002 3010 1309.95 | | 10/06/1990 1001 3011 9891.88 | | | ===============================================
Рисунок 3.3 Реконструкция столбцов
Как видите, структура информации в таблицах это просто основа для активной перестройки структуры в SQL.
ПЕРВИЧНЫЕ КЛЮЧИ БОЛЕЕ ЧЕМ ОДНОГО ПОЛЯ
Ограничение PRIMARY KEY может также быть применено для нескольких полей, составляющих уникальную комбинацию значений. Предположим, что ваш первичный ключ это имя и вы имеете первое имя и последнее имя, сохранёнными в двух различных полях (так, что вы можете организовывать данные с помощью любого из них). Очевидно, что ни первое, ни последнее имя нельзя заставить быть уникальным самостоятельно, но мы можем каждую из этих двух комбинаций сделать уникальной.
Мы можем применить ограничение таблицы PRIMARY KEY для пар:
CREATE TABLE Namefield (firstname char (10) NOT NULL, lastname char (10) NOT NULL city char (10), PRIMARY KEY (firstname, lastname));
Одна проблема в этом подходе - мы можем вынудить появление уникальности, например, введя Mary Smith и M. Smith. Это может ввести в заблуждение, потому что ваши служащие могут не знать, кто из них кто. Обычно более надежный способ определения числового поля, которое могло бы отличать одну строку от другой, - иметь первичный ключ и применять ограничение UNIQUE для двух имен полей.
ПЕРВИЧНЫЙ КЛЮЧ КАК УНИКАЛЬНЫЙ ВНЕШНИЙ КЛЮЧ
Ссылка ваших внешних ключей только на первичные ключи, как мы это делали в типовых таблицах, - хорошая стратегия. Когда вы используете внешние ключи, вы связываете их не просто с родительскими ключами, на которые они ссылаются; вы связываете их с определённой строкой таблицы, где этот родительский ключ будет найден. Сам по себе родительский ключ не обеспечивает никакой информации, которая не была бы уже представлена во внешнем ключе. Смысл, например, поля snum как внешнего ключа в таблице Заказчиков - это связь, которую оно обеспечивает, но не со значением поля snum, на которое он ссылается, а с другой информацией в таблице Продавцов. Такой, например, как имена продавцов, их местоположение и так далее. Внешний ключ это не просто связь между двум идентичными значениями; это связь - с помощью этих двух значений - между двум строками таблицы, указанной в запросе.
Это поле snum может использоваться, чтобы связывать любую информацию в строке из таблицы Заказчиков со ссылочной строкой из таблицы Продавцов, например, чтобы узнать, живут ли они в том же самом городе, кто имеет более длинное имя, имеет ли продавец кроме данного заказчика каких-то других заказчиков, и так далее.
Так как цель первичного ключа состоит в том, чтобы идентифицировать уникальность строки, это более логичный и менее неоднозначный выбор для внешнего ключа. Для любого внешнего ключа, который использует уникальный ключ как родительский ключ, вы должны создать внешний ключ, который использовал бы первичный ключ той же самой таблицы для того же самого действия.
Внешний ключ, который не имеет никакой другой цели, кроме связывания строк, напоминает первичный ключ, используемый исключительно для идентификации строк, и является хорошим средством сохранить структуру вашей БД ясной и простой и, следовательно, создающей меньше трудностей.
PL/I
SQL ТИПЭКВИВАЛЕНТ PL/I
| CHAR | CHAR |
| DECIMAL |
FIXED DECIMAL | | INTEGER | FIXED BINARY |
| FLOAT | FLOAT BINARY |
ПОДРАЗДЕЛЫ SQL
И в интерактивной, и во вложенной формах SQL имеются многочисленные части, или подразделы. Так как вы, вероятно, столкнетесь с этой терминологией при чтении SQL, мы дадим некоторые пояснения. К сожалению, эти термины не используются повсеместно во всех реализациях. Они указаны ANSI и полезны на концептуальном уровне, но большинство SQL-программ практически не обрабатывают их отдельно, так что они, по существу, становятся функциональными категориями команд SQL.
DDL (Язык Определения Данных) - так называемый Язык Описания Схемы в ANSI - состоит из команд, которые создают объекты (таблицы, индексы, просмотры и так далее) в базе данных.
DML (Язык Манипулирования Данными) это набор команд, которые определяют, какие значения представлены в таблицах в любой момент времени.
DCD (Язык Управления Данными) состоит из средств, которые определяют, разрешить ли пользователю выполнять определённые действия, или нет. Они являются составными частями DDL в ANSI.
Не забывайте эти названия. Это не различные языки, а разделы команд SQL, сгруппированные по их функциям.
ПОДЗАПРОСЫ В ПРЕДЛОЖЕНИИ HAVING
Вы можете также использовать подзапросы внутри предложения HAVING. Эти подзапросы могут использовать свои собственные агрегатные функции, если они не производят нескольких значений, или использовать GROUP BY или HAVING. Следующий запрос является примером этого (вывод показан на Рисунке 10.7):
SELECT rating, COUNT (DISTINCT cnum) FROM Customers GROUP BY rating HAVING rating > (SELECT AVG (rating) FROM Customers WHERE city = " San Jose');
=============== SQL Execution Log ============= | | | SELECT rating,count (DISTINCT cnum) | | FROM Customers | | GROUP BY rating | | HAVING rating > | | (SELECT AVG (rating)snum + 1000 | | FROM Custimers | | WHERE city = 'San Jose'); | |================================================ | | rating | | -------- -------- | | 200 2 | ================================================
Рисунок 10.7 Поиск в San Jose заказчиков с оценкой выше среднего
Эта команда подсчитывает заказчиков в San Jose с рейтингами выше среднего. Так как имеются другие оценки, отличные от 300, они должны быть выведены с числом номеров заказчиков, которые имели эту оценку.
ПОДЗАПРОСЫ ВЫБИРАЮТ ОДИНОЧНЫЕ СТОЛБЦЫ
Смысл всех подзапросов, обсуждённых в этой главе, в том, что все они выбирают одиночный столбец. Это обязательно, поскольку полученный вывод сравнивается с одиночным значением. Подтверждением этому является то, что SELECT * не может использоваться в подзапросе. Имеется исключение из этого, когда подзапросы используются с оператором EXISTS, о котором мы будем говорить в Главе 12.
ПОЛЬЗОВАТЕЛИ
Каждый пользователь в среде SQL имеет специальное идентификационное имя или номер. Терминология везде разная, но мы выбрали (следуя ANSI) ссылку на имя или номер как на Идентификатор (ID) доступа. Команда, посланная в базе данных, ассоциируется с определённым пользователем; или иначе - специальным Идентификатором доступа. Поскольку это относится к БД SQL, ID разрешения это имя пользователя, и SQL может использовать специальное ключевое слово USER, которое ссылается на Идентификатор доступа, связанный с текущей командой. Команда интерпретируется и разрешается (или запрещается) на основе информации, связанной с Идентификатором доступа пользователя, подавшего команду.
ПОМЕЩЕНИЕ ТЕКСТА В ВАШЕМ ВЫВОДЕ ЗАПРОСА
Символ 'A', когда ничего не значит сам по себе, является константой, такой, например, как число 1.
Вы можете вставлять константы в предложение SELECT-запроса, включая и текст. Однако символьные константы, в отличие от числовых констант, не могут использоваться в выражениях. Вы можете иметь выражение 1 + 2 в вашем предложении SELECT, но вы не можете использовать выражение типа 'A' + 'B'; это приемлемо, только если мы имеем в виду, что 'A' и 'B' это просто буквы, а не переменные и не символы.
Тем не менее, возможность вставлять текст в вывод ваших запросов - очень удобная штука.
Вы можете усовершенствовать предыдущий пример, представив комиссионные как проценты со знаком процентов (%). Это даст вам возможность помещать в вывод символы и комментарии, как в следующем примере (вывод показан на Рисунке 7.2):
SELECT snum, sname, city, ' % ', comm * 100 FROM Salespeople;
=============== SQL Execution Log ============ | | | SELECT snum, sname, city, '%' comm * 100 | | FROM Salespeople; | | ==============================================| | snum sname city | | ------ -------- ----------- ---- --------- | | 1001 Peel London % 12.000000 | | 1002 Serres San Jose % 13.000000 | | 1004 Motika London % 11.000000 | | 1007 Rifkin Barcelona % 15.000000 | | 1003 Axelrod New York % 10.000000 | | | ===============================================
Рисунок 7.2 Вставка символов в вывод
Обратите внимание, что пробел перед процентом вставляется как часть строки. Эта же особенность может использоваться, чтобы маркировать вывод вместе с вставляемыми комментариями.
Вы должны помнить, что этот же самый комментарий будет напечатан в каждой строке вывода, а не просто один раз для всей таблицы. Предположим, что вы генерируете вывод для отчёта, который указывал бы число заказов, получаемых в течение каждого дня. Вы можете промаркировать ваш вывод (см. Рисунок 7.3), сформировав запрос следующим образом:
SELECT ' For ', odate, ', there are ', COUNT (DISTINCT onum), 'orders.' FROM Orders GROUP BY odate;
Грамматической некорректности вывода на 5 октября невозможно избежать, не создав запроса, ещё более сложного, чем этот. (Вы должны будете использовать два запроса с UNION, который
=============== SQL Execution Log ============== | | | SELECT 'For', odate, ', ' there are ' , | | COUNT (DISTINCT onum), ' orders ' | | FROM Orders | | GROUP BY odate; | | =============================================== | | odate | | ------ ---------- --------- ------ ------- | | For 10/03/1990 , there are 5 orders. | | For 10/04/1990 , there are 2 orders. | | For 10/05/1990 , there are 1 orders. | | For 10/06/1990 , there are 2 orders. | | | ================================================
Рисунок 7.3: Комбинация текста, значений поля, и агрегатов
мы будем рассматривать в Главе 14.) Как видите, одиночный неизменный комментарий для каждой строки таблицы может быть очень полезен, но имеет ограничения. Иногда изящнее и полезнее создать один комментарий для всего вывода в целом или создавать свой собственный комментарии для каждой строки.
Различные программы, использующие SQL, часто обеспечивают специальные средства типа генератора отчетов (например Report Writer), которые разработаны, чтобы форматировать и совершенствовать вывод. Вложенный SQL может также использовать возможности того языка, в который он вложен. SQL сам по себе интересен прежде всего при операциях с данными. Вывод, по существу, это информация; и программа, использующая SQL, может часто использовать эту информацию и помещать её в более привлекательную форму. Это, однако, вне сферы самого SQL.
ПОРЯДОК СТРОК ПРОИЗВОЛЕН
Чтобы поддерживать максимальную гибкость, строки таблицы, по определению, не должны находиться в каком-то определенном порядке. С этой точки зрения структура БД отличается от нашей адресной книги.
Вход в адресную книгу обычно упорядочивается в алфавитном порядке. В системах с РБД имеется одна мощная возможность для пользователей: способность упорядочивать информацию так, чтобы они могли восстанавливать её.
Рассмотрим вторую таблицу. Иногда вам необходимо видеть эту информацию упорядоченной в алфавитном порядке по именам, иногда - в возрастающем или убывающем порядке, а иногда - сгруппированной по отношению к какому-нибудь доктору.
Порядок набора в строках будет конфликтовать со способностью заказчика изменять его, поэтому строки всегда рассматриваются как неупорядоченные. По этой причине вы не можете просто сказать: "Мы хотим посмотреть пятую строку таблицы". Пренебрегая порядком, в котором данные вводились, или любым другим критерием, мы определим не ту строку, хотя она и будет пятой. Строки таблицы не находятся в какой-либо определенной последовательности.
ПРАВИЛЬНОЕ ПОНИМАНИЕ ANY И ALL
В SQL, сказать, что значение больше (или меньше), чем любое (ANY) из набора значений - то же самое, что сказать, что оно больше (или меньше), чем любое отдельное из этих значений. И наоборот, сказать, что значение не равно всему (ALL) набору значений, это то же, что сказать, что нет такого значения в наборе которому оно равно.
ПРЕДИКАТЫ И ИСКЛЮЧЁННЫЕ ПОЛЯ
Похожая проблема, о которой вы должны знать, включает в себя вставку строк в представление с предикатом, базирующемся на одном или более исключённых полях. Например, может показаться разумным создание Londonstaff, как здесь:
CREATE VIEW Londonsta1t AS SELECT snum, sname, comm FROM Salespeople WHERE city = 'London';
В конце концов, зачем включать значение city, если все значения city будут одинаковыми?
А как будет выглядеть картинка, получаемая всякий раз, когда мы пробуем вставить строку?
Так как мы не можем указать значение city как значение по умолчанию, этим значением, вероятно, будет NULL, и оно будет введено в поле city (NULL используется, если другое значение по умолчанию не было определено. См. подробности в Главе 18). Так как в этом случае поле city не будет равняться значению London, вставляемая строка будет исключена из представления.
Это будет верным для любой строки, которую вы попробуете вставить в просмотр Londonstaff. Все они должны быть введены с помощью представления Londonstaff в таблицу Продавцов, а затем исключены из самого представления (если определением по умолчанию был не London, то это особый случай). Пользователь не сможет вводить строки в это представление, хотя всё ещё не известно, может ли он вводить строки в базовую таблицу. Даже если мы добавим WITH CHECK OPTION в определение представления,
CREATE VIEW Londonstate AS SELECT snum, sname, comm FROM Salespeople WHERE city = 'London' WITH CHECK OPTION;
проблема не обязательно будет решена. В результате этого мы получим представление, которое мы могли бы модифицировать или из которого мы могли бы удалять, но не вставлять в него. В некоторых случаях это может быть хорошо; хотя, возможно, нет смысла пользователям, имеющим доступ к этому представлению, иметь возможность добавлять строки. Но вы должны точно определить, что может произойти, прежде чем вы создадите такое представление.
Даже если это не всегда может обеспечить вас нужной информацией, полезно включать в ваше представление все поля, на которые имеется ссылка в предикате. Если вы не хотите видеть эти поля в вашем выводе, вы всегда сможете исключить их из запроса в представлении, в противоположность запросу внутри представления. Другими словами, вы могли бы определить представление Londonstaff так:
CREATE VIEW Londonstaff AS SELECT * FROM Salespeople WHERE city = 'London' WITH CHECK OPTION;
Эта команда заполнит представление одинаковыми значениями в поле city, которые вы можете просто исключить из вывода с помощью запроса, где указаны только те поля, которые вы хотите видеть:
SELECT snum, sname, comm FROM Londonstaff;
ПРЕДИКАТЫ С ПОДЗАПРОСАМИ ЯВЛЯЮТСЯ НЕОБРАТИМЫМИ
Вы должны обратить внимание что предикаты, включающие подзапросы, используют выражение
<скалярная форма> <оператор> <подзапрос>,
а не
<подзапрос> <оператор> <скалярное выражение>
или
<подзапрос> <оператор> <подзапрос>.
Другими словами, вы не должны записывать предыдущий пример так:
SELECT * FROM Orders WHERE (SELECT DISTINCT snum FROM Orders WHERE cnum = 2001) = snum;
В строгой ANSI-реализации это приведет к неудаче, хотя некоторые программы и позволяют делать такие вещи. ANSI также предохраняет от появления в выводе подзапроса обоих значений при сравнении.
ПРЕДИКАТЫ
Здесь определён список различных типов предиката , описанных на следующих страницах:
::= [NOT]
{ | | | | | | } [ANDI OR ]
- это выражение, которое может быть true, false или неизвестным, за исключением
и , которые могут быть только верными или неверными.
Будет получено "неизвестно", если NULL-значения предотвращают вывод полученного ответа. Это будет случаться всякий раз, когда NULL-значение сравнивается с любым значением. Стандартные булевы операторы - AND, OR и NOT - могут использоваться с предикатом. NOT true = false, NOT false = true, а NOT неизвестно = неизвестно. Результаты AND и OR в комбинации с предикатами, показаны в следующих таблицах:
AND
AND True False Неизвестно
True true false неизвестно False false false false Неизвестно неизвестно false неизвестно
OR
OR True False Неизвестно
True true true true False true false неизвестно Неизвестно true неизвестно неизвестно
Эти таблицы читаются способом, наподобие таблицы умножения: вы объединяете верные, неверные, или неизвестные значения из строк с их столбцами, чтобы на перекрестье получить результат. В таблице AND, например, третий столбец (Неизвестно) и первая строка (Тrue) на пересечении в верхнем правом углу дают результат - неизвестно, другими словами: Верно AND Неизвестно = неизвестно. Порядок вычислений определяется круглыми скобками. Они не представляются каждый раз. NOT оценивается первым, далее AND и OR. Различные типы предикатов рассматриваются отдельно в следующем разделе.
(предикат сравнения)
Синтаксис
|
:: =
=
| <
|>
| <
|>=
| <>
Если либо = NULL, либо = неизвестно;
другими словами, это true, если сравнение true, или false, если сравнение false.
имеет стандартные математические значения для числовых значений; для других типов значений эти значения определяются конкретной реализацией.
Оба должны иметь сравнимые типы данных. Если подзапрос используется, он должен содержать одно выражение в предложении SELECT, чьё значение будет заменять второе выражение в предикате сравнения каждый раз, когда действительно выполняется.
Синтаксис
[NOT] BETWEEN
AND
- A BETWEEN B AND C имеет такое же значение, что и - ( A>= B AND <= C). , для которого A NOT BETWEEN B AND C, имеет такое же значение, что и NOT (BETWEEN B AND C).
может быть выведено с помощью нестандартного запроса (*nonstandard*).
Синтаксис
[NOT] IN |
Список значений будет состоять из одного или более значений в круглых скобках с разделением запятыми, которые имеют сравнимый с тип данных. Если используется подзапрос , он должен содержать только одно выражение в предложении SELECT (возможно и больше, но это уже будет вне стандарта ANSI).
Подзапрос фактически выполняется отдельно для каждой строки-кандидата основного запроса, и значения, которые он выведет, будут составлять список значений для этой строки. В любом случае предикат будет верен, если выражение представленное в списке значений , если не указан NOT.
Фраза A NOT IN (B, C) является эквивалентом фразы NOT (A IN (B, C)).
Синтаксис
[NOT] LIKE [ESCAPE
]
это любое *нестандартное* выражение алфавитно-цифрового типа.
может быть, в соответствии со стандартом, только определенным столбцом . Образец состоит из строки, которая будет проверена на совпадение с . Символ окончания это одиночный алфавитно-цифровой символ. Совпадение произойдет, если верны следующие условия:
Для каждого символа подчёркивания в образце , который не предшествует символу окончания , имеется один соответствующий ему символ