Как работает подзапрос?
Значения, которые подзапрос может выводить
DISTINCT с подзапросами
Предикаты с подзапросами
являются необратимыми
Использование агрегатных функций в подзапросах
Использование подзапросов,
которые выдают много строк с помощью оператора IN
Подзапросы в предложении HAVING
Резюме
Работа со SQL
Специальные операторы ANY или SOME
Использование IN или EXISTS вместо ANY
Как ANY может стать неоднозначным?
Специальный оператор ALL
Равенства и неравенства
Правильное
понимание ANY и ALL
Как ANY, ALL и EXISTS поступают с отсутствующими и
неизвестными данными?
Когда подзапрос
возвращается пустым?
ANY и ALL вместо EXISTS с NULL
Использование COUNT вместо EXISTS
Резюме
Работа со SQL
Команды модификации языка DML
Ввод значений
Вставка NULL
Именование
столбца для вставки
Вставка результатов запроса
Удаление строк из таблиц
Изменение значений поля
Модифицирование только определенных строк
Команда UPDATE для нескольких столбцов
Использование выражений
для модификации
МодифицированиеNULL-значений
Резюме
Работа со SQL
Внешний ключ и родительский ключ
Многостолбцовые внешние ключи
Смысл внешнего и родительского ключей
Ограничение внешнего ключа
Как можно представить поля в качестве внешних ключей
Внешний ключ как ограничение таблицы
Внешний ключ как ограничение столбцов
Не указывать список столбцов первичных ключей
Как справочная целостность ограничивает значение
родительского ключа?
Первичный ключ как
уникальный внешний ключ
Ограничения внешнего ключа
Что случится,
если вы выполните команду модификации?
Описание ограничений таблицы
Действия ограничений
Внешние ключи, которые ссылаются обратно на свои
подчинённые таблицы
Резюме
Работа со SQL
Что такое представление?
Команда CREATE VIEW
Модифицирование представлений
Именование столбцов
Комбинирование предикатов представлений и основных запросов
в представлениях
Групповые представления
Представления и объединения
Представления и подзапросы
Что не могут делать
представления?
Удаление представлений
Резюме
Работа со SQL
Модифицирование представления
Определение модифицируемости представления
Модифицируемые представления и представления
ТОЛЬКО_ДЛЯ_ЧТЕНИЯ
Какое представление является
модифицируемым?
Проверка значений, помещаемых в представление
Предикаты и исключённые поля
Проверка представлений, которые базируются на других представлениях
Резюме
Работа со SQL
Пользователи
Регистрация
Предоставление привилегий
Стандартные привилегии
Команда GRANT
Использование аргументов ALL и PUBLIC
Передача прав с помощью WITH GRANT OPTION
Отмена привилегий
Использование представлений для фильтрации привилегии
Другие виды привилегий
Типичные привилегии системы
Создание и удаление пользователей
Резюме
Работа со SQL
Переименование таблиц
Переименование с тем же
самым именем
Одно имя для каждого
Удаление синонимов
Как база данных распределена между пользователями?
Когда сделанные изменения становятся постоянными?
Как SQL общается сразу с несколькими пользователями?
Типы блокировок
Другие способы блокировки данных
Резюме
Работа со SQL
Каталог системы
Типичный каталог системы
Использование представлений в таблицах каталога
Комментарий
в содержании каталога
Остальное содержимое каталога
SYSTENINDEXES - индексация в базе данных
SYSTEMUSERAUTH - пользовательские исистемные
привилегии в базе данных
SYSTEMTABAUTH - привилегии объекта, которые не
определяют авторизацию столбца
SYSTEMCOLAUTH - привилегии объекта, которые определяют
авторизацию столбца
SYSTEMSYNONS - синонимы для таблиц базе данных
Другое использование каталога
Резюме
Работа со SQL
Что такое - вложение SQL?
Зачем вкладывать SQL?
Как делается вложение SQL?
Использование переменных основного языка со SQL
Объявление переменных
Извлечение значений в переменных
Курсор
SQLCODE
Использование SQLCODE для управления циклами
Предложение WHENEVER
Модифицирование курсоров
Переменная INDICATOR
Использование переменной INDICATOR для эмуляции NULL-значений
SQL
Другое использование переменной INDICATOR
Резюме
Работа со SQL
1. cnum
2. rating
3. Другим названием строки является "запись". Другим названием столбца является "поле".
4. Потому что строки, по определению, находятся без какого либо определённого упорядочивания.
1. Символ (или текст) и число
2. Нет
3. Язык Манипулирования Данными (ЯЗЫК DML)
4. Это слово в SQL имеет специальное учебное значение
1. SELECT onum, amt, odate FROM Orders;
2. SELECT * FROM Customers WHERE snum = 1001;
3 SELECT city, sname, snum, comm FROM Salespeople;
4. SELECT rating, cname FROM Customers WHERE city = 'SanJose';
5. SELECT DISTINCT snum FROM Orders;
1. SELECT * FROM Orders WHERE amt > 1000;
2. SELECT sname, city FROM Salespeople WHERE city = 'London' AND comm > .10;
3. SELECT * FROM Customers WHERE rating > 100 OR city = 'Rome'; или SELECT * FROM Customers WHERE NOT rating < = 100 OR city = 'Rome'; или SELECT * FROM Customers WHERE NOT (rating < = 100 AND city < > 'Rome'); Могут быть и другие решения.
4. onum amt odate cnum snum 3001 18.69 10/03/1990 2008 1007 3003 767.19 10/03/1990 2001 1001 3005 5160.45 10/03/1990 2003 1002 3009 1713.23 10/04/1990 2002 1003 3007 75.75 10/04/1990 2004 1002 3008 4723.00 10/05/1990 2006 1001 3010 1309.95 10/06/1990 2004 1002 3011 9891.88 10/06/1990 2006 1001
5. onum amt odate cnum snum 3001 18.69 10/03/1990 2008 1007 3003 767.19 10/03/1990 2001 1001 onum amt odate cnum snum 3006 1098.16 10/03/1990 2008 1007 3009 1713.23 10/04/1990 2002 1003 3007 75.75 10/04/1990 2004 1002 3008 4723.00 10/05/1990 2006 1001 3010 1309.95 10/06/1990 2004 1002 3011 9891.88 10/06/1990 2006 1001
6. SELECT * FROM Salespeople;
1. SELECT * FROM Orders WHERE odate IN (10/03/1990,10/04/1990); и SELECT * FROM Orders WHERE odate BETWEEN 10/03/1990 AND 10/04,1990;
2. SELECT * FROM Customers WHERE snum IN (1001,1004);
3. SELECT * FROM Customers WHERE cname BETWEEN 'A' AND 'H';
4. SELECT * FROM Customers WHERE cname LIKE 'C%';
5. SELECT * FROM Orders WHERE amt < > O AND (amt IS NOT NULL); или SELECT * FROM Orders WHERE NOT (amt = O OR amt IS NULL);
1. SELECT COUNT(*) FROM Orders WHERE odate = 10/03/1990;
2. SELECT COUNT (DISTINCT city) FROM Customers;
3. SELECT cnum, MIN (amt) FROM Orders GROUP BY cnum;
4 SELECT MIN (cname) FROM Customers WHERE cname LIKE 'G%';
5. SELECT city, MAX (rating) FROM Customers GROUP BY city;
6 SELECT odate, count (DISTINCT snum FROM Orders GROUP BY odate;
1. SELECT onum, snum, amt * .12 FROM Orders;
2. SELECT 'For the city ', city, ', the highest rating is ', ", MAX (rating) FROM Customers GROUP BY city;
3 SELECT rating, cname, cnum FROM Customers ORDER BY rating DESC;
4. SELECT odate, SUM (amt) FROM Orders GROUP BY odate ORDER BY 2 DESC;
1. SELECT onum, cname FROM Orders, Customers WHERE Customers.cnum = Orders.cnum;
2. SELECT onum, cname, sname FROM Orders, Customers, Salespeople WHERE Customers.cnum = Orders.cnum AND Salespeople.snum = Orders.snum;
3. SELECT cname, sname, comm FROM Salespeople, Customers WHERE Salespeople.snum = Customers.snum AND comm * .12;
4. SELECT onum, comm * amt FROM Salespeople, Orders, Customers WHERE rating > 100 AND Orders.cnum = Customers.cnum AND Orders.snum = Salespeople.snum;
1. SELECT first.sname, second.sname FROM Salespeople first, Salespeople second WHERE first.city = second.city AND first.sname < second.sname;
Псевдонимам не обязаны иметь именно такие имена.
2. SELECT cname, first.onum, second.onum FROM Orders first, Orders second, Customers WHERE first.cnum = second.cnum AND first.cnum = Customers.cnum AND first.onum < second.onum;
Ваш вывод может иметь некоторые отличия, но в вашем ответе все логические компоненты должны быть такими же.
3. SELECT a.cname, a.city FROM Customers a, Customers b WHERE a.rating = b.rating AND b.cnum = 2001;
1. SELECT * FROM Orders WHERE cnum = (SELECT cnum FROM Customers WHERE cname = 'Cisneros'); или SELECT * FROM Orders WHERE cnum IN (SELECT cnum FROM Customers WHERE cname = 'Cisneros');
2. SELECT DISTINCT cname, rating FROM Customers, Orders WHERE amt > (SELECT AVG (amt) FROM Orders) AND Orders.cnum = Customers.cnum;
3 SELECT snum, SUM (amt) FROM Orders GROUP BY snum HAVING SUM (amt) > (SELECT MAX (amt) FROM Orders);
1. SELECT cnum, cname FROM Customers outer WHERE rating = (SELECT MAX (rating) FROM Customers inner WHERE inner.city = outer.city);
2. Решение с помощью соотнесенного подзапроса:
SELECT snum, sname FROM Salespeople main WHERE city IN (SELECT city FROM Customers inner WHERE inner.snum < > main.snum); Решение с помощью объединения: SELECT DISTINCT first.snum, sname FROM Salespeople first, Customers second WHERE first.city = second.city AND first.snum < > second.snum;
1. SELECT * FROM Salespeople first WHERE EXISTS (SELECT * FROM Customers second WHERE first.snum = second.snum AND rating = 300);
2. SELECT a.snum, sname, a.city, comm FROM Salespeople a, Customers b WHERE a.snum = b.snum AND b.rating = 300;
3. SELECT * FROM Salespeople a WHERE EXISTS (SELECT * FROM Customers b WHERE b.city = a.city AND a.snum < > b.snum);
4. SELECT * FROM Customers a WHERE EXISTS (SELECT * FROM Orders b WHERE a.snum = b.snum AND a.cnum < > b.cnum)
1. SELECT * FROM Customers WHERE rating > = ANY (SELECT rating FROM Customers WHERE snum = 1002);
2. cnum cname city rating snum 2002 Giovanni Rome 200 1003 2003 Liu San Jose 200 1002 2004 Grass Berlin 300 1002 2008 Cisneros SanJose 300 1007
3. SELECT * FROM Salespeople WHERE city < > ALL (SELECT city FROM Customers); или SELECT * FROM Salespeople WHERE NOT city = ANY (SELECT city FROM Customers);
4. SELECT * FROM Orders WHERE amt > ALL (SELECT amt FROM Orders a, Customers b WHERE a.cnum = b.cnum AND b.city = 'London');
5. SELECT * FROM Orders WHERE amt > (SELECT MAX (amt) FROM Orders a, Customers b WHERE a.cnum = b.cnum AND b.city = 'London');
1. SELECT cname, city, rating, 'High Rating' FROM Customers WHERE rating > = 200 UNION SELECT cname, city, rating, ' Low Ratlng' FROM Customers WHERE rating < 200; или SELECT cname, city, rating, 'High Rating' FROM Customers WHERE rating > = 200 UNION SELECT cname, city, rating, ' Low Rating' FROM Customers WHERE NOT rating > = 200;
2. SELECT cnum, cname FROM Customers a WHERE 1 < (SELECT COUNT (-) FROM Orders b WHERE a.cnum = b.cnum) UNION SELECT snum, sname FROM Salespeople a WHERE 1 < (SELECT COUNT (*) FROM Orders b WHERE a.snum = b.snum) ORDER BY 2; 3. SELECT snum FROM Salespeople WHERE city = 'San Jose' UNION (SELECT cnum FROM Customers WHERE city = 'San Jose' UNION ALL SELECT onum FROM Orders WHERE odate = 10/03/1990);
1. INSERT INTO Salespeople (city, cname, comm, cnum) VALUES ('San Jose', 'Blanco', NULL, 1100);
2. DELETE FROM Orders WHERE cnum = 2006;
3. UPDATE Customers SET rating = rating + 100 WHERE city = 'Rome';
4. UPDATE Customers SET snum = 1004 WHERE snum = 1002;
1. INSERT INTO Multicust SELECT * FROM Salespeople WHERE 1 < (SELECT COUNT (*) FROM Customers WHERE Customers.snum = Salespeople.snum);
2. DELETE FROM Customers WHERE NOT EXISTS (SELECT * FROM Orders WHERE cnum = Customers.cnum);
3. UPDATE Salespeople SET comm = comm + (comm * .2) WHERE 3000 < (SELECT SUM (amt) FROM Orders WHERE snum = Salespeople.snum);
UPDATE Salespeople SET comm = comm + (comm * .2) WHERE 3000 < (SELECT SUM (amt) FROM Orders WHERE snum = Salespeople.snum) AND comm + (comm * .2) < 1.0;
1. CREATE TABLE Customers (cnum integer, cname char(10), city char(10), rating integer, snum integer);
2. CREATE INDEX Datesearch ON Orders(odate); (Все индексные имена, используемые в этих ответах - произвольные.)
3. CREATE UNIQUE INDEX Onumkey ON Orders(onum);
4. CREATE INDEX Mydate ON Orders(snum, odate);
5. CREATE UNIQUE INDEX Combination ON Customers(snum, rating);
1. CREATE TABLE Orders (onum integer NOT NULL PRIMARY KEY, amt decimal, odate date NOT NULL, cnum integer NOT NULL, snum integer NOT NULL, UNIOUE (snum, cnum)); или CREATE TABLE Orders (onum integer NOT NULL UNIQUE, amt decimal, odate date NOT NULL, cnum integer NOT NULL, snum integer NOT NULL, UNIQUE (snum, cnum)); Первое решение предпочтительнее.
2. CREATE TABLE Salespeople (snum integer NOT NULL PRIMARY KEY, sname char(15) CHECK (sname BETWEEN 'AA' AND 'MZ'), city char(15), comm decimal NOT NULL DEFAULT = .10);
3. CREATE TABLE Orders (onum integer NOT NULL, amt decimal, odate date, cnum integer NOT NULL, snum integer NOT NULL, CHECK ((cnum > snum) AND (onum > cnum)));
1. CREATE TABLE Cityorders (onum integer NOT NULL PRIMARY KEY, amt decimal, cnum integer, snum integer, city char (15), FOREIGN KEY (onum, amt, snum) REFERENCES Orders (onum, amt, snum), FOREIGN KEY (cnum, city) REFERENCES Customers (cnum, city));
2. CREATE TABLE Orders (onum integer NOT NULL, amt decimal, odate date, cnum integer NOT NULL, snum integer, prev integer, UNIQUE (cnum, onum), FOREIGN KEY (cnum, prev) REFERENCES Orders (cnum,onum));9
1. CREATE VIEW Highratings AS SELECT * FROM Customers WHERE rating = (SELECT MAX (rating) FROM Customers);
2. CREATE VIEW Citynumber AS SELECT city, COUNT (DISTINCT snum) FROM Salespeople GROUP BY city;
3. CREATE VIEW Nameorders AS SELECT sname, AVG (amt), SUM (amt) FROM Salespeople, Orders WHERE Salespeople.snum = Orders.snum GROUP BY sname;
4 CREATE VIEW Multcustomers AS SELECT * FROM Salespeople a WHERE 1 < (SELECT COUNT (*) FROM Customers b WHERE a.snum = b.snum);
1. #1 - не модифицируемый, потому что он использует DISTINCT. #2 - не модифицируемый, потому что он использует объединение, агрегатную функцию и GROUP BY. #3 - не модифицируемый, потому что он основывается на #1, который сам по себе немодифицируем.
2. CREATE VIEW Commissions AS SELECT snum, comm FROM Salespeople WHERE comm BETWEEN .10 AND .20 WITH CHECK OPTION;
3 CREATE TABLE Orders (onum integer NOT NULL PRIMARY KEY, amt decimal, odate date DEFAULT VALUE = CURDATE, snum integer, cnum integer); CREATE VIEW Entryorders AS SELECT onum, amt, snum, cnum FROM Orders;
1. GRANT UPDATE (rating) ON Customers TO Janet;
2. GRANT SELECT ON Orders TO Stephen WITH GRANT OPTION;
3. REVOKE INSERT ON Salespeople FROM Claire;
4. Шаг 1: CREATE VIEW Jerrysview AS SELECT * FROM Customers WHERE rating BETWEEN 100 AND 500 WITH CHECK OPTION;
Шаг 2: GRANT INSERT, UPDATE ON Jerrysview TO Jerry;
5. Шаг 1: CREATE VIEW Janetsview AS SELECT * FROM Customers WHERE rating = (SELECT MIN (rating) FROM Customers);
Шаг 2: GRANT SELECT ON Janetsview TO Janet;
1. CREATE DBSPACE Myspace (pctindex 15, pctfree 40);
2. CREATE SYNONYM Orders FOR Diane.Orders;
3. Они должны быть откатаны назад.
4. Блокировка взаимоисключающего доступа.
5. Только чтение.
1. SELECT a.tname, a.owner, b.cname, b.datatype FROM SYSTEMCATOLOG a, SYSTEMCOLUMNS b WHERE a.tname = b.tname AND a.owner = b.owner AND a.numcolumns > 4;
2. SELECT tname, synowner, COUNT (ALL synonym) FROM SYTEMSYNONS GROUP BY tname, synowner;
3 SELECT COUNT (*) FROM SYSTEMCATALOG a WHERE numcolumns/2 < (SELECT COUNT (DISTINCT cnumber) FROM SYSTEMINDEXES b WHERE a.owner = b.tabowner AND a.tname = b.tname);
1. EXEC SQL BEGIN DECLARE SECTION; SQLCODE:integer; {требуемый всегда} cnum integer; snum integer; custnum: integer; salesnum: integer; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE Wrong_Orders AS CURSOR FOR SELECT cnum, snum FROM Orders a WHERE snum < > (SELECT snum FROM Customers b WHERE a.cnum = b.cnum);
EXEC SQL DECLARE Cust_assigns AS CURSOR FOR SELECT cnum, snum FROM Customers;
begin { основная программа } EXEC SQL OPEN CURSOR Wrong_Orders; while SQLCODE = O do
begin EXEC SQL FETCH Wrong_Orders INTO (:cnum, :snum); if SQLCODE = O then begin
EXEC SQL OPEN CURSOR Cust_Assigns; repeat EXEC SQL FETCH Cust_Assigns INTO (:custnum, :salesnum); until :custnum = :cnum;
EXEC SQL CLOSE CURSOR Cust_assigns;
EXEC SQL UPDATE Orders SET snum = :salesnum WHERE CURRENT OF Wrong_Orders; end; {Если SQLCODE = 0}. end;
EXEC SQL CLOSE CURSOR Wrong_Orders; end; {основная программа}
2.
EXEC SQL BEGIN DECLARE SECTION; SQLCODE: integer; odernum integer; cnum integer; snum integer; custnum: integer; salesnum: integer; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE Wrong_Orders AS CURSOR FOR SELECT onum, cnum, snum FROM Orders a WHERE snum < > (SELECT snum FROM Customers b WHERE a.cnum = b.cnum); EXEC SQL DECLARE Cust _ assigns AS CURSOR FOR SELECT cnum, snum FROM Customers; begin { основная программа } EXEC SQL OPEN CURSOR Wrong_Orders; while SQLCODE = O do {Цикл до тех пор пока Wrong_Orders не опустеет} begin EXEC SQL FETCH Wrong_Orders INTO (:odernum, :cnum, :snum); if SQLCODE = O then begin EXEC SQL OPEN CURSOR Cust_Assigns; repeat EXEC SQL FETCH Cust_Assigns INTO (:custnum, :salesnum); until :custnum = :cnum; EXEC SQL CLOSE CURSOR Cust_assigns; EXEC SQL UPDATE Orders SET snum = :salesnum WHERE CURRENT OF Wrong_Orders; end; {If SQLCODE = 0} end; { While SQLCODE . . . do } EXEC SQL CLOSE CURSOR Wrong_Orders; end; { main program }
3. EXEC SQL BEGIN DECLARE SECTION; SQLCODE integer; newcity packed array[1. .12] of char; commnull boolean; citynull boolean; response char; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE CURSOR Salesperson AS SELECT * FROM SALESPEOPLE; begln { main program } EXEC SQL OPEN CURSOR Salesperson; EXEC SQL FETCH Salesperson INTO (:snum, :sname, :city:i_cit, :comm:i_com); {Выборка первой строки} while SQLCODE = O do {Пока эти строки в таблице Продавцов.} begin if i_com < O then commnull: = true; if i_cit < O then citynull: = true; {Установить логические флаги, которые могут показать NULLS.} if citynull then begin write ('Нет текущего значения city для продавца ', snum, ' Хотите предоставить хотя бы одно? (Y/N)'); {Подсказка покажет значение city, состоящее из NULL-значений.} read (ответ); {Ответ может быть сделан позже.} end {если конечно - citynull} else { не citynull } begin if not commnull then {Чтобы выполнять сравнение и операции только для не-NULL значений связи} begin if city = 'London' then comm: = comm * .02 * .02 else comm: = comm + .02; end; {Даже если значение и не commnull, begin и end здесь для ясности.} write ('Текущий city для продавца', snum, 'есть', city, Хотите его изменить? (Y/N)');
3. Обратите Внимание: Продавец, не назначенный в данное время в определенный город, не будет иметь изменений комиссионных при определении того, находится ли он в Лондоне. read (ответ); {Ответ теперь имеет значение, независимо от того, верен или неверен citynull.} end; {иначе не citynull} if response = 'Y' then begin write ('Введите новое значение city:'); read (newcity); if not commnull then {Эта операция может быть выполнена только для не-NULL значений.} case newcity of: begin 'Barcelona':comm:= comm + .01, 'San Jose': comm: = comm *.01 end; {случно и если не commnull} EXEC SQL UPDATE Salespeople SET city = :newcity, comm = :comm:i_com WHERE CURRENT OF Salesperson; {Переменная индикатора может поместить NULL-значение в поле comm, если так назначено.} end; {Если ответ = 'Y' или если ответ < > 'Y', изменений не будет.} EXEC SQL FETCH Salesperson INTO (:snum, :sname, :city:i_clt, :comm:l_com); {выборка следующей строки} end; {если SQLCODE = 0} EXEC SQL CLOSE CURSOR Salesperson; end; {основной программы}
Типы данных, распознаваемые с помощью ANSI, состоят из символов и различных типов чисел, которые могут классифицироваться как точные числа и приблизительные числа.
Точные числовые типы это числа с десятичной точкой или без десятичной точки. Приблизительные числовые типы это числа в показательной (экспоненциальной по основанию 10) записи.
Для всех прочих типов отличия слишком малы чтобы их как-то классифицировать.
Иногда типы данных используют аргумент, который называют размером аргумента, чей точный формат и значение меняется в зависимости от конкретного типа.
Значения по умолчанию обеспечены для всех типов, если размер аргумента отсутствует.
Ниже представлены типы данных ANSI (имена в круглых скобках - это синонимы):
TEXT | ТЕКСТ |
CHAR (или CHARACTER) | Строка текста в реализационно определенном формате. Размер аргумента здесь это единственное неотрицательное целое число, которое ссылается к максимальной длине строки. Значения этого типа должны быть заключены в одиночные кавычки, например, 'text'. Две рядом стоящие одинарные кавычки ('') внутри строки будут пониматься как одна одинарная кавычка ('). |
ПРИМЕЧАНИЕ:
Здесь и далее фраза Реализационно Определенный или Реализационно Зависимый
указывает, что этот аргумент или формат зависит от конкретной программы в
которой реализуются данные.
EXACT NUMERIC | ТОЧНО ЧИСЛО |
DEC (или DECIMAL) | Десятичное число, то есть число, которое может иметь десятичную точку. Здесь аргумент размера имеет две части: точность и масштаб. Масштаб не может превышать точность. Сначала указывается точность, затем - разделительная запятая и далее - аргумент масштаба. Точность указывает, сколько значащих цифр имеет число. Максимальное десятичное число, составляющее число - реализационно определённое значение, равное или больше, чем этот номер. Масштаб указывает максимальное число цифр справа от десятичной точки. Масштаб = нулю делает поле эквивалентом целого числа. |
NUMERIC | Такое же, как DECIMAL, за исключением того, что максимальное десятичное не может превышать аргумента точности. |
INT (или INTEGER) | Число без десятичной точки. Эквивалентно DECIMAL, но без цифр справа от десятичной точки, то есть с масштабом, равным 0. Аргумент размера не используется (он автоматически устанавливается в реализационно-зависимое значение). |
SMALLINT | Такое же, как INTEGER, за исключением того, что, в зависимости от реализации, размер по умолчанию может (или может не) быть меньшее чем INTEGER. |
APPROXIMATE NUMERIC | Приблизительное число |
FLOAT | Число с плавающей запятой на базе 10 показательной функции. Аргумент размера состоит из одного числа, определяющего минимальную точность. |
REAL | Такое же, как FLOAT, за исключением того, что никакой аргумент размера не используется. Точность установлена по умолчанию как реализационно зависимая. |
DOUBLE PRECISION (или DOUBLE) | Такое же, как REAL, за исключением того, что реализационно определяемая точность для DOUBLE PRECISION должна превышать реализационно определяемую точность REAL. |
Когда используется вложение SQL в другие языки, значения, используемые и произведённые командами SQL, обычно сохраняются в переменных главного языка (см. Главу 25). Эти переменные должны иметь тип данных, совместимый со значениями SQL, которые они будут получать.
В дополнениях, которые не являются частью официального SQL-стандарта, ANSI обеспечивает поддержку при использовании вложения SQL в четыре языка: Паскаль, PL/I, КОБОЛ, и ФОРТРАН. Между прочим, он включает определение эквивалентов SQL для данных типов переменных, используемых в этих языках.
Эквиваленты типов данных четырёх языков, определенные ANSI:
SQL ТИП | ЭКВИВАЛЕНТ PL/I |
---|---|
CHAR | CHAR |
DECIMAL | FIXED DECIMAL |
INTEGER | FIXED BINARY |
FLOAT | FLOAT BINARY |
SQL ТИП | ЭКВИВАЛЕНТ КОБОЛА |
---|---|
CHAR ( |
PIC X ( |
INTEGER | PIC S ( |
NUMERIC | PIC S (< nines with embedded V >) DISPLAY SING LEADING SEPERATE |
SQL ТИП | ЭКВИВАЛЕНТ ПАСКАЛЯ |
---|---|
INTEGER | INTEGER |
REAL | REAL |
CHAR ( |
PACKED ARRAY [1.. |
SQL ТИП | ЭКВИВАЛЕНТ ФОРТРАНА |
---|---|
CHAR | CHAR |
INTEGER | INTEGER |
REAL | REAL |
DOUBLE PRECISION | DOUBLE PRECISION |
Имеется ряд особенностей языка SQL, которые пока не определены как часть стандарта ANSI или стандарта ISO (Международная Организация По Стандартизации), и являются общими для многочисленных реализаций, так как они были получены для практического использования. Конечно, эти особенности меняются от программы к программе, и их обсуждение предназначено только для того, чтобы показать некоторые общие подходы к ним.
Типы данных, поддерживаемые стандартом SQL, собраны в Приложении B. Это CHARACTER и разнообразные числовые типы. Реализация их может оказаться значительно сложнее, чем показано в терминах типов, которые они фактически могут использовать. Мы будем здесь обсуждать ряд таких нестандартных типов данных.
Как упомянуто в Главе 2, тип DATE широко поддерживается, хотя он не является частью стандарта. Мы использовали ранее в нашей таблице Заказов этот тип в формате mm/dd/yyyy. Это стандартный формат IBM в США. Разумеется, возможны и другие форматы, и программные реализации часто поддерживают несколько форматов, позволяя вам выбирать наиболее подходящий.
Реализация, которая предлагает эту особенность, должна быть способна преобразовывать дату одного формата в другой автоматически.
Имеются несколько основных форматов даты с которыми вы можете столкнуться:
Стандарт | Формат | Пример |
---|---|---|
Международная Организация По Стандартизации (ISO) | yyyy-mm-dd | 1990-10-31 |
Японский Индустриальный Стандарт (JIS) | yyyy-mm-dd | 1990-10-31 |
IBM Европейский Стандарт (EUR) | dd.mm.yyyy | 10.31.1990 |
Наличие специального типа даты даёт возможность выполнять арифметические операции с датами. Например, вы можете добавлять число дней к дате и получать другую дату в программе, самостоятельно следящей за числом дней в месяцах, високосными годами и т.д.
Даты могут также сравниваться; например фраза, дата A < дата B , означает, что дата A предшествует дате B по времени. Помимо даты, большое количество программ определяют специальный тип для времени, который может также быть представлен в ряде форматов, включая следующие:
Стандарт | Формат | Пример |
---|---|---|
МЕЖДУНАРОДНАЯ ОРГАНИЗАЦИЯ ПО СТАНДАРТИЗАЦИИ (ISO) | hh-mm-ss | 21.04.37 |
Японский Индустриальный Стандарт (JIS ) | hh-mm-ss | 21.04.37 |
IBM Европейский Стандарт | hh-mm-ss | 21.04.37 |
IBM USA Стандарт (USA) | hh.mm AM/PM | 9.04 PM |
Время может добавляться или сравниваться точно так же, как дата, с автоматической коррекцией числа секунд в минутах или часах. Кроме того, специально встроенные константы, указывающие текущую дату или время (CURDATE или CURTIME), являются общими. Они похожи на константу USER (Пользователь) в которой их значение будет непрерывно меняться.
Можете ли вы включать время и дату в одно поле? Некоторые реализации определяют тип DATE достаточно точно, чтобы включать туда ещё и TIME. В качестве альтернативы третий обобщающий тип, TIMESTAMP, может быть определен как комбинация этих двух.
ANSI поддерживает только один тип для представления текста. Это тип CHAR. Любое поле такого типа должно иметь определённую длину. Если строка, вставленная в поле, меньше длины поля, она дополняется пробелами; строка не может быть длиннее поля.
Хотя и достаточно удобное, это определение всё же имеет некоторые ограничения
для пользователя. Например, символьные поля должны иметь одинаковую длину, чтобы
можно было выполнить команду UNION. Большинство реализаций поддерживают строки
переменной длины для типов данных VARCHAR и LONG VARCHAR (или просто LONG). В то
время как поле типа CHAR всегда может распределить память для максимального числа
символов, которое может сохраняться в поле, поле VARCHAR при любом количестве
символов может распределить только определённое количество памяти, чтобы
сохранить фактическое содержание поля, хотя SQL может установить извне некоторое
дополнительное пространство памяти, чтобы следить за текущей длиной поля.
Поле VARCHAR может быть любой длины, включая реализационно определяемый максимум.
Этот максимум может меняться от 254 до 2048 символов для VARCHAR и до 16000
символов для LONG. LONG обычно используется для текста пояснительного характера
или для данных, которые не могут легко сжиматься в простые значения полей;
VARCHAR может использоваться для любой текстовой строки, чья длина может
меняться. Между прочим, не всегда хорошо использовать VARCHAR вместо CHAR.
Извлечение и модифицирование полей VARCHAR - более сложный и, следовательно,
более медленный процесс, чем извлечение и модифицирование полей CHAR. Кроме
того, некоторое количество памяти VARCHAR остается всегда неиспользованной (в
резерве) для гарантии вмещения всей строки. Вы должны просчитывать, насколько
значения полей могут меняться по длине, а также - способны ли они к объединению
с другими полями, перед тем как решить, использовать CHAR или VARCHAR. Часто тип
LONG используется для сохранения двоичных данных. Естественно, что использование
размера такого "неуклюжего" поля будет ограничивать оперативность SQL.
Проконсультируйтесь с вашим руководством.
Как мы подчеркивали в Главе 7, процесс вывода выполняемого в стандарте SQL, имеет ограничения. Хотя большинство реализаций включают SQL в пакеты, имеющие другие средства для управления этой функцией, некоторые реализации также используют команду типа FORMAT внутри SQL чтобы навязывать выводу запроса определённые формы структуры или ограничения.
Среди возможных функций команды FORMAT существуют такие:
Команда 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.
Для SQL в стандарте ANSI вы можете применять агрегатные функции для столбцов или использовать их значения в скалярных выражениях, таких, например, как comm * 100. Имеется много других полезных функций, которые вы, вероятно, встречали на практике.
Имеется список некоторых общих функций SQL, отличающихся от стандартных агрегатов. Они могут использоваться в предложениях SELECT-запросов точно так же, как агрегатные функции, но эти функции выполняются для одиночных значений, а не для групповых. В следующем списке они классифицированы согласно типам данных, с которыми они работают. Если нет примечаний, то переменные в этом списке стандартизированы для любого выражения значений соответствующего типа, которые могут быть использованы в предложении 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. |
Эти функции могут быть применены для строк текста из столбцов текстовых типов данных, либо из строк литерных текстов, или же из комбинации этих двух.
ФУНКЦИЯ | ЗНАЧЕНИЕ |
---|---|
LEFT(<string>,X) | Возвращает крайние левые (старшие) символы X из строки. |
RICHT(<string>,X) | Возвращает символы X младшего разряда из строки |
ASCII(<string>) | Возвращает код ASCII, которым строка представляется в памяти компьютера. |
CHR(<asciicode>) | Возвращает принтерные символы кода ASCII. |
VALUE(<string>) | Возвращает математическое значение для строки. Считается, что строка имеет тип CHAR или VARCHAR, но состоит из чисел. VALUE('3') произведёт число 3 типа INTEGER. |
UPPER(<string>) | Преобразует все символы строки в символы верхнего регистра. |
LOWER(<string>) | Преобразует все символы строки в символы нижнего регистра. |
INlTCAP(<string>) | Преобразует начальные символы строки в заглавные буквы. В некоторых реализациях может иметь название PROPER. |
LENGTH(<string>) | Возвращает число символов в строке. |
<string>|| | Объединяет две строки в выводе, так чтобы после первой немедленно следовала вторая. (значок || называется оператором сцепления). |
LPAD(<string>,X,'*' ) | Дополняет строку слева звездочками '*' или любым другим указанным символом в количестве, определяемом X. |
RPAD(<string>,X, ") | То же самое что и LPAD, за исключением того, что дополнение делается справа. |
SUBSTR(<string>,X,Y) | Извлекает Y символов из строки, начиная с позиции X. |
Эти функции выполняются только для допустимых значений даты или времени.
ФУНКЦИЯ | ЗНАЧЕНИЕ |
---|---|
DAY( |
Извлекает день месяца из даты. Подобные же функции существуют для MONTH (МЕСЯЦ), YEAR (ГОД), HOUR (ЧАС), SECOND (СЕКУНДА) и так далее. |
WEEKDAY( |
Извлекает день недели из даты. |
Эта функция может быть применена к любому типу данных.
ФУНКЦИЯ | ЗНАЧЕНИЕ |
---|---|
NVL(<column>,<value>) | NVL (NULL-значение) будет менять на <value> каждое NULL значение, найденное в столбце <column>. Если полученное значение <column> не = NULL, NVL ничего не делает. |
Команда 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 (ОТЛИЧИЕ).
В Главе 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-значениями).
Ваша SQL-реализация достаточна хороша, если она доступна многим пользователям, чтобы обеспечивать им некий способ слежения за действиями, выполняемыми в базе данных.
Имеются две основные формы, чтобы реализовать это: Journaling (Протоколирование) и Auditing (Ревизия).
Эти формы отличаются по назначению.
Journaling применяется с целью защиты ваших данных при разрушении вашей
системы. Сначала вы используете реализационно зависимую процедуру, чтобы архивировать текущее содержание вашей базы данных,
поэтому копия её содержания где-нибудь будет сохранена. Затем вы просматриваете
протокол изменений, сделанных в базе данных. Он сохраняется в некоторой области
памяти, но не в главной памяти базы данных, а желательно на отдельном
устройстве, и содержит список всех команд, которые произвели изменения в
структуре или в содержании базы данных.
Если у вас вдруг появились проблемы и
текущее содержание вашей базы данных оказалось нарушенным, вы можете повторно
выполнить все изменения, зарегистрированные в протоколе на резервной копии вашей
базы данных, и снова привести вашу базу данных в состояние, которое было до
момента последней записи в протокол. Типичной командой для начала
протоколирования будет следующая:
SET JOURNAL ON;
Auditing используется с целью защиты. Она следит за тем, кто и какие действия выполнял в базе данных, и сохраняет эту информацию в таблице, доступной только очень немногим привилегированным пользователям. Конечно, вы редко будете прибегать к процедуре ревизии, потому что очень скоро она займет много памяти и вам будет сложно работать в вашей БД. Но вы можете устанавливать ревизию для определённых пользователей, определённых действий или определённых объектов данных. Имеется такая форма команды AUDIT:
AUDIT INSERT ON Salespeople BY Diane;
Или предложение ON, или предложение BY могут быть исключены, устанавливая ревизию либо всех объектов, либо всех пользователей, соответственно. Применение AUDIT ALL вместо AUDIT INSERT приведет к отслеживанию всех действий Diane в таблице Продавцов.
SQL (произносится обычно "СЭКВЭЛ" (или, более англообразно - СКЬЮЭЛ)) означает Структурированный Язык Запросов.
Это язык, который дает возможность создавать реляционные базы данных (и работать с ними), которые представляют собой наборы связанной информации, сохраняемой в таблицах.
Мир БД становится всё более единым, что привело к необходимости создания стандартного языка, который мог бы использоваться для функционирования в большом количестве различных видов компьютерных сред. Стандартный язык даст возможность пользователям, знающим один набор команд, использовать их, чтобы создавать, отыскивать, изменять и передавать информацию, независимо от того, где идёт работа: на персональном компьютере, сетевой рабочей станции или на универсальной ЭВМ.
В нашем, всё более и более взаимосвязанном компьютерном мире, пользователь, снабжённый таким языком, имеет огромное преимущество в использовании и обобщении информации из ряда источников с помощью большого количества способов.
Элегантность и независимость от специфики компьютерных технологий, а также его поддержка лидерами промышленности в области технологии РБД, сделали SQL (и, вероятно, в течение обозримого будущего, оставят его) основным стандартным языком БД. По этой причине любой, кто хочет работать с базами данных 90-х годов (прошлого века), должен знать SQL.
Стандарт SQL определяется ANSI (Американским Национальным
Институтом Стандартов) и в данное время также принимается ISO
(Международной организацией по стандартизации). Однако большинство
коммерческих программ БД расширяют SQL без уведомления ANSI,
добавляя разные особенности в этот язык, которые, как они считают, будут весьма полезны.
Иногда это несколько нарушает стандарт языка, хотя хорошие идеи имеют тенденцию
развиваться и становиться стандартами рынка в силу полезности своих качеств.
В этой книге мы будем в основном следовать стандарту ANSI, но одновременно иногда будем давать и некоторые наиболее распространённые отклонения от его стандарта.
Вы должны проконсультироваться в документации вашего пакета программ, который будете использовать, чтобы знать, где в нём этот стандарт видоизменен.
ПРЕЖДЕ ЧЕМ ВЫ СМОЖЕТЕ ИСПОЛЬЗОВАТЬ SQL, вы должны понять, что такое реляционные базы данных.
В этой главе мы объясним и покажем, насколько РБД полезны. Мы не будем
обсуждать SQL именно здесь, и, если вы уже знаете эти понятия достаточно хорошо, вы можете просто пропустить эту главу.
В любом случае вы должны просмотреть три таблицы, которые предоставляются и
объясняются в конце главы; они станут основой наших примеров в этой книге.
Вторая копия этих таблиц находится в Приложении E, и мы рекомендуем скопировать их для удобства ссылки к ним.
Реляционная база данных это тело связанной информации, сохраняемой в двухмерных таблицах. Это напоминает адресную или телефонную книгу.
В любой книге имеется большое количество разделов, каждый из которых соответствует
определённой особенности. Для каждой такой особенности может быть несколько
независимых фрагментов данных, например: имя, телефонный номер и адрес.
Предположим, что вы должны сформатировать эту адресную книгу в виде таблицы со
строками и столбцами. Каждая строка (называемая также записью) будет
соответствовать определённой личности; каждый столбец будет содержать
значение для каждого типа данных: имени, телефонного номера и адреса, представляемых в каждой строке.
Адресная книга могла бы выглядеть следующим образом:
Имя | Телефон | Адрес |
Gerry Farish | (415)365-8775 | 127 Primrose Ave.,SF |
---|---|---|
Celia Brock | (707)874-3553 | 246 #3rd St.,Sonoma |
Yves Grillet | (762)976-3665 | 778 Modernas,Barcelona |
То, что вы получили, является основой реляционной базы данных, как и было определено в начале этого обсуждения, а именно - двухмерной (строка и столбец) таблицей информации.
Однако РБД редко состоят из одной таблицы. Такая таблица меньше, чем файловая система. Создав несколько таблиц взаимосвязанной информации, вы сможете выполнять более сложные и мощные операции с вашими данными. Мощность БД зависит от связи, которую вы можете создать между фрагментами информации, а не от самого фрагмента информации.
Давайте используем пример нашей адресной книги, чтобы начать обсуждение БД, которая может реально использоваться в деловой ситуации. Предположим, что персонажи в нашей первой таблице (адресной книге) это пациенты больницы. В другой таблице мы могли бы запомнить дополнительную информацию об этих пациентах. Столбцы второй таблицы можно назвать Пациент, Доктор, Страховка и Баланс.
Пациент Доктор Страховка Баланс Farish Drume B.C./B.S. $272.99 Grillet Halben None $44. 76 Brock Halben Health,Inc. $9077.47
Много мощных функций можно использовать для извлечения информации из этих таблиц, согласно указанным параметрам, особенно когда эти параметры включают в себя фрагменты информации, связанные в различных таблицах друг с другом. Например, возьмём докторов. Предположим доктор Halben захотел получить номера телефонов всех своих пациентов. Чтобы извлечь эту информацию, он мог бы связать таблицу с номерами телефонов пациентов (по адресной книге) с таблицей, которая указывала бы, какой из пациентов - его. Хотя в этом простом примере он мог бы держать это в голове и сразу получать номера телефонов пациентов Grillet и Brock, эти таблицы могут быть достаточно большими и сложными.
Программы РБД разрабатывались для того, чтобы обрабатывать большие и сложные совокупности данных такого типа, что, очевидно, является более универсальным методом в деловом мире. Даже если бы БД больницы содержала сотни или тысячи имен, как это вероятно и бывает на практике, одна команда SQL могла бы выдать доктору Halben информацию, в которой он нуждается, почти немедленно.
Чтобы поддерживать максимальную гибкость, строки таблицы, по определению, не должны находиться в каком-то определенном порядке. С этой точки зрения структура БД отличается от нашей адресной книги.
Вход в адресную книгу обычно упорядочивается в алфавитном порядке. В системах с РБД имеется одна мощная возможность для пользователей: способность упорядочивать информацию так, чтобы они могли восстанавливать её.
Рассмотрим вторую таблицу. Иногда вам необходимо видеть эту информацию упорядоченной в алфавитном порядке по именам, иногда - в возрастающем или убывающем порядке, а иногда - сгруппированной по отношению к какому-нибудь доктору.
Порядок набора в строках будет конфликтовать со способностью заказчика изменять его, поэтому строки всегда рассматриваются как неупорядоченные. По этой причине вы не можете просто сказать: "Мы хотим посмотреть пятую строку таблицы". Пренебрегая порядком, в котором данные вводились, или любым другим критерием, мы определим не ту строку, хотя она и будет пятой. Строки таблицы не находятся в какой-либо определенной последовательности.
По этим и другим причинам вы должны иметь в вашей таблице столбец, который идентифицировал бы каждую строку уникально. Обычно этот столбец содержит номер, например: номер пациента, назначаемый каждому пациенту. Конечно, вы могли бы использовать имена пациентов, но возможно, что имеется несколько Mary Smith, и в этом случае вы не будете иметь другого способа отличить этих пациентов друг от друга.
Вот почему номера так необходимы. Такой уникальный столбец (или уникальная группа столбцов), используемый для идентификации каждой строки и хранения всех строк по отдельности, называется первичным ключом таблицы.
Первичные ключи таблицы - важный элемент в структуре базы данных. Они - основа вашей системы записи в файл; и, когда вы хотите найти определённую строку в таблице, вы ссылаетесь на этот первичный ключ. Кроме того, первичные ключи гарантируют, что ваши данные имеют определенную целостность. Если первичный ключ правильно используется и поддерживается, вы будете знать, что нет пустых строк таблицы и что каждая строка отличается от любой другой строки. Мы будем обсуждать ключи и далее, когда поговорим относительно справочной целостности в Главе 19.
В отличие от строк, столбцы таблицы (называемые также полями) упорядочиваются и именуются. Таким образом, в нашей таблице адресной книги можно указать на "адрес столбца" или на "столбец 3". Это означает, что каждый столбец данной таблицы должен иметь уникальное имя, чтобы избежать неоднозначности. Лучше всего, если эти имена указывают на содержание поля. В типовых таблицах этой книги мы будем использовать такие сокращения для имени столбца как cname для имени заказчика и odate для даты заказа. Мы также дадим каждой таблице личный числовой номер столбца в качестве первичного ключа. Следующий раздел будет рассматривать эти таблицы и их ключи более подробно.
Таблицы 1.1, 1.2 и 1.3 составляют реляционную базу данных, которая является минимально достаточной, чтобы легко её отслеживать, и достаточно полной, чтобы иллюстрировать главные понятия и практику использования SQL. Эти таблицы напечатаны в этой главе, а также в Приложении E.
Так как они будут использоваться для иллюстрирования различных особенностей SQL по всей этой книге, мы рекомендуем вам скопировать их для удобства использования.
Вы могли уже заметить, что первый столбец каждой таблицы содержит номера, значения которых различны для каждой строки. Как вы, наверное, уже предположили, это - первичные ключи таблиц. Некоторые из этих номеров также показаны в столбцах других таблиц. В этом нет ничего неверного. Они показывают связь между строками, которые используют значение, принимаемое из первичного ключа, и строками, где это значение используется в самом первичном ключе.
Таблица 1.1: Продавцы ---------------------------------------------- SNUM | SNAME | CITY | COMM --------|-----------|--------------|---------- 1001 | Peel | London | .12 1002 | Serres | San Jose | .13 1004 | Motika | London | .11 1007 | Rifkin | Barcelona | .15 1003 | Axelrod | New York | .10 --------------------------------------------- Таблица 1.2: Заказчики ---------------------------------------------- CNUM | CNAME | CITY | RATING | SNUM -------|------------|---------|--------|------ 2001 | Hoffman | London | 100 | 1001 2002 | Giovanni | Rome | 200 | 1003 2003 | Liu | SanJose | 200 | 1002 2004 | Grass | Berlin | 300 | 1002 2006 | Clemens | London | 100 | 1001 2008 | Cisneros | SanJose | 300 | 1007 2007 | Pereira | Rome | 100 | 1004 ---------------------------------------------- Таблица 1.3: Заказы ----------------------------------------------- ONUM | AMT | ODATE | CNUM | SNUM -------|-----------|-------------|------|------ 3001 | 18.69 | 10/03/1990 | 2008 | 1007 3003 | 767.19 | 10/03/1990 | 2001 | 1001 3002 | 1900.10 | 10/03/1990 | 2007 | 1004 3005 | 5160.45 | 10/03/1990 | 2003 | 1002 3006 | 1098.16 | 10/03/1990 | 2008 | 1007 3009 | 1713.23 | 10/04/1990 | 2002 | 1003 3007 | 75.75 | 10/04/1990 | 2004 | 1002 3008 | 4723.00 | 10/05/1990 | 2006 | 1001 3010 | 1309.95 | 10/06/1990 | 2004 | 1002 3011 | 9891.88 | 10/06/1990 | 2006 | 1001 -----------------------------------------------
Например, поле snum в таблице Заказчиков указывает, какому продавцу назначен данный заказчик. Номер поля snum связан с таблицей Продавцов, которая даёт информацию об этих продавцах. Очевидно, что продавец, которому назначены заказчики, должен уже существовать - то есть значение snum из таблицы Заказчиков должно также быть представлено в таблице Продавцов. Если это так, то говорят, что "система находится в состоянии справочной целостности". Этот вывод будет более полно и формально объяснен в Главе 19.
Таблицы приведены как пример похожей ситуации в реальной жизни, когда вы будете использовать SQL, чтобы следить за продавцами, их заказчиками и заказами заказчиков. Давайте рассмотрим эти три таблицы и значения их полей. Здесь показаны столбцы Таблицы 1.1:
ПОЛЕ СОДЕРЖАНИЕ --------- ---------------------------------------------- snum уникальный номер назначенный каждому продавцу ("номер служащего"). sname имя продавца. city местонахождение продавца (город). comm комиссионные продавцов в десятичной форме. Таблица 1.2 содержит следующие столбцы: ПОЛЕ СОДЕРЖАНИЕ -------- --------------------------------------------------- cnum уникальный номер, назначенный каждому заказчику. cname имя заказчика. city местонахождение заказчика (город). rating код, указывающий уровень предпочтения данного заказчика перед другими. Более высокий номер указывают на большее предпочтение (рейтинг). snum номер продавца, назначенного этому заказчику (из таблицы Продавцов). И столбцы в Таблице 1.3: ПОЛЕ СОДЕРЖАНИЕ --------- --------------------------------------------------- onum уникальный номер, данный каждому приобретению. amt значение суммы приобретений. odate дата приобретения. cnum номер заказчика, делающего приобретение (из таблицы Заказчиков). snum номер продавца, продающего приобретение (из таблицы Продавцов).
Теперь вы знаете, что такое реляционная база данных - понятие, которое звучит сложнее, чем это есть на самом деле. Вы также изучили некоторые фундаментальные принципы того, как сделаны таблицы: как работают строки и столбцы, как первичные ключи отличают строки друг друга и как столбцы могут ссылаться на значения в других столбцах.
Вы поняли, что запись это синоним строки и что поле это синоним столбца. Оба термина встречаются в обсуждении SQL, и мы будем использовать их в равной степени в этой книге.
Вы теперь знакомы с таблицами примеров. Краткие и простые, они способны показать большинство особенностей языка, как вы это увидите далее. В некоторых случаях мы будем использовать одни таблицы или постулаты различных данных в другой таблице, чтобы показать вам некоторые другие возможности.
Теперь вы готовы к погружению в SQL. Следующая глава даст краткое рассмотрение языка и информацию, которая поможет вам обращаться к уже пройденным местам.
1. Какое поле таблицы Заказчиков является первичным ключом? 2. Что является столбцом 4 из таблицы Заказчиков? 3. Как по другому называется строка? Столбец? 4. Почему вы не можете запрашивать для просмотра первые пять строк таблицы? (См. ответы в Приложении A.)
В конце Главы 9 мы говорили, что запросы могут управлять другими запросами. В этой главе вы узнаете, как это делается (большей частью) путём помещения запроса внутрь предиката другого запроса и использования вывода внутреннего запроса в верном или неверном условии предиката.
Вы сможете выяснить, какие виды операторов могут использовать подзапросы, и посмотреть, как подзапросы работают со средствами SQL, такими как DISTINCT, с составными функциями и выводимыми выражениями.
Вы узнаете, как использовать подзапросы с предложением HAVING, и получите некоторые наставления, как правильно использовать подзапросы.
С помощью 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 изменился, а
с помощью простого изменения имени в подзапросе вы можете использовать его для
чего угодно.
Скорее всего, было бы удобнее, чтобы наш подзапрос в предыдущем примере возвращал одно, и только одно, значение.
Имея выбранное поле snum " WHERE city = "London" вместо "WHERE sname = 'Motika", можно получить несколько различных значений. Это может сделать в предикате основного запроса невозможным оценку верности или неверности, и команда выдаст ошибку.
При использовании подзапросов в предикатах, основанных на реляционных операциях (уравнениях или неравенствах, как объяснено в Главе 4), вы должны убедиться, что использовали подзапрос, который будет выдавать одну, и только одну, строку вывода. Если вы используете подзапрос, который не выводит никаких значений вообще, команда не потерпит неудачи, но основной запрос не выведет никаких значений. Подзапросы, которые не производят никакого вывода (или нулевой вывод), вынуждают рассматривать предикат ни как верный, ни как неверный, а как неизвестный. Однако неизвестный предикат имеет тот же самый эффект, что и неверный: никакие строки не выбираются основным запросом (смотри в Главе 5 подробную информацию о неизвестном предикате).
Вот пример плохой стратегии:
SELECT * FROM Orders WHERE snum = (SELECT snum FROM Salespeople WHERE city = Barcelona);
Поскольку мы имеем только одного продавца в Barcelona - Rifkin, то подзапрос будет выбирать одиночное значение snum, и, следовательно, будет принят. Но это только в данном случае. Большинство БД SQL имеют многочисленных пользователей, и, если другой пользователь добавит нового продавца из Barcelona в таблицу, подзапрос выберет два значения, и ваша команда потерпит неудачу.
В некоторых случаях вы можете использовать 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.)
Пожалуйста, учтите, что методика, используемая в предшествующем примере, применима, только когда вы знаете, что два различных поля в таблице должны всегда совпадать, как в нашем случае. Эта ситуация не является типичной в реляционных базах данных (РБД), она является исключением из правил.
Вы должны обратить внимание что предикаты, включающие подзапросы, используют выражение
<скалярная форма> <оператор> <подзапрос>,
а не
<подзапрос> <оператор> <скалярное выражение>
или
<подзапрос> <оператор> <подзапрос>.
Другими словами, вы не должны записывать предыдущий пример так:
SELECT * FROM Orders WHERE (SELECT DISTINCT snum FROM Orders WHERE cnum = 2001) = snum;
В строгой ANSI-реализации это приведет к неудаче, хотя некоторые программы и позволяют делать такие вещи. ANSI также предохраняет от появления в выводе подзапроса обоих значений при сравнении.
Тип функций, который автоматически может производить одиночное значение для любого числа строк, конечно же - агрегатная функция.
Любой запрос, использующий одиночную функцию агрегата без предложения 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";
Вы можете использовать подзапросы, которые производят любое число строк, если
вы применяете специальный оператор 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 предполагает что это правильно. Это предположение может быть отменено полным именем таблицы или префиксом псевдонима, о которых мы поговорим позже, когда будем говорить о соотнесенных подзапросах. Если возможен беспорядок, конечно же, лучше всего использовать префиксы.
Смысл всех подзапросов, обсуждённых в этой главе, в том, что все они выбирают одиночный столбец. Это обязательно, поскольку полученный вывод сравнивается с одиночным значением. Подтверждением этому является то, что SELECT * не может использоваться в подзапросе. Имеется исключение из этого, когда подзапросы используются с оператором EXISTS, о котором мы будем говорить в Главе 12.
Вы можете использовать выражение, основанное на столбце, а не просто сам столбец, в предложении 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 не имеют такого простого функционального значения как, например, первичный ключ, что не всегда хорошо, запрос типа вышеупомянутого невероятно полезен.
Вы можете также использовать подзапросы внутри предложения 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, они должны быть выведены с числом номеров заказчиков, которые имели эту оценку.
Теперь вы используете запросы в иерархической манере. Вы видели, что использование результата одного запроса для управления другим расширяет возможности, позволяя выполнить большее количество функций.
Вы теперь понимаете, как использовать подзапросы с реляционными операциями и со специальным оператором IN, или в предложении WHERE, или в предложении HAVING внешнего запроса.
В следующих главах мы будем рассматривать подзапросы. Сначала, в Главе 11, мы обсудим другой вид подзапроса, который выполняется отдельно для каждой строки таблицы, вызываемой во внешнем запросе. Затем, в Главах 12 и 13, мы представим вам несколько специальных операторов, которые функционируют на всех подзапросах, как это делает IN, за исключением случаев, когда эти операторы могут использоваться только в подзапросах.
1. Напишите запрос, который использовал бы подзапрос для получения всех заказов для заказчика с именем Cisneros. Предположим, что вы не знаете номера этого заказчика, указываемого в поле cnum. 2. Напишите запрос, который вывел бы имена и оценки всех заказчиков, имеющих усреднённые заказы. 3. Напишите запрос, который выбрал бы общую сумму всех приобретений в заказах для каждого продавца, у которого эта общая сумма больше, чем сумма наибольшего заказа в таблице. (См. ответы в Приложении A.)
В этой главе мы рассмотрим тип подзапроса, о котором мы не говорили в Главе 10, - соотнесённый подзапрос. Вы узнаете, как использовать соотнесённые подзапросы в предложениях WHERE и HAVING. Сходства и различия между соотнесёнными подзапросами и объединениями будут обсуждаться далее, и вы сможете расширить ваше знание о псевдонимах и префиксах имени таблицы - когда они необходимы и как их использовать.
Когда вы используете подзапросы в 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);
В вышеупомянутом примере, "внутренний" (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 Использование соотнесённого подзапроса
Следовательно, процедура оценки, выполняемой соотнесённым подзапросом:
В вышеупомянутом примере SQL осуществляет следующую процедуру:
Как видите, вычисления, которые 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 Нахождение продавцов с несколькими заказчиками
Иногда полезно выполнять запросы, которые разработаны специально так, чтобы находить ошибки. Это всегда возможно при появлении дефектной информации, которую можно ввести в вашу БД, и, если она введена, бывает трудно её выявить. Следующий запрос не должен производить никакого вывода. Он просматривает таблицу Заказов, чтобы увидеть, совпадают ли поля snum и cnum в каждой строке таблицы Заказчиков, и выводит каждую строку, где этого совпадения нет. Другими словами, запрос выясняет, тот ли продавец кредитовал каждую продажу (он принимает поле cnum как первичный ключ таблицы Заказчиков, который не будет иметь никаких двойных значений в этой таблице).
SELECT * FROM Orders main WHERE NOT snum = (SELECT snum FROM Customers WHERE cnum = main.cnum);
При использовании механизма справочной целостности (обсуждённого в Главе 19), вы можете быть гарантированы от некоторых ошибок такого вида. Этот механизм не всегда доступен, хотя его использование желательно во всех случаях, причем поиск ошибки запроса, описанный выше, может быть ещё полезнее.
Вы можете также использовать соотнесённый подзапрос, основанный на той же самой таблице, что и основной запрос. Это даст вам возможность извлечь сложные формы произведённой информации. Например, мы можем найти все заказы со значениями сумм приобретений выше среднего для их заказчиков (вывод показан на Рисунке 11.4):
SELECT * FROM Orders outer WHERE amt > (SELECT AVG amt FROM Orders inter WHERE inner.cnum = outer.cnum); =============== SQL Execution Log ============== | | | SELECT * | | FROM Orders outer | | WHERE amt > | | (SELECT AVG (amt) | | FROM Orders inner | | WHERE inner.cnum = outer.cnum | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3006 1098.19 10/03/1990 2008 1007 | | 3010 1309.00 10/06/1990 2004 1002 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================ Рисунок 11.4 Соотнесение таблицы с собой
Конечно, в нашей маленькой (психиатрической больнице) типовой таблице, где большинство заказчиков имеют только один заказ, большинство значений являются одновременно средними и, следовательно, не выбираются. Давайте введём команду другим способом (вывод показан на Рисунке 11.5):
SELECT * FROM Orders outer WHERE amt > = (SELECT AVG (amt) FROM Orders inner WHERE inner.cnum = outer.cnum); =============== SQL Execution Log ============== | | | SELECT * | | FROM Orders outer | | WHERE amt > = | | (SELECT AVG (amt) | | FROM Orders inner | | WHERE inner.cnum = outer.cnum); | | =============================================== | | onum amt odate cnum snum | | ----- -------- ---------- ----- ------ | | 3003 767.19 10/03/1990 2001 1001 | | 3002 1900.10 10/03/1990 2007 1004 | | 3005 5160.45 10/03/1990 2003 1002 | | 3006 1098.19 10/03/1990 2008 1007 | | 3009 1713.23 10/04/1990 2002 1003 | | 3010 1309.95 10/06/1990 2004 1002 | | 3011 9891.88 10/06/1990 2006 1001 | ================================================ Рисунок 11.5 Выбираются заказы, которые >= средней сумме приобретений их заказчиков.
Различие, конечно, в том, что реляционная операция основного предиката включает значения, которые равняются среднему (что обычно означает, что они - единственные заказы данных заказчиков).
Предложение HAVING может принимать подзапросы и соотнесённые подзапросы. Когда вы используете соотнесённый подзапрос в предложении HAVING, вы должны ограничивать внешние ссылки на позиции, которые могли бы непосредственно использоваться в самом предложении HAVING. Вы помните из Главы 6, что предложение HAVING может использовать только агрегатные функции, которые указаны в их предложении SELECT, или поля, используемые в их предложении GROUP BY. Они являются только внешними ссылками, которые вы можете делать. Всё это потому, что предикат предложения HAVING оценивается для каждой группы из внешнего запроса, а не для каждой строки. Следовательно, подзапрос будет выполняться один раз для каждой группы, выведённой из внешнего запроса, а не для каждой строки. Предположим, что вы хотите суммировать значения сумм приобретений покупок из таблицы Заказов, сгруппировав их по датам, удалив все даты, где бы SUM не был по крайней мере на 2000.00 выше максимальной (MAX) суммы:
SELECT odate, SUM (amt) FROM Orders a GROUP BY odate HAVING SUM (amt) > ( SELECT 2000.00 + MAX (amt) FROM Orders b WHERE a.odate = b.odate );
Подзапрос вычисляет значение MAX для всех строк с той же самой датой, что и у текущей агрегатной группы основного запроса. Это должно быть выполнено, как и ранее, с использованием предложения WHERE. Сам подзапрос не должен использовать предложения GROUP BY или HAVING.
Как вы и могли предположить, соотнесённые подзапросы по своей природе близки
объединениям - они оба включают сверку каждой строки одной таблицы с каждой
строкой другой (или псевдонимом) таблицы. Вы найдёте, что большинство
операций, которые могут выполняться с одним из них, будут также работать и с
другим.
Однако в прикладной программе между ними имеется различие, такое как
вышеупомянутая потребность в использовании DISTINCT с объединением и его необязательность с подзапросом.
Имеются также некоторые вещи, которые каждый
из них может делать так, как этого не может другой. Подзапросы, например, могут
использовать агрегатную функцию в предикате, делая возможным выполнение операций
типа нашего предыдущего примера, в котором мы извлекли заказы, усреднённые для
их заказчиков. Объединения, с другой стороны, могут выводить строки из обеих
сравниваемых таблиц, в то время как вывод подзапросов используется только в
предикатах внешних запросов.
Как правило форма запроса, которая кажется наиболее интуитивной, будет, вероятно, лучшей в использовании, но при этом хорошо бы знать обе техники для тех ситуаций, когда та или иная могут не работать.
Вы можете поздравить себя с овладением большого блока понятий SQL -
соотнесённого подзапроса. Вы видели, как соотнесённый подзапрос связан с
объединением, а также - как его можно использовать с агрегатными функциями и в
предложении HAVING. В общем, вы теперь знаете все типы подзапросов.
Следующий шаг - описание некоторых специальных операторов SQL. Они берут
подзапросы как аргументы, как это делает IN, но, в отличие от IN, они могут
использоваться только в подзапросах. Первый из них представлен в
Главе 12 и называется EXISTS.
1. Напишите команду SELECT, использующую соотнесенный подзапрос, которая выберет имена и номера всех заказчиков с максимальными для их городов оценками. 2. Напишите два запроса, которые выберут всех продавцов (по их имени и номеру), имеющих в своих городах заказчиков, которых они не обслуживают. Один запрос - с использованием объединения, а другой - с соотнесённым подзапросом. Которое из решений будет более изящным? (Подсказка: один из способов сделать это состоит в том, чтобы найти всех заказчиков, не обслуживаемых данным продавцом, и определить, находится ли каждый из них в городе продавца.) (См. ответы в Приложении A.)
Теперь, когда вы уже познакомились с подзапросами, мы можем поговорить о некоторых специальных операторах, которые всегда принимают подзапросы как аргументы. Вы узнаете о первом из их в этой главе. Остальные будут описаны в следующей главе.
Оператор EXISTS используется, чтобы указать предикату, производить ли подзапросу вывод или нет. В этой главе вы узнаете, как использовать этот оператор со стандартными и (обычно) соотнесёнными подзапросами. Мы будем также обсуждать специальные вопросы, которые перейдут в игру, когда вы будете использовать этот оператор как относительный агрегат, как пустой указатель NULL и как булев оператор. Кроме того, вы сможете повысить ваш профессиональный уровень относительно подзапросов, исследуя их в более сложных прикладных программах, чем те, которые мы видели до сих пор.
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
В вышеупомянутом примере, EXISTS должен быть установлен так, чтобы легко выбрать один столбец, вместо того чтобы выбирать все столбцы, используя в выборе звёздочку (SELECT *). В этом состоит его отличие от подзапроса, который (как вы видели ранее в Главе 10, мог выбрать только один столбец). Однако в принципе он мало отличается при выборе 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 не был указан, каждый из этих продавцов будет выбран один раз для каждого заказчика, которому он назначен.
Однако для нас может быть полезнее вывести больше информации об этих продавцах, а не только их номера. Мы можем сделать это, объединив таблицу Заказчиков с таблицей Продавцов (вывод для запроса показан на Рисунке 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, оба условия основного предиката должны быть верны в заказе для верного предиката. Следовательно, результаты подзапроса имеют смысл только в тех случаях, когда вторая часть запроса верна, а объединение - выполнимо. Таким образом, комбинация объединения и подзапроса может стать очень мощным способом обработки данных.
Предыдущий пример показал, что 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);
Одна вещь которую 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)
который был показан выше.
В возможных прикладных программах подзапросы могут становиться многократно вкладываемыми. Вы можете вкладывать их два или более в одиночный запрос, и даже один внутрь другого. Так как можно рассмотреть небольшой блок, чтобы получить всю картину работы этой команды, вы можете воспользоваться способом в SQL, который может принимать различные команды из большинства других языков.
Вот запрос, извлекающий строки всех продавцов, которые имеют заказчиков с более чем одним текущим заказом. Это не обязательно самое простое решение этой проблемы, но оно предназначено для того, чтобы показать улучшенную логику SQL. Вывод этой информации связывает все три наши типовые таблицы:
SELECT * FROM Salespeople first WHERE EXISTS (SELECT * FROM Customers second WHERE first.snum = second.snum AND 1 < (SELECT COUNT (*) FROM Orders WHERE Orders.cnum = second.cnum));
Вывод для этого запроса показан на Рисунке 12.5.
=============== SQL Execution Log ============ | | | FROM Salespeople first | | WHERE EXISTS | | (SELECT * | | FROM Customers second | | WHERE first.snum = second.snum | | AND 1 < | | (SELECT CONT (*) | | FROM Orders | | WHERE Orders.cnum = second.cnum)); | | ============================================= | | cnum cname city comm | | ----- -------- ---- -------- | | 1001 Peel London 0.17 | | 1002 Serres San Jose 0.13 | | 1007 Rifkin Barselona 0.15 | ============================================= Рисунок 12.5 Использование EXISTS с комплексным подзапросом
Мы могли бы разобрать вышеупомянутый запрос примерно так:
Берём каждую строку таблицы Продавцов как строку-кандидат (внешний запрос) и выполняем подзапросы. Для каждой строки-кандидата из внешнего запроса ставим в соответствие каждую строку из таблицы Заказчиков (средний запрос). Если текущая строка заказчиков не совпадает с текущей строкой продавца (т.е. если first.snum < > second.snum), предикат среднего запроса неправилен. Всякий раз, когда мы находим заказчика в среднем запросе который совпадает с продавцом во внешнем запросе, мы должны рассматривать сам внутренний запрос чтобы определить, будет ли наш средний предикат запроса верен. Внутренний запрос считает число заказов текущего заказчика (из среднего запроса). Если это число больше 1, предикат среднего запроса верен, и строки выбираются. Это делает EXISTS-предикат внешнего запроса верным для текущей строки продавца и означает, что по крайней мере один из текущих заказчиков продавца имеет более чем один заказ.
Если это не кажется достаточно понятным для вас на данной стадии разбора примера, не волнуйтесь. Сложность этого примера хороша, независимо от того, как часто вы будете использовать её в деловой ситуации. Основная цель примеров такого типа состоит в том, чтобы показать вам некоторые возможности, которые могут оказаться в дальнейшем полезными. После работы со сложными ситуациями, подобными этой, простые запросы, которые являются наиболее часто используемыми в SQL, покажутся вам элементарными.
Кроме того, данный запрос, даже если он кажется удобным, довольно изощрён как способ извлечения информации и делает много работы. Он связывает три разные таблицы, чтобы дать вам эту информацию, а если таблиц больше, чем здесь указано, будет трудно получить её напрямую (хотя это не единственный способ, и не обязательно лучший способ в SQL). Возможно, вам нужно увидеть эту информацию относительно регулярной основы - если, например, вы имеете премию в конце недели для продавца, который получил несколько заказов от одного заказчика. В этом случае нужно было бы вывести команду и сохранить её, чтобы использовать снова и снова, по мере того как данные будут меняться (лучше всего сделать это с помощью представления, которое мы будем проходить в Главе 20).
EXISTS, хотя он и кажется простым, может быть одним из самых непонятных операторов SQL. Однако он обладает гибкостью и мощностью. В этой главе овладели большинством возможностей, которые предоставляет EXISTS. В дальнейшем ваше понимание улучшенной логики подзапроса значительно расширится.
Следующим шагом будет овладение тремя другими специальными операторами, которые принимают подзапросы как аргументы: это ANY, ALL и SOME. Как вы увидите в Главе 13, это альтернативные формулировки некоторых возможностей, которые вы уже использовали, но которые иногда могут оказаться предпочтительными.
1. Напишите запрос, который использовал бы оператор EXISTS для извлечения всех продавцов, имеющих заказчиков с оценкой 300. 2. Как бы вы решили предыдущую проблему, используя объединение? 3. Напишите запрос, использующий оператор EXISTS, который выберет всех продавцов с заказчиками, размещёнными в их городах, которые ими не обслуживаются. 4. Напишите запрос, который извлекал бы из таблицы Заказчиков каждого заказчика, назначенного продавцу, который в данный момент имеет по крайней мере ещё одного заказчика (кроме заказчика, которого вы выберете) с заказами в таблице Заказов (подсказка: это может быть похоже на структуру в примере с нашим трехуровневым подзапросом). (См. ответы в Приложении A.)
Глава 13. Использование операторов
ANY, ALL и SOME
Теперь, когда вы овладели оператором EXISTS, вы узнаете о трёх специальных операторах, ориентируемых на подзапросы. (Фактически имеются только два, так как ANY и SOME - одно и то же.) Если вы поймёте работу этих операторов, вы будете понимать все типы подзапросов предиката, используемых в SQL. Кроме того, вам будут представлены различные способы того, как данный запрос может быть сформирован с использованием различных типов подзапросов предиката, и вы поймёте преимущества и недостатки каждого из этих подходов.
ANY, ALL и SOME напоминают EXISTS, принимая подзапросы как аргументы; однако они отличаются от EXISTS тем, что используются совместно с реляционными операциями. В этом отношении они напоминают оператор IN, когда тот используется с подзапросами: они берут все значения, выведенные подзапросом и обрабатывают их как модуль. Однако, в отличие от IN, они могут использоваться только с подзапросами.
Операторы SOME и ANY взаимозаменяемы везде, и там, где мы используем ANY, SOME будет работать точно так же. Различие в терминологии состоит в том, чтобы позволить людям использовать тот термин, который является однозначным. Это может создать проблему, поскольку, как мы это увидим, наша интуиция может иногда вводить в заблуждение.
Вот новый способ нахождения продавцов с заказчиками, размещенными в их городах (вывод для этого запроса показан на Рисунке 13.1):
SELECT * FROM Salespeople WHERE city = ANY (SELECT city FROM Customers);
Оператор ANY берёт все значения, выведенные подзапросом, (для этого случая это все значения city в таблице Заказчиков), и оценивает их как верные, если любое (ANY) из них равняется значению города текущей строки внешнего запроса.
=============== SQL Execution Log ============ | SELECT * | | FROM Salespeople | | WHERE city = ANY | | (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.1 Использование оператора ANY
Это означает, что подзапрос должен выбирать значения такого же типа, что и те, которые сравниваются в основном предикате. В этом его отличие от EXISTS, который просто определяет, производит ли подзапрос результаты или нет, и фактически не использует эти результаты.
Мы можем также использовать оператор 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 могут быть несколько неоднозначны из-за способа использования этого термина в английском языке, как вы это скоро увидите. С приходом понимания различия способов формулирования данного запроса, вы сможете поработать над процедурами, которые сейчас кажутся вам трудными или неудобными.
Как говорилось выше, 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
С помощью ALL, предикат будет верным, если каждое значение, выбранное подзапросом, удовлетворяет условию в предикате внешнего запроса. Если мы хотим пересмотреть наш предыдущий пример, чтобы вывести только тех заказчиков, чьи оценки фактически выше, чем у каждого заказчика в Париже, мы можем ввести следующее, чтобы получить вывод, показанный в Рисунке 13.9:
SELECT * FROM Customers WHERE rating > ALL (SELECT rating FROM Customers WHERE city = Rome): =============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE rating > ALL | | (SELECT rating | | FROM Customers | | WHERE city = 'Rome'); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ------ | | 2004 Grass Berlin 300 1002 | | 2008 Cisneros San Jose 300 1007 | ============================================ Рисунок 13.9 Использование оператора ALL
Этот оператор проверяет значения рейтинга всех заказчиков в Риме. Затем он находит заказчиков с оценкой, большей, чем у любого из заказчиков в Риме. Самая высокая оценка в Риме - у Giovanni (200). Следовательно, выбираются только значения выше 200.
Как и в случае с ANY, мы можем использовать EXISTS для создания альтернативной формулировки такого же запроса (вывод показан на Рисунке 13.10):
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.10 Использование EXISTS в качестве альтернативы ALL
ALL чаще используется с неравенствами, нежели с равенствами, так как значение может быть "равным для всех" результатом подзапроса, только если все результаты фактически идентичны. Посмотрите на следующий запрос:
SELECT * FROM Customers WHERE rating = ALL (SELECT rating FROM Customers WHERE city = " San Jose');
Эта команда допустима, но с этими данными мы не получим никакого вывода. Только в единственном случае вывод будет выдан этим запросом - если все значения оценки в San Jose окажутся идентичными. В этом случае можно сказать следующее:
SELECT * FROM Customers WHERE rating = (SELECT DISTINCT rating FROM Customers WHERE city = " San Jose');
Основное различие в том, что эта последняя команда должна потерпеть неудачу, если подзапрос выведет много значений, в то время как вариант с ALL просто не даст никакого вывода. В общем, не самая удачная идея - использовать запросы, которые работают только в определённых ситуациях, подобно этой. Так как ваша БД будет постоянно меняться, это неудачный способ узнать о её содержании. Однако ALL может более эффективно использоваться с неравенствами, то есть с операцией "< >". Но учтите, что сказанное в SQL о значении, которое не равняется всем результатам подзапроса, будет отличаться от того же, но сказанного с учётом грамматики английского языка. Очевидно, если подзапрос возвращает много различных значений, как это обычно бывает, ни одно отдельное значение не может быть равно им всем в обычном смысле. В SQL выражение < > ALL в действительности означает "не равен любому" результату подзапроса. Другими словами, предикат верен, если данное значение не найдено среди результатов подзапроса. Следовательно, наш предыдущий пример противоположен по смыслу этому примеру (с выводом, показанным на Рисунке 13.11):
SELECT * FROM Customers WHERE rating < > ALL (SELECT rating FROM Customers WHERE city = " San Jose'); =============== SQL Execution Log ============ | | | SELECT * | | FROM Customers | | WHERE rating < > ALL | | (SELECT rating | | FROM Customers | | WHERE city = 'San Jose'); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ------ | | 2001 Hoffman London 100 1001 | | 2006 Clemens London 100 1001 | | 2007 Pereira Rome 100 1004 | ============================================= Рисунок 13.11 Использование ALL с < >
Вышеупомянутый подзапрос выбирает все оценки для города San Jose. Он выводит набор из двух значений: 200 (для Liu) и 300 (для Cisneros). Затем основной запрос выбирает все строки с оценкой, не совпадающей ни с одной из них, другими словами - все строки с оценкой 100. Вы можете сформулировать тот же самый запрос с помощью операторов NOT IN:
SELECT* FROM Customers WHERE rating NOT IN (SELECT rating FROM Customers WHERE city = " San Jose');
Вы могли бы также использовать оператор ANY:
SELECT * FROM Customers WHERE NOT rating = ANY (SELECT rating FROM Customers WHERE city = " San Jose');
Вывод будет одинаков для всех трёх условий.
В SQL, сказать, что значение больше (или меньше), чем любое (ANY) из набора значений - то же самое, что сказать, что оно больше (или меньше), чем любое отдельное из этих значений. И наоборот, сказать, что значение не равно всему (ALL) набору значений, это то же, что сказать, что нет такого значения в наборе которому оно равно.
Как было сказано, имеются некоторые различия между EXISTS и операторами, представленными в этой главе, в том, как они обрабатывают оператор NULL. ANY и ALL также отличаются друг от друга тем, как они реагируют, если подзапрос не произвел никаких значений, чтобы использовать их в сравнении. Эти различия могут привести к непредвиденным результатам в ваших запросах, если вы не будете их учитывать.
Одно существенное различие между 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, естественно, ни одно из этих сравнений не имеет значения.
Значения 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 является выше, чем допустимое значение. Более того, результат будет тот же, если мы будем проверять для более низкого значения.
Подчеркнём, что все формулировки с 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
Итак, вы узнали много нового в этой главе. Подзапросы - непростая тема, и мы потратили много времени, чтобы показать их варианты и неоднозначность. То, чему вы теперь научились, вещи достаточно глубокие. Вы знаете несколько технических решений одной проблемы, и поэтому можете выбрать то, которое больше подходит для ваших целей. Кроме того, вы поняли, как различные формулировки будут обрабатывать пустые значения (NULL) и ошибки.
Теперь, когда вы полностью изучили запросы - наиболее важный, и, вероятно, наиболее сложный аспект SQL - другой материал будет относительно прост для понимания.
У нас есть ещё одна глава о запросах, которая покажет вам, как объединить вывод любого количества запросов в единое тело с помощью формирования объединения нескольких запросов, используя оператор UNION.
1. Напишите запрос, который выбирал бы всех заказчиков, чьи оценки равны или больше, чем любая (ANY) оценка заказчика Serres. 2. Что будет выведено вышеупомянутой командой? 3. Напишите запрос, использующий ANY или ALL, который находил бы всех продавцов, которые не имеют никаких заказчиков, живущих в их городе. 4. Напишите запрос, который выбирал бы все заказы с суммой, больше, чем любая (в обычном смысле) для заказчиков в Лондоне. 5. Напишите предыдущий запрос с использованием MAX. (См. ответы в Приложении A.)
В предшествующих главах мы обсуждали различные способы вложения запросов
друг в друга. Имеется другой способ объединения нескольких запросов -
формирование их в объединение.
В этой главе вы научитесь использовать
предложение UNION в SQL. UNION отличается от подзапросов тем, что в нём ни один
из двух (или более) запросов не управляется другим запросом. Все запросы
выполняются независимо друг от друга, а уже вывод их объединяется.
Вы можете поместить несколько запросов вместе и объединить их вывод, используя предложение 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 Формирование объединения из двух запросов
Когда два (или более) запроса подвергаются объединению, их столбцы вывода должны быть совместимы для объединения. Это означает, что каждый запрос должен указывать одинаковое количество столбцов и в том же порядке и каждый должен иметь тип, совместимый с каждым. Значение совместимости типов меняется. ANSI следит за этим очень строго, и поэтому числовые поля должны иметь одинаковый числовой тип и размер, хотя некоторые имена, используемые ANSI для этих типов, являются синонимами. (Смотрите в Приложении B подробности о числовых типах ANSI.) Кроме того, символьные поля должны иметь одинаковое количество символов (значение предназначенного числа - не обязательно такое же, как используемое число).
Хорошо, что некоторые SQL-программы обладают большей гибкостью, чем это определяется ANSI. Типы, не определённые ANSI, такие как DATA и BINARY, обычно должны совпадать с другими столбцами такого же нестандартного типа.
Длина строки также может стать проблемой. Большинство программ разрешают поля переменной длины, но они не обязательно будут использоваться с UNION. С другой стороны, некоторые программы (и ANSI тоже) требуют, чтобы символьные поля были точно одинаковой длины. В этих вопросах вы должны проконсультироваться с документацией вашей собственной программы.
Другое ограничение на совместимость: пустые значения (NULL) запрещены в любом столбце объединения, причем эти значения необходимо запретить и для всех соответствующих столбцов в других запросах объединения. Пустые значения (NULL) запрещены ограничением NOT NULL, которое будет обсуждаться в Главе 18. Кроме того, вы не можете использовать UNION в подзапросах, а также не можете использовать агрегатные функции в предложении SELECT-запроса в объединении. (Большинство программ пренебрегают этими ограничениями.)
UNION будет автоматически исключать дубликаты строк из вывода. Это нечто, не свойственное SQL, так как одиночные запросы обычно содержат DISTINCT для устранения дубликатов.
Например, запрос, вывод которого показан на Рисунке 14.2,
SELECT snum, city FROM Customers;
имеет двойную комбинацию значений (snum=1001, city=London), потому что мы не указали, чтобы SQL устранил дубликаты. Однако, если мы используем
=============== SQL Execution Log ============ | | | SELECT snum, city | | FROM Customers; | | ============================================= | | snum city | | ----- -------- | | 1001 London | | 1003 Rome | | 1002 San Jose | | 1002 Berlin | | 1001 London | | 1004 Rome | | 1007 San Jose | | | ============================================= Рисунок 14.2 Одиночный запрос с дублированным выводом
UNION в комбинации этого запроса с ему подобным в таблице Продавцов, то эта избыточная комбинация будет устранена. Рисунок 14.3 показывает вывод следующего запроса.
SELECT snum, city FROM Customers UNION SELECT snum, city FROM Salespeople.; =============== SQL Execution Log ============ | | | FROM Customers | | UNION | | SELECT snum, sity | | FROM Salespeople; | | ============================================= | | | | ----- -------- | | 1001 London | | 1002 Berlin | | 1007 San Jose | | 1007 New York | | 1003 Rome | | 1001 London | | 1003 Rome | | 1002 Barcelona | | 1007 San Jose | | | ----------------------------------------------- Рисунок 14.3 UNION устраняет вывод дубликатов
Вы можете получить нечто похожее (в некоторых программах SQL), используя UNION ALL вместо просто UNION, наподобие этого:
SELECT snum, city FROM Customers UNION ALL SELECT snum, city FROM Salespeople;
Иногда вы можете вставлять константы и выражения в предложения 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 Выбор наивысших и наинизших заказов, определяемых с помощью строк
До сих пор мы не оговаривали, что данные нескольких запросов будут выводиться в каком-то особом порядке. Мы просто показывали вывод сначала из одного запроса, а затем из другого. Конечно, вы не можете полагаться на вывод, приходящий в произвольном порядке. Мы как раз сделаем так, чтобы этот способ выполнения примеров был более простым. Вы можете использовать предложение 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, обсуждающую столбцы вывода.)
Операция, которая бывает часто полезна, это объединение двух запросов, в котором второй запрос выбирает строки, исключённые первым. Наиболее часто вы будете делать это, чтобы не исключать строки, которые не удовлетворили предикату при объединении таблиц. Это называется внешним объединением.
Предположим, что некоторые из ваших заказчиков ещё не были назначены
продавцам. Вы можете захотеть увидеть имена и города всех ваших заказчиков, с
именами продавцов, не учитывая тех, кто ещё не был назначен. Вы можете
достичь этого, формируя объединение из двух запросов, один из которых выполняет
объединение, а другой выбирает заказчиков с пустыми (NULL) значениями поля snum.
Этот последний запрос должен вставлять пробелы в поля, соответствующие полю
sname в первом запросе. Как и раньше, вы можете вставлять текстовые строки в ваш
вывод чтобы идентифицировать запрос, который вывел данную строку.
Использование
этой методики во внешнем объединении дает возможность использовать предикаты для
классификации, а не для исключения. Мы и раньше использовали пример нахождения продавцов
с заказчиками, размещёнными в их городах. Однако, вместо просто выбора
только этих строк, вы, возможно, захотите, чтобы ваш вывод перечислял всех
продавцов и указывал тех, кто не имел заказчиков в их городах, и кто имел.
Следующий запрос, чей вывод показан на Рисунке 14.6, выполнит это:
SELECT Salespeople.snum, sname, cname, comm FROM Salespeople, Customers WHERE Salespeople.city = Customers.city. UNION SELECT snum, sname, ' NO MATCH ', comm FROM Salespeople WHERE NOT city = ANY (SELECT city FROM Customers) ORDER BY 2 DESC; =============== SQL Execution Log ============ | | | FROM Salespeople | | WHERE NOT city = ANYate) | | (SELECT city | | FROM Customers) | | ORDER BY 2 DESC; | | ============================================= | | | | ----- ------- --------- ------------ | | 1002 Serres Cisneros 0.1300 | | 1002 Serres Liu 0.1300 | | 1007 Rifkin NO MATCH 0.1500 | | 1001 Peel Clemens 0.1200 | | 1001 Peel Hoffman 0.1200 | | 1004 Motika Clemens 0.1100 | | 1004 Motika Hoffman 0.1100 | | 1003 Axelrod NO MATCH 0.1000 | | | =============================================== Рисунок 14.6 Внешнее объединение
Строка ' NO MATCH ' была дополнена пробелами, чтобы получить совпадение поля cname по длине (не во всех реализациях SQL). Второй запрос выбирает даже те строки, которые исключил первый. Вы можете также добавить комментарий или выражение в ваш запрос в виде дополнительного поля. Если вы сделаете это, вы должны будете добавить некоторый дополнительный комментарий или выражение в той же самой позиции среди выбранных полей для каждого запроса в операции объединения. Совмещение UNION предотвращает добавление дополнительного поля для первого запроса, но не для второго. Вот запрос, который добавляет строки к выбранным полям и указывает, совпадает ли данный продавец с его заказчиком в его городе:
SELECT a.snum, sname, a.city, ' MATCHED ' FROM Salespeople a, Customers b WHERE a.city = b.city UNION SELECT snum, sname, city, 'NO MATCH' FROM Salespeople WHERE NOT city = ANY (SELECT city FROM Customers) ORDER BY 2 DESC;
Рисунок 14.7 показывает вывод этого запроса.
=============== SQL Execution Log ============ | | | WHERE a.city = b.city | | UNION | | SELECT snum,sname,city, 'NO MATCH' | | FROM Salespeople | | WHERE NOT city = ANYate) | | (SELECT city | | FROM Customers) | | ORDER BY 2 DESC; | | ============================================= | | | | ----- ------- ------------ --------- | | 1002 Serres San Jose MATCHED | | 1007 Rifkin Barselona NO MATCH | | 1001 Peel London MATCHED | | 1004 Motika London MATCHED | | 1003 Axelrod New York NO MATCH | | | =============================================== Рисунок 14.7 Внешнее объединение с полем комментария
Это неполное внешнее объединение, так как оно включает только несовпадающие поля одной из объединяемых таблиц. Полное внешнее объединение должно включать всех заказчиков, имеющих и не имеющих продавцов в их городах. Такое условие будет более полным, как вы это сможете увидеть (вывод следующего запроса показан на Рисунке 14,8):
SELECT snum, city, 'SALESPERSON - MATCH' FROM Salespeople WHERE NOT city = ANY (SELECT city FROM Customers) UNION SELECT snum, city, 'SALESPERSON - NO MATCH' FROM Salespeople WHERE NOT city = ANY (SELECT city FROM Customers)) UNION (SELECT cnum, city, 'CUSTOMER - MATCHED' FROM Customers WHERE city = ANY (SELECT city FROM Salespeople) UNION SELECT cnum, city, 'CUSTOMER - NO MATCH' FROM Customers WHERE NOT city = ANY (SELECT city FROM Salespeople)) ORDER BY 2 DESC; =============== SQL Execution Log =============== | | | FROM Salespeople) | | ORDER BY 2 DESC; | | | | ================================================ | | | | ---- -------- ------------------------ | | 2003 San Jose CUSTOMER - MATCHED | | 2008 San Jose CUSTOMER - MATCHED | | 2002 Rome CUSTOMER - NO MATCH | | 2007 Rome CUSTOMER - NO MATCH | | 1003 New York SALESPERSON - MATCHED | | 1003 New York SALESPERSON - NO MATCH | | 2001 London CUSTOMER - MATCHED | | 2006 London CUSTOMER - MATCHED | | 2004 Berlin CUSTOMER - NO MATCH | | 1007 Barcelona SALESPERSON - MATCHED | | 1007 Barcelona SALESPERSON - NO MATCH | | | ================================================== Рисунок 14.8 Полное внешнее объединение
(Понятно, что эта формула, использующая ANY, эквивалентна объединению в предыдущем примере.) Сокращённое внешнее объединение, с которого мы начинали, используется чаще, чем последний пример. Этот пример, однако, имеет другой смысл. Всякий раз, когда вы выполняете объединение более чем двух запросов, вы можете использовать круглые скобки, чтобы определить порядок оценки. Другими словами, вместо
query X UNION query Y UNION query Z;
вы должны указать, или
(query X UNION query Y)UNION query Z;
или
query X UNION (query Y UNION query Z);
Это потому, что UNION и UNION ALL могут быть скомбинированы, чтобы удалять одни дубликаты, не удаляя другие.
Предложение
(query X UNION ALL query Y)UNION query Z;
не обязательно воспроизведёт те же результаты, что и предложение
query X UNION ALL(query Y UNION query Z);
если дублирующие строки в нём, будут удалены.
Теперь вы знаете, как использовать предложение UNION, которое даёт возможность объединять любое число запросов в единое тело вывода. Если вы имеете ряд подобных таблиц - таблиц, содержащих похожую информацию, но принадлежащих разным пользователям и охватывающих различные особенности, - возможно, что объединение сможет обеспечить простой способ для слияния и упорядочивания вывода. Аналогично внешние объединения дают вам новый способ использования условий не для исключения вывода, а для его маркировки или обработки его частей, когда встречается условие, отличающееся от того, которое не выполняется.
Этим заканчиваются наши главы о запросах. Вы теперь имеете довольно полное представление о поиске данных в SQL. Следующий шаг - изучение того, как значения вводятся в таблицы и как таблицы создаются "с нуля". Как вы увидите, запросы иногда используются внутри других типов команд так же хорошо, как и сами по себе.
1. Создайте объединение из двух запросов, которое показало бы имена, города и оценки всех заказчиков. Те из них, которые имеют поле rating=200 и более, должны, кроме того, иметь слова - "Высокий Рейтинг", а остальные должны иметь слова "Низкий Рейтинг". 2. Напишите команду, которая вывела бы имена и номера каждого продавца и каждого заказчика, которые имеют больше чем один текущий заказ. Результат представьте в алфавитном порядке. 3. Сформируйте объединение из трех запросов. Первый выбирает поля snum всех продавцов в San Jose; второй, поля cnum всех заказчиков в San Jose; и третий - поля onum всех заказов на 3 октября. Сохраните дубликаты между последними двумя запросами, но устраните любую избыточность вывода между каждым из них и самым первым. (Примечание: в данных типовых таблицах не содержится никакой избыточности. Это только пример.) (См. ответы в Приложении A.)
В этой главе представлены команды, управляющие значениями, представляемыми в таблице. Когда вы закончите эту главу, вы будете способны помещать строки в таблицу, удалять их и изменять индивидуальные значения, представленные в каждой строке.
Будет показано использование запросов при формировании полной группы строк для вставки, а также - как может использоваться предикат для управления изменением значений и удаления строк.
Материал в этой главе составляет полный объём знаний, показывающий, как создавать и управлять информацией в базе данных (БД). Более мощные способы проектирования предикатов будут обсуждены в следующей главе.
Значения могут быть помещены и удалены из полей тремя командами языка DML (Язык Манипулирования Данными):
INSERT (ВСТАВИТЬ), UPDATE (МОДИФИЦИРОВАТЬ), DELETE (УДАЛИТЬ).
Не удивляйтесь, все они упоминались ранее в SQL как команды модификации.
Все значения в SQL вводятся с использованием команды модификации INSERT.
В самой простой форме INSERT использует следующий синтаксис:
INSERT INTO
VALUES (<value>, . . .);
Так, например, чтобы ввести строку в таблицу Продавцов, вы можете использовать следующее условие:
INSERT INTO Salespeople VALUES (1001, 'Peel', 'London', .12);
Команды DML не производят никакого вывода, но ваша программа должна дать вам некоторое подтверждение того, что данные были использованы.
Имя таблицы (в нашем случае - Salespeople (Продавцы)) должно быть предварительно определено в команде CREATE TABLE (см. Главу 17), а каждое значение в предложении значений должно совпадать с типом данных столбца, в который оно вставляется. В ANSI эти значения не могут являться выражениями, что означает, что 3 разрешено, а выражение 2 + 1 - нет. Значения, конечно же, вводятся в таблицу в поимённом порядке, поэтому первое значение с именем автоматически попадает в столбец (поле) 1, второе в столбец 2, и так далее.
Если вам нужно ввести пустое значение (NULL), вы вводите его точно так же, как и обычное значение. Предположим, что ещё нет поля city для мистера Peel. Вы можете вставить в строку значение=NULL в это поле следующим образом:
INSERT INTO Salespeople VALUES (1001, 'Peel', NULL, .12);
Так как значение NULL это специальный маркёр, а не просто символьное значение, оно не заключается в одинарные кавычки.
Вы можете также указывать столбцы, куда вы хотите вставить значение имени.
Это позволяет вам вставлять имена в любом порядке.
Предположим, что вы берёте
значения для таблицы Заказчиков из отчёта, выводимого на принтер, который
помещает их в таком порядке: city, cname, cnum; и, для упрощения, вы хотите
ввести значения в том же порядке:
INSERT INTO Customers (city, cnamе, cnum) VALUES ('London', 'Honman', 2001);
Обратите внимание, что столбцы rating и snum отсутствуют. Это значит, что в эти строки автоматически установлены значения по умолчанию. По умолчанию может быть введено значение NULL или другое значение, определяемое по умолчанию. Если ограничение запрещает использование значения NULL в данном столбце и в этот столбец не установлено значение по умолчанию, этот столбец должен быть обеспечен значением для любой команды INSERT, которая относится к таблице (смотри в Главе 18 информацию об ограничениях на значения NULL и "по умолчанию").
Вы можете также использовать команду INSERT, чтобы получать или выбирать значения из одной таблицы и помещать их в другую для использования их вместе с запросом. Чтобы сделать это, вы просто заменяете предложение VALUES (из предыдущего примера) на соответствующий запрос:
INSERT INTO Londonstaff SELECT * FROM Salespeople WHERE city = 'London';
Здесь выбираются все значения произведённые запросом - то есть все строки из таблицы Продавцов со значениями city = "London" - и помещаются в таблицу Londonstaff. Чтобы это работало, таблица Londonstaff должна отвечать следующим условиям:
Общее правило таково, что вставляемые столбцы таблицы должны совпадать со столбцами, выводимыми подзапросом, в данном случае - для всей таблицы Продавцов.
Londonstaff это теперь независимая таблица, которая получила некоторые
значения из таблицы Продавцов (Salespeople). Если значения в таблице Продавцов
будут вдруг изменены, это никак не отразится на таблице Londonstaff (хотя вы
могли бы создать такой эффект с помощью Представления (VIEW), описанного в
Главе 20).
Так как или запрос, или команда INSERT могут
указывать столбцы по имени, вы можете, если захотите, переместить только
выбранные столбцы, а также переупорядочить только те столбцы, которые вы
выбрали.
Предположим, например, что вы решили сформировать новую таблицу с именем Daytotals, которая просто будет следить за общим количеством долларов сумм приобретений, упорядоченных по каждому дню. Вы можете ввести эти данные независимо от таблицы Заказов, но сначала вы должны заполнить таблицу Daytotals информацией, ранее представленной в таблице Заказов. Учитывая, что таблица Заказов охватывает последний финансовый год, а не только несколько дней, как в нашем примере, вы можете увидеть преимущество использования следующего условия INSERT в подсчёте и вводе значений:
INSERT INTO Daytotals (date, total) SELECT odate, SUM (amt) FROM Orders GROUP BY odate;
Обратите внимание, что, как предложено ранее, имена столбцов таблицы Заказов и таблицы Daytotals не должны быть одинаковыми. Кроме того, если дата приобретения и общее количество это единственные столбцы в таблице и они находятся в данном заказе, их имена могут быть исключены из вывода из-за их очевидной простоты.
Вы можете удалять строки из таблицы командой модификации DELETE. Она может удалять только строки, а не индивидуальные значения полей, так что параметр поля является необязательным или недоступным. Чтобы удалить всё содержание таблицы Продавцов, вы можете ввести следующее условие:
DELETE FROM Salespeople;
Теперь, когда таблица пуста, её можно окончательно удалить командой DROP TABLE (это объясняется в Главе 17). Обычно вам нужно удалить только некоторые определённые строки из таблицы. Чтобы определить, какие строки будут удалены, вы используете предикат так же, как вы это делали для запросов. Например, чтобы удалить продавца Axelrod из таблицы, вы можете ввести
DELETE FROM Salespeople WHERE snum = 1003;
Мы использовали поле snum вместо поля sname, потому что лучшая тактика - использование первичных ключей, когда вы хотите, чтобы действию подвергалась одна, и только одна, строка. Для вас это аналогично действию первичного ключа. Конечно, вы можете также использовать DELETE с предикатом, который выбирал бы группу строк, как показано в этом примере:
DELETE FROM Salespeople WHERE city = 'London';
Теперь, когда вы уже можете вводить и удалять строки таблицы, вы должны узнать, как изменять некоторые или все значения в существующей строке. Это выполняется командой UPDATE. Эта команда содержит предложение UPDATE, в котором указано имя используемой таблицы, и предложение SET, которое указывает на изменение, выполняемое для определенного столбца. Например, чтобы изменить оценки всех заказчиков на 200, вы можете ввести
UPDATE Customers SET rating = 200;
Конечно, вы не всегда захотите указывать все строки таблицы для изменения единственного значения, так что UPDATE, наподобие DELETE, может использовать предикаты. Вот как, например, можно выполнить изменение, одинаковое для всех заказчиков продавца Peel (имеющего snum=1001):
UPDATE Customers SET rating = 200 WHERE snum = 1001;
Однако вы не обязаны ограничивать себя модифицированием единственного столбца с помощью команды 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".
Вы можете использовать скалярные выражения в предложении SET команды UPDATE, включив его в выражение поля, которое будет изменено. В этом их отличие от предложения VALUES команды INSERT, в котором выражения не могут использоваться; это свойство скалярных выражений - весьма полезная особенность. Предположим, что вы решили удвоить комиссионные всем вашим продавцам. Вы можете использовать следующее выражение:
UPDATE Salespeople SET comm = comm * 2;
Всякий раз, когда вы обращаетесь к указанному значению столбца в предложении SET, произведённое значение может получится из текущей строки; прежде в ней будут сделаны какие-то изменения с помощью команды UPDATE. Естественно, вы можете скомбинировать эти особенности и сказать "удвоить комиссию всем продавцам в Лондоне" таким предложением:
UPDATE Salespeople SET comm = comm * 2 WHERE city = 'London';
Предложение SET это не предикат. Оно может вводить пустые NULL-значения так же, как оно вводило значения, не используя какого-то специального синтаксиса (такого, например, как IS NULL). Так что, если вы хотите установить все оценки заказчиков в Лондоне в NULL, вы можете ввести следующее предложение:
UPDATE customers SET rating = NULL WHERE city = 'London';
что обнулит все оценки заказчиков в Лондоне.
Теперь вы овладели мастерством управления содержанием вашей базы данных с помощью трёх простых команд:
INSERT - используемой, чтобы помещать строки в базу данных; DELETE - чтобы удалять их; REFERENCES - чтобы изменять значения в уже вставленных строках.
Вы обучались использованию предиката с командами UPDATE и DELETE, чтобы определять, на которую из строк будет воздействовать команда. Конечно, предикаты как таковые не значимы для INSERT, потому что обсуждаемая строка не существует в таблице до окончания выполнения команды INSERT. Однако вы можете использовать запросы с INSERT, чтобы сразу помещать все наборы строк в таблицу. Причём вы можете делать это со столбцами в любом порядке.
Вы узнали, что значения по умолчанию могут помещаться в столбцы, если вы не устанавливаете эти значения явно.
Вы
также видели использование стандартного значения по умолчанию, каковым является
NULL.
Кроме того, вы поняли, что UPDATE может использовать выражение, тогда как
INSERT не может.
Следующая глава расширит ваше познания, показав вам, как использовать подзапросы с этими командами. Эти подзапросы напоминают те, с которыми вы уже знакомы, но имеются некоторые специальные выводы и ограничения при использовании подзапросов в командах DML, что мы обсудим в Главе 16.
1. Напишите команду, которая поместила бы следующие значения в указанном заказе в таблицу Продавцов: city - San Jose, name - Bianco, comm - NULL, cnum - 1100. 2. Напишите команду, которая удалила бы все заказы заказчика Clemens из таблицы Заказов. 3. Напишите команду, которая увеличила бы оценку всех заказчиков в Риме на 100. 4. Продавец Serres оставил компанию. Переназначьте его заказчиков продавцу Motika. (См. ответы в Приложении A.)
В этой главе вы узнаете, как использовать подзапросы в командах модификации. Вы обнаружите, что нечто подобное вы уже видели при использовании подзапросов в запросах. Понимание того, как подзапросы используются в командах SELECT, сделает их применение в командах модификации более уверенным, хотя и останутся некоторые вопросы. Завершением команды SELECT является подзапрос, но не предикат, и поэтому его использование отличается от использования простых предикатов с командами модификации, которые вы уже выполняли раннее с командами UPDATE и DELETE. Вы использовали простые запросы, чтобы производить значения для INSERT, а теперь мы можем расширить эти запросы, чтобы включать в них подзапросы.
Важный принцип, который надо соблюдать при работе с командами модификации: нельзя в предложении FROM любого подзапроса модифицировать таблицу, к которой обращаетесь с помощью основной команды. Это относится ко всем трём командам модификации. Хотя имеется большое количество ситуаций, в которых будет полезно сделать запрос той таблицы, которую вы хотите модифицировать, причем во время её модификации, это слишком усложняет операцию, чтобы использовать её на практике.
Не делайте ссылки к текущей строке таблицы, указанной в команде, которая является соотнесённым подзапросом.
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.
Последовательность команд в предшествующем разделе может быть критичной. Продавец Serres находится в San Jose и, следовательно, будет вставлен с помощью первой команды. Вторая команда попытается вставить его снова, поскольку он имеет ещё одного заказчика в San Jose. Если имеются любые ограничения в таблице SJpeople, которые вынуждают её иметь уникальные значения, эта вторая вставка потерпит неудачу (как и должно быть).
Дублирующие строки это плохо. (См. в Главе 18 подробности об ограничениях.) Было бы лучше, если бы вы могли как-то выяснить, что эти значения уже были вставлены в таблицу, прежде чем попытаетесь сделать это снова, с помощью добавления другого подзапроса (использующего операторы типа EXISTS, IN, < > ALL и так далее) к предикату.
К сожалению, чтобы сделать эту работу, вы должны будете сослаться на саму таблицу SJpeople в предложении FROM этого нового подзапроса, а, как мы говорили ранее, вы не можете ссылаться на таблицу, которая задействована (целиком) в любом подзапросе команды модификации. В случае с INSERT это будет также препятствовать соотнесённым подзапросам, основанным на таблице, в которую вы вставляете значения. Это имеет значение, потому что, с помощью INSERT, вы создаете новую строку в таблице. "Текущая строка" не будет существовать до тех пор, пока INSERT не закончит её обрабатывать.
Запрещение на обращение к таблице, которая модифицируется командой 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.
Вы можете также использовать подзапросы в предикате команды 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, следовательно, не будут переданы в основной предикат.
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));
Невозможность обратиться к таблице, задействованной в любом подзапросе, из команды модификации (UPDATE) исключает целые категории возможных действий. Например, вы не можете просто выполнить такую операцию как удаление всех заказчиков с оценками ниже средней. Вероятно, лучше всего вы могли бы сначала (Шаг 1.) выполнить запрос, получающий среднюю величину, а затем (Шаг 2.) удалить все строки с оценкой ниже этой величины.
Шаг 1.
SELECT AVG (rating) FROM Customers;
Вывод = 200.
Шаг 2.
DELETE FROM Customers WHERE rating < 200;
Теперь вы овладели тремя командами, которые управляют всем содержимым вашей БД. Осталось только несколько общих вопросов относительно ввода и стирания значений таблицы, когда, например, эти команды могут выполниться данным пользователем в данной таблице и когда действия, сделанные ими, становятся постоянными.
Подведём итог: вы используете команду INSERT, чтобы добавлять строки в таблицу. Вы можете или дать имена значениям этих строк в предложении VALUES (когда только одна строка может быть добавлена), или вывести значения с помощью запроса (когда любое число строк можно добавить одной командой).
Если используется запрос, он не может обращаться к таблице, в которую вы делаете вставку, каким бы способом вы её ни делали, ни в предложении FROM, ни с помощью внешней ссылки (как это делается в соотнесённых подзапросах). Всё это относится к любым подзапросам внутри этого запроса.
Запрос, однако, оставляет вам свободу использования соотнесённых подзапросов или подзапросов, которые дают в предложении FROM имя таблице, которое уже было указано в предложении FROM внешнего запроса (это - общий случай для запросов).
DELETE и UPDATE используются, чтобы, соответственно, удалить строки из таблицы и изменить в них значения. Оба они применимы ко всем строкам таблицы, если не используется предикат, определяющий, какие строки должны быть удалены или модифицированы. Этот предикат может содержать подзапросы, которые могут быть связаны с таблицей, удаляемой или модифицируемой с помощью внешней ссылки. Эти подзапросы, однако, не могут ссылаться на таблицу, модифицируемой любым предложением FROM.
Может показаться, что мы прошли материал SQL, который обладает не самой понятной логикой. Сначала мы сделали запрос таблицы, которая уже заполнена данными. Потом мы показали, как можно фактически помещать эти значения изначально. Но, как вы видите, полное ознакомление с запросами здесь неоценимо.
Теперь, когда мы показали вам, как заполнять значениями таблицы, которые уже были созданы (по определению), мы покажем (начиная со следующей главы), откуда появляются эти таблицы.
1. Предположите, что имеется таблица Multicust с такими же именами столбцов, что и в таблице Продавцов. Напишите команду, которая вставила бы всех продавцов (из таблицы Продавцов), имеющих более чем одного заказчика, в эту таблицу. 2. Напишите команду, которая удаляла бы всех заказчиков не имеющих текущих заказов. 3. Напишите команду, которая увеличила бы на двадцать процентов комиссионные всех продавцов, имеющих сумму текущих заказов выше $3,000. (См. ответы в Приложении A.)
До этой главы мы запрашивали таблицы данных и выполняли команды по извлечению этих данных, считая, что эти таблицы уже были созданы кем-то до нас.
Это действительно наиболее реальная ситуация, когда несколько человек создают таблицы, которые затем используются другими людьми. Наша цель состоит в том, чтобы, охватив информацию сначала более широко, перейти затем к более узким вопросам.
В этой главе мы будем обсуждать создание, изменение и удаление таблиц. Всё это относится к самим таблицам, а не к данным, которые в них содержатся. Будете вы или не будете выполнять эти операции самостоятельно, но их концептуальное понимание увеличит ваше понимание языка SQL и природу таблиц.
Эта глава вводит нас в область SQL, называемую DDL (Язык Определения Данных), где создаются объекты данных SQL.
Эта глава также покажет другой вид объектов данных SQL - Индекс. Индексы используются, чтобы сделать поиск более эффективным и (иногда) заставлять значения отличаться друга от друга. Они обычно работают незаметно для вас, но если вы попробуете поместить значения в таблицу и они будут отклонены из-за их неуникальности, это будет означать, что другая строка имеет то же самое значение для этого поля и что это поле имеет уникальный индекс или ограничение, которое предписывает ему уникальность. Обсуждение этого продолжится в Главе 18.
Таблицы создаются командой CREATE TABLE. Эта команда создает пустую таблицу - таблицу без строк. Значения вводятся с помощью DML-команды INSERT (См. Главу 15). Команда CREATE TABLE определяет имя таблицы и описание набора имён столбцов, указанных в определенном порядке. Она также определяет типы данных и размеры столбцов. Каждая таблица должна иметь по крайней мере один столбец.
Синтаксис команды CREATE TABLE:
CREATE TABLE table-name (<column name> <data type>[(size)], <column name> <data type> [(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);
Порядок столбцов в таблице определяется порядком, в котором они указаны. Имена столбца не должны разделяться при переносе строки (что делается для удобочитаемости) и отделяются запятыми.
Индекс это упорядоченный (буквенный или числовой) список столбцов или групп столбцов в таблице. Таблицы могут иметь большое количество строк, а, так как строки не находятся в каком-нибудь определенном порядке, их поиск по указанному значению может потребовать времени. Индексный адрес это и забота, и в то же время обеспечение способа объединения всех значений в группы из одной или больше строк, которые отличаются одна от другой.
В Главе 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);
Теперь тот продавец, который имеет отношение к этой таблице, сможет найти собственную клиентуру очень быстро.
Индексу в предыдущем примере, к счастью, не предписывается уникальность, несмотря на наше замечание, что это является одним из назначений индекса. Данный продавец может иметь любое число заказчиков. Однако этого не случится, если мы используем ключевое слово UNIQUE перед ключевым словом INDEX. Поле сnum, в качестве первичного ключа, станет первым кандидатом для уникального индекса:
CREATE UNIQUE INDEX Custid ON Customers (cnum);
Предыдущий пример - косвенный способ заставить поле cnum работать как первичный ключ таблицы Заказчиков. Базы данных воздействуют на первичные и другие ключи более непосредственно. Мы будем обсуждать это далее в Главах 18 и 19.
Главным признаком индекса является его имя, поэтому он может быть удален. Обычно пользователи не знают о существовании индекса. SQL автоматически определяет, разрешено ли пользователю использовать индекс, и, если да, разрешает использовать его. Однако, если вы хотите удалить индекс, вы должны знать его имя.
Синтаксис для удаления индекса:
DROP INDEX index name;
Удаление индекса не влияет на содержимое полей.
Команда ALTER TABLE не является частью стандарта ANSI, но это широко распространённая и довольно содержательная форма, хотя её возможности несколько ограничены. Она используется для того, чтобы изменить определение существующей таблицы. Обычно она добавляет столбцы к таблице. Иногда она может удалять столбцы или изменять их размеры, а также, в некоторых программах, добавлять или удалять ограничения (рассмотренные в Главе 18). Типичный синтаксис добавления столбца к таблице:
ALTER TABLE
ADD <column name> <data type> <size>;
Столбец будет добавлен со значениями NULL для всех строк таблицы. Новый столбец станет последним по порядку столбцом таблицы. Вообще-то можно добавить сразу несколько новых столбцов, отделив их запятыми, в одной команде. Имеется возможность удалять или изменять столбцы. Наиболее частым изменением столбца может быть просто увеличение его размера или добавление (удаление) ограничения.
Ваша система должна убедиться, что любые изменения не противоречат существующим данным: например, при попытке добавить ограничение к столбцу, который уже имел значение, при нарушении которого ограничение будет отклонено. Лучше всего дважды проверить это. Как минимум - посмотрите документацию вашей системы чтобы убедиться, гарантирует ли она, что именно это было причиной. Из-за нестандартного характера команды ALTER TABLE вам всё равно необходимо посмотреть тот раздел вашей системной документации, где говорится об особых случаях.
ALTER TABLE не действует, когда таблица должна быть переопределена, но вы должны разрабатывать вашу БД по возможности так, чтобы не слишком ей в этом доверяться. Изменять структуры таблицы, когда она уже в использовании, опасно! Просмотрите внимательно таблицы, которые, являясь вторичными таблицами с извлеченными данными из другой таблицы (см. Главу 20), не долго будут работать правильно, а программы, использующие вложенный SQL (Глава 25), начнут работать неправильно или не всегда правильно. Кроме того, изменение может стереть всех пользователей, имеющих разрешение на обращение к таблице.
По этим причинам вы должны разрабатывать ваши таблицы так, чтобы использовать ALTER TABLE только в крайнем случае. Если ваша система не поддерживает ALTER TABLE, или если вы хотите избежать её использования, вы можете просто создать новую таблицу, с необходимыми изменениями при создании, и использовать команду INSERT с SELECT * запросом чтобы переписать в нее данные из старой таблицы. Пользователям, которым был предоставлен доступ к старой таблице (см. Главу 22), должен быть предоставлен доступ к новой таблице.
Вы должны быть владельцем (т.е. создателем) таблицы, чтобы иметь возможность удалить её. Поэтому не беспокойтесь о случайном разрушении ваших данных, SQL сначала потребует, чтобы вы очистили таблицу прежде чем удалить её из БД. Таблица с находящимися в ней строками не может быть удалена. Обратитесь к Главе 15 за подробностями относительно того, как удалять строки из таблицы.
Синтаксис для удаления вашей таблицы, если конечно она является пустой, следующий:
DROP TABLE <table name>;
При подаче этой команды имя таблицы больше не распознаётся, и нет такой команды, которая могла бы быть дана этому объекту. Вы должны убедиться, что эта таблица не ссылается внешним ключом к другой таблице (внешние ключи обсуждаются в Главе 19) и что она не используется в определении представления (Глава 20). Эта команда фактически не является частью стандарта ANSI, но она поддерживается и полезна. К счастью, она более проста и, следовательно, более непротиворечива, чем ALTER TABLE. ANSI просто не имеет способа для определения разрушенных или неправильных таблиц.
Теперь вы уже бегло ориентируетесь в основах определений данных. Вы можете создавать, изменять и удалять таблицы. В то время как только первая из этих функций - часть официального стандарта SQL, другие будут время от времени меняться, особенно ALTER TABLE.
DROP TABLE позволяет вам избавиться от ненужных таблиц. Она уничтожает только пустые таблицы и, следовательно, не разрушает данные.
Вы теперь знаете об индексах, а также, как их создавать и удалять. SQL не даёт вам большого контроля над ними, так как реализация, которую вы используете, сама определяет, как быстро выполняются различные команды. Индексы это один из инструментов, дающий вам возможность воздействовать непосредственно на эффективность ваших команд в SQL. Мы рассмотрели индексы здесь, чтобы отличать их от ограничений, с которыми их нельзя путать. Ограничения это тема Главы 18 и Главы 19.
1. Напишите предложение CREATE TABLE, которое вывело бы нашу таблицу Заказчиков. 2. Напишите команду, которая давала бы возможность пользователю быстро извлекать заказы, сгруппированные по датам, из таблицы Заказов. 3. Если таблица Заказов уже создана, как вы можете заставить поле onum быть уникальным (если допустить что все текущие значения уникальны)? 4. Создайте индекс, который разрешал бы каждому продавцу быстро отыскивать его заказы, сгруппированные по датам. 5. Предположим, что каждый продавец имеет только одного заказчика с данной оценкой. Введите команду, которая его извлечет. (См. ответы в Приложении A.)
В Главе 17 вы узнали, как создаются таблицы. Теперь мы более основательно покажем вам, как вы можете устанавливать ограничения в таблицах. Ограничения это часть определений таблицы, вводящая ограничения на значения, которые вы можете вводить в столбцы. До этого места в данной книге ограничениями на значения, которые вы могли вводить, были тип данных и размер вводимых значений, которые должны быть совместимы с теми столбцами, куда эти значения помещаются (как определено для команды CREATE TABLE или команды ALTER TABLE).
Ограничения дают вам значительно б́ольшие возможности, и скоро вы это увидите. Вы также узнаете, как определять значения по умолчанию. По умолчанию - это значение, которое вставляется автоматически в любой столбец таблицы, когда значение для этого столбца отсутствует в команде INSERT для этой таблицы. NULL это наиболее широко используемое значение по умолчанию, но в этой главе будет показано, как определять и другие значения по умолчанию.
Когда вы создаёте таблицу (или, когда вы её изменяете), вы можете указывать ограничения на значения, которые могут быть введены в поля. Если вы это сделали, SQL будет отклонять любые значения, нарушающие критерии, которые вы определили.
Есть два основных типа ограничений: ограничение столбца и ограничение таблицы. Различие между ними в том, что ограничение столбца применяется только к отдельным столбцам, в то время как ограничение таблицы применяется к группам из одного и более столбцов.
Вы вставляете ограничение столбца в конец имени столбца после типа данных и перед запятой. Ограничение таблицы помещается в конец имени таблицы после последнего имени столбца, но перед заключительной круглой скобкой. Далее показан синтаксис для команды CREATE TABLE, расширенной для включения в неё ограничения:
CREATE TABLE <table name> (<column name> <data type> <column constraint>, <column name> <data type> <column constraint> ... <table constraint> (<column name> [, <column name> ])...);
(Для краткости мы опустили аргумент размера, который иногда используется с типом данных.) Поля данных в круглых скобках после ограничения таблицы это поля, к которым применено данное ограничение. Ограничение столбца, естественно, применяется к тем столбцам, после чьих имен оно следует. Остальная часть этой глава будет описывать различные типы ограничений и их использование.
Вы можете использовать команду 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, текущая таблица должна быть пустой.
В Главе 17 мы обсудили использование уникальных индексов, чтобы заставить поля иметь различные значения для каждой строки. Эта практика осталась с прежних времен, когда SQL поддерживал ограничение UNIQUE. Уникальность это свойство данных в таблице, и поэтому его более логично назвать ограничением этих данных, а не просто свойством логического отличия, связывающим объект данных (индекс). Несомненно, уникальные индексы - один из самых простых и наиболее эффективных методов предписания уникальности. По этой причине некоторые реализации ограничения UNIQUE используют уникальные индексы; то есть они создают индекс, не сообщая вам об этом. Остается фактом, что вероятность беспорядка в базе данных достаточно мала, если вы предписываете уникальность вместе с ограничением.
Время от времени вам нужно будет убедиться, что все значения, введённые в столбец, отличаются друг от друга. Например, первичные ключи. Если вы помещаете ограничение столбца UNIQUE в поле при создании таблицы, база данных отклонит любую попытку ввода в это поле для одной из строк значения, которое уже представлено в другой строке. Это ограничение может применяться только к полям, которые были объявлены как непустые (NOT NULL), так как не имеет смысла позволить одной строке таблицы иметь значение NULL, а затем исключать другие строки с NULL-значениями как дубликаты.
Вот дальнейшее усовершенствование нашей команды создания таблицы Продавцов:
CREATE TABLE Salespeople (Snum integer NOT NULL UNIQUE, Sname char (10) NOT NULL UNIQUE, city char (10), comm decimal);
Когда вы объявляете поле sname уникальным, убедитесь, что две Mary Smith будут введены различными способами, например, Mary Smith и M. Smith. В то же время это не так уж необходимо с функциональной точки зрения, потому что поле snum в качестве первичного ключа всё равно обеспечит отличие этих двух строк, что проще, нежели помнить, что эти Smith не идентичны. Столбцы (не первичные ключи), чьи значения требуют уникальности, называются ключами-кандидатами или уникальными ключами.
Вы можете также определить группу полей как уникальную с помощью команды
UNIQUE ограничения таблицы. Объявление группы полей уникальной отличается от
объявления уникальными индивидуальных полей, так как это комбинация значений, а
не просто индивидуальное значение, которое обязано быть уникальным.
Уникальность
группы это представление порядка так, что бы пары строк со значениями столбцов "a",
"b" и "b", "a" рассматривались отдельно одна от другой. Наша БД сделана так,
чтобы каждый заказчик был назначен одному, и только одному, продавцу. Это
означает, что каждая комбинация номера заказчика (cnum) и номера продавца (snum)
в таблице Заказчиков должна быть уникальной. Вы можете убедиться в этом, создав
таблицу Заказчиков таким способом:
CREATE TABLE Customers (cnum integer NOT NULL, cname char (10) NOT NULL, city char (10), rating integer, snum integer NOT NULL, UNIQUE (cnum, snum));
Обратите внимание, что оба поля в ограничении таблицы UNIQUE всё ещё используют ограничение столбца NOT NULL. Если бы мы использовали ограничение столбца UNIQUE для поля cnum, такое ограничение таблицы было бы необязательным. Если значения поля cnum различны для каждой строки, то не может быть двух строк с идентичной комбинацией значений полей cnum и snum. То же самое получится, если мы объявим поле snum уникальным, хотя это и не будет соответствовать нашему примеру, так как продавец будет назначен многочисленным заказчикам. Следовательно, ограничение UNIQUE таблицы наиболее полезно, когда вы не хотите, чтобы отдельные поля были уникальными.
Предположим, например, что мы разработали таблицу для отслеживания всех заказов каждый день для каждого продавца. Каждая строка такой таблицы представляет сумму чисел любых заказов, а не просто индивидуальный заказ. В этом случае мы могли бы устранить некоторые возможные ошибки, убедившись, что на каждый день имеется не более чем одна строка для данного продавца или что каждая комбинация полей snum и odate является уникальной. Вот как, например, мы могли бы создать таблицу с именем Salestotal:
CREATE TABLE Salestotal (cnum integer NOT NULL, odate date NULL, totamt decimal, UNIQUE (snum, odate));
Кроме того, имеется команда, которую вы будете использовать, чтобы помещать текущие данные в эту таблицу:
INSERT INTO Salestotal SELECT snum, odate, SUM (amt) FROM Orders GROUP BY snum, odate;
До этого мы воспринимали первичные ключи исключительно как логические понятия. Хоть мы и знаем, что такое первичный ключ и как он должен использоваться в любой таблице, мы не в курсе, "знает" ли об этом 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), а не для идентификации строк.
Ограничение 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 для двух имен полей.
Конечно, имеется любое число ограничений, которые можно устанавливать для данных, вводимых в ваши таблицы, чтобы увидеть, например, находятся ли данные в соответствующем диапазоне или правильном формате, о чем SQL, естественно, не может знать заранее. Для этого SQL предоставляет вам ограничение CHECK, позволяющее установить условие, которому должно удовлетворять значение, вводимое в таблицу, прежде чем оно будет принято.
Ограничение CHECK состоит из ключевого слова CHECK, сопровождаемого предложением предиката, который использует указанное поле. Любая попытка модифицировать или вставить значение поля, которое могло бы сделать этот предикат неверным, будет отклонена.
Давайте рассмотрим ещё раз таблицу Продавцов. Столбец комиссионных выражается десятеричным числом и поэтому может быть умножен непосредственно на сумму приобретений, в результате чего будет получена сумма комиссионных (в долларах) продавца с установленным справа значком доллара ($). Кто-то может использовать понятие процента, однако можно ведь об этом и не знать. Если человек введёт по ошибке 14 вместо .14 для указания, в процентах, своих комиссионных, это будет расценено как 14.0 , что является законным десятеричным значением и будет нормально воспринято системой. Чтобы предотвратить эту ошибку, мы можем наложить ограничение CHECK столбца, чтобы убедиться, что вводимое значение меньше 1.
CREATE TABLE Salespeople (snum integer NOT NULL PRIMARY KEY, sname char(10) NOT NULL UNIQUE, city char(10), comm decimal CHECK (comm < 1));
Мы можем также использовать ограничение 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);
Кроме того, если вы хотите, вы можете наложить ограничения, гарантирующие, что введенные символы - числа и что они в пределах значений нашего диапазона.
Вы можете также использовать CHECK в качестве табличного ограничения. Это полезно в тех случаях, когда вы хотите включить более одного поля строки в условие. Предположим, что комиссионные .15 и выше будут разрешены только для продавца из Барселоны. Вы можете указать это с помощью следующего табличного ограничения CHECK:
CREATE TABLE Salespeople (snum integer NOT NULL UNIQUE, sname char (10) NOT NULL UNIQUE, city char(10), comm decimal, CHECK (comm < .15 OR city = 'Barcelona'));
Как видите, два различных поля должны быть проверены, чтобы определить, верен
предикат или нет. Имейте в виду, что это - два разных поля одной и той же
строки. Хотя вы можете использовать несколько полей, SQL не может проверить
более одной строки одновременно. Вы не можете, например, использовать
ограничение CHECK, чтобы удостовериться, что все комиссионные в данном городе
одинаковы. Чтобы сделать это, SQL должен всякий раз, просматривая другие строки
таблицы, когда вы модифицируете или вставляете строку, видеть, что значение
комиссионных указано для текущего города. SQL этого делать не умеет.
Фактически
вы могли бы использовать сложное ограничение CHECK для вышеупомянутого, если бы
знали заранее, каковы должны быть комиссионные в разных городах. Например, вы
могли бы установить такое ограничение:
CHECK ((comm = .15 AND city = 'London') OR (comm = .14 AND city = 'Barcelona') OR (comm = 11 AND city = 'San Jose')..)
Мы подали вам идею. Чем налагать такой комплекс ограничений, вы могли бы просто использовать представление с предложением WITH CHECK OPTION, которое имеет все эти условия в своем предикате (смотри в Главах 20 и 21 информацию о представлении и о WITH CHECK OPTION).
Пользователи могут обращаться к представлению таблицы вместо самой таблицы. Одним из преимуществ этого будет то, что процедура изменения в ограничении не будет такой болезненной или трудоёмкой. Представление с WITH CHECK OPTION - хороший заменитель ограничению CHECK, что будет показано в Главе 21.
Когда вы вставляете строку в таблицу без указания в ней значений для каждого поля, SQL должен иметь значение по умолчанию для включения его в определённое поле, или же команда будет отклонена. Наиболее общим значением по умолчанию является NULL. Это - значение по умолчанию для любого столбца, которому не было дано ограничение NOT NULL или который имеет другое значение по умолчанию. Значение DEFAULT (ПО УМОЛЧАНИЮ) указывается в команде CREATE TABLE тем же способом, что и ограничение столбца, хотя, с технической точки зрения, значение DEFAULT - не ограничительного свойства: оно не ограничивает значения, которые вы можете вводить, а просто определяет, что может случиться, если вы не введёте любое из них.
Предположим, что вы работаете в офисе Нью-Йорка и подавляющее большинство ваших продавцов живут в Нью-Йорке. Вы можете указать Нью-Йорк в качестве значения поля city по умолчанию для вашей таблицы Продавцов:
CREATE TABLE Salespeople (snum integer NOT NULL UNIQUE, sname char(10) NOT NULL UNIQUE, city char(10) DEFAULT = 'New York', comm decimal CHECK (comm < 1);
Конечно, вводить значение Нью-Йорк в таблицу каждый раз, когда назначается новый продавец, не так уж необходимо, и можно просто пренебречь им (не вводя его), даже если оно должно иметь некоторое значение. Значение по умолчанию такого типа более предпочтительно, чем, например, длинный конторский номер, указывающий на ваш собственный офис в таблице Заказов. Длинные числовые значения более предрасположены к ошибке, поэтому, если подавляющее большинство (или все) ваших заказов должны иметь ваш собственный конторский номер, желательно устанавливать для них значение по умолчанию.
Другой способ использования значения по умолчанию - использовать его как альтернативу NULL. Так как NULL (фактически) является false при любом сравнении, ином, нежели IS NULL, он может быть исключён с помощью большинства предикатов.
Иногда вам нужно видеть пустые значения ваших полей, не обрабатывая их каким-то определённым образом. Вы можете установить значения по умолчанию, типа нуль или пробел, которые функционально меньше по значению, чем просто не установленное значение - пустое значение (NULL). Различие между ними и обычным NULL в том, что SQL будет обрабатывать их так же, как и любое другое значение.
Предположим, что заказчикам не назначены оценки изначально. Каждые шесть месяцев вы повышаете оценку всем вашим заказчикам, имеющим оценку ниже средней, включая и тех, кто предварительно не имел никакого назначения оценки. Если вы хотите выбрать всех этих заказчиков как группу, следующий запрос исключит всех заказчиков с оценкой = NULL:
SELECT * FROM Customers WHERE rating < = 100;
Однако, если вы назначили значение по умолчанию = 000, в поле rating,
заказчики без оценок будут выбраны наряду с другими. Приоритет каждого метода
зависит от ситуации. Если вы будете делать запрос с помощью поля оценки, то
захотите ли вы включить строки без значений, или исключите их?
Другая
характеристика значений по умолчанию этого типа позволит объявить поле
оценки как NOT NULL. Если вы используете его по умолчанию, чтобы избежать
значений = NULL, то это, вероятно, хорошая защита от ошибок. Вы можете также
использовать ограничения UNIQUE или PRIMARY KEY в этом поле. Если вы сделаете
это, то имеете в виду, что только одна строка одновременно может иметь значение
по умолчанию. Любую строку, которая содержит значение по умолчанию нужно будет
модифицировать, прежде чем другая строка с установкой по умолчанию будет
вставлена. Это не так, как при обычном использовании значений по умолчанию,
поэтому ограничения UNIQUE и PRIMARY KEY (особенно последнее) обычно не
устанавливаются для строк со значениями по умолчанию.
Вы теперь владеете несколькими способами управления значениями, которые могут быть введены в ваши таблицы. Вы можете использовать ограничение NOT NULL, чтобы исключать NULL; ограничение UNIQUE, чтобы вынуждать все значения в группе из одного или более столбцов отличаться друг от друга; ограничение PRIMARY KEY, для того чтобы делать в основном то же самое что и UNIQUE, но с различным окончанием, и наконец ограничение CHECK - для определения ваших собственных специальных условий, чтобы значения, встреченные перед ними, могли бы быть введены.
Кроме того, вы можете использовать предложение DEFAULT, которое будет
автоматически вставлять значение по умолчанию в любое поле с именем, не
указанным в INSERT, так же, как вставляется значение NULL, когда предложение
DEFAULT не установлено и отсутствует ограничение NOT NULL.
Ограничения FOREIGN
KEY или REFERENCES, о которых вы узнаете в Главе 19,
очень похожи на них, за исключением того, что они связывают группу из одного или
более полей с другой группой, и таким образом, сразу воздействуют на значения,
которые могут быть введены в любую из этих групп.
1. Создайте таблицу Заказов так, чтобы все значения полей onum, а также все комбинации полей cnum и snum, отличались друг от друга и чтобы значения NULL исключались из поля даты. 2. Создайте таблицу Продавцов так, чтобы комиссионные по умолчанию составляли 10%, не разрешались значения NULL, поле snum являлось первичным ключом и чтобы все имена были в алфавитном порядке между A и M включительно (учитывая, что все имена будут напечатаны в верхнем регистре). 3. Создайте таблицу Заказов учётом того, что поле onum больше, чем поле cnum, а cnum больше, чем snum. Запрещены значения NULL в любом из этих трех полей. (См. ответы в Приложении A.)
Ранее в этой книге мы указывали на определённые связи, которые существуют между некоторыми полями типовых таблиц. Поле snum таблицы Заказчиков, например, соответствует полю snum в таблице Продавцов и таблице Заказов. Поле cnum таблицы Заказчиков также соответствует полю cnum таблицы Заказов. Мы назвали этот тип связи справочной целостностью, и в ходе обсуждения вы видели, как её можно использовать.
В этой главе мы будем исследовать справочную целостность более подробно и выясним всё относительно ограничений, которые вы можете использовать, чтобы её поддерживать. Вы также увидите, как предписывается это ограничение, когда вы используете команды модификации DML.
Поскольку справочная целостность включает в себя связь полей или групп полей, часто в разных таблицах, это действие может быть несколько сложнее, чем другие ограничения. По этой причине хорошо иметь с ней полное знакомство, даже если вы не планируете создавать таблицы.
Ваши команды модификации могут стать эффективнее с помощью ограничения справочной целостности (как и с помощью других ограничений, но ограничение справочной целостности может воздействовать на другие таблицы помимо тех, в которых оно определено), а определённые функции запроса, такие как объединения, являются многократно структурированными, в терминах связей справочной целостности (как подчеркивалось в Главе 8).
Когда все значения в поле одной таблицы представлены в поле другой таблицы, мы говорим, что первое поле ссылается на второе. Это указывает на прямую связь между значениями двух полей. Например, каждый из заказчиков в таблице Заказчиков имеет поле snum, которое указывает на продавца, назначенного в таблице Продавцов. Для каждого заказа в таблице Заказов имеется один, и только этот, продавец и один, и только этот, заказчик. Это отображается с помощью полей snum и cnum в таблице Заказов.
Когда одно поле в таблице ссылается на другое, оно называется внешним ключом, а поле, на которое оно ссылается, называется родительским ключом. Так что поле snum таблицы Заказчиков это внешний ключ, а поле snum, на которое оно ссылается в таблице Продавцов, это родительский ключ.
Аналогично, поля cnum и snum таблицы Заказов это внешние ключи, которые ссылаются на их родительские ключи с именами в таблице Заказчиков и в таблице Продавцов. Имена внешнего ключа и родительского ключа не обязательно должны быть одинаковыми, это только соглашение, которому мы следуем, чтобы сделать соединение более понятным.
В реальности внешний ключ не обязательно состоит только из одного поля. Подобно первичному ключу, внешний ключ может иметь любое число полей, которые все обрабатываются как единый модуль. Внешний ключ и родительский ключ, на который он ссылается, конечно же, должны иметь одинаковый номер и тип поля и находиться в одинаковом заказе. Внешние ключи, состоящие из одного поля, - те, что мы использовали в наших типовых таблицах, - наиболее распространённые.
Чтобы сохранить простоту нашего обсуждения, мы будем часто говорить о внешнем ключе как об одиночном столбце. Это не случайно. Если это не отметить, любой скажет о поле, которое является внешним ключом, что это также относится и к группе полей, которые являются внешними ключами.
Когда поле является внешним ключом, оно определённым образом связано с таблицей, на которую оно ссылается. Вы фактически говорите: "каждое значение в этом поле (внешнем ключе) непосредственно привязано к значению в другом поле (родительском ключе)." Каждое значение (каждая строка) внешнего ключа должно недвусмысленно ссылаться на одно, и только это, значение (строку) родительского ключа. Если это так, то ваша система, как говорится, будет в состоянии справочной целостности.
Вы можете увидеть это на примере. Внешний ключ snum в таблице Заказчиков имеет значение 1001 для строк Hoffman и Clemens.
Предположим, что мы имели две строки в таблице Продавцов со значением в поле snum = 1001. Как мы узнаем, к которому из двух продавцов были назначены заказчики Hoffman и Clemens? Аналогично, если нет никаких таких строк в таблице Продавцов, мы получим Hoffman и Clemens, назначенными к продавцу, которого нет!
Понятно, что каждое значение во внешнем ключе должно быть представлено один, и только один, раз в родительском ключе.
Фактически данное значение внешнего ключа может ссылаться только к одному значению родительского ключа, не предполагая обратной возможности: т.е. любое число внешних ключей может ссылаться на единственное значение родительского ключа. Вы можете увидеть это в типовых таблицах наших примеров. И Hoffman, и Clemens назначены к Peel, так что оба их значения внешнего ключа совпадают с одним и тем же родительским ключом, что очень хорошо. Значение внешнего ключа должно ссылаться только на одно значение родительского ключа, зато на одно значение родительского ключа может ссылаться с помощью любого количества значений внешнего ключа.
SQL поддерживает справочную целостность с ограничением FOREIGN KEY. Хотя ограничение FOREIGN KEY это новая особенность в SQL, оно ещё не обеспечивает его универсальности. Кроме того, некоторые его реализации более сложны, чем другие. Эта функция должна ограничивать значения, которые вы можете ввести в вашу БД, чтобы заставить внешний ключ и родительский ключ соответствовать принципу справочной целостности.
Одно из действий ограничения Внешнего Ключа - отбрасывание значений для полей, ограниченных как внешний ключ, который ещё не представлен в родительском ключе. Это ограничение также воздействует на вашу способность изменять или удалять значения родительского ключа (мы будем обсуждать это позже в этой главе).
Вы используете ограничение FOREIGN KEY в команде CREATE TABLE (или ALTER TABLE), содержащей поле, которое вы хотите объявить внешним ключом. Вы даёте имя родительскому, ключу на которое вы будете ссылаться внутри ограничения FOREIGN KEY. Помещение этого ограничения в команду - такое же, что и для других ограничений, обсуждённых в предыдущей главе.
Подобно большинству ограничений, оно может быть ограничением таблицы или столбца, в форме таблицы, позволяющей использовать многочисленные поля как один внешний ключ.
Синтаксис ограничения таблицы FOREIGN KEY:
FOREIGN KEY <column list> REFERENCES <pktable> [<column list>]
Первый список столбцов это список из одного или более столбцов таблицы,
которые разделены запятыми и будут созданы или изменены этой командой.
Pktable
это таблица, содержащая родительский ключ. Она может быть таблицей, которая
создаётся или изменяется текущей командой.
Второй список столбцов это список
столбцов, которые будут составлять родительский ключ.
Списки двух столбцов должны быть совместимы, т.е.:
* Они должны иметь одинаковое число столбцов.
* В данной последовательности первый, второй, третий, и т.д. столбцы списка столбцов внешнего ключа должны иметь те же типы данных и размеры, что и первый, второй, третий, и т.д. столбцы списка столбцов родительского ключа. Столбцы в списках обоих столбцов не должны иметь одинаковых имён, хотя мы и использовали такой способ в наших примерах чтобы сделать связь более понятной.
Создадим таблицу Заказчиков с полем snum, определённым в качестве внешнего ключа, ссылающегося на таблицу Продавцов:
CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY cname char(10), city char(10), snum integer, FOREIGN KEY (snum) REFERENCES Salespeople (snum);
Имейте в виду, что, при использовании ALTER TABLE вместо CREATE TABLE для применения ограничения FOREIGN KEY, значения, которые вы указываете во внешнем ключе и родительском ключе, должны быть в состоянии справочной целостности. Иначе команда будет отклонена. Хотя ALTER TABLE очень полезна из-за её удобства, вы должны будете в вашей системе, по возможности, каждый раз сначала формировать структурные принципы, типа справочной целостности.
Вариант ограничения столбца ограничением FOREIGN KEY по-другому называется ссылочное ограничение (REFERENCES), так как оно фактически не содержит в себе слов FOREIGN KEY, а просто использует слово REFERENCES и далее имя родительского ключа, как здесь:
CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY, cname char(10), city char(10), snum integer REFERENCES Salespeople (snum));
Поле Customers.snum определяется как внешний ключ, у которого родительский ключ - Salespeople.snum. Это эквивалентно такому ограничению таблицы:
FOREIGN KEY (snum) REGERENCES Salespeople (snum)
Используя ограничение 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);
Это средство встраивалось в язык, чтобы поощрять вас использовать первичные ключи в качестве родительских ключей.
Поддержание справочной целостности требует некоторых ограничений на значения, которые могут быть представлены в полях, объявленных как внешний ключ и родительский ключ. Родительский ключ должен быть структурирован, чтобы гарантировать, что каждое значение внешнего ключа будет соответствовать одной указанной строке. Это означает, что он (ключ) должен быть уникальным и не должен содержать никаких пустых значений (NULL). Этого недостаточно для родительского ключа в случае выполнения такого требования, как при объявлении внешнего ключа. SQL должен быть уверен, что двойные значения или пустые значения (NULL) не были введены в родительский ключ. Следовательно, вы должны убедиться, что все поля, которые используются как родительские ключи, имеют или ограничение PRIMARY KEY, или ограничение UNIQUE, наподобие ограничения NOT NULL.
Ссылка ваших внешних ключей только на первичные ключи, как мы это делали в типовых таблицах, - хорошая стратегия. Когда вы используете внешние ключи, вы связываете их не просто с родительскими ключами, на которые они ссылаются; вы связываете их с определённой строкой таблицы, где этот родительский ключ будет найден. Сам по себе родительский ключ не обеспечивает никакой информации, которая не была бы уже представлена во внешнем ключе. Смысл, например, поля snum как внешнего ключа в таблице Заказчиков - это связь, которую оно обеспечивает, но не со значением поля snum, на которое он ссылается, а с другой информацией в таблице Продавцов. Такой, например, как имена продавцов, их местоположение и так далее. Внешний ключ это не просто связь между двум идентичными значениями; это связь - с помощью этих двух значений - между двум строками таблицы, указанной в запросе.
Это поле snum может использоваться, чтобы связывать любую информацию в строке из таблицы Заказчиков со ссылочной строкой из таблицы Продавцов, например, чтобы узнать, живут ли они в том же самом городе, кто имеет более длинное имя, имеет ли продавец кроме данного заказчика каких-то других заказчиков, и так далее.
Так как цель первичного ключа состоит в том, чтобы идентифицировать
уникальность строки, это более логичный и менее неоднозначный выбор для внешнего
ключа. Для любого внешнего ключа, который использует уникальный ключ как
родительский ключ, вы должны создать внешний ключ, который использовал
бы первичный ключ той же самой таблицы для того же самого действия.
Внешний ключ,
который не имеет никакой другой цели, кроме связывания строк, напоминает
первичный ключ, используемый исключительно для идентификации строк, и является
хорошим средством сохранить структуру вашей БД ясной и простой и, следовательно,
создающей меньше трудностей.
Внешний ключ, в частности, может содержать только те значения, которые фактически представлены в родительском ключе, или пустые (NULL). Попытка ввести другие значения в этот ключ будет отклонена.
Вы можете объявить внешний ключ как NOT NULL, но это не обязательно и, в большинстве случаев, нежелательно. Например, предположим, что вы вводите заказчика, не зная заранее, к какому продавцу он будет назначен. Лучший выход в этой ситуации - использовать значение NOT NULL, которое должно быть изменено позже на конкретное значение.
Давайте условимся, что все внешние ключи созданные в наших таблицах примеров, объявлены и предписаны с ограничениями внешнего ключа следующим образом:
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);
Имеется несколько атрибутов таких определений, о которых нужно поговорить.
Причина, по которой мы решили сделать поля cnum и snum в таблице Заказов единым
внешним ключом, это гарантия того, что для каждого заказчика, содержащегося в
заказах, продавец, кредитующий этот заказ - тот же, что и указанный в таблице
Заказчиков.
Чтобы создать такой внешний ключ, мы должны были бы поместить
ограничение таблицы UNIQUE в два поля таблицы Заказчиков, даже если оно
необязательно для самой этой таблицы. Пока поле cnum в этой таблица имеет
ограничение PRIMARY KEY, оно будет уникально в любом случае, и, следовательно,
невозможно получить ещё одну комбинацию поля cnum с каким-то другим полем.
Создание внешнего ключа таким способом поддерживает целостность БД (даже если при этом вам будет запрещено внутреннее прерывание по ошибке) и кредитование любого продавца, отличного от того, который назначен именно этому заказчику.
С точки зрения поддержания целостности БД, внутренние прерывания (или исключения), конечно же, нежелательны. Если вы их допускаете и, в то же время, хотите поддерживать целостность вашей БД, вы можете объявить поля snum и cnum в таблице Заказов независимыми внешними ключами этих полей в таблице Продавцов и таблице Заказчиков, соответственно.
Фактически не обязательно использовать поля snum в таблице Заказов, как это делали мы, хотя это полезно было сделать для разнообразия. Поле cnum, связывая каждый заказ заказчиков в таблице Заказчиков и в таблице Заказов, должно всегда быть общим, чтобы находить правильное поле snum для данного заказа (не разрешая никаких исключений). Это означает, что мы записываем фрагмент информации - какой заказчик назначен к какому продавцу - дважды, и нужно будет выполнять дополнительную работу чтобы удостовериться, что обе версии согласуются.
Если мы не имеем ограничения внешнего ключа, как сказано выше, эта ситуация будет особенно проблематична, потому что каждый порядок нужно будет проверять вручную (вместе с запросом), чтобы удостовериться, что именно соответствующий продавец кредитовал каждую соответствующую продажу. Наличие такого типа информационной избыточности в вашей БД называется деморализация (denormalization), что нежелательно в идеальной реляционной базе данных, хотя практически и может быть разрешено. Деморализация может заставить некоторые запросы выполняться быстрее, поскольку запрос в одной таблице выполняется всегда значительно быстрее, чем в объединении.
Как ограничения воздействуют на возможность и невозможность использования команды модификации 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.
Как было упомянуто ранее, ограничение FOREIGN KEY может представить имя это в частной таблице, как таблице родительского ключа. Будучи далеко не простой, эта особенность может пригодиться.
Предположим, что мы имеем таблицу Employees с полем manager (администратор). Это поле содержит номера каждого из служащих, некоторые из которых являются ещё и администраторами. Но, так как каждый администратор одновременно является служащим, то он, естественно, будет также представлен и в этой таблице.
Давайте создадим таблицу, где номер служащего (столбец с именем empno), объявляется как первичный ключ, а администратор как внешний ключ будет ссылаться на нее:
CREATE TABLE Employees (empno integer NOT NULL PRIMARY KEY, name char(10) NOT NULL UNIOUE, manager integer REFERENCES Employees);
(Так как внешний ключ это ссылаемый первичный ключ таблицы, список столбцов может быть исключен.)
Имеется содержание этой таблицы:
EMPNO NAME MANAGER 1003 Terrence 2007 2007 Atali NULL 1688 McKenna 1003 2002 Collier 2007
Как вы видите, каждый из них (но не Atali) ссылается на другого служащего в таблице как на своего администратора. Atali, имеющий наивысший номер в таблице, должен иметь значение, установленное в NULL. Это дает другой принцип справочной целостности. Внешний ключ, который ссылается обратно на частную таблицу, должен позволять значения = NULL. Если это не так, то как бы вы могли вставить первую строку? Даже если эта первая строка ссылается на саму себя, значение родительского ключа должно уже быть установлено, когда вводится значение внешнего ключа. Этот принцип будет верен, даже если внешний ключ ссылается обратно к частной таблице не напрямую, а с помощью ссылки к другой таблице, которая затем ссылается обратно к таблице внешнего ключа.
Например, предположим, что наша таблица Продавцов имеет дополнительное поле, которое ссылается на таблицу Заказчиков так, что каждая таблица ссылается на другую, как показано в следующем операторе CREATE TABLE:
CREATE TABLE Salespeople (snum integer NOT NULL PRIMARY KEY, sname char(10) NOT NULL, city char(10), comm declmal, cnum integer REFERENCES Customers); CREATE TABLE Customers (cnum integer NOT NULL PRIMARY KEY, cname char(10) NOT NULL, city char(10), rating integer, snum integer REFERENCES Salespeople);
Это называется перекрестной ссылкой. SQL поддерживает это теоретически, но
практически это может составить проблему. Любая таблица из этих двух, созданная
первой, является ссылочной таблицей, которая ещё не существует для другой. В
интересах обеспечения перекрестной ссылки, SQL фактически позволяет это, но
никакая таблица не будет пригодна для использования, пока они обе находятся в
процессе создания.
С другой стороны, если эти две таблицы создаются различными
пользователями, проблема становится ещё более трудной. Перекрестна ссылка может
стать полезным инструментом, но она не без неоднозначности и опасностей.
Предшествующий пример не совсем пригоден для использования, потому что он
ограничивает продавца одиночным заказчиком, и, кроме того, совсем не обязательно
использовать перекрёстную ссылку, чтобы достичь этого.
Мы рекомендуем чтобы вы были осторожны в его использовании и анализировали, как ваши программы управляют эффектами модификации и удаления, а также процессами привилегий и диалоговой обработки запросов, перед тем как вы создаёте перекрестную систему справочной целостности. (Привилегии и диалоговая обработка запросов будут обсуждаться, соответственно, в Главах 22 и 23.)
Теперь вы имеете достаточно хорошее управление справочной целостностью. Основная идея в том, что все значения внешнего ключа ссылаются на указанную строку родительского ключа. Это означает, что каждое значение внешнего ключа должно быть представлено один раз, и только один раз, в родительском ключе.
Всякий раз, когда значение помещается во внешний ключ, родительский ключ проверяется, чтобы удостовериться, что его значение представлено; иначе команда будет отклонена.
Родительский ключ должен иметь Первичный Ключ (PRIMARY KEY) или Уникальное (UNIQUE) ограничение, гарантирующее, что значение не будет представлено более чем один раз. Попытка изменить значение родительского ключа, которое в настоящее время представлено во внешнем ключе, будет вообще отклонена. Ваша система может, однако, предложить вам выбор, чтобы получить значение внешнего ключа, установленного в NULL или для получения нового значения родительского ключа и указания, какой из них может быть получен независимо для команд UPDATE и DELETE.
Этим завершается наше обсуждение команды CREATE TABLE. Далее мы представим вам другой тип команды CREATE. В Главе 20 вы обучитесь представлению объектов данных, которые выглядят и действуют подобно таблице, но в действительности являются результатами запросов. Некоторые функции ограничений могут также выполняться представлениями, так что вы сможете лучше оценить вашу потребность в ограничениях, после того как вы прочитаете следующие три главы.
1. Создайте таблицу с именем Cityorders. Она должна содержать такие же поля onum, amt и snum, что и таблица Заказов, и такие же поля cnum и city, что и таблица Заказчиков, так что заказ каждого заказчика будет вводиться в эту таблицу вместе с его городом. Поле оnum будет первичным ключом Cityorders. Все поля в Cityorders должны иметь ограничения при сравнении с таблицами Заказчиков и Заказов. Допускается, что родительские ключи в этих таблицах уже имеют соответствующие ограничения. 2. Усложним проблему. Переопределите таблицу Заказов следующим образом: добавьте новый столбец с именем prev, который будет идентифицирован для каждого заказа, поле onum предыдущего заказа для этого текущего заказчика. Выполните это с использованием внешнего ключа, ссылающегося на саму таблицу Заказов. Внешний ключ должен ссылаться также на поле cnum заказчика, обеспечивающее определенную предписанную связь между текущим порядком и ссылаемым. (См. ответы в Приложении A.)
Эта глава знакомит вас со структурой языка SQL, а также с некоторыми общими понятиями, такими как типы данных, которые поля могут содержать, и некоторыми неоднозначностями, которые существуют в SQL. Она должна обеспечить связь с более конкретной информацией в последующих главах. Вы не должны запоминать каждую подробность, упомянутую в этой главе.
Здесь дан лишь краткий обзор; многие подробности даны, чтобы впоследствии обращаться к ним по мере овладения языком. Мы поместили всё это в начало книги, чтобы ориентировать вас на мир SQL без упрощенного подхода к его проблемам и, в тоже время, дать вам повторяющиеся в дальнейшем места для ссылки на них, когда у вас появятся вопросы. Этот материал может стать более понятным, когда мы перейдём к описанию конкретных команд SQL в Главе 3.
SQL это язык, ориентированный специально на реляционные базы данных (РБД). Он
выполняет большую работу, которую вы должны были бы делать, если бы использовали
универсальный язык программирования, например C. Чтобы сформировать РБД на C,
вам необходимо было бы начать с нуля. Вы должны были бы определить
объект, называемый таблицей, которая могла бы увеличиваться, чтобы иметь любое
число строк, а затем создавать постепенно процедуры для вставки и извлечения
значений.
Если бы вы захотели найти некоторые конкретные строки, вам
необходимо было бы выполнить по шагам процедуру, подобную следующей:
(Конечно, это не фактический набор C-команд, а только логика шагов, которые должны были бы быть включены в реальную программу.) SQL сделает всё это за вас. Команды в SQL могут работать со всеми группами таблиц как с единым объектом и могут обрабатывать любое количество информации, извлечённой или полученной из них в виде единого модуля.
Как мы уже сказали во Введении, стандарт SQL определяется с помощью кода ANSI
(Американский Национальный Институт Стандартов). SQL не изобретался ANSI. Это,
по существу, изобретение IBM. Но другие компании подхватили SQL сразу же. По
крайней мере одна компания (Oracle) отвоевала у IBM право на рыночную продажу SQL-продуктов.
После того как появился ряд конкурирующих программ SQL на рынке,
ANSI определил стандарт, к которому они должны быть приведены. (Определение таких
стандартов и является функцией ANSI). Однако после этого появились некоторые
проблемы. Возникли они, в результате стандартизации ANSI, в виде некоторых
ограничений. Так как не всегда ANSI определяет то, что является наиболее
полезным, то программы пытаются соответствовать стандарту ANSI, не позволяя ему
ограничивать их слишком сильно. Это, в свою очередь, ведет к случайным
несогласованностям. Программы Баз Данных обычно придают ANSI SQL дополнительные
особенности и часто ослабляют многие ограничения.
Поэтому распространённые разновидности ANSI будут также рассмотрены. Хотя мы,
очевидно, не сможем рассмотреть каждое исключение или разновидность, удачные
идеи имеют тенденцию к внедрению и использованию в различных программах, даже
когда они не определены стандартом ANSI. ANSI это вид минимального стандарта,
и вы можете делать больше, чем он позволяет, хотя и должны выполнять его
указания при выполнении задач, которые он определяет.
Имеются два SQL: Интерактивный и Вложенный. Большей частью обе формы работают одинаково, но используются различно. Интерактивный SQL используется для функционирования непосредственно в базе данных, чтобы производить вывод для использования его заказчиком. В этом SQL - когда вы введёте команду, она сейчас же выполнится, и вы сможете сразу увидеть вывод (если он вообще получится).
Вложенный SQL состоит из команд SQL, помещённых внутри программ, которые обычно написаны на другом языке (типа КОБОЛа или ПАСКАЛя). Это делает такие программы более мощными и эффективным.
Однако, допуская эти языки, приходится иметь дело со структурой SQL и стилем управления данных, который требует некоторых расширений интерактивного SQL. Передача SQL-команд во вложенный SQL является пропускаемой ("passed off") для переменных или параметров, используемых программой, в которую они были вложены.
В этой книге мы будем представлять SQL в интерактивной форме. Это даст нам возможность обсуждать команды и их действия, не заботясь о том, как они связаны с помощью интерфейса с другими языками. Интерактивный SQL это форма, наиболее полезная для непрограммистов. Всё, что вы узнаете относительно интерактивного SQL, в основном применимо и к вложенной форме. Изменения, необходимые для использования вложенной формы, будут рассмотрены в последней главе этой книги.
И в интерактивной, и во вложенной формах SQL имеются многочисленные части, или подразделы. Так как вы, вероятно, столкнетесь с этой терминологией при чтении SQL, мы дадим некоторые пояснения. К сожалению, эти термины не используются повсеместно во всех реализациях. Они указаны ANSI и полезны на концептуальном уровне, но большинство SQL-программ практически не обрабатывают их отдельно, так что они, по существу, становятся функциональными категориями команд SQL.
DDL (Язык Определения Данных) - так называемый Язык Описания Схемы в ANSI - состоит из команд, которые создают объекты (таблицы, индексы, просмотры и так далее) в базе данных.
DML (Язык Манипулирования Данными) это набор команд, которые определяют, какие значения представлены в таблицах в любой момент времени.
DCD (Язык Управления Данными) состоит из средств, которые определяют, разрешить ли пользователю выполнять определённые действия, или нет. Они являются составными частями DDL в ANSI.
Не забывайте эти названия. Это не различные языки, а разделы команд SQL, сгруппированные по их функциям.
Не все типы значений, которые могут находиться в полях таблицы, логически
одинаковы. Наиболее очевидное различие - между числами и текстом. Вы не можете
помещать числа в алфавитном порядке или вычитать одно имя из другого.
Так как системы с РБД базируются на связях между фрагментами
информации, различные типы данных должны отличаться друга от друга так, чтобы
соответствующие процессы и сравнения могли быть в них выполнены. В SQL это
делается с помощью назначения каждому полю типа данных, указывающего тип
значения, которое это поле может содержать. Все значения в данном поле должны
иметь одинаковый тип. В таблице Заказчиков, например, cname и city содержат
строки текста для оценки, а snum и cnum это числа. По этой причине вы не
можете ввести значение Highest (Наивысший) или значение None (Никакой) в поле
rating, которое имеет числовой тип данных. Это ограничение удачно, так как оно
налагает некоторую структурность на ваши данные. Вы часто будете сравнивать
некоторые или все значения в данном поле, поэтому вы можете выполнять действие
только на определенных строках, а не на всех. Вы не могли бы сделать этого, если бы значения полей имели смешанный тип данных.
К сожалению, определение этих типов данных является основной областью, в которой большинство коммерческих программ БД и официальный стандарт SQL не всегда совпадают. ANSI SQL-стандарт распознаёт только text и number, в то время как большинство коммерческих программ используют другие специальные типы. Такие как DATA (ДАТА) и TIME (ВРЕМЯ) - фактически, почти стандартные типы (хотя точный формат их меняется). Некоторые пакеты также поддерживают такие типы как, например, MONEY (ДЕНЬГИ) и BINARY (ДВОИЧНЫЙ). (MONEY это специальная "валютная" система исчисления, используемая компьютерами.)
Вся информация в компьютере передается двоичными числами, а затем преобразовывается в другие системы, чтобы мы могли легко использовать их и понимать.
ANSI определяет несколько числовых типов, различия между которыми довольно тонки, и иногда их путают. Разрешённые ANSI-типы данных перечислены в Приложении B. Сложность числовых типов ANSI можно, по крайней мере частично, объяснить усилием сделать вложенный SQL совместимым с рядом других языков. Два типа чисел ANSI - INTEGER (ЦЕЛОЕ ЧИСЛО) и DECIMAL (ДЕСЯТЕРИЧНОЕ ЧИСЛО) (которые можно сокращать как INT и DEC, соответственно), будут адекватны для наших целей, так же как и для целей большинства практических деловых прикладных программ. Естественно, что тип ЦЕЛОЕ можно представить как ДЕСЯТЕРИЧНОЕ ЧИСЛО, которое не содержит никаких цифр справа от десятичной точки.
Тип для текста - CHAR (или СИМВОЛ), относящийся к строке текста. Поле типа
CHAR имеет длину, определяемую максимальным числом
символов, которые могут быть введены в это поле. Большая часть реализаций также
имеют нестандартный тип, называемый VARCHAR (ПЕРЕМЕННОЕ ЧИСЛО СИМВОЛОВ), который
является текстовой строкой и может иметь любую длину до определённого
реализацией максимума (обычно 254 символа). Значения CHARACTER и VARCHAR
включаются в одиночные кавычки как 'текст'. Различие между CHAR и VARCHAR в том,
что CHAR должен резервировать достаточное количество памяти для максимальной
длины строки, а VARCHAR распределяет память по мере необходимости.
Символьные типы состоят из всех печатных символов, включая числа. Однако число 1 это не то
же, что символ "1". Символ "1" - только печатный фрагмент текста, не
определяемый системой как числовое значение 1. Например 1 + 1 = 2, но
"1" + "1" не равно "2". Символьные значения сохраняются в компьютере как
двоичные значения, но показываются пользователю как печатный текст.
Преобразование выполняется по формату, определяемому системой, которую вы используете. Этот формат преобразования будет одним из двух стандартных типов (возможно, с расширениями), используемых в компьютерных системах: ASCII-код (используемый во всех персональных и малых компьютерах) и EBCDIC-код (Расширенном Двоично-Десятеричном Код Обмена Информации) (используемый в больших компьютерах). Определенные операции, такие как упорядочивание в алфавитном порядке значений поля, будет изменяться вместе с форматом. Применение этих двух форматов будет обсуждаться в Главе 4.
Мы должны следить за рынком, а не за ANSI, в использовании типа DATE (ДАТА). (В системе, которая не распознает тип ДАТА, вы, конечно, можете объявить дату как символьное или числовое поле, но это сделает большинство операций более трудоёмкими.)
Вы должны просмотреть свою документацию по пакету программ, который вы будете использовать, чтобы выяснить точно, какие типы данных он поддерживает.
Вы можете понять из предшествующего обсуждения, что имеются самостоятельные отдельные несоответствия между продуктами мира SQL. SQL появился из коммерческого мира БД как инструмент и был позже превращён в стандарт ANSI. К сожалению, ANSI не всегда определяет наиболее полезное, поэтому программы пытаются соответствовать стандарту ANSI, не позволяя ему ограничивать их слишком сильно. ANSI - вид минимального стандарта - вы можете делать, больше чем он это позволяет, но вы должны быть способны получить те же самые результаты при выполнении той же самой задачи.
SQL обычно находится в компьютерных системах, которые имеют больше чем одного
пользователя и, следовательно, должен различать их (ваше семейство PC может
иметь любое число пользователей, но оно обычно не имеет способов, чтобы отличать
одного пользователя от другого).
Обычно в такой системе каждый пользователь имеет некий вид
кода проверки прав, который идентифицирует его или её (терминология изменяется).
В начале сеанса с компьютером пользователь входит в систему (регистрируется),
сообщая компьютеру, кто этот пользователь, идентифицируясь с помощью
определенного ID (Идентификатора). Любое количество людей, использующих ID доступа, являются отдельными пользователями; и, аналогично, один человек
может представлять большое количество пользователей (в разное время), используя
различные идентификаторы доступа к SQL. Действия в большинстве сред SQL
приведены к специальному Идентификатору доступа, который точно соответствует
определённому пользователю. Таблица или другой объект принадлежит пользователю,
который имеет над ним полную власть. Пользователь может или может не иметь
привилегии на выполнение действия над объектом. Для наших целей мы договоримся,
что любой пользователь имеет необходимые привилегии для выполнения любого
действия, пока мы не возвратимся специально к обсуждению привилегий в
Главе 22. Специальное значение USER (ПОЛЬЗОВАТЕЛЬ)
может использоваться как аргумент в команде. Оно указывает на доступный
Идентификатор пользователя, выдавшего команду.
Ключевые слова это слова, которые имеют специальное значение в SQL. Они могут быть командами, но не текстом и не именами объектов. Мы будем выделять ключевые слова, печатая их ЗАГЛАВНЫМИ БУКВАМИ. Вы должны быть внимательны, чтобы не путать ключевые слова с терминами.
В SQL есть определённые специальные термины, которые используются для его описания. Среди них такие слова как "запрос", "предложение" и "предикат", которые являются важнейшими в описании и понимании языка, но не означают что-нибудь самостоятельное для SQL.
Команды или предложения являются инструкциями, с помощью которых вы обращаетесь к БД SQL.
Команды состоят из одной или более отдельных логических частей, называемых предложениями.
Предложения начинаются ключевым словом и состоят из ключевых слов и аргументов. Например, предложения, с которыми вы можете сталкиваться, это "FROM Salespeope" и "WHERE city = "London"". Аргументы завершают или изменяют значение предложения. В примерах выше, Salespeople - аргумент, а FROM - ключевое слово предложения FROM. Аналогично "city = "London"" - аргумент предложения WHERE.
Объекты это структуры БД, которым даны имена и которые сохраняются в памяти. Сюда относятся базовые таблицы, представления и индексы.
Чтобы показать вам, как формируются команды, мы будем делать это на
примерах. Имеется, однако, более формальный метод описания команд, использующий
стандартизированные условные обозначения. Мы будем использовать его в
последующих главах для удобства, чтобы понимать эти условные обозначения в случае,
если вы столкнетесь с ними в других SQL-документах.
Квадратные скобки ( [ ] ) будут указывать части, которые могут не использоваться, а многоточия ( ... )
указывают, что всё, предшествующее им, может повторяться любое число раз. Слова в
угловых скобках (< >) - специальные термины, которые объясняют, что они собой
представляют. Мы значительно упростили стандартную терминологию SQL, но без ухудшения его понимания.
Мы кратко рассмотрели в этой главе основы. Но нашим намерением и было - бегло рассмотреть основы SQL так, чтобы вы смогли охватить весь объём информации. Когда мы возвратимся к основам в следующей главе, некоторые вещи будут конкретизированы.
Теперь вы знаете кое-что относительно SQL: какова его структура, как он используется, как он представляет данные и как они определяются (и некоторые несогласованности, появляющиеся при этом), некоторые условные обозначения и термины, используемые для их описания. Всё это - слишком большой объём информации для одной главы; мы не ожидаем, что вы запомнили все эти подробности, но вы сможете вернуться к ним позже, если понадобится.
Главе 3 мы будем работать, показывая конкретно, как формируются команды и что они делают. Мы представим вам команду SQL, используемую для извлечения информации из таблиц, которая является наиболее часто используемой командой SQL. К концу главы вы будете способны извлекать конкретную информацию из вашей БД с высокой степенью точности.
Какое наибольшее основное различие между типами данных в SQL?
Распознает ANSI тип данных DATA?
Какой подраздел SQL используется, чтобы помещать значения в таблицы?
Что такое - ключевое слово?
(См. ответы в Приложении A.)
ПРЕДСТАВЛЕНИЕ (VIEW) (я называю его также "просмотр" - прим. ред.) это объект данных, который не содержит никаких данных его владельца. Это тип таблицы, чьё содержание выбирается из других таблиц с помощью выполнения запроса. По мере изменения значений в таблицах, эти изменения автоматически отражаются представлением.
В этой главе вы узнаете, что такое представления, как они создаются, и немного о их возможностях и ограничениях. Использование представлений, основанных на улучшенных средствах запросов, таких как объединение и подзапрос, разработанных очень тщательно, в некоторых случаях даст больший выигрыш по сравнению с запросами.
Таблицы, с которыми вы имели дело до сих пор, назывались базовыми таблицами. Это таблицы, которые содержат данные. Однако имеется другой вид таблиц - представления. Представления это таблицы, чьё содержание выбирается или получается из других таблиц. Они работают в запросах и операторах DML точно так же, как и основные таблицы, но не содержат никаких собственных данных.
Представления подобны окнам, через которые вы просматриваете информацию (как она есть, или в другой форме, как вы потом увидите), которая реально хранится в базовой таблице. Представление это фактически запрос, который выполняется всякий раз, когда представление становится темой команды. Вывод запроса при этом в каждый момент становится содержанием представления.
Вы создаёте представление командой 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 не упоминалось в запросе и, следовательно, не было включено в представление.
Представление может теперь изменяться командами модификации DML, но модификация не будет воздействовать на само представление. Команды будут на самом деле перенаправлены в базовую таблицу:
UPDATE Salesown SET city = 'Palo Alto' WHERE snum = 1004;
Его действие идентично выполнению той же команды в таблице Продавцов. Однако, если значение комиссионных продавца будет обработано командой UPDATE
UPDATE Salesown SET comm = .20 WHERE snum = 1004;
она будет отвергнута, так как поле comm отсутствует в представлении Salesown. Это важное замечание, показывающее, что не все представления могут быть модифицированы. Мы будем исследовать проблемы модификации представлений в Главе 21.
В нашем примере поля наших представлений имеют свои имена, полученные прямо из имён полей основной таблицы. Это удобно. Однако вам нужно снабдить ваши столбцы новыми именами:
Имена, которые могут стать именами полей, даются в круглых скобках () после имени таблицы. Они не будут запрошены, если совпадают с именами полей запрашиваемой таблицы. Тип данных и размер этих полей будут отличаться от запрашиваемых полей, которые "передаются" в них. Обычно вы не указываете новых имен полей, но если вы всё-таки сделали это, вы должны делать это для каждого поля в представлении.
Когда вы делаете запрос представления, вы, собственно, выполняете запрос. Основной способ для 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 в синтаксисе запроса.
Групповые представления это представления, наподобие запроса 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-запросы могут дать вам полный комплекс возможностей, так что представления обеспечивают вас чрезвычайно гибким и мощным инструментом чтобы определить точно, как ваши данные могут быть использованы.
Они могут также делать вашу работу более простой, переформатируя данные удобным для вас способом и исключив двойную работу.
Представления не требуют, чтобы их вывод осуществлялся из одной базовой таблицы. Так как почти любой допустимый запрос SQL может быть использован в представлении, оно может выводить информацию из любого числа базовых таблиц или из других представлений. Мы можем, например, создать представление, которое показывало бы заказы продавца и заказчика по имени:
CREATE VIEW Nameorders AS SELECT onum, amt, a.snum, sname, cname FROM Orders a, Customers b, Salespeople c WHERE a.cnum = b.cnum AND a.snum = c.snum;
Теперь вы можете выбрать (SELECT) все заказы заказчика или продавца (*), или можете увидеть эту информацию для любого заказа.
Например, чтобы увидеть все заказы продавца Rifkin, вы должны ввести следующий запрос (вывод показан на Рисунке 20.3):
SELECT * FROM Nameorders WHERE sname = 'Rifkin'; =============== SQL Execution Log ============== | | | SELECT * | | FROM Nameorders | | WHERE sname = 'Rifkin'; | | =============================================== | | onum amt snum sname cname | | ------ -------- ----- ------- ------- | | 3001 18.69 1007 Rifkin Cisneros | | 3006 1098.16 1007 Rifkin Cisneros | | | ================================================ Рисунок 20.3 Заказы Rifkin, показанные в Nameorders
Вы можете также объединять представления с другими таблицами, базовыми таблицами или представлениями, поэтому вы можете увидеть все заказы продавца Axelrod и значения его комиссионных в каждом порядке:
SELECT a.sname, cname, amt comm FROM Nameorders a, Salespeople b WHERE a.sname = 'Axelrod' AND b.snum = a.snum;
Вывод для этого запроса показан на Рисунке 20.4. В предикате мы могли бы написать: "WHERE a.sname = 'Axelrod' AND b.sname = 'Axelrod'", но предикат, который мы использовали здесь, более общеупотребителен. Кроме того, поле snum это первичный ключ таблицы Продавцов, и, следовательно, должно, по определению, быть уникальным.
=============== SQL Execution Log ============== | | | SELECT a.sname, cname, amt * comm | | FROM Nameorders a, Salespeople b | | WHERE a.sname = 'Axelrod' | | AND b.snum = a.snum; | | =============================================== | | onum amt snum sname cname | | ------ -------- ----- ------- ------- | | 3001 18.69 1007 Rifkin Cisneros | | 3006 1098.16 1007 Rifkin Cisneros | | | ================================================ Рисунок 20.4 Объединение основной таблицы с представлением
Если бы там, например, было два Axelrod, вариант с именем будет объединять вместе их данные. Более предпочтительный вариант - использовать поле snum, чтобы хранить его отдельно.
Представления могут также использовать и подзапросы, включая соотнесённые подзапросы. Предположим, ваша компания предусматривает премию для тех продавцов, которые имеют заказчика с самым высоким заказом для любой указанной даты. Вы можете проследить эту информацию с помощью представления:
CREATE VIEW Elitesalesforce AS SELECT b.odate, a.snum, a.sname, FROM Salespeople a, Orders b WHERE a.snum = b.snum AND b.amt = (SELECT MAX (amt) FROM Orders c WHERE c.odate = b.odate);
Если, с другой стороны, премия будет назначаться только продавцу, который имел самый высокий заказ за последние десять лет, вам необходимо будет проследить их в другом представлении, основанном на первом:
CREATE VIEW Bonus AS SELECT DISTINCT snum, sname FROM Elitesalesforce a WHERE 10 < = (SELECT COUNT (*) FROM Elitesalestorce b WHERE a.snum = b.snum);
Извлечение из этой таблицы продавца, который будет получать премию, выполняется простым запросом:
SELECT * FROM Bonus;
Теперь мы видим истинную мощность SQL. Извлечение той же полученной информации программами RPG или COBOL будет более длительной процедурой. В SQL это - только вопрос из двух комплексных команд, сохранённых как представление совместно с простым запросом. При самостоятельном запросе мы должны заботится об этом каждый день, потому что информация которую извлекает запрос, непрерывно меняется, чтобы отражать текущее состояние базы данных.
Имеется большое количество типов представлений (включая многие из наших примеров в этой главе), которые являются доступными только для чтения. Это означает, что их можно запрашивать, но они не могут подвергаться действиям команд модификации. (Мы будем рассматривать эту тему в Главе 21.) Имеются также некоторые виды запросов, которые недопустимы в определениях представлений.
Одиночное представление должно основываться на одиночном запросе.
ОБЪЕДИНИТЬ (UNION) и ОБЪЕДИНИТЬ ВС╗ (UNION ALL) не разрешаются.
УПОРЯДОЧИТЬ ПО (ORDER BY) никогда не используется в определении представлений.
Вывод запроса формирует содержание представления, которое напоминает базовую таблицу и является - по определению - неупорядоченным.
Синтаксис удаления представления из базы данных подобен синтаксису удаления базовых таблиц:
DROP VIEW <имя представления>
В этом нет необходимости, потому что содержимое представления не является созданным и сохраняется на период действия определенной команды. Базовая таблица, из которой представление выводится, не задействуется, когда представление удалено. Помните, вы должны являться владельцем представления, чтобы иметь возможность удалить его.
Теперь, когда вы можете использовать представления, ваша способность отслеживать и обрабатывать содержание БД значительно расширилась. Всё, что вы можете создать запросом, вы всегда сможете определить как представление. Запросы этих представлений это, фактически, запрос запроса. Использование представлений и для удобства, и для защиты также удобно, как и многие возможности представлений для форматирования и получения значений из постоянно меняющегося содержания вашей БД.
Имеется один главный вывод относительно представлений: это способность к модификации.
Как показано, вы можете модифицировать представления так же, как и базовую таблицу, с помощью изменений, применяемых к таблице, из которой получается представление, но это не всегда возможно.
1. Создайте представление, которое показывало бы всех заказчиков, имеющих самые высокие рейтинги. 2. Создайте представление, которое показывало бы номер продавца в каждом городе. 3. Создайте представление, которое показывало бы усреднённый и общий заказы для каждого продавца после его имени. Предполагается, что все имена уникальны. 4. Создайте представление, которое показывало бы каждого продавца с несколькими заказчиками. (См. ответы в Приложении A.)
В этой главе рассказано о командах модификации языка DML - ВСТАВИТЬ (INSERT), ИЗМЕНИТЬ (UPDATE) и УДАЛИТЬ (DELETE) - и о том, когда они применяются для представлений. Как сказано в предыдущей главе, использование команд модификации в представлениях это косвенный способ использования их в ссылочных таблицах с помощью запросов представлений. Однако не все представления могут модифицироваться.
В этой главе мы будем обсуждать правила, определяющие, является ли представление модифицируемым. Кроме того, вы научитесь использовать предложение WITH CHECK OPTION, управляющее указанными значениями, которые можно вводить в таблицу с помощью представления.
Как сказано в Главе 18, это, в некоторых случаях, может быть желательным вариантом непосредственного ограничения таблицы.
Один из наиболее трудных и неоднозначных аспектов представлений - непосредственное их использование с командами модификации 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 это пример представления только_для_чтения: оно
может быть только запрошено, но не изменено.
Если команды модификации могут выполняться в представлении, представление, как уже говорилось, будет модифицируемым; в противном случае оно предназначено только для чтения при запросе. Не противореча этой терминологии, мы будем использовать выражение "модифицировать представление" (updating a view), что означает возможность выполнения в представление любой из трёх команд модификации DML (Вставить, Изменить и Удалить), которые могут изменять значения.
Как вы определите, является ли представление модифицируемым? В теории базы данных, это - пока обсуждаемая тема. Основной принцип таков: модифицируемое представление это представление, в котором команда модификации может выполниться, чтобы изменить одну, и только одну, строку основной таблицы в каждый момент времени, не воздействуя на любые другие строки любой таблицы. Использование этого принципа на практике, однако, затруднено.
Кроме того, некоторые представления, которые теоретически являются модифицируемыми, на самом деле не являются модифицируемыми в SQL.
Вот критерии, по которым определяют, является ли в SQL-представление модифицируемым, или нет:
Одно из этих ограничений - то, что модифицируемые представления фактически подобны окнам в базовых таблицах. Они показывают кое-что, но не обязательно всё, из содержимого таблицы. Они могут ограничивать определенные строки (использованием предикатов) или специально именованные столбцы (с исключениями), но они представляют значения непосредственно и не выводят информацию с использованием составных функций и выражений.
Они также не сравнивают строки таблиц друг с другом (как в объединениях и подзапросах или как с DISTINCT).
Различия между модифицируемыми представлениями и представлениями только_чтение не случайны.
Цели для которых вы их используете, часто различны. Модифицируемые представления в основном используются точно так же, как и базовые таблицы. Фактически пользователи не могут даже осознать, является ли объект, который они запрашивают, базовой таблицей или представлением. Это превосходный механизм защиты для скрытия частей таблицы, которые являются конфиденциальными или не относятся к потребностям данного пользователя. (В Главе 22 мы покажем вам, как разрешить пользователям обращаться к представлению, а не к базовой таблице).
Представления только_чтение, с другой стороны, позволяют вам получать и
переформатировать данные более рационально. Они дают вам библиотеку сложных
запросов, которые вы можете выполнить и повторить снова, сохраняя полученную
вами информацию до последней минуты.
Кроме того, результаты этих запросов в
таблицах, которые могут затем использоваться в запросах самостоятельно
(например, в объединениях), имеют преимущество над просто выполнением запросов.
Представления только_чтение могут также иметь прикладные программы защиты. Например, вы можете захотеть, чтобы некоторые пользователи видели агрегатные данные, такие как усредненное значение комиссионных продавца, а не представление индивидуальных значений комиссионных.
Вот некоторые примеры модифицируемых представлений и представлений только_чтение.
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);
Это - модифицируемое представление.
Другой вывод о модифицируемости представления - вы можете вводить значения, которые "проглатываются" (swallowed) в базовой таблице. Рассмотрим такое представление:
CREATE VIEW Highratings AS SELECT cnum, rating FROM Customers WHERE rating = 300;
Это представление - модифицируемое. Оно просто ограничивает ваш доступ к определенным строкам и столбцам в таблице. Предположим, что вы вставляете (INSERT) следующую строку:
INSERT INTO Highratings VALUES (2018, 200);
Команда INSERT допустима в данном представлении. Строка будет вставлена с помощью представления Highratings в таблицу Заказчиков. Однако, когда она появится там, она исчезнет из представления, поскольку значение оценки не равно 300. Это - обычная проблема.
Значение 200 может быть просто напечатано, но теперь строка находится уже в таблице Заказчиков, где вы не можете даже увидеть её. Пользователь не сможет понять, почему введя строку он не может её увидеть и будет неспособен при этом удалить её. Вы можете быть гарантированы от модификаций такого типа с помощью включения WITH CHECK OPTION (С ОПЦИЕЙ ПРОВЕРКИ) в определение представления. Мы можем использовать WITH CHECK OPTION в определении представления Highratmgs.
CREATE VIEW Highratings AS SELECT cnum, rating FROM Customers WHERE rating = 300 WITH CHECK OPTION;
Вышеупомянутая вставка будет отклонена.
WITH CHECK OPTION производит действие все_или_ничего (all-or-nothing). Вы помещаете его в определение представления, а не в команду DML, так что или все команды модификации в представлении будут проверяться, или ни одна не будет проверена. Обычно вы хотите использовать опцию проверки, применяя её в определении представления, что может быть удобно. В общем, вы должны использовать эту опцию, если у вас нет причины разрешать представлению помещать в таблицу значения, которые оно само не может содержать.
Похожая проблема, о которой вы должны знать, включает в себя вставку строк в представление с предикатом, базирующемся на одном или более исключённых полях. Например, может показаться разумным создание 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;
Еще одно надо упомянуть относительно предложения WITH CHECK OPTION в ANSI: оно не делает каскадированного изменения: оно применяется только в представлениях, в которых оно определено, но не в представлениях, основанных на этом представлении. Например, в предыдущем примере
CREATE VIEW Highratings AS SELECT cnum, rating FROM Customers WHERE rating = 300 WITH CHECK OPTION;
попытка вставить или модифицировать значение оценки, не равное 300, потерпит неудачу. Однако мы можем создать второе представление (с идентичным содержанием), основанное на первом:
CREATE VIEW Myratings AS SELECT * FROM Highratings;
Теперь мы можем модифицировать оценки, не равные 300:
UPDATE Myratings SET rating = 200 WHERE cnum = 2004;
Эта команда, выполняемая так, как если бы она выполнялась как первое представление, будет допустима. Предложение WITH CHECK OPTION просто гарантирует, что любая модификация в представлении произведет значения, которые удовлетворяют предикату этого представления.
Модификация других представлений, базирующихся на первом текущем, является всё ещё допустимой, если эти представления не защищены предложениями WITH CHECK OPTION внутри этих представлений. Даже если такие предложения установлены, они проверяют только те предикаты представлений, в которых они содержатся. Так, например, даже если представление Myratings создавалось следующим образом,
CREATE VIEW Myratings AS SELECT * FROM Highratings WITH CHECK OPTION;
проблема не будет решена. Предложение WITH CHECK OPTION будет исследовать только предикат представления Myratings. Пока у Myratings фактически не имеется никакого предиката, WITH CHECK OPTION ничего не будет делать. Если используется предикат, то он будет проверяться всякий раз, когда представление Myratings будет модифицироваться, но предикат Highratings все равно будет проигнорирован. Это - дефект в стандарте ANSI, который у большинства программ исправлен. Вы можете попробовать использовать представление наподобие последнего примера и посмотреть, избавлена ли ваша система от этого дефекта. (Попытка выяснить это самостоятельно может иногда оказаться проще и яснее, чем поиск ответа в документации системы.)
Вы теперь полностью овладели знаниями о представлениях. Кроме правил, определяющих, является ли данное представление модифицируемыми в SQL, вы познакомились с основными понятиями, на которых эти правила базируются - т.е., что модификации в представлениях допустимы только тогда, когда SQL может недвусмысленно определить, какие значения базовой таблицы можно изменять. Это означает, что команда модификации при выполнении не должна требовать ни изменений для многих строк сразу, ни сравнений между многочисленными строками либо базовой таблицы, либо вывода запроса.
Поскольку объединения включают в себя сравнение строк, они также запрещены.
Вы также поняли различие между некоторыми способами, которые используют модифицируемые представления и представления только_чтение.
Вы научились воспринимать модифицируемые представления как окна, отображающие данные одиночной таблицы, но не обязательно исключающие или реорганизующие столбцы посредством выбора только определенных строк отвечающих условию предиката.
Представления только_чтение, с другой стороны, могут содержать более допустимые запросы SQL; они могут, следовательно, стать способом хранения запросов, которые вам нужно часто выполнять в неизменной форме. Кроме того, наличие запроса, чей вывод обрабатывается как объект данных, дает вам возможность иметь ясность и удобство при создании запросов в выводе запросов.
Вы можете теперь в представлении предотвращать создание командами модификации строк в базовой таблице, которые не представлены в самом представлении, с помощью предложения WITH CHECK OPTION в определения представления.
Вы можете также использовать WITH CHECK OPTION как один из способов ограничения в базовой таблице. В автономных запросах, вы обычно используете один или более столбцов в предикате, не представленные среди выбранных для вывода, что не вызывает никаких проблем. Но если эти запросы используются в модифицируемых представлениях, появляются проблемы, так как эти запросы производят представления, которые не могут иметь вставляемых в них строк.
Вы видели некоторые подходы к этим проблемам. В Главах 20 и 21, мы говорили, что представления имеют прикладные программы защиты. Вы можете дать пользователям возможность обращаться к представлениям, не разрешая в то же время обращаться к таблицам, в которых эти представления непосредственно находятся. Глава 22 будет исследовать вопросы доступа к объектам данных в SQL.
1. Какое из этих представлений - модифицируемое? #1 CREATE VIEW Dailyorders AS SELECT DISTINCT cnum, snum, onum, odate FROM Orders; #2 CREATE VIEW Custotals AS SELECT cname, SUM (amt) FROM Orders, Customers WHERE Orders.cnum = customer.cnum GROUP BY cname; #3 CREATE VIEW Thirdorders AS SELECT * FROM Dailyorders WHERE odate = 10/03/1990; #4 CREATE VIEW Nullcities AS SELECT snum, sname, city FROM Salespeople WHERE city IS NULL OR sname BETWEEN 'A' AND 'MZ'; 2. Создайте представление таблицы Продавцов с именем Commissions (Комиссионные). Это представление должно включать только поля comm и snum. С помощью этого представления можно будет вводить или изменять комиссионные, но только для значений между .10 и .20. 3. Некоторые SQL-реализации имеют встроенную константу, представляющую текущую дату, иногда называемую " CURDATE ". Слово CURDATE может, следовательно, использоваться в операторе SQL и заменяться текущей датой, когда его значение станет доступным, с помощью таких команд как SELECT или INSERT. Мы будем использовать представление таблицы Заказов с именем Entryorders для вставки строк в таблицу Заказов. Создайте таблицу заказов так, чтобы CURDATE автоматически вставлялась в поле odate, если не указано другое значение. Затем создайте представление Entryorders так, чтобы значения не могли быть указаны. (См. ответы в Приложении A.)
В этой главе вы научитесь работать с привилегиями. Как сказано в Главе 2, SQL используется обычно в средах, которые требуют распознавания пользователей и различий между пользователями систем.
Вообще говоря, администраторы баз данных сами создают пользователей и дают им
привилегии.
С другой стороны - пользователи, которые создают таблицы, сами имеют
права на управление этими таблицами. Привилегии это то, что определяет, может ли
указанный пользователь выполнить данную команду. Имеется несколько типов
привилегий, соответствующих нескольким типам операций. Привилегии даются и
отменяются двумя командами SQL: GRANT (ДОПУСК) и REVOKE (ОТМЕНА). Эта глава
покажет вам, как эти команды используются.
Каждый пользователь в среде SQL имеет специальное идентификационное имя или номер. Терминология везде разная, но мы выбрали (следуя ANSI) ссылку на имя или номер как на Идентификатор (ID) доступа. Команда, посланная в базе данных, ассоциируется с определённым пользователем; или иначе - специальным Идентификатором доступа. Поскольку это относится к БД SQL, ID разрешения это имя пользователя, и SQL может использовать специальное ключевое слово USER, которое ссылается на Идентификатор доступа, связанный с текущей командой. Команда интерпретируется и разрешается (или запрещается) на основе информации, связанной с Идентификатором доступа пользователя, подавшего команду.
В системах с многочисленными пользователями имеется некоторый вид процедуры входа в систему, которую пользователь должен выполнить, чтобы получить доступ к компьютерной системе. Эта процедура определяет, какой ID доступа будет связан с текущим пользователем. Обычно каждый человек, использующий БД, должен иметь свой собственный ID доступа и при регистрации превращается в действительного пользователям. Однако часто пользователи, имеющие много задач, могут регистрироваться под различными ID доступа, или наоборот - один ID доступа может использоваться несколькими пользователями. С точки зрения SQL нет никакой разницы между этими двум случаями; он воспринимает пользователя просто как его ID доступа.
БД SQL может использовать собственную процедуру входа в систему или она может позволить другой программе, вроде операционной системы (основная программа которая работает на вашем компьютере), обрабатывать файл регистрации и получать ID доступа из этой программы. Тем или другим способом, но SQL будет иметь ID доступа, чтобы связать его с вашими действиями, а для вас будет иметь значение ключевое слово USER.
Каждый пользователь БД SQL имеет набор привилегий. Это то, что пользователю разрешается делать (возможно это - файл регистрации, который может рассматриваться как минимальная привилегия). Эти привилегии могут изменяться со временем: новые - добавляться, старые - удаляться. Некоторые из этих привилегий определены в ANSI SQL, но имеются и дополнительные привилегии, которые также являются необходимыми.
SQL-привилегий, как определено ANSI, недостаточно в большинстве ситуаций реальной жизни. С другой стороны, типы привилегий, которые необходимы, могут видоизменяться, в зависимости от используемой системы, относительно которой ANSI не может дать никаких рекомендаций. Привилегии, которые не являются частью стандарта SQL, могут использовать похожий синтаксис, не полностью совпадающий со стандартом.
SQL-привилегии, определённые ANSI, это привилегии объекта. Это означает, что пользователь имеет привилегию для выполнения данной команды только на определенном объекте в БД. Очевидно, что привилегии должны различать эти объекты, но система привилегий, основанная исключительно на привилегиях объекта, не может адресовать всё, что нужно SQL, как мы увидим позже в этой главе.
Привилегии объекта связаны одновременно и с пользователями, и с таблицами. То есть привилегия дается определенному пользователю в указанной таблице, базовой таблице или представлении. Вы должны помнить, что пользователь, создавший таблицу (любого вида), является владельцем этой таблицы.
Это означает, что пользователь имеет все привилегии в этой таблице и может передавать привилегии другим пользователям этой таблицы.
Вот привилегии, которые можно назначить пользователю:
SELECT Пользователь может выполнять запросы в таблице. INSERT Пользователь может выполнять команду INSERT в таблице. UPDATE Пользователь может выполнять команду UPDATE в таблице. Вы можете ограничить эту привилегию для определенных столбцов таблицы. DELETE Пользователь с этой привилегией может выполнять команду DELETE в таблице. REFERENCES Пользователь определить внешний ключ, который использует один или более столбцов этой таблицы, как родительский ключ. Вы можете ограничить эту привилегию для определённых столбцов. (Смотрите в Главе 19 подробности относительно внешнего ключа и родительского ключа.)
Кроме того, вы столкнётесь с такими нестандартными привилегиями объектов, как, например, INDEX (ИНДЕКС), дающей право создавать индекс в таблице, SYNONYM (СИНОНИМ), дающей право создавать синоним для объекта, который будет объяснён в Главе 23, и ALTER (ИЗМЕНИТЬ), дающей право выполнять команду ALTER TABLE в таблице.
Механизм SQL назначает пользователям эти привилегии с помощью команды 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 имеет право помещать нового продавца в таблицу.
Вы не обязаны ограничивать себя предоставлением единственной привилегии отдельному пользователю командой GRANT. Списки привилегий или пользователей, разделяемых запятыми, являются совершенно приемлемыми.
Stephen может предоставить и SELECT, и INSERT в таблице Заказов для Adrian:
GRANT SELECT, INSERT ON Orders TO Adrian;
или и для Adrian, и для Diane:
GRANT SELECT, INSERT ON Orders TO Adrian, Diane;
Когда привилегии и пользователи перечислены таким образом, весь список привилегий предоставляется всем указанным пользователям. В строгой ANSI интерпретации вы не можете предоставить привилегии во многих таблицах сразу одной командой, но в некоторых реализациях это ограничение может быть ослаблено, что позволяет указывать несколько таблиц, отделяя их запятыми, так чтобы весь список привилегий мог быть предоставлен для всех указанных таблиц.
Все привилегии объекта используют один и тот же синтаксис, кроме команд 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 поддерживает два аргумента для команды 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 для индивидуальных пользователей.
Иногда создателю таблицы необходимо, чтобы другие пользователи могли получить привилегии в его таблице. Обычно это делается в системах, где один человек или более создают несколько (или все) базовые таблицы в базе данных, а затем передают ответственность за них тем, кто будет фактически с ними работать. SQL позволяет делать это с помощью предложения WITH GRANT OPTION.
Если бы Diane хотела, чтобы Adrian имел право предоставлять привилегию SELECT в таблице Заказчиков другим пользователям, она дала бы ему привилегию SELECT с использованием предложения WITH GRANT OPTION:
GRANT SELECT ON Customers TO Adrian WITH GRANT OPTION;
После этого Adrian получил право передавать привилегию SELECT третьим лицам; он может выдать команду
GRANT SELECT ON Diane.Customers TO Stephen;
или даже
GRANT SELECT ON Diane.Customers TO Stephen WITH GRANT OPTION;
Пользователь с помощью GRANT OPTION в особой привилегии в данной таблице может, в свою очередь, предоставить эту привилегию в той же таблице, с или без GRANT OPTION, любому другому пользователю. Это не меняет принадлежности самой таблицы; как и прежде таблица принадлежит ее создателю. (Поэтому пользователи, получившие права, должны устанавливать префикс ID доступа владельца, когда обращаются к этим таблицам. Следующая глава покажет вам этот способ.) Пользователь же с помощью GRANT OPTION во всех привилегиях для данной таблицы будет иметь всю полноту власти в той таблице.
Также как 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;
Однако здесь имеется некоторая неясность. Кто имеет право отменять
привилегии? Когда пользователь с правом передавать привилегии другим, теряет это
право, пользователи, которым он предоставил эти привилегии, также их потеряют?
Так как это нестандартная особенность, нет никаких авторитетных ответов на эти
вопросы, но наиболее общий подход таков:
Вы можете сделать действия привилегий более точными, используя представления. Всякий раз, когда вы передаёте привилегию в базовой таблице пользователю, она автоматически распространяется на все строки, а при использовании возможных исключений UPDATE и REFERENCES - на все столбцы таблицы.
Создавая представление, которое ссылается на основную таблицу и затем переносит привилегию на представление, а не на таблицу, вы можете ограничивать эти привилегии любыми выражениями в запросе, содержащемся в представлении. Это значительно улучшает базисные возможности команды GRANT.
Чтобы создавать представление, вы должны иметь привилегию 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 в базовую таблицу. Фактически пользователи могли бы сделать запрос обоих представлений в объединении и увидеть все строки сразу.
Вы, разумеется, хотите знать, кто же имеет право первым создать таблицу. Эта область привилегий не относится к ANSI, но не может игнорироваться. Все стандартные привилегии ANSI вытекают из этой привилегии - привилегии создателей таблиц, которые могут передавать привилегии объекта. Если все ваши пользователи будут создавать в системе базовые таблицы с разными размерами, это приведет к избыточности в них и к неэффективности системы.
Возникают и другие вопросы:
Пока ANSI не принимает в этом участия, а SQL используется в различных средах, мы не можем дать окончательный ответ на эти вопросы. Мы предлагаем рассмотреть здесь часть наиболее общих выводов.
Привилегии, которые не определяются в терминах специальных объектов данных,
называются привилегиями системы или правами базы данных. На базисном уровне они
будут, вероятно, включать в себя право создавать объекты данных, возможно,
отличающиеся от базовых таблиц (обычно создаваемых несколькими пользователями) и
представлений (обычно создаваемых большинством пользователей).
Привилегии
системы для создания представлений должны дополнять, а не заменять привилегии
объекта, которые ANSI требует от создателей представлений (описанных ранее в
этой главе). Кроме того, в системе любого размера всегда имеются некоторые типы суперпользователей - пользователей, которые автоматически имеют большинство или
все привилегии и которые могут передать свой статус суперпользователя
кому-нибудь с помощью привилегии или группы привилегий. Администратор Базы
Данных (DBA) является термином наиболее часто используемым для такого
суперпользователя и для привилегий, которыми он обладает.
При обычном подходе имеется три базовых привилегии системы: CONNECT
(Подключить), RESOURCE (Ресурс) и DBA (Администратор Базы Данных). Проще можно
сказать, что CONNECT состоит из права зарегистрироваться и права создавать
представления и синонимы (см. Главу 23), если переданы
привилегии объекта. RESOURCE состоит из права создавать базовые таблицы. DBA это
привилегия суперпользователя, дающая пользователю высокие полномочия в базе
данных. Один или более пользователей с функциями администратора базы данных
может иметь эту привилегию. Некоторые системы кроме того имеют специального
пользователя, иногда называемого SYSADM или SYS (Системный Администратор Базы
Данных), который имеет наивысшие полномочия; это специальное имя, а не просто
пользователь со специальной DBA-привилегией. Фактически только один человек
имеет право зарегистрироваться с именем SYSADM, являющимся его идентификатором
доступа. Различие - весьма тонкое и функционирует по разному в различных
системах.
Для наших целей мы будем ссылаться на высокопривилегированного
пользователя, который разрабатывает БД и управляет ею, имея полномочия DBA, понимая, что фактически эти полномочия - та же самая привилегия. Команда GRANT, в измененной форме,
пригодна для использования как с
привилегиями объекта, так и с системными привилегиями. Для начала передача прав
может быть сделана с помощью DBA. Например, DBA может передать привилегию для
создания таблицы пользователю Rodriguez следующим образом:
GRANT RESOURCE TO Rodriguez;
Естественно, появляется вопрос, откуда возьмётся пользователь с именем Rodriguez? Как определить его ID допуска? В большинстве реализаций DBA создаёт пользователя, автоматически предоставляя ему привилегию CONNECT.
В этом случае обычно добавляется предложение IDENTIFIED BY, указывающее пароль. (Если же нет, операционная система должна определить, можете ли вы зарегистрироваться в БД с данным ID доступа.) DBA может, например, ввести:
GRANT CONNECT TO Thelonius IDENTIFIED BY Redwagon;
что приведет к созданию пользователя с именем Thelonius, даст ему право регистрироваться и назначит ему пароль Redwagon, и всё это в одном предложении.
Раз Thelonious - уже опознанный пользователь, он или DBA могут использовать эту же команду, чтобы изменить пароль Redwagon.
Хотя это и удобно, но всё же имеется ограничение и в этом подходе: невозможность иметь пользователя, который мог бы зарегистрироваться, хотя бы временно. Если вы хотите запретить пользователю регистрироваться, вы должны использовать для REVOKE привилегию CONNECT, которая "удаляет" этого пользователя. Некоторые реализации позволяют вам создавать и удалять пользователей, независимо от их привилегий при регистрации. Когда вы предоставляете привилегию CONNECT пользователю, вы создаете этого пользователя. При этом, чтобы сделать это вам самим, вы должны иметь DBA-привилегию. Если этот пользователь будет создавать базовые таблицы (а не только представления), ему нужно также предоставить привилегию RESOURCE. Но это сразу порождает другую проблему.
Если вы сделаете попытку удалить привилегию CONNECT пользователя, который имеет созданные им таблицы, команда будет отклонена, потому что её действие оставит таблицу без владельца, а это не разрешается. Вы должны сначала удалить все таблицы, созданные этим пользователем, прежде чем удалить его привилегию CONNECT. Если эти таблицы - не пустые, то вы, вероятно, захотите передать их данные в другие таблицы с помощью команды INSERT, которая использует запрос. Вам не нужно удалять отдельно привилегию RESOURSE; достаточно удалить CONNECT, чтобы удалить пользователя.
Хотя всё выше сказанное это вполне стандартный подход к привилегиям системы, он также имеет значительные ограничения. Появились альтернативные подходы, которые более конкретно определены и точнее управляют привилегиями системы.
Это несколько выводит нас за пределы стандарта SQL, как он определён в настоящее время, и, в некоторых реализациях, можно полностью выйти за пределы стандарта SQL. Эти вещи, вероятно, не будут слишком вас касаться, если вы не DBA или не пользователь высокого уровня. Обычные пользователи просто должны быть знакомыми с привилегиями системы в принципе, справляясь в своей документации только в случае специальных сообщений.
Привилегии дают вам возможность видеть SQL под новым углом зрения, когда SQL выполняет действия через специальных пользователей в специальной системе базы данных.
Сама команда GRANT достаточно проста: с её помощью вы предоставляете те или иные привилегии объекта одному или более пользователям. Если вы предоставляете привилегию WITH GRANT OPTION пользователю, этот пользователь может, в свою очередь, предоставить эту привилегию другим. Теперь вы понимаете намеки на использование привилегий в представлениях - чтобы усовершенствовать привилегии в базовых таблицах, или как альтернативы ограничениям - и на некоторые преимущества и недостатки такого подхода.
Привилегии системы, которые необходимы, но не входят в область стандарта SQL, обсуждались в их наиболее общей форме, поэтому вы будете знакомиться с ними на практике. Глава 23 продолжит обсуждение таких вопросов о выводе в SQL как сохранение или восстановление изменений, создание ваших собственных имён для таблиц, принадлежащих другим людям и понимание того, что происходит, когда различные пользователи пытаются обращаться к одному и тому же объекту одновременно.
1. Передайте Janet право на изменение оценки заказчика. 2. Передайте Stephan право передавать другим пользователям право делать запросы в таблице Заказов. 3. Отнимите привилегию INSERT (ВСТАВКА) в таблице Продавцов у Claire и у всех пользователей, которым она была предоставлена. 4. Передайте Jerry право вставлять в или модифицировать таблицу Заказчиков с сохранением его возможности оценивать значения в диапазоне от 100 до 500. 5. Разрешите Janet делать запросы в таблице Заказчиков, но запретите ей уменьшать оценки в той же таблице Заказчиков. (См. ответы в Приложении A.)
В этой главе будут рассмотрены аспекты языка SQL, имеющие отношение к базе данных (БД) как к единому целому, включая использование многочисленных имён для объектов данных, размещение запоминаемых данных, восстановление и сохранение изменений в БД, а также координирование одновременных действий многочисленных пользователей.
Этот материал даст вам возможность сконфигурировать вашу БД, отменять действия ошибок, и определять, как действия одного пользователя в БД будут влиять на действия других пользователей.
Каждый раз, когда вы ссылаетесь в команде на базовую таблицу или представление, не являющиеся вашей собственностью, вы должны установить в команде префикс имени владельца так, чтобы SQL знал, где искать. Так как это со временем становится неудобным, большинство реализаций SQL позволяют вам создавать синонимы для таблиц (что не является стандартом ANSI). Синоним это альтернативное имя, наподобие прозвища.
Когда вы создаёте синоним, вы становитесь его собственником, так что нет никакой необходимости, чтобы он предшествовал другому пользовательскому идентификатору доступа (имени пользователя).
Если вы имеете по крайней мере одну привилегию в одном или более столбцах таблицы, вы можете создать для них синоним. (Некоторое отношение к этому может иметь специальная привилегия для создания синонимов.)
Adrian может создать синоним с именем Clients для таблицы с именем Diane.Customers с помощью команды CREATE SYNONYM:
CREATE SYNONYM Clients FOR Diane.Customers;
Теперь Adrian может использовать таблицу с именем Clients в команде точно так же, как её использует Diane.Customers. Синоним Clients это собственность, используемая исключительно Adrian.
Префикс (прозвище) пользователя это фактически часть имени любой таблицы. Всякий раз, когда вы не указываете ваше имя пользователя вместе с именем вашей собственной таблицы, SQL сам заполняет для вас это место. Следовательно, два одинаковых имени таблицы, но связанные с различными владельцами, становятся неидентичными и не приводят к какому-нибудь беспорядку (по крайней мере в SQL). Это означает, что два пользователя могут создать две полностью не связанные таблицы с одинаковыми именами, но это также будет означать, что один пользователь может создать представление, основанное на имени другого пользователя, стоящем после имени таблицы. Это иногда делается, когда представление рассматривается как сама таблица - например, если представление просто использует CHECK OPTION как заменитель ограничения CHECK в базовой таблице (смотрите подробности в Главе 22).
Вы можете также создавать ваши собственные синонимы, имена которых будут такими же, что и первоначальные имена таблиц.
Например, Adrian может определить Customers как свой синоним для таблицы Diane.Customers:
CREATE SYNONYM Customers FOR Diane.Customers;
С точки зрения SQL теперь имеются два разных имени одной таблицы: Diane.Customers и Adrian.Customers. Однако каждый из этих пользователей может обращаться к этой таблице просто Customers. SQL, как говорилось выше, сам добавит недостающие имена пользователей.
Если вы планируете иметь таблицу Заказчиков, используемую большим числом пользователей, лучше всего, чтобы они обращались к ней с помощью одного и того же имени. Это даст вам возможность, например, использовать это имя в вашем внутреннем общении без ограничений. Чтобы создать единое имя для всех пользователей, вы создаёте общий синоним.
Например, если все пользователи будут вызывать таблицу Заказчиков с именем Customers, вы можете ввести
CREATE PUBLIC SYNONYM Customers FOR Customers;
Мы пронимаем, что таблица Заказчиков, это ваша собственность, поэтому никакого префикса имени пользователя в этой команде не указывается. В основном общие синонимы создаются владельцами объектов или привилегированными пользователями типа DBA. Пользователям, кроме того, должны ещё быть предоставлены привилегии в таблице Заказчиков, чтобы они могли иметь к ней доступ.
Даже если имя является общим, сама таблица общей не является. Общие синонимы становятся собственными с помощью команды PUBLIC, а не с помощью их создателей.
Общие и другие синонимы могут удаляться командой DROP SYNONYM. Синонимы удаляются их владельцами, кроме общих синонимов, которые удаляются соответствующими привилегированными пользователями, обычно DBA.
Чтобы удалить, например, синоним Clients, когда вместо него уже появился общий синоним Customers, Adrian может ввести
DROP SYNONYM Clients;
Сама таблица Заказчиков, естественно, становится недействующей.
Таблицы и другие объекты данных сохраняются в БД и находятся там связанными с определёнными пользователями, которые ими владеют. В некотором смысле вы могли бы сказать, что они сохраняются
в "именной области пользователя", хотя это никак не отражает их физического расположения, но зато, как и большинство вещей в 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, которая воздействует на содержание или структуру БД - например, команда модификации 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.
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 обеспечивает вас средством управления параллелизмом для точного указания места получения результата.
ANSI указывает, для управления параллелизмом, что все одновременные команды будут выполняться по принципу: ни одна команда не должна быть выдана, пока предыдущая не будет завершена (включая команды COMMIT или ROLLBACK). Точнее, нужно просто не позволить таблице быть доступной более чем для одной транзакции в данный момент времени. Однако в большинстве ситуаций необходимость иметь базу данных, доступную сразу многим пользователям, приводит к некоторому компромиссу в управлении параллелизмом.
Некоторые реализации SQL предлагают пользователям выбор, позволяя им самим находить золотую середину между согласованностью данных и доступом к БД. Этот выбор доступен пользователю, DBA, или тому и другому. На самом деле они осуществляют это управление вне SQL, даже если и воздействуют на процесс работы самого SQL.
Механизм, используемый SQL для управления параллелизмом операций, называется блокировкой. Блокировки задерживают определенные операции в БД, пока другие операции или транзакции не завершены. Задержанные операции выстраиваются в очередь и выполняются только тогда, когда блокировка снята (некоторые инструменты блокировок дают вам возможность указывать NOWAIT, которая будет отклонять команду, вместо того чтобы поставить её в очередь, позволяя вам делать что-нибудь другое).
Блокировки в многопользовательских системах необходимы. Следовательно, должен быть некий тип схемы блокировки по умолчанию, которая могла бы применяться ко всем командам в базе данных. Такая схема по умолчанию может быть определена для всей БД, или в качестве параметра в команде CREATE DBSPACE или команде ALTER DBSPACE и таким образом использовать их по-разному в различных DBS.
Кроме того, системы обычно обеспечиваются неким типом обнаружителя зависания, который может обнаруживать ситуации, где две операции имеют блокировки, блокирующие друг друга. В этом случае одна из команд будет прокручена обратно и получит сброс блокировки. Так как терминология и специфика схем блокировок меняются от программы к программе, мы можем смоделировать наши рассуждения на примере программы базы данных DB2 фирмы IBM. IBM - лидер в этой области (как, впрочем, и во многих других), и поэтому такой подход наиболее оправдан. С другой стороны, некоторые реализации могут иметь значительные различия в синтаксисе и в функциях, но в основном их действия должны быть очень похожими.
Имеется два базовых типа блокировок: распределяемые блокировки и специальные блокировки.
Распределяемые (или S-блокировки) могут быть установлены более чем одним пользователем в данный момент времени. Это дает возможность любому числу пользователей обращаться к данным, но не изменять их. Специальные/исключающие блокировки (или X-блокировки) не позволяют никому, кроме владельца этой блокировки, обращаться к данным. Специальные блокировки используются для команд, которые изменяют содержание или структуру таблицы. Они действуют до конца транзакции. Общие блокировки используются для запросов. Насколько они продолжительны, зависит фактически от уровня изоляции.
Что такое уровень изоляции блокировки? Это то, что определяет, сколько таблиц будет блокировано. В DB2 имеется три уровня изоляции, два из которых можно применить и к распределенным, и к специальным блокировкам, а третий, ограниченный, чтобы использовать эти блокировки совместно. Они управляются командами, поданными вне SQL, так что мы можем обсуждать их, не указывая точного синтаксиса. Точный синтаксис команд связанных с блокировками, различен для различных реализаций.
Последующее обсуждение полезно прежде всего на концептуальном уровне. Уровень изоляции "повторное чтение" гарантирует, что внутри данной
транзакции все записи, извлечённые с помощью запросов, не могут быть изменены. Поскольку записи, модифицируемые в транзакции, являются
субъектами специальной блокировки, пока транзакция не завершена, они не могут быть изменены в любом случае.
С другой стороны, для запросов повторное чтение означает, что вы можете решить заранее, какие строки вы хотите заблокировать и выполнить
запрос который их выберет. Выполняя запрос, вы имеете гарантию, что никакие изменения не будут сделаны в этих строках до тех пор, пока вы
не завершите текущую транзакцию.
В то время как повторное чтение защищает пользователя, который поместил блокировку, оно может
одновременно значительно снизить производительность. Уровень указателя стабильности предохраняет каждую запись от изменений на время, когда она читается, или от чтения на время её изменения.
Второй случай это специальная блокировка, и она применяется, пока изменение не завершено или пока оно не отменено
(т.е. на время отката изменения).
Следовательно, когда вы модифицируете группу записей, использующих указатель стабильности, эти записи будут заблокированы, пока транзакция
не закончится, что аналогично действию, производимому уровнем "повторное чтение". Различие между этими двум уровнями
- в их воздействии на запросы. В случае уровня "указатель стабильности", строки таблицы, которые в данное время не используются запросом, могут быть изменены.
Третий уровень изоляции DB2 это уровень "только чтение". "Только чтение" фиксирует фрагмент данных; хотя на самом деле он блокирует всю таблицу. Следовательно, он не может использоваться с командами модификации. Любое содержание таблицы как единого целого в момент выполнения команды будет отражено в выводе запроса. Это не обязательно, как в случае с уровнем указатель стабильности. Блокировка "только чтение" гарантирует, что ваш вывод будет внутренне согласован, если, конечно, нет необходимости во второй блокировке, не связывающей большую часть таблицы с уровнем "повторное чтение". Блокировка "только чтение" удобна тогда, когда вы делаете отчёты, которые должны быть внутренне согласованы, и давать доступ к большинству или ко всем строкам таблицы, не связывая базу данных.
Некоторые реализации выполняют блокировку страницы вместо блокировки строки. Это может быть либо возможность вашего управления, либо нечто, заложенное уже в конструкцию системы.
Страница это блок накопления памяти, обычно равный 1024 байт. Страница может состоять из одной или более строк таблицы, возможно, сопровождаемых индексами и другой сопутствующей информацией, а может состоять даже из нескольких строк другой таблицы. Если вы блокируете страницы вместо строк, все данные в этих страницах будут блокированы точно так же, как и в индивидуальных строках, согласно уровням изоляции, описанным выше.
Основным преимуществом такого подхода является эффективность. Когда SQL не следит за блокированностью и разблокированностью строк индивидуально, он работает быстрее. С другой стороны, язык SQL был разработан так, чтобы максимизировать свои возможности, и произвольно блокирует строки, которые не обязательно было блокировать. Похожая возможность доступна в некоторых системах - это блокировка областей DBS. Области базы данных имеют тенденцию быть больше, чем страница, так что этот подход удовлетворяет и достоинству увеличения производительности и недостатку блокирования страниц. Вообще-то лучше отключать блокировку низкого уровня, если вам кажется, что появились значительные проблемы с эффективностью.
Ключевые определения, с которыми вы познакомились в этой главе:
Синонимы это объекты в том смысле, что они имеют имена и (иногда) владельцев, но, естественно, они не могут существовать без таблицы, чьё имя они замещают. Они могут быть общими и, следовательно, доступными каждому, кто имеет доступ к объекту, или они могут принадлежать определенному пользователю.
Области DBS, или просто DBS, это подразделы базы данных, которые распределены для пользователей. Связанные таблицы, (например, таблицы, которые будут часто объединяться), лучше хранить в общей для них DBS.
СOMMIT и ROLLBACK это команды, используемые для выполнения изменений в базе данных в то время, когда предыдущая команда COMMIT или команда ROLLBACK начинают сеанс и оставляют изменения или игнорируют их как группу.
Средство Управление Параллелизмом определяет, в какой степени одновременно поданные команды будут мешать друг другу. Оно является адаптируемым средством, находящим компромисс между производительностью базы данных и изоляцией действующих команд.
1. Создайте область базы данных с именем Myspace, которая выделяет 15 процентов своей области для индексов и 40 процентов на расширение строк. 2. Вы получили право SELECT в таблице Заказов продавца Diane. Введите команду так, чтобы вы могли ссылаться к этой таблице как к "Orders", не используя имя "Diane" в качестве префикса. 3. Если произойдёт сбой питания, что случится со всеми изменениями, сделанными во время текущей транзакции? 4. Если вы не можете видеть строку из-за её блокировки, какой это тип блокировки? 5. Если вы хотите получить общее, максимальное, и усреднённое значения сумм приобретений для всех заказов и не хотите при этом запрещать другим пользоваться таблицей, какой уровень изоляции будет этому соответствовать? (См. ответы в Приложении A.)
В этой главе вы узнаете, как типовая база данных SQL сохраняет самоорганизованность. Самоорганизованность обеспечивается реляционной базой данных, создаваемой и поддерживаемой с помощью программы. Вы можете обращаться к таблицам самостоятельно для получения информации о привилегиях, таблицах, индексах, и так далее. В этой главе мы покажем вам некоторые типы содержимого такой БД.
Чтобы функционировать как БД SQL, ваша компьютерная система должна следить за многими вещами: таблицами, представлениями, индексами, синонимами, привилегиями, пользователями и т.д. Имеются различные способы делать это, но ясно, что наиболее логичный, эффективный и согласованный способ делать это в реляционной среде состоит в том, чтобы сохранять эту информацию в таблицах. Это даёт возможность компьютеру размещать информацию и управлять ею, используя те же самые процедуры, которые он использует чтобы размещать и управлять данными, которые он хранит для вас.
Хотя это - вопрос конкретной программы, а не стандарта ANSI, большинство БД SQL используют набор SQL-таблиц, хранящих служебную информацию, для своих внутренних потребностей. Этот набор называется в различных публикациях системный каталог, словарь данных или просто системные таблицы (Термин "словарь данных" может также относится к общему архиву данных, включая информацию о физических параметрах БД, - данных, которые хранятся вне SQL. Следовательно, имеются программы баз данных, которые имеют и системный каталог, и словарь данных.)
Таблицы системного каталога напоминают обычные SQL-таблицы: те же строки и столбцы данных. Например, одна таблица каталога обычно содержит информацию о таблицах, существующих в БД, по одной строке на каждую таблицу базы данных; другая содержит информацию о различных столбцах таблиц, по одной строке на столбец, и так далее.
Таблицы каталога создаются и заполняются с помощью самой БД и идентифицируются с помощью специальных имён, таких, например, как SYSTEM.
База данных создает эти таблицы и модифицирует их автоматически; таблицы каталога не могут быть непосредственно подвергнуты действию команды модификации. Если это случится, это значительно запутает всю систему и сделает её неработоспособной. Однако в большинстве систем каталог может быть запрошен пользователем. Это очень полезно, потому что это даёт возможность узнать кое-что о БД, которую вы используете. Конечно, не вся информация всегда доступна всем пользователям. Подобно другим таблицам, доступ к каталогу ограничен для пользователей без соответствующих привилегий. Так как каталог принадлежит самой системе, имеется некоторая неясность относительно того, кто имеет привилегии и кто может предоставить привилегии в этом каталоге. Обычно привилегии каталога предоставляет суперпользователь, например, администратор системы, зарегистрированный как SYSTEM или DBA. Кроме того, некоторые привилегии могут предоставляться пользователям автоматически.
Давайте рассмотрим некоторые таблицы, которые мы могли бы найти в типовом каталоге системы:
Таблицы Содержание ------------- ------------------------------------------- SYSTEMCATALOG Таблицы (базовые и представления) SYSTEMCOLUMNS Столбцы таблицы SYSTEMTABLES Каталог представления в SYSTEMCATALOG SYSTEMINDEXES Индексы в таблице SYSTEMUSERAUTH Пользователи базы данных SYSTEMTABAUTH Объектные привилегии пользователей SYSTEMCOLAUTH Столбцовые привилегии пользователей SYSTEMSYNONS Синонимы таблиц
Теперь, если наш DBA предоставит пользователю Stephen право просматривать SYSTEMCATALOG такой командой
GRANT SELECT ON SYSTEMCATALOG TO Stephen;
то Stephen сможет увидеть некоторую информацию обо всех таблицах в БД (мы имеем здесь пользователя DBA, пользователя Chris - владельца трёх наших типовых таблиц, а также Adrian - владельца представления Londoncust).
SELECT tname, owner, numcolumns, type, CO FROM SYSTEMCATALOG; =============== SQL Execution Log =============== | | | SELECT tname, owner, numcolumns, type, CO | | FROM SYSTEMCATALOG; | | | | ================================================ | | tname owner numcolumns type CO | | ------------- ------- ---------- ---- --- | | SYSTEMCATALOG SYSTEM 4 B | | Salespeople Chris 4 B | | Customers Chris 5 B | | Londoncust Adrian 5 V Y | | Orders Chris 5 B | | | ================================================== Рисунок 24.1 Содержание таблицы SYSTEMCATALOG
Как видите, каждая строка описывает свою таблицу. Первый столбец - имя; второй - имя пользователя, который владеет ею; третий - число столбцов таблицы; четвертый - код из одного символа: это или B (для базовой таблицы), или V (для представления). Последний столбец имеет пустые (NULL) значения, если его тип не равен V, и этот столбец указывает, определена или нет возможность проверки.
Обратите внимание что SYSTEMCATALOG (СИСТЕМНЫЙ КАТАЛОГ) представлен как одна из таблиц в вышесказанном списке. Для простоты мы исключили остальные каталоги системы из вывода. Таблицы системного каталога обычно показываются в SYSTEMCATALOG.
Поскольку 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;
Большинство версий 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
Здесь показаны определения остальных ваших системных таблиц с типовым запросом для каждого:
Имена столбцов в таблице SYSTEMINDEXES и их описания:
СТОЛБЕЦ ОПИСАНИЕ ------------- ----------------------------------------------- iname Имя индекса (используемое для его исключения) iowner Имя пользователя, создавшего индекс tname Имя таблицы, которая содержит индекс cnumber Номер столбца в таблице tabowner Пользователь, который владеет таблицей, содержащей индекс numcolumns Число столбцов в индексе cposition Позиция текущего столбца среди набора индексов isunique Уникален ли индекс (Y или N)
ТИПОВОЙ ЗАПРОС Индекс считается неуникальным, если он вызывает продавца в snum-столбце таблицы Заказчиков:
SELECT iname, iowner, tname, cnumber, isunique FROM SYSTEMINDEXES WHERE iname = 'salesperson';
Вывод для этого запроса показан на Рисунке 24.4.
=============== SQL Execution Log ================ | | | SELECT iname, iowner, tname, cnumber, isunique | | FROM SYSTEMINDEXES | | WHERE iname = 'salespeople' | | ; | | ================================================= | | iname iowner tname cnumber isunique | | ----------- ------ ---------- ------- -------- | | salesperson Stephan Customers 5 N | | | =================================================== Рисунок 24.4 Строка из таблицы SYSTEMINDEXES
Имена столбцов для SYSTEMUSERAUTH и их описания:
СТОЛБЕЦ ОПИСАНИЕ -------------- ----------------------------------------------- username Идентификатор (ID) доступа пользователя password Пароль пользователя, вводимый при регистрации resource Где пользователь имеет права RESOURCE dba Где пользователь имеет права DBA
Мы будем использовать простую схему системных привилегий из Главы 22, где были представлены три системные привилегии: CONNECT (ПОДКЛЮЧИТЬ), RESOURCE (РЕСУРСЫ) и DBA.
Все пользователи получают CONNECT по умолчанию при регистрации, поэтому она не описана в таблице выше. Возможные состояния столбцов resource и dba: Y (Да, пользователь имеет привилегии) или No (Нет, пользователь не имеет привилегий).
Пароли (password) доступны только высокопривилегированным пользователям, если таковые существуют. Следовательно, запрос этой таблицы можно вообще делать только для получения информации относительно привилегий системы и пользователей.
ТИПОВОЙ ЗАПРОС Чтобы найти всех пользователей, которые имеют привилегию RESOURCE, и увидеть, какие из них - DBA, вы можете ввести следующее условие:
SELECT username, dba FROM SYSTEMUSERAUTH WHERE resource = 'Y';
Вывод для этого запроса показан на Рисунке 24.5.
Здесь показаны имена столбцов в таблице SYSTEMTABAUTH и их описания:
СТОЛБЕЦ ОПИСАНИЕ ------------ --------------------------------------------- username Пользователь, который имеет привилегии grantor Пользователь, который передает привилегии по имени пользователя tname Имя таблицы, в которой существуют привилегии owner Владелец tname selauth Имеет ли пользователь привилегию SELECT insauth Имеет ли пользователь привилегию INSERT delauth Имеет ли пользователь привилегию DELETE
Возможные значения каждой из перечисленных привилегий объекта (имена столбцов которых оканчиваются на auth): Y, N и G.
G указывает, что пользователь имеет привилегию с возможностью передачи привилегий. В каждой строке по крайней мере один из этих столбцов должен иметь состояние, отличное от N (другими словами, иметь хоть какую-то привилегию).
=============== SQL Execution Log ================ | | | SELECT username, dba | | FROM SYSTEMUSERAUTH | | WHERE resource = 'Y' | | ; | | ================================================= | | username dba | | ----------- ------ | | Diane N | | Adrian Y | | | =================================================== Рисунок 24.5 Пользователи, имеющие привилегию RESOURCE
Первые четыре столбца этой таблицы составляют первичный ключ. Это означает что каждая комбинация из tname, владелец-пользователь (не забудьте, что две различные таблицы с различными владельцами могут иметь одно и тоже имя), пользователь и пользователь, передающий права (грантор), должна быть уникальной. Каждая строка этой таблицы содержит все привилегии (которые не являются определённым столбцом), предоставляемые одним определённым пользователем другому определённому пользователю в конкретном объекте.
UPDATE и REFERENCES, являются привилегиями, которые могут быть определёнными столбцами и находиться в различных таблицах каталога. Если пользователь получает привилегии в таблице от более чем одного пользователя, такие привилегии могут быть отдельными строками, созданными в этой таблице. Это необходимо для каскадного отслеживания при вызове привилегий.
ТИПОВОЙ ЗАПРОС Чтобы найти все привилегии SELECT, INSERT и DELETE, которые Adrian предоставляет пользователям в таблице Заказчиков, вы можете ввести следующее (вывод показан на Рисунке 24.6):
SELECT username, selauth, insauth, delauth FROM SYSTEMTABAUTH WHERE grantor = 'Adrian' ANDtname = 'Customers'; =============== SQL Execution Log ================ | | | SELECT username, selauth, insauth, delauth | | FROM SYSTEMTABAUTH | | WHERE grantor = 'Adrian' | | AND tname = 'Customers' | | ; | | ================================================= | | username selauth insauth delauth | | ----------- ------- -------- -------- | | Claire G Y N | | Norman Y Y Y | | | =================================================== Рисунок 24.6 Пользователи, получившие привилегии от Adrian
Выше показано, что Adrian предоставил Claire привилегии INSERT и SELECT в таблице Заказчиков, позднее предоставив ей права на передачу привилегий. Пользователю Norman он предоставил привилегии SELECT, INSERT и DELETE, но не дал возможность передачи ни одной из них. Если Claire имела привилегию DELETE в таблице Заказчиков от какого-то другого источника, в этом запросе это показано не будет.
СТОЛБЕЦ ОПИСАНИЕ ------------- ------------------------------------------- username Пользователь, который имеет привилегии grantor Пользователь, который предоставляет привилегии другому пользователю tname Имя таблицы, в которой существуют привилегии cname Имя столбца, в котором существуют привилегии owner Владелец tname updauth Имеет ли пользователь привилегию UPDATE в этом столбце refauth Имеет ли пользователь привилегию REFERENCES в этом столбце
Столбцы updauth и refauth могут быть в состоянии Y, N или G, но не могут быть одновременно в состоянии N в одной и той же строке. Это первые пять столбцов таблицы, которые не составляют первичный ключ. Он отличается от первичного ключа SYSTEMTABAUTH, в котором содержится поле cname, указывающее на определенный столбец обсуждаемой таблицы, для которой применяются одна или обе привилегии. Отдельная строка в этой таблице может существовать для каждого столбца в любой данной таблице, в которой одному пользователю передаются привилегии определенного столбца с помощью другого пользователя.
Как и в случае с SYSTEMTABAUTH, та же привилегия может быть описана в более чем одной строке этой таблицы, если она была передана более чем одним пользователем.
ТИПОВОЙ ЗАПРОС Чтобы выяснить, в каких столбцах какой таблицы вы имеете привилегию REFERENCES, вы можете ввести следующий запрос (вывод показан на Рисунке 24.7):
SELECT owner, tname, cname FROM SYSTEMCOLAUTH WHERE refauth IN ('Y', 'G') AND username = USE ORDER BY 1, 2;
который показывает, что эти две таблицы, имеющие различных владельцев, но одинаковые имена, в действительности - совершенно разные таблицы (т.е. это не два синонима для одной таблицы).
=============== SQL Execution Log ================ | | | SELECT OWNER, TNAME, CNAME | | FROM SYSTEMCOLAUTH | | WHERE refaulth IN ('Y' , 'G' ) | | AND username = USER | | ORDER BY 1, 2 | | ; | | ================================================= | | owner tname cname | | ----------- ----------- ------- | | Diane Customers cnum | | Diane Salespeople sname | | Diane Salespeople sname | | Gillan Customers cnum | =================================================== Рисунок 24.7 Столбцы, в которых пользователь имеет привилегию INSERT
Это имена столбцов в таблице SYSTEMSYNONS и их описания:
СТОЛБЕЦ ОПИСАНИЕ ------------- -------------------------------------------- synonym Имя синонима synowner Пользователь, который является владельцем синонима (может быть PUBLIC (ОБЩИЙ)) tname Имя таблицы, используемой владельцем tabowner Имя пользователя, который является владельцем таблицы
ТИПОВОЙ ЗАПРОС Предположим, что Adrian имеет синоним Clients для таблицы Заказчиков, принадлежащей Diane, и что имеется общий синоним Customers для этой же таблицы. Вы делаете запрос таблицы для всех синонимов в таблице Заказчиков (вывод показан на Рисунке 24.8):
SELECT * FROM SYSTEMSYNONS WHERE tname = 'Customers' =============== SQL Execution Log ================ | | | SELECT * | | FROM SYSTEMSYNONS | | WHERE tname = 'Customers' | | ; | | ================================================= | | synonym synowner tname tabowner | | ----------- ----------- ---------- ---------- | | Clients Adrian Customers Diane | | Customers PUBLIC Customers Diane | | | =================================================== Рисунок 24.8 Синонимы для таблицы Заказчиков
Конечно, вы можете выполнять более сложные запросы в системном каталоге.
Объединения, например, могут быть очень удобны.
Эта команда позволит вам увидеть столбцы таблиц и базовые индексы, установленные для каждого
(вывод показан на Рисунке 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.
Итак, система SQL использует набор таблиц, называемый системным каталогом, в структуре базы данных. Эти таблицы могут запрашиваться, но не модифицироваться. Кроме того, вы можете добавлять комментарии столбцов в (и удалять их из) таблицы SYSTEMCATALOG и SYSTEMCOLUMNS.
Создание представлений в этих таблицах - превосходный способ точно определить, какая пользовательская информация может быть доступной.
Теперь, когда вы узнали о каталоге, вы завершили ваше обучение SQL в диалоговом режиме. Следующая глава этой книги расскажет вам, как SQL используется в программах, которые написаны на других языках, но которые способны извлечь пользу из возможностей SQL, взаимодействуя с его таблицами баз данных.
1. Сделайте запрос каталога, чтобы вывести для каждой таблицы, имеющей более чем четыре столбца, имя таблицы, имя владельца, а также имена столбцов и тип данных этих столбцов. 2. Сделайте запрос каталога, чтобы выяснить, сколько синонимов существует для каждой таблицы в базе данных. Не забудьте, что один и тот же синоним, принадлежащий двум различным пользователям - это фактически два разных синонима. 3. Выясните, сколько таблиц имеют индексы в более чем пятидесяти процентах их столбцов. (См. ответы в Приложении A.)
В этой главе вы узнаете, как SQL используется для расширения программ, написанных на других языках. Хотя непроцедурность языка SQL делает его очень мощным,
одновременно это накладывает на него большое
количество ограничений. Чтобы преодолеть эти ограничения, вы можете включать SQL в программы, написанные на том или
ином процедурном языке (имеющем определённый алгоритм).
Для наших примеров мы выбрали Паскаль, считая, что
этот язык наиболее прост в понимании для начинающих, и ещё потому, что Паскаль - один из языков, для которых ANSI имеет полуофициальный стандарт.
Чтобы вложить SQL в другой язык, вы должны использовать пакет программ, который обеспечивал бы поддержку вложения SQL в этот язык и, конечно же, поддержку самого языка. Естественно, вы должны быть знакомы с языком, который вы используете.
В основном вы будете использовать команды SQL для работы в таблицах баз данных, передачи результатов вывода в программу и получение ввода из программы, в которую они вкладываются, обобщённо ссылаясь к главной программе (которая может или не может принимать их из диалога или посылать обратно в диалог пользователя и программы).
Хотя мы и потратили некоторое время на то, чтобы показать возможности SQL, но, если вы - опытный программист, вы, вероятно, отметили, что сам по себе он не очень полезен при написании программ. Самое очевидное ограничение - то, что, в то время как SQL может сразу выполнить пакет команды, интерактивный SQL в основном выполняет по одной команде в каждый момент времени.
Логические конструкции типа if ... then ("если ... то"), for ... do ("для ... выполнить") и while ... repeat ("пока ... повторять"), используемые для структур большинства компьютерных программ, здесь отсутствуют, так что вы не сможете принять решение - выполнять ли, как выполнять или как долго выполнять одно действие в результате другого действия. Кроме того, интерактивный SQL не может делать ничего со значениями, кроме ввода их в таблицу, размещения или распределения их с помощью запросов и, конечно, вывода их на какое-то устройство.
Более традиционные языки, однако, разработаны так, чтобы программист мог начинать обработку данных и, основываясь на её результатах, решать, делать ли то действие или другое, или же повторять действие до тех пор, пока не встретится некоторое условие, создавая логические маршруты и циклы. Значения сохраняются в переменных, которые могут использоваться и изменяться с помощью любого числа команд. Это даёт вам возможность указывать пользователям на ввод или вывод этих команд из файла и возможность форматировать вывод сложными способами (например, преобразовывать числовые данные в диаграммы).
Цель вложенного SQL состоит в том, чтобы объединить эти возможности, позволяющие вам создавать сложные процедурные программы, которые адресуют базу данных (БД) посредством 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 или ей подобную.
Основной способ, которым SQL и части базового языка ваших программ будут связываться друг с другом - значения переменных. Естественно, что разные языки распознают различные типы данных для переменных. ANSI определяет эквиваленты SQL для четыре базовых языков: ПЛ/1, Паскаль, КОБОЛ и ФОРТРАН; всё это подробно описано в Приложении B. Эквиваленты для других языков определяет проектировщик.
Имейте в виду, что такие типы как DATE не распознаются ANSI, и, следовательно, никакие эквивалентные типы данных для базовых языков не существуют в стандарте ANSI. Более сложные типы данных базового языка, такие как матрицы, не имеют эквивалентов в 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, будут вставлены в вывод. Это показывает, что можно использовать и циклы, и условия как нормальные для главного языка.
Все переменные, на которые имеется ссылка в предложениях 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.
Кроме помещения значений переменных в таблицы с помощью команды
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 должна использоваться только при следующих условиях:
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-курсор как устройство, которое, аналогично этому, отмечает ваше место в выводе запроса, хотя аналогия не полная.
Курсор это вид переменной, которая связана с запросом. Значением этой переменной может быть каждая строка, которая выводится при запросе. Подобно главным переменным, курсоры должны быть объявлены, прежде чем они будут использованы. Это делается командой 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 выполнена с ошибкой. Переменная SQLCODE (называемая еще SQLCOD в ФОРТРАНе) предназначена для того, чтобы обеспечить эту функцию. Она должна быть определена как переменная главного языка и должна иметь тип данных, который в главном языке соответствует одному из точных числовых типов SQL, как это показано в Приложении B. Значение SQLCODE устанавливается каждый раз, когда выполняется команда SQL.
В основном существуют три возможности:
Теперь мы можем усовершенствовать наш предыдущий пример для выхода из цикла автоматически, при условии что курсор пуст, все строки выбраны или произошла ошибка:
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;
Это удобно для выхода при выполненном условии, что все строки выбраны. Но если вы получили ошибку, вы должны предпринять нечто такое, что описано для третьего случая, выше. Для этой цели, SQL предоставляет предложение GOTO. Фактически SQL позволяет вам применять его достаточно широко, так что программа может выполнить команду GOTO автоматически, если будет произведено определенное значение SQLCODE.
Вы можете сделать это совместно с предложением WHENEVER. Вот блок из примера для этого случая:
EXEC SQL WHENEVER SQLERROR GOTO Error_handler; EXEC SQL WHENEVER NOT FOUND CONTINUE;
SQLERROR это другой способ сообщить, что SQLCODE < 0; а NOT FOUND это другой способ сообщить, что SQLCODE
= 100. (Некоторые реализации называют последний случай ещё SQLWARNING.)
Error_handler это имя того места в программе, в которое будет передано выполнение программы, если произошла ошибка (GOTO может
состоять из одного или двух слов). Такое место определяется любым способом главного языка, например, с помощью метки в Паскале или имени раздела или имени параграфа в КОБОЛе (в
дальнейшем мы будем использовать термин "метка"). Метка более удачно идентифицирует стандартную процедуру, распространяемую проектировщиком для включения во все программы.
CONTINUE не делает чего-то специального для значения SQLCODE. Оно
также является значением по умолчанию, если вы не используете команду WHENEVER, определяющую значение SQLCODE. Однако эти неактивные
определения дают вам возможность переключаться вперёд и назад, выполняя и не выполняя действия в различных точках (метках) вашей программы.
Например, если ваша программа включает в себя несколько команд INSERT, использующих запросы, которые реально должны производить значения, вы
могли бы напечатать специальное сообщение или сделать что-то такое, что поясняло бы, что запросы возвращаются пустыми и никакие значения не были вставлены. В этом случае, вы можете ввести следующее:
EXEC SQL WHENEVER NOT FOUND GOTO No_rows;
No_rows это метка в некотором коде, содержащем определенное действие. С другой стороны, если вам нужно сделать выборку в программе позже, вы можете ввести следующее в этой точке:
EXEC SQL WHENEVER NOT FOUND CONTINUE;
чтобы выполнение выборки повторялось до тех пор, пока все строки не будут извлечены, что является нормальной процедурой не требующей специальной обработки.
Курсоры могут также быть использованы, чтобы выбирать группу строк из таблицы, которые могут быть затем модифицированы или удалены одна за другой. Это дает вам возможность, обходить некоторые ограничения предикатов, используемых в командах 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
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) значения это специальные маркеры, определяемые самим 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. Последнее в принципе не запрещено, но результат часто бывает непредсказуем.
Другая возможность состоит в том, чтобы обрабатывать переменную 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 нужны здесь только для понимания}
В более сложной программе вы можете захотеть установить булеву переменную в "true/верно", чтобы указать, что значение city = NULL. Затем вы можете просто проверять эту переменную всякий раз, когда вам это необходимо.
Переменная 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.
Команды SQL вкладываются в процедурные языки, чтобы объединить возможности двух подходов. Некоторые дополнительные средства SQL необходимы
для выполнения этой работу. Вложенные команды SQL, транслируемые программой, называемой прекомпилятором, в форму, пригодную для использования транслятором главного языка, и используемые в этом главном языке как вызовы процедуры к подпрограммам, которые создаёт прекомпилятор, называются модулями доступа.
ANSI поддерживает вложение SQL в языки ПАСКАЛЬ, ФОРТРАН, КОБОЛ и PL/I. Другие языки также используются, особенно С.
В попытке кратко описать вложенный SQL, наиболее важное в этой главе:
Обратите внимание:
ответы для этих упражнений написаны в псевдокодах, являющихся английским языком описания логики, которой должна
следовать программа. Это сделано для того, чтобы помочь читателям, которые могут быть не знакомы с ПАСКАЛем (или любым другим языком).
Кроме того, это лучше сфокусирует ваше внимание на включаемых понятиях, опуская частности того или другого языка. Чтобы не противоречить нашим примерам, стиль псевдокода будет напоминать Паскаль.
Мы опустим из программ всё, что не относится напрямую к рассматриваемым вопросам, например, определение устройств ввода-вывода, подключение к базе данных и так далее. Конечно, имеется много способов выполнения таких упражнений; и совсем не обязательно, что представленные варианты решений являются самыми удачными.
1. Разработайте простую программу, которая выберет все комбинации полей snum и cnum из таблиц Заказов и Заказчиков, и выясните, всегда ли предыдущая комбинация - такая же, как последующая. Если комбинация из таблицы Заказов не найдена в таблице Заказчиков, значение поля snum для этой строки будет изменено на удовлетворяющее условию совпадения. Вы должны помнить, что курсор с подзапросом модифицируем (ANSI-ограничение также применимо к просмотрам) и что базисная целостность базы данных это не тоже самое, что проверка на ошибку (т.е. первичные ключи уникальны, все поля cnums в таблице Заказов правильны, и так далее). Проверьте раздел объявлений и убедитесь, что там объявлены все используемые курсоры. 2. Предположим, что ваша программа предписывает ANSI запрещение курсоров или просмотров, использующих модифицируемые подзапросы. Как вы должны изменить вышеупомянутую программу? 3. Разработайте программу, которая подсказывает пользователям изменить значения поля city продавца, автоматически увеличивает комиссионные на .01 для продавца, переводимого в Барселону, и уменьшает их на .01 для продавца, переводимого в Сан-Хосе. Кроме того, продавец находящийся в Лондоне, должен потерять .02 из своих комиссионных, независимо от того, меняет он город, или нет, в то время как продавец, не находящийся в Лондоне должен иметь увеличение комиссионных на .02. Изменение в комиссионных, основывающееся на нахождении продавца в Лондоне, может применяться независимо от того, куда тот переводится. Выясните, может ли поле city или поле comm содержать NULL-значения, и обработайте их, как это делается в SQL. Предупреждение! Эта программа имеет некоторые сокращения. (См. ответы в Приложении A.)
В этой главе мы покажем вам, как извлекать информацию из таблиц. Вы узнаете, как пропускать или переупорядочивать столбцы и как автоматически устранять избыточность данных в вашем выводе. В заключение вы узнаете, как устанавливать условие (проверку), которую вы можете использовать, чтобы определить, какие строки таблицы используются в выводе. Эта последняя особенность будет далее описана в более поздних главах и является одной из наиболее изящных и мощных в SQL.
Как мы говорили ранее, SQL это Структурированный Язык Запросов. Запросы, вероятно, наиболее часто используемый аспект SQL. Фактически маловероятно, для категории SQL-пользователей, чтобы этот язык использовался для чего-то другого. По этой причине мы будем начинать наше обсуждение SQL с обсуждения запроса и того, как он выполняется на этом языке.
Запрос это команда, которую вы даёте вашей программе базы данных и которая сообщает ей, что нужно вывести определённую информацию из таблиц в память. Эта информация обычно посылается непосредственно на экран компьютера или терминала, которым вы пользуетесь, хотя в большинстве случаев её можно также послать на принтер, сохранить в файле (как объект в памяти компьютера) или предоставить как вводную информацию для другой команды или процесса.
Запросы обычно рассматриваются как часть языка DML. Однако, так как запрос не меняет информацию в таблицах, а просто показывает её пользователю, мы будем рассматривать запросы как самостоятельную категорию среди команд DML, которые производят действия, а не просто показывают содержание базы данных (БД).
Любой запрос SQL имеет в своём составе одну команду. Структура этой команды обманчиво проста, потому что вы можете расширять её так, чтобы выполнить сложные оценки и обработку данных. Эта команда называется 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
Другими словами, эта команда просто выводит все данные из таблицы. Большинство программ будут также давать заголовки столбца, как выше, а некоторые позволяют определить детальное форматирование вывода, но это уже вне стандартной спецификации.
Вот объяснение каждой части этой команды:
Естественно, запрос такого характера не обязательно будет упорядочивать вывод любым указанным способом. Та же самая команда, выполненная с теми же самыми данными, но в другое время, не сможет вывести тот же самый заказ. Обычно строки обнаруживаются в том порядке, в котором они найдены в таблице, поскольку, как мы установили в предыдущей главе, этот порядок произволен. Это не обязательно будет тот порядок, в котором данные вводились или сохранялись. Вы можете упорядочивать вывод непосредственно командами SQL с помощью специального предложения. Позже мы покажем, как это делается. А сейчас просто запомните, что, в отсутствие явного упорядочивания, в вашем выводе нет никакого определенного порядка.
Использование возврата каретки (клавиша ENTER) является произвольным. Мы должны точно установить, как удобнее составить запрос - в несколько строк или в одну строку - следующим образом:
SELECT snum, sname, city, comm FROM Salespeople;
С тех пор как SQL использует точку с запятой, чтобы указывать конец команды, большинство программ SQL обрабатывают возврат каретки (через нажатие Возврат или клавиши ENTER ) как пробел. Хорошая идея - использовать возвраты каретки и выравнивание, как мы делали ранее, чтобы сделать ваши команды более лёгкими для чтения и более понятными.
Если вы хотите видеть все столбцы таблицы, имеется необязательное сокращение, которое вы можете использовать. Звёздочка (*) может применяться для вывода полного списка столбцов следующим образом:
SELECT * FROM Salespeople;
Это приведет к тому же результату, что и наша предыдущая команда.
В общем случае команда SELECT начинается с ключевого слова SELECT, сопровождаемого пробелом. После этого должен следовать список имён столбцов, которые вы хотите видеть, отделяемых запятыми. Если вы хотите видеть все столбцы таблицы, вы можете заменить этот список звездочкой (*). Ключевое слово FROM, следующее далее, сопровождается пробелом и именем таблицы, запрос к которой делается. В конце должна использоваться точка с запятой (;) для окончания запроса и указания на то, что команда готова к выполнению.
Команда SELECT способна извлечь строго определенную информацию из таблицы. Сначала мы можем предоставить возможность увидеть только опредёленные столбцы таблицы. Это выполняется легко: простым исключением столбцов, которые вы не хотите видеть, из команды SELECT. Например, запрос
SELECT sname, comm FROM Salespeople;
будет производить вывод, показанный на Рисунке 3.2.
=============== SQL Execution Log ============ | | | SELECT snum, comm | | FROM Salespeople; | | | | ==============================================| | sname comm | | ------------- --------- | | Peel 0.12 | | Serres 0.13 | | Motika 0.11 | | Rifkin 0.15 | | Axelrod 0.10 | =============================================== Рисунок 3.2 Выбор определенных столбцов
Могут иметься таблицы, которые имеют большое количество столбцов, содержащих данные, не все из которых нужны для выполнения поставленной задачи. Следовательно, вы можете найти способ подбора и выбора только полезных для вас столбцов.
Даже если столбцы таблицы, по определению, упорядочены, это не означает, что вы будете восстанавливать их в том же порядке. Конечно, звёздочка (*) покажет все столбцы в их естественном порядке, но если вы укажете столбцы отдельно, вы можете получить их в том порядке, в котором хотите. Давайте рассмотрим таблицу Заказов, содержащую дату приобретения (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.
DISTINCT (ОТЛИЧИЕ) - аргумент, который обеспечивает вас способом устранять дублирующие значения из вашего предложения SELECT. Предположим, что вы хотите знать, какие продавцы в настоящее время имеют свои заказы в таблице Заказов. Под заказом (здесь и далее) будет пониматься запись в таблицу Заказов, регистрирующая приобретения, сделанные в определённый день определённым заказчиком у определённого продавца на определённую сумму. Вам не нужно знать, сколько заказов имеет каждый; вам нужен только список номеров продавцов (snum). Поэтому вы можете ввести:
SELECT snum FROM Orders;
для получения вывода показанного в Рисунке 3.4
=============== SQL Execution Log ============ | | | SELECT snum | | FROM Orders; | | | | ============================================= | | snum | | ------- | | 1007 | | 1001 | | 1004 | | 1002 | | 1007 | | 1003 | | 1002 | | 1001 | | 1002 | | 1001 | ============================================= Рисунок 3.4 SELECT с дублированием номеров продавцов
Для получения списка без дубликатов, для удобочитаемости, вы можете ввести следующее:
SELECT DISTINCT snum FROM Orders;
Вывод для этого запроса показан на Рисунке 3.5.
Другими словами, DISTINCT следит за тем, какие значения были ранее, чтобы они не дублировались в списке. Это полезный способ избежать избыточности данных, но важно, чтобы при этом вы понимали, что вы делаете. Если вы не хотите потерять некоторые данные, вы не должны безоглядно использовать DISTINCT, потому что это может скрыть какую-то проблему или какие-то важные данные. Например, вы могли бы предположить, что имена всех ваших заказчиков различны. Если кто-то помещает второго Clemens в таблицу Заказчиков, а вы используете SELECT DISTINCT cname, вы не будете даже знать о существовании двойника. Вы можете получить не того Clemens и даже не знать об этом. Так как вы не ожидаете избыточности, в этом случае вы не должны использовать DISTINCT.
DISTINCT может указываться только один раз в данном предложении SELECT. Если предложение выбирает несколько полей,
=============== SQL Execution Log =========== | | | SELECT DISTINCT snum | | FROM Orders; | | | | ============================================= | | snum | | ------- | | 1001 | | 1002 | | 1003 | | 1004 | | 1007 | ============================================= Рисунок 3.5 SELECT без дублирования
DISTINCT опускает строки, где все выбранные поля идентичны. Строки, в которых некоторые значения одинаковы, а некоторые - различны, будут сохранены. DISTINCT фактически приводит к показу всей строки вывода, не указывая полей (за исключением случав, когда он используется внутри агрегатных функций, как описано в Главе 6), так что нет никакого смысла его повторять.
Вместо DISTINCT вы можете указать ALL. Это будет иметь противоположный эффект, дублирование строк вывода сохранится. Так как это - тот самый случай, когда вы не указываете ни DISTINCT ни ALL, то ALL - по существу скорее пояснительный, а не действующий аргумент.
Таблица имеет тенденцию становиться очень большой, поскольку с течением времени всё большее и большее количество строк в неё добавляется. Поскольку обычно только определённые строки интересуют вас в данное время, 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 с числовым полем в предикате
Теперь вы знаете несколько способов, как заставить таблицу выдавать вам ту информацию, какую вы хотите, а не просто вываливать наружу всё её содержание. Вы можете переупорядочивать столбцы таблицы или отбрасывать любой из них. Вы можете решать, хотите вы видеть дублированные значения, или нет.
Наиболее важно то, что вы можете устанавливать условие, называемое предикатом, которое определяет или не определяет, из тысяч таких же строк, будет ли выбрана для вывода указанная строка.
Предикаты могут становиться очень сложными, предоставляя вам высокую точность в решении того, какие строки вам выбирать с помощью запроса. Именно эта способность решать точно, что вы хотите видеть, делает запросы SQL такими мощными. Следующие несколько глав будут посвящены в большей мере особенностям, которые расширяют мощность предикатов. В Главе 4 вам будут представлены операции, иные, нежели те, которые используются в условиях предиката, а также способы объединения многочисленных условий в единый предикат.
Напишите команду SELECT, которая вывела бы номер заказа, сумму и дату для всех строк из таблицы Заказов.
Напишите запрос, который вывел бы все строки из таблицы Заказчиков, для которых номер продавца = 1001.
Напишите запрос, который вывел бы таблицу со столбцами в следующем порядке: city, sname, snum, comm.
Напишите команду SELECT, которая вывела бы оценку (rating), сопровождаемую именем каждого заказчика в San Jose.
Напишите запрос, который вывел бы значения snum всех продавцов в текущем заказе из таблицы Заказов без каких бы то ни было повторений.
(См. ответы в Приложении A.)
В Главе 3 вы узнали, что предикаты могут оценивать равенство в операции как true/верное или false/неверное. Они могут также оценивать другие виды связей, помимо равенств.
Эта глава будет исследовать другие реляционные операции, используемые в SQL. Вы также узнаете, как использовать булевы операции, чтобы изменять и объединять значения предиката. С помощью булевых операций (или, проще говоря, логических операций) одиночный предикат может содержать любое число условий. Это позволяет создавать очень сложные предикаты. Использование круглых скобок в структуре этих сложных предикатов будет также разъясняться.
Реляционная операция - математический символ, который указывает на определённый тип сравнения двух значений. Вы уже видели, как используются равенства, такие как 2 + 3 = 5 или city = "London". Но имеются также и другие реляционные операции. Предположим, что вы хотите видеть всех Продавцов с их комиссионными, выше определенного значения. Вы можете использовать тип сравнения "больше чем" (>). Вот реляционные операции (операции сравнения), которыми располагает SQL:
Эти операции имеют стандартное значение для чисел. Для символов их определение зависит от формата преобразования, ASCII или EBCDIC, который вы используете.
SQL сравнивает символьные значения в терминах основных чисел, как определено в формате преобразования. Даже значение символа, такого как "1", который представляет число, не обязательно равняется числу, которое он представляет. Вы можете использовать реляционные операции, чтобы установить алфавитный порядок, например, "a" < "n", где a идёт раньше в алфавитном порядке, но всё это ограничивается с помощью параметра преобразования формата.
И в ASCII, и в EBCDIC символы сортируются по значению: символ имеет значение меньше, чем все другие символы, которым он предшествует в алфавитном порядке и которые имеют с ним один вариант регистра (верхний или нижний). В ASCII все символы верхнего регистра меньше, чем все символы нижнего регистра, поэтому "Z" < "a", а все числа - меньше чем все символы, поэтому "1" < "Z". То же относится и к EBCDIC.
Чтобы сохранить обсуждение более простым, мы допустим, что вы будете использовать текстовый формат ASCII. Проконсультируйтесь в документации вашей системы, если вы не уверены, какой формат вы используете или как он работает.
Значения, сравниваемые здесь, называются скалярными значениями. Скалярные значения производятся скалярными выражениями; 1 + 2 это скалярное выражение, которое производит скалярное значение 3. Скалярное значение может быть символом или числом, хотя очевидно, что только числа используются с арифметическими операциями, такими как + (сложение) или * (умножение).
Предикаты обычно сравнивают значения скалярных величин, используя реляционные операции или специальные операции SQL, чтобы увидеть, верно ли это сравнение. Некоторые операции/операторы SQL описаны в Главе 5.
Предположим, что вы хотите увидеть всех заказчиков с оценкой (rating) выше 200. Так как 200 - скалярное значение, как и значение в столбце оценки, для их сравнения вы можете использовать реляционную операцию.
SELECT * FROM Customers WHERE rating > 200;
Вывод для этого запроса показан на Рисунке 4.1.
Конечно, если бы мы захотели увидеть ещё и заказчиков с оценкой, равной 200, мы использовали бы предикат
rating > = 200
Основные булевы операции также распознаются в 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 (или любой другой язык) может быть сведён до уровня булевой логики.
Булевы операции, и как они работают:
Связывая предикаты с булевыми операциями, вы можете значительно расширить их возможности. Предположим, вы хотите видеть всех заказчиков в 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 и неверного предиката запроса. Они будут также удалены из вывода. Вывод показан для строк, которые оставлены.
В этой главе вы значительно расширили ваше знакомство с предикатами. Теперь вы можете находить значения, которые связаны с данным значением любым способом, определяемым различными реляционными операциями.
Вы можете также использовать булевы операции AND и OR, чтобы несколько условий, каждое из которых автономно в предикатах, объединять в единый предикат.
Булева операция NOT, как вы уже видели, может изменять значение условия или группы условий на противоположное.
Булевы и Реляционные операции могут эффективно управляться с помощью круглых скобок, которые определяют порядок, в котором операции будут выполнены. Эти операции применимы к любому уровню сложности, и вы поняли, как сложные условия могут создаваться из этих простых частей.
Теперь, когда мы показали, как используются стандартные математические операции, мы можем перейти к операциям которые являются специфичными для SQL. Это мы сделаем в Главе 5.
Напишите запрос, который может выдать вам все заказы со значениями суммы выше $1,000.
Напишите запрос, который может выдать вам поля sname и city для всех продавцов в Лондоне с комиссионными выше .10.
Напишите запрос к таблице Заказчиков, чей вывод включит всех заказчиков с оценкой =< 100, если они не находятся в Риме.
Что может быть выведено в результате следующего запроса?
SELECT * FROM Orders WHERE (amt < 1000 OR NOT (odate = 10/03/1990 AND cnum > 2003));
Что может быть выведено в результате следующего запроса?
SELECT * FROM Orders WHERE NOT ((odate = 10/03/1990 OR snum > 1006) AND amt > = 1500 );
Как можно проще переписать такой запрос?
SELECT snum, sname, city, comm FROM Salespeople WHERE (comm > + .12 OR comm < .14);
(См. ответы в Приложении A.)
В дополнение к реляционным и булевым операциям, обсуждённым в
Главе 4, SQL использует специальные операторы: IN, BETWEEN,
LIKE и IS NULL.
В этой главе вы узнаете, как их использовать и как реляционные
операторы позволяют создавать более сложные и мощные предикаты. Обсуждение
оператора IS NULL будет включать отсутствие данных и значение NULL, которое
указывает на то, что данные отсутствуют.
Вы также узнаете о вариантах использования оператора, NOT применяющегося с этими операторами.
Оператор 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 с номерами
Оператор 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 в алфавитных порядках
LIKE применим только к полям типа CHAR или VARCHAR, с которыми он используется для поиска подстрок. Т.е. он ищет поле символа, чтобы увидеть, совпадает ли с условием часть его строки. В качестве условия он использует групповые символы-шаблоны (wildсards) - специальные символы, которые могут соответствовать чему-нибудь.
Имеются два типа шаблонов, используемых с LIKE:
Давайте найдём всех заказчиков, чьи имена начинаются с 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 ( // ) и любой последовательностью символов в конце строки (%).
Часто в таблице будут записи, которые не имеют никаких значений поля, например, потому что информация не завершена, или потому что это
поле просто не заполнялось. SQL учитывает такой вариант, позволяя вам вводить
значение NULL (ПУСТОЙ) в поле, вместо значения. Когда значение поля равно NULL,
это означает, что программа базы данных специально промаркировала это поле как
не имеющее никакого значения для этой строки (записи).
Это отличается от
просто назначения полю значения нуль или пробела, которые база данных будет
обрабатывать так же, как и любое другое значение. Точно так же как NULL не
является техническим значением, оно не имеет и типа данных. Оно может помещаться
в любой тип поля. Тем не менее, NULL в SQL часто упоминается как "нуль".
Предположим, что вы получили нового заказчика, который ещё не был назначен продавцу. Чем ждать продавца, к которому его нужно назначить, вы можете ввести заказчика в базу данных теперь же, так что он не потеряется при перестановке. Вы можете ввести строку для заказчика со значением NULL в поле snum и заполнить это поле значением позже, когда продавец будет назначен.
Так как 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 очень важны, и мы вернёмся к ним позже.
Операнды могут непосредственно предшествовать булеву 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
Теперь вы можете создавать предикаты в терминах связей, специально определённых в SQL. Вы можете искать значения в определённом диапазоне (BETWEEN) или в числовом наборе (IN), или вы можете искать символьные значения, которые соответствуют тексту внутри параметров (LIKE).
Вы также изучили кое-что о том, как SQL поступает при отсутствии данных (а это реально), используя NULL вместо конкретных значений. Вы можете извлекать или исключать значения NULL из вашего вывода, используя оператор IS NULL.
Теперь, когда вы имеете в вашем распоряжении весь набор стандартных математических и специальных операторов/операций, вы можете переходить к специальным функциям SQL, которые работают на всех группах значений, а не просто на одиночном значении, что важно. Это уже тема Главы 6.
Напишите два запроса, которые могли бы вывести все заказы на 3 или 4 октября 1990.
Напишите запрос, который выберет всех заказчиков, обслуживаемых продавцами Peel или Motika. (Подсказка: из наших типовых таблиц поле snum связывает вторую таблицу с первой.)
Напишите запрос, который может вывести всех заказчиков, чьи имена начинаются с буквы, попадающей в диапазон от A до G.
Напишите запрос, который выберет всех пользователей, чьи имена начинаются с буквы C.
Напишите запрос, который выберет все заказы, имеющие нулевые значения или NULL в поле amt (сумма).
(См. ответы в Приложении A.)
В этой главе вы перейдёте о простого использования запросов к извлечению
значений из базы данных (БД) и определите, как можно использовать эти значения,
чтобы получить из них информацию. Это делается с помощью агрегатных (обобщающих)
функций, которые берут группы значений из поля и сводят их до одиночного значения.
Вы узнаете, как использовать эти функции, как определить группы значений, к
которым они будут применяться, и как определить, какие группы выбираются для
вывода.
Вы увидите также, при каких условиях вы сможете объединить значения поля с этой полученной информацией в
одном запросе.
Запросы могут производить обобщённое групповое значение полей точно так же, как и значение одного поля. Это делается с помощью агрегатных функций. Агрегатные функции производят одиночное значение для всей группы таблицы.
Вот список этих функций:
Агрегатные функции используются, подобно именам полей в предложении 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 Выбор средней суммы
Функция COUNT несколько отличается от всех остальных. Она считает число значений в данном столбце или число строк в таблице. Когда она считает значения столбца, она используется с DISTINCT, чтобы производить счёт чисел различных значений в данном поле. Мы могли бы использовать её, например, чтобы сосчитать количество продавцов, описанных в настоящее время в таблице Заказов (вывод показан на Рисунке 6.3):
SELECT COUNT (DISTINCT snum) FROM Orders;
Обратите внимание в вышеупомянутом примере, что 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 вы обычно применяете для включения повторяемых значений, так как они
эффективнее общих и средних значений всех столбцов.
Чтобы подсчитать общее число строк в таблице, используйте функцию 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 скрыл от вас эту информацию.
Агрегатные функции могут также (в большинстве реализаций) использовать аргумент ALL, который помещается перед именем поля, подобно DISTINCT, но означает противоположное: включать дубликаты. ANSI технически не позволяет этого для COUNT, но многие реализации ослабляют это ограничение.
Различия между ALL и * при использовании с COUNT:
Пока * является единственным аргументом который включает NULL-значения, и только он используется с COUNT; функции, кроме COUNT, игнорируют NULL-значения в любом случае.
Следующая команда подсчитает (COUNT) число не-NULL-значений в поле rating в таблице Заказчиков (включая повторения):
SELECT COUNT (ALL rating) FROM Customers;
До этого вы использовали агрегатные функции с одиночными полями как аргументами. Вы можете также использовать агрегатные функции с аргументами, которые состоят из скалярных выражений, включающих одно или более полей. (Если вы это делаете, DISTINCT не разрешается.)
Предположим, что таблица Заказов имеет ещё один столбец, который хранит предыдущий неуплаченный баланс (поле blnc) для каждого заказчика. Вы должны найти этот текущий баланс добавлением суммы приобретений к предыдущему балансу.
Вы можете найти наибольший неуплаченный баланс следующим образом:
SELECT MAX (blnc + (amt)) FROM Orders;
Для каждой строки таблицы этот запрос будет складывать blnc и amt для данного заказчика и выбирать самое большое значение, которое он найдёт. Конечно, пока заказчики могут иметь несколько заказов, их неуплаченный баланс оценивается отдельно для каждого заказа. Возможно, заказ с более поздней датой будет иметь самый большой неуплаченный баланс. Иначе старый баланс должен быть выбран, как в запросе выше.
Фактически имеется большое количество ситуаций в SQL, где можно использовать скалярные выражения с полями или вместо полей, как вы увидите в Главе 7.
Предложение GROUP BY позволяет вам определять подмножество значений в особом поле в терминах другого поля и применять агрегатную функцию к подмножеству. Это дает возможность объединять поля и агрегатные функции в едином предложении SELECT.
Например, предположим, что вы хотите найти наибольшую сумму продажи, полученную каждым продавцом. Вы можете сделать раздельный запрос для каждого из них, выбрав MAX (amt) из таблицы Заказов для каждого значения поля snum. GROUP BY, однако, позволит вам поместить всё в одну команду:
SELECT snum, MAX (amt) FROM Orders GROUP BY snum;
Вывод для этого запроса показан на Рисунке 6.5.
=============== SQL Execution Log ============== | | | SELECT snum, MAX (amt) | | FROM Orders | | GROUP BY snum; | | =============================================== | | snum | | ------ -------- | | 1001 767.19 | | 1002 1713.23 | | 1003 75.75 | | 1014 1309.95 | | 1007 1098.16 | | | ================================================ Рисунок 6.5 Нахождение максимальной суммы продажи у каждого продавца
GROUP BY применяет агрегатные функции, независимо от серий групп, которые определяются с помощью значения поля в целом. В этом случае каждая группа состоит из всех строк с тем же самым значением поля snum, и MAX функция применяется отдельно для каждой такой группы. Это значение поля, к которому применяется GROUP BY, имеет, по определению, только одно значение на группу вывода так же, как это делает агрегатная функция. Результатом является совместимость, которая позволяет агрегатам и полям объединяться таким образом.
Вы можете также использовать GROUP BY с несколькими полями. Совершенствуя вышеупомянутый пример, предположим, что вы хотите увидеть наибольшую сумму продаж, получаемую каждым продавцом каждый день. Чтобы сделать это, вы должны сгруппировать таблицу Заказов по датам продавцов и применить функцию MAX к каждой такой группе:
SELECT snum, odate, MAX ((amt)) FROM Orders GROUP BY snum, odate;
Вывод для этого запроса показан на Рисунке 6.6.
=============== SQL Execution Log ============== | | | SELECT snum, odate, MAX (amt) | | FROM Orders | | GROUP BY snum, odate; | | =============================================== | | snum odate | | ------ ---------- -------- | | 1001 10/03/1990 767.19 | | 1001 10/05/1990 4723.00 | | 1001 10/06/1990 9891.88 | | 1002 10/03/1990 5160.45 | | 1002 10/04/1990 75.75 | | 1002 10/06/1990 1309.95 | | 1003 10/04/1990 1713.23 | | 1014 10/03/1990 1900.10 | | 1007 10/03/1990 1098.16 | | | ================================================ Рисунок 6.6 Нахождение наибольшей суммы приобретений на каждый день
Конечно же, пустые группы в дни, когда текущий продавец не имел заказов, не будут показаны в выводе.
Предположим, что в предыдущем примере вы хотели бы увидеть только максимальную сумму приобретений, значение которой выше $3000.00. Вы не сможете использовать агрегатную функцию в предложении WHERE (если вы не используете подзапрос, описанный позже), потому что предикаты оцениваются в терминах одиночной строки, а агрегатные функции оцениваются в терминах групп строк. Это означает, что вы не сможете сделать что-нибудь подобно следующему:
SELECT snum, odate, MAX (amt) FROM Orders WHERE MAX ((amt)) > 3000.00 GROUP BY snum, odate;
Это будет отклонением от строгой интерпретации ANSI. Чтобы увидеть максимальную стоимость приобретений свыше $3000.00, вы можете использовать предложение HAVING.
Предложение HAVING определяет критерии, используемые, чтобы удалять определенные группы из вывода, точно так же, как предложение WHERE делает это для отдельных строк.
Правильной командой будет следующая:
SELECT snum, odate, MAX ((amt)) FROM Orders GROUP BY snum, odate HAVING MAX ((amt)) > 3000.00;
Вывод для этого запроса показан на Рисунке 6. 7.
=============== SQL Execution Log ============== | | | SELECT snum, odate, MAX (amt) | | FROM Orders | | GROUP BY snum, odate | | HAVING MAX (amt) > 3000.00; | | =============================================== | | snum odate | | ------ ---------- -------- | | 1001 10/05/1990 4723.00 | | 1001 10/06/1990 9891.88 | | 1002 10/03/1990 5160.45 | | | ================================================ Рисунок 6.7 Удаление групп агрегатных значений
Аргументы в предложении HAVING следуют тем же самым правилам, что и в предложении SELECT, состоящем из команд, использующих GROUP BY. Они должны иметь одно значение на группу вывода.
Следующая команда будет запрещена:
SELECT snum, MAX (amt) FROM Orders GROUP BY snum HAVING odate = 10/03/1988;
Поле оdate не может быть вызвано предложением HAVING, потому что оно может иметь (и действительно имеет) больше чем одно значение на группу вывода. Чтобы избегать такой ситуации, предложение HAVING должно ссылаться только на агрегаты и поля, выбранные GROUP BY. Имеется правильный способ сделать вышеупомянутый запрос (вывод показан на Рисунке 6.8):
SELECT snum, MAX (amt) FROM Orders WHERE odate = 10/03/1990 GROUP BY snum; =============== SQL Execution Log ============== | | | SELECT snum, odate, MAX (amt) | | FROM Orders | | GROUP BY snum, odate; | | =============================================== | | snum | | ------ -------- | | 1001 767.19 | | 1002 5160.45 | | 1014 1900.10 | | 1007 1098.16 | | | ================================================ Рисунок 6.8 Максимальное значение суммы продаж у каждого продавца на 3 октября
Поскольку поля odate нет, не может быть и выбранных полей, и значение этих данных меньше, чем в некоторых других примерах. Вывод должен, вероятно, включать что-нибудь такое, что говорит: "это - самые большие заказы на 3 октября". В Главе 7 мы покажем, как вставлять текст в ваш вывод.
Как говорилось ранее, HAVING может использовать только аргументы, которые имеют одно значение на группу вывода. Практически ссылки на агрегатные функции - наиболее общие, но и поля, выбранные с помощью GROUP BY, также допустимы. Например, мы хотим увидеть наибольшие заказы для Serres и Rifkin:
SELECT snum, MAX (amt) FROM Orders GROUP BY snum HAVING snum B (1002,1007);
Вывод для этого запроса показан на Рисунке 6.9.
=============== SQL Execution Log ============== | | | SELECT snum, MAX (amt) | | FROM Orders | | GROUP BY snum | | HAVING snum IN (1002, 1007); | | =============================================== | | snum | | ------ -------- | | 1002 5160.45 | | 1007 1098.16 | | | ================================================ Рисунок 6.9 Использование HAVING с полями GROUP BY
В строгой интерпретации ANSI SQL вы не можете использовать агрегат агрегата. Предположим, что вы хотите выяснить, в какой день имелась наибольшая сумма продаж. Если вы попробуете сделать так,
SELECT odate, MAX (SUM (amt)) FROM Orders GROUP BY odate;
то ваша команда будет, вероятно, отклонена. (Некоторые реализации не предписывают этого ограничения, что выгодно, потому что вложенные агрегаты могут быть очень полезны, даже если они и несколько проблематичны.) В вышеупомянутой команде, например, SUM должен применяться к каждой группе поля odate, а MAX - ко всем группам, производящим одиночное значение для всех групп. Однако предложение GROUP BY подразумевает, что должна иметься одна строка вывода для каждой группы поля odate.
Теперь вы используете запросы несколько по-иному. Способность получать, а не просто размещать значения, очень мощна. Это означает, что вы не обязательно должны следить за определённой информацией, если вы можете сформулировать запрос так, чтобы её получить. Запрос будет давать вам поминутные результаты, в то время как таблица общего или среднего значений будет хороша только некоторое время после её модификации. Это не должно наводить на мысль, что агрегатные функции могут полностью вытеснить потребность в отслеживании информации, такой, например, как эта.
Вы можете применять эти агрегаты для групп значений, определённых предложением GROUP BY. Эти группы имеют значение поля в целом и могут постоянно находиться внутри других групп, которые имеют значение поля в целом. В то же время, предикаты ещё используются, чтобы определять, какие строки агрегатной функции применяются.
Объединенные вместе, эти особенности делают возможным производить агрегаты, основанные на чётко определённых подмножествах значений в поле. Затем вы можете определять другое условие для исключения определенных результатов групп с предложением HAVING.
Теперь, когда вы стали знатоком того, как запрос производит значения, мы покажем вам, в Главе 7, чт́о вы можете делать со значениями, которые он производит.
Напишите запрос, который сосчитал бы все суммы продаж на 3 октября.
Напишите запрос, который сосчитал бы число различных не-NULL-значений поля city в таблице Заказчиков.
Напишите запрос, который выбрал бы наименьшую сумму для каждого заказчика.
Напишите запрос, который выбирал бы в алфавитном порядке заказчиков, чьи имена начинаются с буквы G.
Напишите запрос, который выбрал бы высший рейтинг в каждом городе.
Напишите запрос, который сосчитал бы число заказчиков, регистрирующих каждый
день свои заказы. (Если продавец имел более одного заказа в данный день,
он должен учитываться только один раз.)
(См. ответы в Приложении A.)
Эта глава расширит ваши возможности в работе с выводом, производимым запросом. Вы узнаете, как вставлять текст и константы между выбранными полями, как использовать выбранные поля в математических выражениях, чьи результаты затем становятся выводом, и как сделать, чтобы ваши значения выводились в определенном порядке. Эта последняя особенность включена, чтобы упорядочивать ваш вывод по любым столбцам, любым полученным значениям этого столбца или по обоим.
Большинство основанных на SQL баз данных предоставляют специальные средства, позволяющие совершенствовать вывод ваших запросов. Конечно, они претерпевают значительные изменения от программы к программе, и их обсуждения здесь не будет, однако имеются пять особенностей, созданных в стандарте SQL, которые позволяют вам делать нечто большее, чем просто вывод значений полей и агрегатных данных.
Предположим, вы хотите выполнять простые числовую обработку данных, чтобы затем помещать их в форму, больше соответствующую вашим потребностям. SQL позволяет вам помещать скалярные выражения и константы среди выбранных полей. Эти выражения могут дополнять или замещать поля в предложениях SELECT и могут включать в себя одно или более выбранных полей.
Например, вы можете представить комиссионные вашего продавца в процентном отношении, а не как десятеричные числа.
Для этого достаточно:
SELECT snum, sname, city, comm * 100 FROM Salespeople;
Вывод для этого запроса показан на Рисунке 7.1.
Последний столбец предшествующего примера не помечен (т.е. без наименования), потому что это столбец вывода. Столбцы вывода это столбцы данных, созданные в запросе способом, иным, нежели просто извлечение их из таблицы. Вы создаёте их всякий раз, когда используете агрегатные функции,
=============== 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.1 Помещение выражения в вашем запросе
константы или выражения в предложении SELECT-запроса. Так как имя столбца - один из атрибутов таблицы, столбцы, которые приходят не из таблиц, не имеют никаких имён. Другими словами, непомеченные столбцы вывода могут обрабатываться так же, как и столбцы, извлечённые из таблиц, почти во всех ситуациях.
Символ '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.
Как мы подчеркивали, таблицы это неупорядоченные наборы данных, и данные, которые выводятся из них, не обязательно появляются в какой-то определённой последовательности. SQL использует команду ORDER BY, чтобы дать возможность упорядочить вывод. Эта команда упорядочивает вывод запроса согласно значениям в том или ином количестве выбранных столбцов. Несколько столбцов упорядочиваются один относительно другого так же, как с GROUP BY, и вы можете определять возрастание (ASC) или убывание (DESC) для каждого столбца. По умолчанию установлено возрастание. Давайте рассмотрим нашу таблицу заказа, приводимую в заказ с помощью номера заказчика (обратите внимание на значения в cnum столбце):
SELECT * FROM Orders ORDER BY cnum DESC;
Вывод показан на Рисунке 7.4.
=============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | ORDER BY cnum DESC; | | =============================================== | | onum amt odate cnum snum | | ------ -------- ---------- ----- ----- | | 3001 18.69 10/03/1990 2008 1007 | | 3006 1098.16 10/03/1990 2008 1007 | | 3002 1900.10 10/03/1990 2007 1004 | | 3008 4723.00 10/05/1990 2006 1001 | | 3011 9891.88 10/06/1990 2006 1001 | | 3007 75.75 10/04/1990 2004 1002 | | 3010 1309.95 10/06/1990 2004 1002 | | 3005 5160.45 10/03/1990 2003 1002 | | 3009 1713.23 10/04/1990 2002 1003 | | 3003 767.19 10/03/1990 2001 1001 | | | ================================================ Рисунок 7.4 Упорядочивание вывода с помощью убывания поля
Мы можем также упорядочивать таблицу с помощью другого столбца, например, с помощью поля amt, внутри упорядочивания поля cnum. (вывод показан в Рисунке 7.5):
SELECT * FROM Orders ORDER BY cnum DESC, amt DESC; =============== SQL Execution Log ============== | | | SELECT * | | FROM Orders | | ORDER BY cnum DESC, amt DESC; | | =============================================== | | onum amt odate cnum snum | | ------ -------- ---------- ----- ----- | | 3006 1098.16 10/03/1990 2008 1007 | | 3001 18.69 10/03/1990 2008 1007 | | 3002 1900.10 10/03/1990 2007 1004 | | 3011 9891.88 10/06/1990 2006 1001 | | 3008 4723.00 10/05/1990 2006 1001 | | 3010 1309.95 10/06/1990 2004 1002 | | 3007 75.75 10/04/1990 2004 1002 | | 3005 5160.45 10/03/1990 2003 1002 | | 3009 1713.23 10/04/1990 2002 1003 | | 3003 767.19 10/03/1990 2001 1001 | | | ================================================ Рисунок 7.5: Упорядочивание вывода с помощью нескольких полей
Вы можете использовать ORDER BY таким способом одновременно с любым числом столбцов. Обратите внимание, что во всех случаях столбцы, которые упорядочиваются, должны быть указаны в выборе SELECT. Это требование ANSI, которое в большинстве случаев, но не всегда, предписано системе. Следующая команда, например, будет запрещена:
SELECT cname, city FROM Customers GROUP BY cnum;
Так как поле cnum не было выбранным полем, GROUP BY не сможет найти его, чтобы использовать для упорядочивания вывода. Даже если ваша система позволяет это, смысл упорядочивания не будет понятен из вывода, так что включение (в предложение SELECT) всех столбцов, используемых в предложении ORDER BY, в принципе желательно.
ORDER BY может, кроме того, использоваться с GROUP BY для упорядочивания групп. При этом ORDER BY всегда идёт последним.
Вот пример из предыдущей главы с добавлением предложения ORDER BY. Перед группированием вывода порядок групп был произвольным; и мы теперь заставим группы размещаться в последовательности:
SELECT snum, odate, MAX (amt) FROM Orders GROUP BY snum, odate ORDER BY snum;
Вывод показан на Рисунке 7.6.
=============== SQL Execution Log ============== | | | SELECT snum, odate, MAX (amt) | | FROM Orders | | GROUP BY snum, odate | | ORDER BY snum ; | | =============================================== | | snum odate amt | | ----- ---------- -------- | | 1001 10/06/1990 767.19 | | 1001 10/05/1990 4723.00 | | 1001 10/05/1990 9891.88 | | 1002 10/06/1990 5160.45 | | 1002 10/04/1990 75.75 | | 1002 10/03/1990 1309.95 | | 1003 10/04/1990 1713.23 | | 1004 10/03/1990 1900.10 | | 1007 10/03/1990 1098.16 | | | ================================================ Рисунок 7.6 Упорядочивание с помощью группы
Так как мы не указывали на возрастание или убывание порядка, возрастание используется по умолчанию.
Вместо имён столбцов вы можете использовать их порядковые номера для указания поля, используемого при упорядочивании вывода. Эти номера могут ссылаться не на порядок столбцов в таблице, а на их порядок в выводе. Другими словами, поле, упомянутое в предложении SELECT первым, для ORDER BY - поле 1, независимо от того, каким по порядку оно стоит в таблице. Например, вы можете использовать следующую команду, чтобы увидеть определенные поля таблицы Продавцов упорядоченными по убыванию к наименьшему значению комиссионных (вывод показан на Рисунке 7.7):
SELECT sname, comm FROM Salespeople ORDER BY 2 DESC; =============== SQL Execution Log ============ | | | (SELECT sname, comm | | FROM Salespeople | | ORDER BY 2 DESC; | | ============================================= | | sname comm | | -------- -------- | | Peel 0.17 | | Serres 0.15 | | Rifkin 0.13 | | | =============================================== Рисунок 7.7 Упорядочивание, использующее номера
Одна из основных целей этого свойства ORDER BY - дать вам возможность использовать GROUP BY со столбцами вывода, так же как и со столбцами таблицы. Столбцы, производимые агрегатной функцией, константы или выражения в предложении SELECT запроса абсолютно пригодны для использования с GROUP BY, если на них ссылаются с помощью номера.
Например, давайте сосчитаем заказы каждого из наших продавцов и выведем результаты в убывающем порядке, как показано в Рисунке 7.8:
SELECT snum, COUNT (DISTINCT onum) FROM Orders GROUP BY snum ORDER BY 2 DESC; =============== SQL Execution Log ============== | | | SELECT snum, odate, MAX (amt) | | FROM Orders | | GROUP BY snum | | ORDER BY 2 DESC; | | =============================================== | | snum | | ----- ---------- | | 1001 3 | | 1002 3 | | 1007 2 | | 1003 1 | | 1004 1 | | | ================================================ Рисунок 7.8 Упорядочивание с помощью столбца вывода
В этом случае вы должны использовать номер столбца, так как столбец вывода не имеет имени; и вы не должны использовать саму агрегатную функцию. Строго говоря, по правилам ANSI SQL, следующее не будет работать, хотя некоторые системы и пренебрегают этим требованием:
SELECT snum, COUNT (DISTINCT onum) FROM Orders GROUP BY snum GROUP BY COUNTОМ (DISTINCT onum) DESC;
Это будет отклонено большинством систем!
Если имеются пустые значения (NULL) в поле, которое вы используете для упорядочивания вашего вывода, они могут или следовать за, или предшествовать каждому другому значению в поле. Это возможность, которую ANSI оставил для отдельных программ. Программа использует ту или иную форму.
В этой главе вы изучили, как заставить ваши запросы делать больше, чем просто выводить значения полей или объединять функциональные данные таблиц. Вы можете использовать поля в выражениях: например, вы можете умножить числовое поле на 10 или даже умножить его на другое числовое поле. Кроме того, вы можете помещать константы, включая и символы, в ваш вывод, что позволяет помещать текст непосредственно в запрос и получать его в выводе вместе с данными таблицы. Это дает возможность помечать или объяснять ваш вывод различными способами.
Вы также изучили, как упорядочивать ваш вывод. Даже если таблица сама по себе остаётся неупорядоченной, предложение ORDER BY даёт вам возможность управлять порядком вывода строк данного запроса. Вывод запроса может быть в порядке возрастания или убывания, и столбцы могут быть вложенными один внутрь другого.
Понятие выводимых столбцов объяснялось в этой главе. Вы теперь знаете, что выводимые столбцы можно использовать, чтобы упорядочить вывод запроса, но эти столбцы - без имени и, следовательно, должны определяться их порядковым номером в предложении ORDER BY.
Теперь, когда вы увидели, что можно делать с выводом запроса, основанного на одиночной таблице, настало время перейти к возможностям улучшенного запроса и узнать, как сделать запрос любого числа таблиц в одной команде, определив связи между ними. Это будет темой Главы 8.
Предположим, что каждый продавец имеет 12% комиссионных. Напишите запрос к таблице Заказов, который выведет номер заказа, номер продавца и сумму комиссионных продавца по этому заказу.
Напишите запрос к таблице Заказчиков, который мог бы найти высший рейтинг в каждом городе. Вывод должен быть в такой форме:
For the city (city), the highest rating is: (rating).
Напишите запрос, который выводил бы список заказчиков в нисходящем порядке. Вывод поля оценки/рейтинга (rating) должен сопровождаться именем заказчика и его номером.
Напишите запрос, который выводил бы общие заказы на каждый день и помещал результаты в нисходящем порядке.
(См. ответы в Приложении A.)
До этого каждый запрос, который мы рассматривали, основывался на одиночной таблице. В этой главе вы узн́аете, как сделать запрос любого числа таблиц с помощью одной команды. Это чрезвычайно мощное средство, потому что оно не только объединяет вывод из многочисленных таблиц, но и определяет связи между ними. Вы обучитесь различным формам, использующим эти связи, а также их настройке и использованию, чтобы удовлетворять возможным специальным требованиям.
Одна из наиболее важных особенностей запросов SQL - их способность определять связи между многочисленными таблицами и выводить информацию из них, в терминах этих связей, всю внутри одной команды. Этот вид операции называется объединением, которое является одним из видов операций в реляционных базах данных. Как установлено в Главе 1, главное в реляционном подходе это связи, которые можно создавать между позициями данных в таблицах. Используя объединения, мы непосредственно связываем информацию с любым числом таблиц и таким образом способны создавать связи между сравнимыми фрагментами данных. При объединении, таблицы, представленные списком в предложении FROM, отделяются запятыми. Предикат запроса может ссылаться к любому столбцу любой связанной таблицы и, следовательно, может использоваться для связи между ими. Обычно предикат сравнивает значения в столбцах различных таблиц, чтобы определить, удовлетворяет ли WHERE установленному условию.
Полное имя столбца таблицы фактически состоит из имени таблицы, сопровождаемого точкой, и затем имени столбца. Вот несколько примеров имён:
Salespeople.snum Salespeople.city Orders.odate
До этого вы могли опускать имена таблиц, потому что вы запрашивали единовременно только одну таблицу, а SQL достаточно интеллектуален, чтобы присвоить соответствующий префикс имени таблицы. Даже когда вы делаете запрос нескольких таблиц, вы ещё можете опускать имена таблиц, если все их столбцы имеют различные имена. Но так бывает не всегда. Например, мы имеем две типовые таблицы со столбцами, называемыми city.
Если мы должны связать эти столбцы (кратковременно), мы должны будем указать их с именами Salespeople.city или Customers.city, чтобы SQL мог их различать.
Предположим, что вы хотите поставить в соответствие вашему продавцу ваших заказчиков в городе, в котором они живут, поэтому вы увидите все комбинации продавцов и заказчиков для этого города. Вы должны будете брать каждого продавца и искать в таблице Заказчиков всех заказчиков того же самого города. Вы могли бы сделать это, введя следующую команду (вывод показан на Рисунке 8.1):
SELECT Customers.cname, Salespeople.sname, Salespeople.city FROM Salespeople, Customers WHERE Salespeople.city = Customers.city; =============== SQL Execution Log ============ | SELECT Customers.cname, Salespeople.sname, | | Salespeople.city | | FROM Salespeople, Customers | | WHERE Salespeople.city = Customers.city | | ============================================= | | cname cname city | | ------- -------- ---- | | Hoffman Peel London | | Hoffman Peel London | | Liu Serres San Jose | | Cisneros Serres San Jose | | Hoffman Motika London | | Clemens Motika London | ============================================= Рисунок 8.1 Объединение двух таблиц
Так как это city имеется и в таблице Продавцов, и таблице Заказчиков, имена таблиц должны использоваться как префиксы. Хотя это необходимо, только когда два или более полей имеют одно и то же имя, в любом случае это хорошая идея: включать имя таблицы в объединение для лучшего понимания и непротиворечивости. Несмотря на это, мы будем в наших примерах далее использовать имена таблиц только тогда, когда необходимо, так что будет ясно, когда они необходимы, а когда нет.
Что SQL в основном делает в объединении, так это исследует каждую комбинацию строк двух или более возможных таблиц и проверяет эти комбинации по их предикатам. В предыдущем примере требовалась строка продавца Peel из таблицы Продавцов и объединение её с каждой строкой таблицы Пользователей, по одной в каждый момент времени.
Если комбинация производит значение, которое делает предикат верным, и если поле city из строк таблиц Заказчика равно London, то Peel - это то запрашиваемое значение, которое комбинация выберет для вывода. То же самое будет затем выполнено для каждого продавца в таблице Продавцов (у некоторых из которых не было никаких заказчиков в этих городах).
Эта особенность часто используется просто для эксплуатации связей, встроенных в БД. В предыдущем примере мы установили связь между двумя таблицами в объединении. Это прекрасно. Но эти таблицы уже были соединены через 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. Обычно вы не создаёте сложных связей, подобно этой, и по этой причине вы, вероятно, будете строить наиболее общие объединения по равенству, но вы должны хорошо знать и другие возможности.
Вы можете также создавать запросы, объединяющие более двух таблиц. Предположим, что мы хотим найти все заказы заказчиков, не находящихся в тех городах, где находятся их продавцы. Для этого необходимо связать все три наши типовые таблицы (вывод показан на Рисунке 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 в таблице Заказов).
Теперь вы больше не ограничиваетесь просмотром одной таблицы в каждый момент времени. Кроме того, вы можете делать сложные сравнения между любыми полями любого количества таблиц и использовать полученные результаты, чтобы решать, какую информацию вы хотели бы видеть. Фактически эта методика настолько полезна для построения связей, что она часто используется для создания их внутри одиночной таблицы. Это будет правильным: вы сможете объединить таблицу с собой, а это очень удобна вещь. Это будет темой Главы 9.
Напишите запрос, который вывел бы список номеров заказов сопровождающихся именем заказчика, который создавал эти заказы.
Напишите запрос, который выдавал бы имена продавца и заказчика для каждого заказа после номера заказа.
Напишите запрос, который выводил бы всех заказчиков, обслуживаемых продавцом с комиссионными выше 12%. Выведите имя заказчика, имя продавца и ставку комиссионных продавца.
Напишите запрос, который вычислил бы сумму комиссионных продавца для каждого заказа заказчика с оценкой выше 100.
(См. ответы в Приложении A.)
В Главе 8 мы рассмотрели, как объединить две или более таблиц. Интересно, что та же самая методика может использоваться для объединения двух копий одной таблицы. В этой главе мы будем исследовать этот процесс. Как вы увидите, объединение таблицы с самой собой - далеко не простая вещь и может быть очень полезна для определения некоторых видов связей между данными в конкретной таблице.
Для объединения таблицы с собой вы можете сделать каждую строку таблицы одновременно и комбинацией её с собой, и комбинацией с каждой другой строкой таблицы. Вы затем оцениваете каждую комбинацию в терминах предиката так же, как в объединениях нескольких таблиц. Это позволит легко создавать определенные виды связей между различными позициями внутри одной таблицы с помощью обнаружения, например, пар строк со значением поля.
Вы можете представить объединение таблицы с собой как объединение двух копий одной и той же таблицы. Таблица на самом деле не копируется, но SQL выполняет команду так, как если бы это было сделано. Другими словами: это такое же объединение, как и любое другое объединение между двум таблицами, за исключением того, что в данном случае обе таблицы идентичны.
Синтаксис команды для объединения таблицы с собой - тот же, что и для объединения нескольких таблиц. Когда вы объединяете таблицу с собой, все одинаковые имена столбца дополняются префиксами имени таблицы. Чтобы ссылаться к этим столбцам внутри запроса, вы должны иметь два различных имени для этой таблицы.
Вы можете сделать это с помощью определения временных имён, называемых "переменными диапазона", "переменными корреляции" или просто "псевдонимами".
Вы определяете их в предложении FROM запроса. Это очень просто: вы вводите имя таблицы, оставляете пробел, а затем указываете псевдоним для неё.
Вот пример, который находит все пары заказчиков, имеющих один и тот же рейтинг (вывод показан на Рисунке 9.1):
SELECT first.cname, second.cname, first.rating FROM Customers first, Customers second WHERE first.rating = second.rating; =============== SQL Execution Log ============== | | | Giovanni Giovanni 200 | | Giovanni Liu 200 | | Liu Giovanni 200 | | Liu Liu 200 | | Grass Grass 300 | | Grass Cisneros 300 | | Clemens Hoffman 100 | | Clemens Clemens 100 | | Clemens Pereira 100 | | Cisneros Grass 300 | | Cisneros Cisneros 300 | | Pereira Hoffman 100 | | Pereira Clemens 100 | | Pereira Pereira 100 | | | =============================================== Рисунок 9.1 Объединение таблицы с собой
(Обратите внимание, что на Рисунке 9.1, как и в некоторых дальнейших примерах, полный запрос не может уместиться в окне вывода и, следовательно, будет усекаться.)
В вышеприведённой команде, SQL ведёт себя так, как если бы он соединял две таблицы, называемые 'first' и 'second'. Обе они - фактически - таблица Заказчика, но псевдонимы разрешают им быть обработанными независимо. Псевдонимы 'first' и 'second' были установлены в предложении FROM запроса сразу после имени таблицы.
Обратите внимание, что псевдонимы могут использоваться в предложении SELECT, даже если они не определены в предложении FROM. Это очень удобно. SQL будет сначала принимать любые такие псевдонимы на веру, но будет отклонять команду, если они не определены далее в предложении FROM запроса.
Псевдоним существует, только пока команда выполняется!
Когда запрос заканчивается, псевдонимы, используемые в нём, больше не имеют никакого значения.
Теперь, когда имеются две копии таблицы Заказчиков, чтобы работать с ними, SQL может обрабатывать эту операцию точно так же, как и любое другое объединение: берёт каждую строку из одного псевдонима и сравнивает её с каждой строкой из другого псевдонима.
Обратите внимание, что наш вывод имеет два значения для каждой комбинации, причем второй раз - в обратном порядке. Это потому что каждое значение показано первый раз в каждом псевдониме, а второй раз (симметрично) - в предикате. Следовательно, значение A в псевдониме сначала выбирается в комбинации со значением B во втором псевдониме, а затем значение A во втором псевдониме выбирается в комбинации со значением B в первом псевдониме. В нашем примере, Hoffman выбран вместе с Clemens, а затем Clemens выбран вместе с Hoffman. Тот же самый случай - с Cisneros и Grass, Liu и Giovanni, и так далее. Кроме того, каждая строка была сравнена сама с собой, чтобы вывести строки, такие как Liu и Liu. Простой способ избежать этого - установить порядок на два значения так, чтобы одно могло быть меньше, чем другое, или предшествовать ему в алфавитном порядке. Это делает предикат асимметричным, поэтому те же самые значения в обратном порядке не будут выбраны снова, например:
SELECT first.cname, second.cname, first.rating FROM Customers first, Customers second WHERE first.rating = second.rating AND first.cname < second.cname;
Вывод этого запроса показан на Рисунке 9.2.
Hoffman предшествует Pereira в алфавитном порядке, поэтому комбинация удовлетворяет обоим условиям предиката и появляется в выводе. Когда та же сама комбинация появляется в обратном порядке - когда Pereira в псевдониме первой таблицы сравнивается с Hoffman во второй таблице псевдонима - второе условие не выполняется. Аналогично Hoffman не выбирается при наличии того же рейтинга, что и у него самого, потому что его имя не предшествует ему самому в алфавитном порядке. Если бы вы захотели включить сравнение строк с ними же самими
=============== SQL Execution Log ============== | | | SELECT first.cname, second.cname, first.rating | | FROM Customers first, Customers second | | WHERE first.rating = second.rating | | AND first.cname < second.cname | | =============================================== | | cname cname rating | | ------- --------- ------- | | Hoffman Pereira 100 | | Giovanni Liu 200 | | Clemens Hoffman 100 | | Pereira Pereira 100 | | Gisneros Grass 300 | ================================================= Рисунок 9.2 Устранение избыточности вывода в объединении с собой
в запросах, подобно этому, вы могли бы просто использовать < = вместо <.
Таким образом, мы можем использовать эту особенность SQL для проверки определенных видов ошибок. При просмотре таблицы Заказов вы видите, что поля cnum и snum должны иметь постоянную связь. Так как каждый заказчик должен быть назначен одному, и только одному, продавцу, каждый раз, когда определенный номер заказчика появляется в таблице Заказов, он должен совпадать с таким же номером продавца.
Следующая команда будет определять любые несогласованности в этой области:
SELECT first.onum, tirst.cnum, first.snum, second.onum, second.cnum,second.snum FROM Orders first, Orders second WHERE first.cnum = second.cnum AND first.snum < > second.snum;
Хотя это выглядит сложно, логика достаточно проста. Команда будет брать первую строку таблицы Заказов, запоминать её под первым псевдонимом и проверять её в комбинации с каждой строкой таблицы Заказов под вторым псевдонимом, одну за другой. Если комбинация строк удовлетворяет предикату, она выбирается для вывода. В этом случае предикат будет рассматривать эту строку, найдёт строку, где поле cnum=2008, а поле snum=1007, и затем рассмотрит каждую следующую строку с тем же самым значением поля cnum. Если он находит, что какая-то из них имеет значение, отличное от значения поля snum, предикат будет верен, и выведет выбранные поля из текущей комбинации строк. Если же значение snum с данным значением cnum в нашей таблице совпадает, эта команда не произведет никакого вывода.
Хотя объединение таблицы с собой это первая ситуация, когда ясна необходимость наличия псевдонимов, вы не ограничены в их использовании тем, чтобы только отличать копию одной таблицы от её оригинала. Вы можете использовать псевдонимы в любое время, когда вы хотите создать альтернативные имена для ваших таблиц в команде. Например, если ваши таблицы имеют очень длинные и сложные имена, вы могли бы определить простые односимвольные псевдонимы, типа a и b, и использовать их вместо имён таблиц в предложении SELECT и предикате. Они будут также использоваться с соотнесенными подзапросами (обсуждаемыми в Главе 11).
Вы можете использовать любое количество псевдонимов для одной таблицы в запросе, хотя использование более двух в данном предложении 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 Объединение таблицы с собой и с другой таблицей
Теперь вы понимаете возможности объединения и можете использовать их для ограничения связей с этой же таблицей, между различными таблицами, или в обоих случаях. Вы видели некоторые варианты объединения при использовании его возможностей.
Вы теперь познакомились с терминами "порядковые переменные", "корреляционные переменные" и "предложения" (эта терминология будет меняться от продукта к продукту, так что мы предлагаем вам познакомится со всеми тремя терминами). Кроме того, вы поняли, немного, как в действительности работают запросы.
Следующим шагом, после комбинации нескольких таблиц или нескольких копий одной таблицы в запросе, будет комбинация нескольких запросов, где один запрос будет производить вывод, который будет затем управлять работой другого запроса. Это другое мощное средство SQL, о котором мы расскажем в Главе 10 и более детально - в последующих главах.
Напишите запрос, который вывел бы все пары продавцов, живущих в одном и том же городе. Исключите комбинации продавцов с самими собой, а также дубликаты строк, выводимых в обратным порядке.
Напишите запрос, который выведет все пары заказов по данным заказчикам, имена этих заказчиков и исключит дубликаты из вывода, как в предыдущем вопросе.
Напишите запрос, который вывел бы имена (cname) и города (city) всех заказчиков.
(Ответы см. в Приложении А.)
В этом приложении содержатся сокращённые описания различных команд SQL. Цель состоит в том, чтобы дать вам быструю и точную справку и определение SQL.
Первый раздел этого приложения определяет элементы, используемые для создания команд SQL; второй - подробности синтаксиса и предложения с кратким описанием самих команд.
Вот стандартные условные обозначения (они называются BNF-условиями):
Кроме того, мы будем использовать следующую последовательность ( .,.. ) чтобы указывать, что предшествующее этому может повторяться любое число раз, с индивидуальными событиями, отделяемыми запятыми. Атрибуты, которые не являются частью официального стандарта, будут отмечены в описании как (*нестандартные*).
Этот раздел определяет элементы команд SQL.
Они разделены на две категории: Основные элементы языка и Функциональные элементы языка.
Основные элементы - это создаваемые блоки языка; когда SQL исследует команду, то он сначала оценивает каждый символ в тексте команды в терминах этих элементов. Разделитель <separator> отделяет одну часть команды от другой; всё, что находится между разделителями <separator>, обрабатывается как модуль. Основываясь на этом разделении, SQL интерпретирует команду.
Функциональные элементы - это разнообразные элементы, отличающиеся от ключевых слов, которые могут интерпретироваться как модули. Это части команды, отделяемые с помощью разделителей <separator>, имеющие специальное значение в SQL. Некоторые из них являются специальными для определенных команд и будут описаны вместе с этими командами позже в этом приложении.
Перечисленные здесь являются общими элементами для всех описываемых команд.
Функциональные элементы могут определяться в терминах друг друга или даже в собственных терминах. Например, предикат <predicate>, наш последний и наиболее сложный случай, содержит предикат <predicate> внутри собственного определения. Это потому, что предикат <predicate>, использующий AND или OR, может содержать любое число предикатов <predicate>, которые могут работать автономно. Мы представляли вам предикат <predicate> в отдельной секции в этом приложении из-за разнообразия и сложности этого функционального элемента языка. Он будет постоянно присутствовать при обсуждении других функциональных частей команд.
БАЗОВЫЕ ЭЛЕМЕНТЫ ЯЗЫКА
ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ <separator> <comment> | <space> | <newline> <comment> --<string> <newline> <space> пробел <newline> определяемый реализацией конец символьной строки <identifier> <letter>[{<letter or digit> | <underscore}... ] ИМЕЙТЕ В ВИДУ: Следуя строгому стандарту ANSI, символы должны быть набраны в верхнем регистре, а идентификатор <identifier> не должен быть длиннее 18-ти символов. ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ <underscore> - <percent sign> % <delimiter> любое из следующих: , ( ) <> . : = + " - | <> >= <= или <string> <string> [любой печатаемый текст в одинарных кавычках] Примечание: В <string>, две последовательных одинарных кавычки ( ' ' ) интерпретируются как одна ( ' ). <SQL term> окончание/терминатор, зависящее от главного языка. (*только вложенный SQL*)
Следующая таблица показывает функциональные элементы команд SQL и их определения: ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ <query> Предложение SELECT <subquery> Заключённое в круглые скобки предложение SELECT внутри другого условия, которое фактически оценивается отдельно для каждой строки-кандидата другого предложения. <value expression> <primary> | <primary> <operator> <primary> | <primary> <operator> <value expression> <operator> любое из следующих: + - / * <primary> <column name> | <literal> | <aggregate function> | <built-in constant> | <nonstandard function> <literal> <string> | <mathematical expressio> ЭЛЕМЕНТ ОПРЕДЕЛЕНИЕ <built-in constant> USER | <implementation-defined constant> <table name> <identifier> <column spec> [<table name> | <alias>.]<column name> <grouping column> <column spec> | <integer> <ordering column> <column spec> | <integer> <colconstraint> NOT NULL | UNIQUE | CHECK (<predicate>) | PRIMARY KEY | REFERENCES <table name>[(<column name>)] <tabconstraint> UNIQUE (<column list>) | CHECK (<predicate>) | PRIMARY KEY (<column list>) | FOREIGN KEY (<column list>) | REFERENCES <table name>[(<column list>)] <defvalue> ЗНАЧЕНИЕ ПО УМОЛЧАНИЮ = <value expression> <data type> Допустимый тип данных (См. в Приложении B описание типов, обеспечиваемых ANSI, или в Приложении C - другие общие типы.) <size> Значение зависит от <data type>(См. Приложение B.) <cursor name> <identifier> <index name> <identifier> <synonym> <identifier>(*нестандартный*) <owner> <Authorization ID> <column list> <column spec> .,.. <value list> <value expression> .,.. <table reference> { <table name> [<alias>] } .,..
Здесь определён список различных типов предиката <predicate>, описанных на следующих страницах:
<predicate> ::= [NOT]
{ <comparison predicate> | <in predicate> | <null predicate> | <between predicate> | <like predicate> | <quantified predicate> | <exists predicate> } [ANDI OR <predicate> ]
<predicate> - это выражение, которое может быть true, false или неизвестным, за исключением
<exists predicate> и <null predicate>, которые могут быть только верными или неверными.
Будет получено "неизвестно", если NULL-значения предотвращают вывод полученного ответа. Это будет случаться всякий раз, когда NULL-значение сравнивается с любым
значением. Стандартные булевы операторы - AND, OR и NOT - могут использоваться с предикатом
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. Различные типы предикатов <predicate> рассматриваются отдельно в следующем разделе.
<comparison predicate> (предикат сравнения)
Синтаксис
<value expresslon> <relational op> <value expresslon> |
<subquery>
<relatlonal op> :: =
=
| <
|>
| <
|>=
| <>
Если либо <value expression> = NULL, либо <comparison predicate>
= неизвестно;
другими словами, это true, если сравнение true, или false, если сравнение false.
<relational op> имеет стандартные математические значения для числовых
значений; для других типов значений эти значения определяются конкретной реализацией.
Оба <value expression> должны иметь сравнимые типы данных. Если подзапрос <subquery> используется, он должен содержать одно выражение <value expression> в предложении SELECT, чьё значение будет заменять второе выражение <value expression> в предикате сравнения <comparision predicate> каждый раз, когда <subquery> действительно выполняется.
<between predicate>
Синтаксис
<value expression> [NOT] BETWEEN <value expression>
AND <value expression>
<between predicate> - A BETWEEN B AND C имеет такое же значение, что и <predicate> - ( A>= B AND
<= C). <between predicate>, для которого A NOT
BETWEEN B AND C, имеет такое же значение, что и NOT (BETWEEN B AND C).
<value expression> может быть выведено с помощью нестандартного запроса <subquery> (*nonstandard*).
<in prediicate>
Синтаксис
<value expression> [NOT] IN <value list> | <subquery>
Список значений <value list> будет состоять из одного или более
значений в круглых скобках с разделением запятыми, которые имеют сравнимый с <value expression>
тип данных. Если используется подзапрос <subquery>, он должен содержать только одно выражение
<value expression> в предложении SELECT (возможно и больше, но это уже будет вне стандарта ANSI).
Подзапрос <subquery> фактически выполняется отдельно для каждой строки-кандидата основного запроса, и значения, которые он выведет, будут
составлять список значений <value list> для этой строки. В любом случае предикат
<in predicate> будет верен, если выражение <value expression>
представленное в списке значений <value list>, если не указан NOT.
Фраза A NOT IN (B, C) является эквивалентом фразы NOT (A IN (B, C)).
<like predicate>
Синтаксис
<charvalue> [NOT] LIKE <pattern> [ESCAPE
<escapechar>]
<charvalue> это любое *нестандартное* выражение <value expression> алфавитно-цифрового типа.
<charvalue> может быть, в соответствии со стандартом, только определенным
столбцом <column spec>. Образец <pattern>
состоит из строки,
Если совпадение произошло, <like predicate> верен, если не был указан NOT. Фраза NOT LIKE 'текст' - эквивалентна NOT (A LIKE 'текст').
<null predicate>
Синтаксис
<column spec> IS [NOT] NULL
<column spec> = IS NULL, если NULL значение представлено в этом столбце. Это сделает <null predicate> верным, если не указан NULL. Фраза <column spec> IS NOT NULL, имеет тот же результат что и NOT (<column spec> IS NULL).
<quantified predicate>
Синтаксис
<value expression> <relational op>
<quantifier> <subquery>
<quantifier> :: = ANY | ALL | SOME
Предложение SELECT подзапроса <subquery> должно содержать одно, и только одно, выражение значения <value expression>. Все значения, выведенные подзапросом <subquery>, составляют набор результатов <result set>. <value expression> сравнивается, используя оператор связи <relational operator>, с каждым членом набора результатов <result set>. Это сравнение оценивается следующим образом:
<exists predicate>
Синтаксис:
EXISTS (<subquery>)
Если подзапрос <subquery> выводит одну или более строк вывода, <exists predicate> верен; и неверен в ином случае.
Этот раздел подробно описывает синтаксис различных команд SQL. Это даст вам возможность быстро отыскивать команду, находить синтаксис и краткое описание её работы.
ИМЕЙТЕ ВВИДУ: команды, которые начинаются словами - EXEC SQL, а также команды или предложения, заканчивающиеся словом, <sql term> могут использоваться только во вложенном SQL.
Синтаксис
EXEC SQL BEGIN DECLARE SECTION <SQL term> <host-language variable declarations> EXEC SQL END DECLARE SECTION <SQL term>
Эта команда создает главный раздел программы для объявления в ней главных переменных, которые будут использоваться во вкладываемых операторах SQL. Переменная SQLCODE должна быть включена как одна из объявляемых переменных главного языка.
Синтаксис
EXEC SQL CLOSE CURSOR <cursor name> <SQL term>;
Эта команда указывает курсору закрыться, после чего ни одно значение не сможет быть выбрано из него до тех пор, пока он не будет снова открыт.
Синтаксис
COMMIT WORK;
Эта команда оставляет неизменными все изменения, сделанных в базе данных, до тех пор, пока начавшаяся транзакция не закончится и не начнется новая транзакция.
(*NONSTANDARD*) (НЕСТАНДАРТНАЯ)
Синтаксис
CREATE [UNIQUE] INDEX <Index name>
ON <table name> (<column list>);
Эта команда создает эффективный маршрут с быстрым доступом для поиска строк, содержащих указанные столбцы. Если UNIQUE указана, таблица не сможет содержать дубликаты (двойники) значений в этих столбцах.
Синтаксис
CREATE IPUBLICl SYNONYM <synonym> FOR
<owner>.<table name>;
Эта команда создает альтернативное (синоним) имя таблицы. Синоним принадлежит его создателю, а сама таблица - обычно другому пользователю. Используя синоним, его владелец может не ссылаться на таблицу её полным (с включением имени владельца) именем. Если PUBLIC указан, синоним принадлежит каталогу SYSTEM и, следовательно, доступен всем пользователям.
Синтаксис
CREATE TABLE <table name>
({<column name> <data type>[<size>]
[<colconstralnt> . . .]
[<defvalue>]} . , . . <tabconstraint> . , . .);
Команда создает таблицу в базе данных. Эта таблица будет принадлежать её создателю. Столбцы будут рассматриваться в поимённом порядке.
<data type> определяет тип данных, которые столбец будет содержать. Стандарт <data type> описывается в
Приложении B; все прочие используемые типы данных <data type> обсуждались
в Приложении C. Значение размера <size> зависит от типа
данных <data type>.
<colconstraint> и <tabconstraint> налагают ограничения на значения, которые могут быть введены в столбце.
<defvalue> определяет значение (по умолчанию), которое будет вставлено автоматически, если никакого другого значения не указано для этой строки. (См. в
Главе 17 подробности о самой команде CREATE TABLE и в Главах 18 И
19 - подробности об ограничениях и о <defvalue>).
Синтаксис
CREATE VIEW <table name>
AS <query>
[WITH CHECK OPTION];
Просмотр обрабатывается как любая таблица в командах SQL. Когда команда
ссылается на имя таблицы <table name>, запрос <query> выполняется, и его
вывод соответствует содержанию таблицы, указанной в этой команде.
Некоторые
просмотры могут модифицироваться, что означает, что команды модификации могут
выполняться в этих просмотрах и передаваться в таблицу, на которую была ссылка в
запросе <query>. Если указано предложение WITH CHECK
OPTION, эта модификация должны также удовлетворять условию предиката <predicate>
в запросе <query>.
Синтаксис
EXEC SQL DECLARE <cursor name> CURSOR FOR
<query><SQL term>
Эта команда связывает имя курсора <cursor name> с запросом <query>. Когда курсор открыт (см. OPEN CURSOR ), запрос <query> выполняется, и его результат может быть выбран (командой FETCH) для вывода. Если курсор - модифицируемый, таблица, на которую ссылается запрос <query>, может получить изменение содержания с помощью операции модификации в курсоре (См. в Главе 25 о модифицируемых курсорах).
Синтаксис
DELETE FROM <table name>
{ [WHERE <predicate>]; }
| WHERE CURRENT OF <cursorname><SQL term>
Если предложение WHERE отсутствует, ВСЕ строки таблицы удаляются. Если предложение WHERE использует предикат <predicate>, строки, которые удовлетворяют условию этого предиката <predicate>, удаляются. Если предложение WHERE имеет аргумент CURRENT OF (ТЕКУЩИЙ) в имени курсора <cursor name>, строка из таблицы <table name>, на которую в данный момент имеется ссылка с помощью имени курсора <cursor name>, будет удалена. Форма WHERE CURRENT может использоваться только во вложенном SQL и только с модифицируемыми курсорами.
Синтаксис
EXEC SQL <embedded SQL command> <SQL term>
EXEC SQL используется, чтобы указывать начало всех команд SQL, вложенных в другой язык.
Синтаксис
EXEC SQL FETCH <cursorname>
INTO <host-varlable llst><SQL term>
FETCH принимает вывод из текущей строки запроса <query>, вставляет её в список главных переменных <host-variable list>, и перемещает курсор на следующую строку. Список <host-variable list> может включать переменную indicator в качестве целевой переменной (См. Главу 25).
Синтаксис (стандартный)
GRANT ALL [PRIVILEGES]
| {SELECT
| INSERT
| DELETE
| UPDATE [(<column llst>)]
| REFERENCES [(<column llst>)l } . , . .
ON <table name> . , . .
TO PUBLIC | <Authorization ID> . , . .
[WITH GRANT OPTION];
Аргумент ALL (ВСЕ), с или без PRIVILEGES (ПРИВИЛЕГИИ), включает каждую привилегию в список привилегий. PUBLIC (ОБЩИЙ) включает всех существующих пользователей и всех созданных в будущем. Эта команда даёт возможность передать права для выполнения действий в таблице с указанным именем. REFERENCES позволяет дать права на использование столбцов в списке столбцов <column list> как родительский ключ для внешнего ключа. Другие привилегии состоят из права выполнять команды, для которых привилегии указаны их именами в таблице. UPDATE подобен REFERENCES и может накладывать ограничения на определенные столбцы. GRANT OPTION даёт возможность передавать эти привилегии другим пользователям.
Синтаксис (нестандартный)
GRANT DBA
| RESOURCE
| CONNECT ... .
TO <Authorization ID> . , . .
[IDENTIFIED BY> password>
CONNECT дает возможность передавать право на регистрацию и некоторые другие ограниченные права.
RESOURCE дает пользователю право создавать таблицы.
DBA дает возможность передавать почти все права.
IDENTIFIED BY используется вместе с CONNECT для создания или изменения пароля пользователя.
Синтаксис
INSERT INTO <table name> (<column llst>)
VALUES (<value llst>) I <query>;
INSERT создает одну или больше новых строк в таблице <table name>. Если используется предложение VALUES, значения строк вставляются в таблицу <table name>. Если запрос <query> указан, каждая строка вывода будет вставлена в таблицу <table name>. Если список столбцов <column list> отсутствует, все столбцы таблицы <table name , принимаются в упорядоченном виде.
Синтаксис
EXEC SQL OPEN CURSOR <cursorname><SQL term>
OPEN CURSOR выполняет запрос, связанный с курсором <cursor name>. Вывод может теперь извлекать по одной строке для каждой команды FETCH.
Синтаксис
REVOKE { ALL [PRIVILEGES]
| <privilege> . , . . } [ON <table name>]
FROM { PUBLIC
| <Authorization ID> . , . . };
Привилегия <privelege> может быть любой из указанных в команде GRANT. Пользователь, дающий REVOKE, должен иметь те же привилегии, что и пользователь, который давал GRANT. Предложение ON может быть использовано, если применяется привилегия специального типа для особого объекта.
Синтаксис
ROLLBACK WORK;
Команда отменяет все изменения в базе данных, сделанные в течение текущей транзакции. Она, кроме того, заканчивается текущую и начинает новую транзакцию.
Синтаксис
SELECT { IDISTINCT | ALL] <value expression> . , . . } / *
[INTO <host variable list> (*только внедрённый*)]
FROM <table reference> . , . .
[WHERE <predicate>]
[GROUP BY <grouping column> . , . .]
[HAVING <predicate>]
[ORDER BY <ordering column> [ASC | DESC] . , . . ];
Это предложение организует запрос и выводит значения из базы данных (см. Главы 3 - 14 ).
Применяются следующие правила:
Предложение SELECT оценивает каждую строку-кандидат таблицы, в которой строки показаны независимо.
Строка-кандидат определяется следующим образом:
Каждая строка-кандидат производит значения,
которые делают предикат <predicate> в предложении WHERE верным, неверным, или неизвестным. Если GROUP BY
не используется, каждое <value expression> применяется, в свою очередь, для
каждой строки-кандидата, чьё значение делает предикат верным, и результатом этой
операции является вывод.
Если GROUP BY используется, строки-кандидаты комбинируются, используя агрегатные
функции. Если никакого предиката <predicate> не установлено, каждое выражение <value expression> применяется к каждой строке-кандидату или к каждой группе.
Если указан DISTINCT, дубликаты (двойники) строк будут удалены из вывода.
Синтаксис
<query> {UNION [ALL] <query> } . . . ;
Вывод двух или более запросов <query> будет объединён. Каждый запрос <query> должен содержать один и тот же номер <value expression> в предложении SELECT и в таком порядке, что 1.. n каждого совместим по типу данных <data type> и размеру <size> с 1.. n всех других.
Синтаксис
UPDATE <table name>
SET { <column name> = <value expression> } . , . .
{[ WHERE <predlcate>]; }
| {[WHERE CURRENT OF <cursorname>]
<SQL term>]}
UPDATE изменяет значения в каждом столбце <column name> на соответствующее значение <value expression>. Если предложение WHERE использует предикат <predicate>, то только строки таблиц, чьи текущие значения делают этот предикат <predicate> верным, могут быть изменены. Если WHERE использует предложение CURRENT OF, то значения в строке таблицы <table name>, находящиеся в курсоре <cursor name>, меняются. WHERE CURRENT OF пригодно для использования только во вложенном SQL, и только с модифицируемыми курсорами. При отсутствии предложения WHERE, все строки меняются.
Синтаксис
EXEC SQL WHENEVER <SQLcond> <actlon> <SQL term>
<SQLcond> :: = SQLERROR | NOT FOUND | SQLWARNING
(последнее - нестандартное)
<action> :: = CONTINUE | GOTO <target> | GOTO <target>
<target> :: = зависит от главного языка
==================== ТАБЛИЦА 1: ПРОДАВЦЫ ================ ---------------------------------------------- snum | sname | city | comm --------|-----------|--------------|---------- 1001 | Peel | London | .12 1002 | Serres | San Jose | .13 1004 | Motika | London | .11 1007 | Rifkin | Barcelona | .15 1003 | Axelrod | New York | .10 --------------------------------------------- ================== ТАБЛИЦА 2: ЗАКАЗЧИКИ =============== ---------------------------------------------- cnum | cname | city | rating | snum -------|------------|---------|--------|------ 2001 | Hoffman | London | 100 | 1001 2002 | Giovanni | Rome | 200 | 1003 2003 | Liu | SanJose | 200 | 1002 2004 | Grass | Berlin | 300 | 1002 2006 | Clemens | London | 100 | 1001 2008 | Cisneros | SanJose | 300 | 1007 2007 | Pereira | Rome | 100 | 1004 ---------------------------------------------- ================== ТАБЛИЦА 3: ЗАКАЗЫ ================== ----------------------------------------------- onum | amt | odate | cnum | snum -------|-----------|-------------|------|------ 3001 | 18.69 | 10/03/1990 | 2008 | 1007 3003 | 767.19 | 10/03/1990 | 2001 | 1001 3002 | 1900.10 | 10/03/1990 | 2007 | 1004 3005 | 5160.45 | 10/03/1990 | 2003 | 1002 3006 | 1098.16 | 10/03/1990 | 2008 | 1007 3009 | 1713.23 | 10/04/1990 | 2002 | 1003 3007 | 75.75 | 10/04/1990 | 2004 | 1002 3008 | 4723.00 | 10/05/1990 | 2006 | 1001 3010 | 1309.95 | 10/06/1990 | 2004 | 1002 3011 | 9891.88 | 10/06/1990 | 2006 | 1001 -----------------------------------------------