Viacheslav Eremin | Qmail Reference
 
 
 

6. ОБОБЩЕНИЕ ДАННЫХ С ПОМОЩЬЮ АГРЕГАТНЫХ ФУНКЦИЙ

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

ЧТО ТАКОЕ АГРЕГАТНЫЕ ФУНКЦИИ ?

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

* COUNT - производит номера строк или не-NULL значения полей которые выбрал запрос.

* SUM - производит арифметическую сумму всех выбранных значений данного пол.

* AVG - производит усреднение всех выбранных значений данного пол.

* MAX - производит наибольшее из всех выбранных значений данного пол.

* MIN - производит наименьшее из всех выбранных значений данного пол.

КАК ИСПОЛЬЗОВАТЬ АГРЕГАТНЫЕ ФУНКЦИИ ?

Агрегатные функции используются подобно именам полей в предложении 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

Функция COUNT несколько отличается от всех. Она считает число значений в данном столбце, или число строк в таблице. Когда она считает значения столбца, она используется с DISTINCT чтобы производить счет чисел различных значений в данном поле. Мы могли бы использовать ее, например, чтобы сосчитать номера продавцов в настоящее врем описанных в таблице Порядков ( вывод показывается в Рисунке 6.3 ):
      SELECT COUNT ( DISTINCT snum ) 
	 FROM Orders; 
ИСПОЛЬЗОВАНИЕ DISTINCT
Обратите внимание в вышеупомянутом примере, что DISTINCT, сопровождаемый именем пол с которым он применяется, помещен в круглые скобки, но не сразу после SELECT, как раньше. Этого использования DISTINCT с COUNT применяемого к индивидуальным столбцам, требует стандарт ANSI, но большое количество программ не предъявляют к ним такого требования.
   ===============  SQL Execution Log ============ 
  |                                               | 
  | SELECT COUNT (DISTINCT snum)                  | 
  | FROM  Orders;                                 | 
  | ==============================================| 
  |                                               | 
  | -------                                       | 
  |       5                                       | 
  |                                               | 
  |                                               | 
   =============================================== 
Рисунок 6.3: Подсчет значений пол

Вы можете выбирать многочисленные счета( COUNT ) из полей с помощью DISTINCT в одиночном запросе который, как мы видели в
Главе 3, не выполнялись когда вы выбирали строки с помощью DISTINCT. DISTINCT может использоваться таким образом, с любой функцией агрегата, но наиболее часто он используется с COUNT. С MAX и MIN, это просто не будет иметь никакого эффекта, а SUM и AVG, вы обычно применяете для включения повторяемых значений, так как они законно эффективнее общих и средних значений всех столбцов.
ИСПОЛЬЗОВАНИЕ COUNT СО СТРОКАМИ, А НЕ ЗНАЧЕНИЯМИ
Чтобы подсчитать общее число строк в таблице, используйте функцию COUNT со звездочкой вместо имени пол, как например в следующем примере, вывод из которого показан на Рисунке 6.4:
SELECT COUNT (*) 
 FROM Customers 
COUNT со звездочкой включает и NULL и дубликаты, по этой причине DISTINCT не может быть использован. DISTINCT может производить более высокие номера чем COUNT особого пол, который удаляет все
   ===============  SQL Execution Log ============ 
  |                                               | 
  | SELECT COUNT (*)                              | 
  | FROM  Customers;                              | 
  | ==============================================| 
  |                                               | 
  | -------                                       | 
  |       7                                       | 
  |                                               | 
  |                                               | 
   =============================================== 
Рисунок 6. 4: Подсчет строк вместо значений

строки, имеющие избыточные или NULL данные в этом поле. DISTINCT не применим c COUNT (*), потому, что он не имеет никакого действия в хорошо разработанной и поддерживаемой базе данных. В такой базе данных, не должно быть ни таких строк, которые бы являлись полностью пустыми, ни дубликатов ( первые не содержат никаких данных, а последние полностью избыточны ). Если, с другой стороны, все таки имеются полностью пустые или избыточные строки, вы вероятно не захотите чтобы COUNT скрыл от вас эту информацию.
ВКЛЮЧЕНИЕ ДУБЛИКАТОВ В АГРЕГАТНЫЕ ФУНКЦИИ
Агрегатные функции могут также ( в большинстве реализаций ) использовать аргумент ALL, который помещается перед именем пол, подобно DISTINCT, но означает противоположное: - включать дубликаты. ANSI технически не позволяет этого для COUNT, но многие реализации ослабляют это ограничение. Различи между ALL и * когда они используются с COUNT -

* ALL использует имя_поля как аргумент.

* ALL не может подсчитать значения NULL.

Пока * является единственным аргументом который включает 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

Предложение 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: Нахождение наибольшей суммы приобретений на каждый день

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

ПРЕДЛОЖЕНИЕ HAVING

Предположим, что в предыдущем примере, вы хотели бы увидеть только максимальную сумму приобретений значение которой выше $3000.00. Вы не сможете использовать агрегатную функцию в предложении WHERE ( если вы не используете подзапрос, описанный позже ), потому что предикаты оцениваются в терминах одиночной строки, а агрегатные функции оцениваются в терминах групп строк. Это означает что вы не сможете сделать что-нибудь подобно следующему:
 
              SELECT snum, odate, MAX (amt) 
                 FROM Oreders 
                 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 
                WHEREodate = 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, некоторые вещи которые вы можете делать со значениями которые он производит.

РАБОТА С SQL



1. Напишите запрос который сосчитал бы все суммы приобретений на 3 Октября.

2. Напишите запрос который сосчитал бы число различных не-NULL значений пол city в таблице Заказчиков.

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

4. Напишите запрос который бы выбирал заказчиков в алфавитном порядке, чьи имена начинаются с буквы G.

5. Напишите запрос который выбрал бы высшую оценку в каждом городе.

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

(См.
Приложение A для ответов.)

7. ФОРМИРОВАНИЕ ВЫВОДОВ ЗАПРОСОВ

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

СТРОКИ И ВЫРАЖЕНИЯ

Большинство основанных на 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 не cможет найти его чтобы использовать для упорядочения вывода. Даже если ваша система позволяет это, смысл упорядочения не будет понятен из вывода, так что включение (в предложение SELECT) всех столбцов, используемых в предложении ORDER BY, в принципе желательно.

УПОРЯДОЧЕНИЕ АГРЕГАТНЫХ ГРУПП

ORDER BY может кроме того, использоваться с GROUP BY для упорядочения групп. Если это так, то ORDER BY всегда приходит последним. Вот - пример из последней главы с добавлением предложения ORDER BY. Перед сгруппированием вывода, порядок групп был произвольным; и мы, теперь, заставим группы размещаться в последовательности:
 
                  SELECT snum, odate, MAX (amt) 
                     FROM Orders 
                     GROUP BY snum, odate 
                     GROUP 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 
                 GROUP BY 2 DESC; 
 
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | (SELECT sname, comm                           | 
              | FROM Salespeople                              | 
              | ORDER BY 2 DESC;                              | 
              | ============================================= | 
              |    sname       comm                           | 
              |   --------   --------                         | 
              |   Peel           0.17                         | 
              |   Serres         0.13                         | 
              |   Rifkin         0.15                         | 
              |                                               | 
               =============================================== 
Рисунок 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

Если имеются пустые значения (NULL) в поле которое вы используете для упорядочивания вашего вывода, они могут или следовать или предшествовать каждому другому значению в поле. Это - возможность которую ANSI оставил для индивидуальных программ. Данная программа использует ту или иную форму.

РЕЗЮМЕ

В этой главе, вы изучили как заставить ваши запросы делать больше, чем просто выводить значения полей или объединять функциональные данные таблиц. Вы можете использовать пол в выражениях: например, вы можете умножить числовое поле на 10 или даже умножить его на другое числовое поле. Кроме того, вы можете помещать константы, включая и символы, в ваш вывод, что позволяет вам помещать текст непосредственно в запрос и получать его в выводе вместе с данными таблицы. Это дает вам возможность помечать или объяснять ваш вывод различными способами. Вы также изучили как упорядочивать ваш вывод. Даже если таблица сама по себе остается неупорядоченной, предложение ORDER BY дает вам возможность управлять порядком вывода строк данного запроса. Вывод запроса может быть в порядке возрастания или убывания, и столбцы могут быть вложенными один внутрь другого. Понятие выводимых столбцов объяснялось в этой главе. Вы теперь знаете что выводимые столбцы можно использовать чтобы упорядочивать вывод запроса, но эти столбцы - без имени, и следовательно должны определяться их порядковым номером в предложении ORDER BY. Теперь, когда вы увидели что можно делать с выводом запроса основанного на одиночной таблице, настало время чтобы перейти к возможностям улучшенного запроса и узнать как сделать запрос любого числа таблиц в одной команде, определив связи между ними как вы это обычно делали. Это будет темой
Главы 8.

РАБОТА С SQL

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

2. Напишите запрос к таблице Заказчиков который мог бы найти высшую оценку в каждом городе. Вывод должен быть в такой форме:
 
       For the city (city), the highest rating is: (rating). 
3. Напишите запрос который выводил бы список заказчиков в нисходящем порядке. Вывод пол оценки( rating ) должен сопровождаться именем заказчика и его номером.

4. Напишите запрос который бы выводил общие порядки на каждый день и помещал результаты в нисходящем порядке.

( См.
Приложение A для ответов. )

8. ЗАПРАШИВАНИЕ МНОГОЧИСЛЕННЫХ ТАБЛИЦ ТАКЖЕ КАК ОДНОЙ

ДО ЭТОГО, КАЖДЫЙ ЗАПРОС КОТОРЫЙ МЫ ИССЛЕДОВАЛИ основывался на одиночной таблице. В этой главе, вы узнаете как сделать запрос любого числа таблиц с помощью одной команды. Это - чрезвычайно мощное средство потому что оно не только объединяет вывод из многочисленных таблиц, но и определяет связи между ними. Вы обучитесь различным формам кото- рые могут использовать эти связи, а также устанавливать и использовать их, чтобы удовлетворять возможным специальным требованиям.

ОБЪЕДИНЕНИЕ ТАБЛИЦ

Одна из наиболее важных особенностей запросов 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.

РАБОТА С SQL

1. Напишите запрос который бы вывел список номеров порядков сопровождающихся именем заказчика который создавал эти порядки.

2. Напишите запрос который бы выдавал имена продавца и заказчика для каждого порядка после номера порядков.

3. Напишите запрос который бы выводил всех заказчиков обслуживаемых продавцом с комиссионными выше 12% . Выведите им заказчика, им продавца, и ставку комиссионных продавца.

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

( См.
Приложение A для ответов. )

9. ОБЪЕДИНЕНИЕ ТАБЛИЦЫ С СОБОЙ

В ГЛАВЕ 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 ведет себя так, как если бы он соединял две таблицы называемые 'первая и 'вторая'. Обе они - фактически, таблицы Заказчика, но псевдонимы разрешают им быть обработанными независимо. Псевдонимы первый и второй были установлены в предложении FROM запроса, сразу после имени копии таблицы. Обратите внимание что псевдонимы могут использоваться в предложении SELECT, даже если они не определены в предложении FROM. Это - очень хорошо. SQL будет сначала допускать любые такие псевдонимы на веру, но будет отклонять команду если они не определены далее в предложении FROM запроса. Псевдоним существует - только пока команда выполняется !

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

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

УСТРАНЕНИЕ ИЗБЫТОЧНОСТИ

Обратите внимание что наш вывод имеет два значение для каждой комбинации, причем второй раз в обратном порядке. Это потому, что каждое значение показано первый раз в каждом псевдониме, и второй раз( сим- метрично) в предикате. Следовательно, значение A в псевдониме сначала выбирается в комбинации со значением B во втором псевдониме, а затем значение A во втором псевдониме выбирается в комбинации со значением B в первом псевдониме. В нашем примере, Hoffman выбрался вместе с Clemens, а затем Clemens выбрался вместе с Hoffman. Тот же самый случай с Cisneros и Grass, Liu и Giovanni, и так далее. Кроме того каждая строка была сравнена сама с собой, чтобы вывести строки такие как - Liu и Liu. Простой способ избежать этого состoит в том, чтобы налагать порядок на два значения, так чтобы один мог быть меньше чем другой или предшествовал ему в алфавитном порядке. Это делает предикат асимметричным, поэтому те же самые значения в обратном порядке не будут выбраны снова, например:
 
            SELECT tirst.cname, second.cname, first.rating 
               FROM Customers first, Customers second 
               WHERE first.rating = second.rating 
                  AND first.cname < second.cname; 
Вывод этого запроса показывается в Рисунке 9.2.

Hoffman предшествует Periera в алфавитном порядке, поэтому комбинация удовлетворяет обеим условиям предиката и появляется в выводе. Когда та же сама комбинация появляется в обратном порядке - когда Periera в псевдониме первой таблицы сравнивается с 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, поскольку они сравнивают значения только в одном столбце вывода. Вы должны также понимать, что не всегда обязательно использовать каждый псевдоним или таблицу которые упомянуты в предложении FROM запроса, в предложении SELECT. Иногда, предложение или таблица становятся запрашиваемыми исключительно потому что они могут вызываться в предикате запроса. Например, следующий запрос находит всех заказчиков размещенных в городах где продавец 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 и более тщательно в последующих главах.

РАБОТА С SQL

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

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

3. Напишите запрос который вывел бы имена(cname) и города(city) всех заказчиков с такой же оценкой(rating) как у Hoffmanа. Напишите запрос использующий поле cnum Hoffmanа а не его оценку, так чтобы оно могло быть использовано если его оценка вдруг изменится.

( См.
Приложение A для ответов. )

10. ВСТАВКА ОДНОГО ЗАПРОСА ВНУТРЬ ДРУГОГО

В КОНЕЦЕ ГЛАВЫ 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 С ПОДЗАПРОСАМИ

Вы можете, в некоторых случаях, использовать 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

Вы можете использовать подзапросы которые производят любое число строк если вы используете специальный оператор 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

Вы можете также использовать подзапросы внутри предложения 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, за исключением когда эти операторы могут использоваться только в подзапросах.

РАБОТА С SQL

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

2. Напишите запрос который вывел бы имена и оценки всех заказчиков которые имеют усредненные порядки.

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

( См.
Приложение A для ответов. )

11. СООТНЕСЕННЫЕ ПОДЗАПРОСЫ

В этой главе, мы представим вас типу подзапроса о котором мы не говорили в Главе 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: Использование соотнесенного подзапроса

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

1. Выбрать строку из таблицы именованной в внешнем запросе. Это будет текущая строка-кандидат.

2. Сохранить значения из этой строки-кандидата в псевдониме с именем в предложении FROM внешнего запроса.

3. Выполнить подзапрос. Везде, где псевдоним данный для внешнего запроса найден ( в этом случае "внешний" ), использовать значение для текущей строки-кандидата. Использование значения из строки- кандидата внешнего запроса в подзапросе называется - внешней ссылкой.

4. Оценить предикат внешнего запроса на основе результатов подзапроса выполняемого в шаге 3. Он определяет - выбирается ли строка-кандидат для вывода.

5. Повторить процедуру для следующей строки-кандидата таблицы, и так далее пока все строки таблицы не будут проверены.

В вышеупомянутом примере, SQL осуществляет следующую процедуру:

1. Он выбирает строку Hoffman из таблицы Заказчиков. 2. Сохраняет эту строку как текущую строку-кандидат под псевдонимом - "внешним".

3. Затем он выполняет подзапрос. Подзапрос просматривает всю таблицу Порядков чтобы найти строки где значение cnum поле - такое же как значение outer.cnum, которое в настоящее время равно 2001, - поле cnum строки Hoffmanа. Затем он извлекает поле odate из каждой строки таблицы Порядков для которой это верно, и формирует набор значений пол odate.

4. Получив набор всех значений пол odate, для пол cnum = 2001, он проверяет предикат основного запроса чтобы видеть имеется ли значение на 3 Октября в этом наборе. Если это так(а это так), то он выбирает строку Hoffmanа для вывода ее из основного запроса.

5. Он повторяет всю процедуру, используя строку Giovanni как строку-канди- дата, и затем сохраняет повторно пока каждая строка таблицы Заказчиков не будет проверена.

Как вы можете видеть, вычисления которые SQL выполняет с помощью этих простых инструкций - это полный комплекс. Конечно, вы могли бы решить ту же самую проблему используя объединение, следующего вида ( вывод для этого запроса показывается в Рисунке 11.2 ):
  
              SELECT * 
                 FROM Customers first, Orders second 
                 WHERE first.cnum = second.cnum 
                    AND second.odate = 10/03/1990; 
Обратите внимание что Cisneros был выбран дважды, по одному разу для каждого порядка который он имел для данной даты. Мы могли бы устранить это используя SELECT DISTINCT вместо просто SELECT. Но это необязательно в варианте подзапроса. Оператор IN, используемый в варианте подзапроса, не делает никакого различи между значениями которые выбираются подзапросом один раз и значениями которые выбираются неоднократно. Следовательно DISTINCT необязателен.
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT *                                      | 
              | FROM  Customers first, Orders second          | 
              | WHERE first.cnum = second.cnum                | 
              | (SELECT COUNT (*)                             | 
              | FROM Customers                                | 
              | WHERE snum = main.snum;                       | 
              | ============================================= | 
              |   cnum     cname                              | 
              |  -----    --------                            | 
              |   1001     Peel                               | 
              |   1002     Serres                             | 
                ============================================= 
Рисунок 11. 2 Использование объединения вместо соотнесенного подзапроса

Предположим что мы хотим видеть имена и номера всех продавцов которые имеют более одного заказчика. Следующий запрос выполнит это для вас ( вывод показывается в Рисунке 11.3 ):
 
             SELECT snum, sname 
                FROM Salespeople main 
                WHERE 1 < 
                    ( SELECT COUNT (*) 
                         FROM Customers 
                         WHERE snum = main.snum ); 
Обратите внимание что предложение FROM подзапроса в этом примере не использует псевдоним. При отсутствии имени таблицы или префикса псевдонима, SQL может для начала принять, что любое поле выводится из таблицы с именем указанным в предложении FROM текущего запроса. Если поле с этим именем отсутствует( в нашем случае - snum ) в той таблице, SQL будет проверять внешние запросы. Именно поэтому, префикс имени таблицы обычно необходим в соотнесенных подзапросах - для отмены этого предположения. Псевдонимы также часто запрашиваются чтобы давать вам возможность ссылаться к той же самой таблице во внутреннем и внешнем запросе без какой-либо неоднозначности.
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT snum sname                             | 
              | FROM  Salespeople main                        | 
              | WHERE 1 <                                     | 
              | AND second.odate = 10/03/1990;                | 
              | ============================================= | 
              |   cnum     cname     city    rating    snum   | 
              |  -----    --------   ----    ------   -----   | 
              |   2001    Hoffman    London     100    1001   | 
              |   2003    Liu        San Jose   200    1002   | 
              |   2008    Cisneros   San Jose   300    1007   | 
              |   2007    Pereira    Rome       100    1004   | 
                ============================================= 
Рисунок 11.3: Нахождение продавцов с многочисленными заказчиками

ИСПОЛЬЗОВАНИЕ СООТНЕСЕННЫХ ПОДЗАПРОСОВ ДЛЯ НАХОЖДЕНИЯ ОШИБОК

Иногда полезно выполнять запросы которые разработаны специально так чтобы находить ошибки. Это всегда возможно при дефектной информации которую можно ввести в вашу базу данных, и, если она введена, бывает трудно ее определить. Следующий запрос не должен производить никако- го вывода. Он просматривает таблицу Порядков чтобы видеть совпадают ли пол 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, вы должны ограничивать внешние ссылки к позициям которые могли бы непосредственно использоваться в самом предложе- нии 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.

РАБОТА С SQL

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

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

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

( См.
Приложение A для ответов. )

12. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА EXISTS

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

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

КАК РАБОТАЕТ EXISTS?

EXISTS - это оператор, который производит верное или неверное значение, другими словами, выражение Бул ( см.
Главу 4 для обзора этого термина ).

Это означает что он может работать автономно в предикате или в комбинации с другими выражениями Бул использующими Булевые операторы AND, OR, и NOT. Он берет подзапрос как аргумент и оценивает его как верный если тот производит любой вывод или как неверный если тот не делает этого. Этим он отличается от других операторов предиката, в которых он не может быть неизвестным. Например, мы можем решить, извлекать ли нам некоторые данные из таблицы Заказчиков если, и только если, один или более заказчиков в этой таблице находятся в San Jose ( вывод для этого запроса показывается в Рисунке 12.1 ):
 
              SELECT cnum, cname, city 
                  FROM Customers 
                  WHERE EXISTS 
                      ( SELECT * 
                          FROM Customers 
                          WHERE city = " San Jose' ); 
Внутренний запрос выбирает все данные для всех заказчиков в San Jose. Оператор EXISTS во внешнем предикате отмечает, что некоторый вывод был произведен подзапросом, и поскольку выражение EXISTS было полным предикатом, делает предикат верным. Подзапрос( не соотнесенный ) был выполнен только один раз для всего внешнего запроса, и следова-
 
 
               ===============  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, когда используется этим способом, делает предикат верным или неверным для всех строк сразу, что это не так уж полезно для извлечения определенной информации.

ВЫБОР СТОЛБЦОВ С ПОМОЩЬЮ EXISTS

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

ИСПОЛЬЗОВАНИЕ 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 не указан, каждый из этих продавцов будет выбран один раз для каждого заказчика к которому он назначен.

КОМБИНАЦИЯ ИЗ EXISTS И ОБЪЕДИНЕНИЯ

Однако для нас может быть полезнее вывести больше информации об этих продавцах а не только их номера. Мы можем сделать это объединив таблицу Заказчиков с таблицей Продавцов ( вывод для запроса показывается в Рисунке 12.3 ):
 
             SELECT DISTINCT first.snum, sname, first.city 
                FROM Salespeople first, Customers second 
                WHERE EXISTS 
                   ( SELECT * 
                      FROM Customers third 
                      WHERE second.snum = third.snum 
                            AND second.cnum < > third.cnum ) 
                   AND first.snum = second.snum; 
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT DISTINCT first.snum, sname, first.city | 
              | FROM  Salespeople first, Customers second     | 
              | WHERE EXISTS                                  | 
              | (SELECT *                                     | 
              | FROM Customers third                          | 
              | WHERE second.snum = third.snum                | 
              | AND second.cnum < > third.cnum)               | 
              | AND first.snum = second.snum;                 | 
              | ============================================= | 
              |   cnum     cname     city                     | 
              |  -----    --------   ----                     | 
              |   1001    Peel       London                   | 
              |   1002    Serres     San Jose                 | 
                ============================================= 
Рисунок 12.3: Комбинация EXISTS с объединением

Внутренний запрос здесь - как и в предыдущем варианте, фактически сообщает, что псевдоним был изменен. Внешний запрос - это объединение таблицы Продавцов с таблицей Заказчиков, наподобие того что мы видели прежде. Новое предложение основного предиката ( AND first.snum = second.snum ) естественно оценивается на том же самом уровне что и предложение EXISTS. Это - функциональный предикат самого объединения, сравнивающий две таблицы из внешнего запроса в терминах пол snum, которое являются для них общим. Из-за Булева оператора AND, оба условия основного предиката должны быть верны в порядке для верного предиката. Следовательно, результаты подзапроса имеют смысл только в тех случаях когда вторая часть запроса верна, а объединение - выполнимо. Таким образом, комбинация объединения и подзапроса может стать очень мощным способом обработки данных.

ИСПОЛЬЗОВАНИЕ NOT EXISTS

Предыдущий пример дал понять что EXISTS может работать в комбинации с операторами Бул. Конечно, то что является самым простым способом для использования и вероятно наиболее часто используется с EXISTS - это оператор NOT. Один из способов которым мы могли бы найти всех продав- цов только с одним заказчиком будет состоять в том, чтобы инвертировать наш предыдущий пример. ( Вывод для этого запроса показывается в Рисунке 12.4:) SELECT DISTINCT snum FROM Customers outer WHERE NOT EXISTS ( SELECT * FROM Customers inner WHERE inner.snum = outer.snum AND inner.cnum < > outer.cnum );

EXISTS И АГРЕГАТЫ

Одна вещь которую EXISTS не может сделать - взять функцию агрегата в подзапросе. Это имеет значение. Если функция агрегата находит любые строки для операций с ними, EXISTS верен, не взирая на то, что это - значение функции ; если же агрегатная функция не находит никаких строк, EXISTS неправилен.
 
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT DISTINCT snum                          | 
              | FROM  Salespeople outer                       | 
              | WHERE NOT EXISTS                              | 
              | (SELECT *                                     | 
              | FROM Customers inner                          | 
              | WHERE inner.snum = outer.snum                 | 
              | AND inner.cnum < > outer.cnum);               | 
              | ============================================= | 
              |   cnum                                        | 
              |  -----                                        | 
              |   1003                                        | 
              |   1004                                        | 
              |   1007                                        | 
                ============================================= 
Рисунок 12.4: Использование EXISTS с NOT

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

В любом случае, вы можете получить тот же самый результат более легко, выбрав поле которое вы использовали в агрегатной функции, вместо использования самой этой функции. Другими словами, предикат - EXISTS (SELECT COUNT (DISTINCT sname) FROM Salespeople) - будет эквивалентен - EXISTS (SELECT sname FROM Salespeople) который был позволен выше.

БОЛЕЕ УДАЧНЫЙ ПРИМЕР ПОДЗАПРОСА

Возможные прикладные программы подзапросов могут становиться мно- гократно вкладываемыми. Вы можете вкладывать их два или более в одиночный запрос, и даже один внутрь другого. Так как можно рассмотреть небольшой кусок чтобы получить всю картину работы этой команды, вы можете воспользоваться способом в 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, это - альтернативные формулировки некоторых вещей которые вы уже использовали, но которые в некоторых случаях, могут оказаться более предпочтительными.

РАБОТА С SQL

1. Напишите запрос который бы использовал оператор EXISTS для извлечения всех продавцов которые имеют заказчиков с оценкой 300.

2. Как бы вы решили предыдущую проблему используя объединение ?

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

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

( См.
Приложение A для ответов. )

13. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРОВ ANY, ALL И SOME

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

ANY, ALL, и SOME напоминают EXISTS который воспринимает подзапросы как аргументы; однако они отличаются от EXISTS тем, что используются сов- местно с реляционными операторами. В этом отношении, они напоминают оператор IN когда тот используется с подзапросами; они берут все значения выведенные подзапросом и обрабатывают их как модуль. Однако, в отличие от IN, они могут использоваться только с подзапросами.

СПЕЦИАЛЬНЫЕ ОПЕРАТОРЫ ANY или SOME

Операторы 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 ИЛИ EXISTS ВМЕСТО ОПЕРАТОРА ANY

Мы можем также использовать оператор IN чтобы создать запрос аналогичный предыдущему :
 
             SELECT * 
                FROM Salespeople 
                WHERE city IN 
                    ( SELECT city 
                         FROM Customers ); 
Этот запрос будет производить вывод показанный в Рисунке 13.2.

Однако, оператор ANY может использовать другие реляционные опера- торы кроме равняется ( = ), и таким образом делать сравнения которые являются выше возможностей IN. Например, мы могли бы найти всех продавцов с их заказчиками которые следуют им в алфавитном порядке ( вывод показан на Рисунке 13.3)
 
             SELECT * 
                FROM Salespeople 
                WHERE sname < ANY 
                   ( SELECT cname 
                       FROM Customers); 
 
 
               ===============  SQL Execution Log ============ 
              | SELECT *                                      | 
              | FROM  Salespeople                             | 
              | WHERE city IN                                 | 
              | (SELECT  city                                 | 
              | FROM Customers);                              | 
              | ============================================= | 
              |   cnum     cname     city         comm        | 
              |  -----    --------   ----       --------      | 
              |   1001    Peel       London         0.12      | 
              |   1002    Serres     San Jose       0.13      | 
              |   1004    Motika     London         0.11      | 
                ============================================= 
Рисунок 13. 2: Использование IN в качестве альтернативы к ANY
 
 
               ===============  SQL Execution Log ============ 
              | SELECT *                                      | 
              | FROM  Salespeople                             | 
              | WHERE sname < ANY                             | 
              | (SELECT  cname                                | 
              | FROM Customers);                              | 
              | ============================================= | 
              |   cnum     cname     city         comm        | 
              |  -----    --------   ----       --------      | 
              |   1001    Peel       London         0.12      | 
              |   1004    Motika     London         0.11      | 
              |   1003    Axelrod    New York       0.10      | 
                ============================================= 
Рисунок 13. 3: Использование оператора ANY с оператором "неравно" ( < )

продавцов для их заказчиков которые упорядочены в алфавитном порядке ( вывод показан на Рисунке 13.3)
 
             SELECT * 
                FROM Salespeople 
                WHERE sname < ANY 
                   ( SELECT cname 
                       FROM Customers); 
 
Все строки были выбраны для Serres и Rifkin, потому что нет других за- казчиков чьи имена следовали бы за ими в алфавитном порядке. Обратите внимание что это является d основным эквивалентом следую- щему запросу с 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 МОЖЕТ СТАТЬ НЕОДНОЗНАЧНЫМ

Как подразумевалось выше, ANY не полностью однозначен. Если мы создаем запрос чтобы выбрать заказчиков которые имеют больший рейтинг чем любой заказчик в Риме, мы можем получить вывод который несколько отличался бы от того что мы ожидали ( как показано в Рисунке 13.5 ):
 
          SELECT * 
              FROM Customers 
              WHERE rating > ANY 
                  ( SELECT rating 
                      FROM Customers 
                      WHERE city = Rome ); 
В Английском языке, способ которым мы обычно склонны интерпретировать оценку " больше чем любой ( где city = Rome ) " , должен вам сообщить что это значение оценки должно быть выше чем значение оценки в каждом случае где значение city = Rome. Однако это не так, в случае ANY - используемом в SQL . ANY оценивает как верно, если подзапрос находит любое значение которое делает условие верным.
 
               ===============  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 также находит Periera в Риме с оценкой 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

С помощью 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' ); 
Эта команда допустима, но , c этими данными, мы не получим никакого вывода. Только в единственном случае вывод будет выдан этим запросом - если все значения оценки в 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' ); 
Вывод будет одинаков для всех трех условий.

ПРАВИЛЬНОЕ ПОНИМАНИЕ ANY И ALL

В SQL, сказать что - значение больше( или меньше ) чем любое(ANY) из на- бора значений - тоже самое что сказать, что оно больше( или меньше ) чем любое одно отдельное из этих значений. И наоборот, сказать что значение не равно всему(ALL) набору значений, тоже что сказать, что нет такого значения в наборе которому оно равно.

КАК ANY, ALL, И EXIST ПОСТУПАЮТ С ОТСУТСТВУЮЩИМИ И НЕИЗВЕСТНЫМИ ДАННЫМИ

Как было сказано, имеются некоторые различи между EXISTS и операто- рами представленными в этой главе относительно того как они обрабатывают оператор NULL. ANY и ALL также отличаются друг от друга тем как они реагируют если подзапрос не произвел никаких значений чтобы использовать их в сравнении. Эти различи могут привести к непредвиденным ре- зультатам на Ваши запросы, если вы не будете их учитывать.

КОГДА ПОДЗАПРОС ВОЗВРАЩАЕТСЯ ПУСТЫМ

Одно значительное различие между ALL и ANY - способ действия в cитуации когда подзапрос не возвращает никаких значений. В принципе, всякий раз, ко- гда допустимый подзапрос не в состоянии сделать вывод, 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, естественно, ни одно из этих сравнений не имеет значения.

ANY И ALL ВМЕСТО EXISTS С ПУСТЫМ УКАЗАТЕЛЕМ( NULL )

Значения NULL также имеют некоторые проблемы с операторами наподобие этих. Когда SQL сравнивает два значения в предикате, одно из кото- рых пустое (NULL), то результат неизвестен ( смотрите
Главу 5 ). Неизвестный предикат, подобен неверному и является причиной того что строка не выбирается, но работать он будет иначе в некоторых похожих запросах, в зависимости от того, используют они ALL или ANY вместо EXISTS. Рассмотрим наш предыдущий пример:
 
             SELECT * 
                FROM Customers 
                WHERE rating > ANY 
                    ( SELECT rating 
                        FROM Customers 
                        WHERE city = 'Rome' ); 
и еще один пример:
 
           SELECT * 
              FROM Customers outer 
              WHERE EXISTS 
                 ( SELECT * 
                     FROM Customers inner 
                     WHERE outer.rating > inner.rating 
                     AND inner.city = 'Rome' ); 
В общем, эти два запроса будут вести себя одинаково. Но предположим, что появилось пустое(NULL) значение в столбце rating таблицы Заказчиков:
 
 
  CNUM        CNAME        CITY        RATING        SNUM 
 
   2003         Liu          SanJose      NULL        1002 
 
В варианте с ANY, где оценка Liu выбрана основным запросом, значе- ние NULL делает предикат неизвестным а строка Liu не выбирается для вывода. Однако, в варианте с NOT EXISTS когда эта строка выбрана основным запросом, значение NULL используется в предикате под- запроса, дела его неизвестным в каждом случае. Это означает что под- запрос не будет производить никаких значений, и EXISTS будет неправилен. Это, естественно, делает оператор NOT EXISTS верным. Следовательно, строка Liu будет выбрана для вывода. Это основное расхождение, в отличие от других типов предикатов, где значение EXISTS независимо от того верно оно или нет - всегда неизвестно. Все это является аргументом в пользу использования варианта формулировки с ANY. Мы не считаем что значение NULL является выше чем допустимое значе- ние. Более того, результат будет тот же, если мы будем проверять для более низкого значения.

ИСПОЛЬЗОВАНИЕ COUNT ВМЕСТО EXISTS

Подчеркнем, что все формулировки с ANY и ALL могут быть в точности выполнены с EXISTS, в то врем как наоборот будет неверно. Хотя в этом случае, также верно и то что EXISTS и NOT EXISTS подзапросы могут обманывать при выполнении тех же самых подзапросов с COUNT(*) в предложении SELECT подзапроса. Если больше чем ноль строк выводе будет подсчитано, это эквивалентно EXISTS; в противном случае это работает также как NOT EXISTS. Следующее является этому примером (вывод показывается в Рисунке 13.12 ):
 
 
              SELECT * 
                 FROM Customers outer 
                 WHERE NOT EXISTS 
                     ( SELECT * 
                         FROM Customers inner 
                         WHERE outer.rating < = inner.rating 
                           AND inner.city = 'Rome' ); 
 
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT *                                      | 
              | FROM  Customers outer                         | 
              | WHERE NOT EXISTS                              | 
              | (SELECT *                                     | 
              | FROM Customers inner                          | 
              | WHERE outer.rating <= inner.rating            | 
              | AND inner.city = 'Rome');                     | 
              | ============================================= | 
              |   cnum     cname     city     rating   snum   | 
              |  -----    --------   ----     ------  ------  | 
              |   2004    Grass      Berlin      300    1002  | 
              |   2008    Cisneros   San Jose    300    1007  | 
                ============================================= 
Рисунок 13.12: Использование EXISTS с соотнесенным подзапросом

Это должно также быть выполнено как
 
            SELECT * 
               FROM Customers outer 
               WHERE 1 > 
                   ( SELECT COUNT (*) 
                        FROM Customers inner 
                        WHERE outer.rating < = inner.rating 
                           AND inner.city = 'Rome' ); 
Вывод к этому запросу показывается в Рисунке 13.13. Теперь Вы начинаете понимать сколько способов имеется в SQL. Если это все кажется несколько путанным на этой стадии, нет причины волноваться. Вы обучаетесь чтобы использовать ту технику которая лучше всего отвечает вашим требованиям и наиболее понятна для вас. Начиная с этого места, мы хотим показать Вам большое количество воз- можностей, что бы вы могли найти ваш собственный стиль.
 
 
               ===============  SQL Execution Log ============ 
              |                                               | 
              | SELECT *                                      | 
              | FROM  Customers outer                         | 
              | WHERE 1 >                                     | 
              | (SELECT COUNT (*)                             | 
              | FROM Customers inner                          | 
              | WHERE outer.rating <= inner.rating            | 
              | AND inner.city = 'Rome');                     | 
              | ============================================= | 
              |   cnum     cname     city     rating   snum   | 
              |  -----    --------   ----     ------  ------  | 
              |   2004    Grass      Berlin      300    1002  | 
              |   2008    Cisneros   San Jose    300    1007  | 
                ============================================= 
Рисунок 13.13: Использование COUNT вместо EXISTS

РЕЗЮМЕ

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

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

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

РАБОТА С SQL

1. Напишите запрос который бы выбирал всех заказчиков чьи оценки равны или больше чем люба( ANY ) оценка заказчика Serres.

2. Что будет выведено вышеупомянутой командой?

3. Напишите запрос использующий ANY или ALL, который бы находил всех продавцов которые не имеют никаких заказчиков размещенных в их городе.

4. Напишите запрос который бы выбирал все порядки с суммой больше чем люба ( в обычном смысле ) для заказчиков в Лондоне.

5. Напишите предыдущий запрос с использованием - MAX.

( См.
Приложение A для ответов. )

14. ИСПОЛЬЗОВАНИЕ ПРЕДЛОЖЕНИЯ UNION

В предшествующих главах, мы обсуждали различные способы которыми запросы могут помещаться один внутрь другого. Имеется другой способ объединения многочисленных запросов - то есть формирование их в объединение. В этой главе, вы научитесь использовать предложение 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 И УСТРАНЕНИЕ ДУБЛИКАТОВ

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; 

ИСПОЛЬЗОВАНИЕ СТРОК И ВЫРАЖЕНИЙ С UNION

Иногда, вы можете вставлять константы и выражения в предложении SELECT используемые с UNION. Это не следует строго указаниям ANSI, но это полезна и необычно используемая возможность. Константы и выражения которые вы используете, должны встречать совместимые стандарты которые мы выделяли ранее. Эта свойство полезно, например, чтобы устанавливать комментарии указывающие какой запрос вывел данную строку. Предположим что вы должны сделать отчет о том, какие продавцы производят наибольшие и наименьшие порядки по датам. Мы можем объединить два запроса, вставив туда текст чтобы различать вывод для каждого из них.
 
     SELECT a.snum, sname, onum, 'Highest on', odate 
        FROM (Salespeople a, Orders b 
        WHERE a.snum = b.snum 
        AND b.amt = 
          ( SELECT MAX (amt) 
               FROM Orders c 
               WHERE c.odate = b.odate ) 
 
        UNION 
 
     SELECT a.snum, (sname, (onum ' Lowest on', odate 
        FROM ( Salespeople a, Orders b 
        WHERE a.snum = b.snum 
        AND b.amt = 
           ( SELECT  MIN (amt) 
                FROM Orders c 
                WHERE c.odate = b.odate ); 
Вывод из этой команды показывается в Рисунке 14.4.

Мы должны были добавить дополнительный пробел в строку 'Lowest on', чтобы сделать ее совпадающей по длине со строкой 'Highest on'. Обратите внимание что Peel выбран при наличии и самого высокого и самого низкого ( фактически он единственный ) порядка на 5 Октября. Так как вставляемые строки двух этих запросов различны, строки не будут устранены как дубликаты.
 
 
                ===============  SQL Execution Log ============ 
               |                                               | 
               | AND b.amt =                                   | 
               | ( SELECT min (amt)                            | 
               | FROM Orders c                                 | 
               | WHERE c.odate = b.odate);                     | 
               | ============================================= | 
               |                                               | 
               | -----  ------- ------  ---------- ----------- | 
               |  1001  Peel     3008   Highest on  10/05/1990 | 
               |  1001  Peel     3008   Lowest  on  10/05/1990 | 
               |  1001  Peel     3011   Highest on  10/06/1990 | 
               |  1002  Serres   3005   Highest on  10/03/1990 | 
               |  1002  Serres   3007   Lowest  on  10/04/1990 | 
               |  1002  Serres   3010   Lowest  on  10/06/1990 | 
               |  1003  Axelrod  3009   Highest on  10/04/1990 | 
               |  1007  Rifkin   3001   Lowest  on  10/03/1990 | 
                =============================================== 
 
Рисунок 14.4: Выбор наивысших и наинизших порядков, определяемых с помощью строк

ИСПОЛЬЗОВАНИЕ UNION С ORDER BY

До сих пор, мы не оговаривали что данные многочисленных запросов будут выводиться в каком то особом порядке. Мы просто показывали вывод сна- чала из одного запроса а затем из другого. Конечно, вы не можете полагаться на вывод приходящий в произвольном порядке. Мы как раз сделаем так чтобы этот способ для выполнения примеров был более простым. Вы можете, использовать предложение ORDER BY чтобы упорядочить вывод из объединения, точно так же как это делается в индивидуальных запросах. Давайте пересмотрим наш последний пример чтобы упорядочить имена с помощью их порядковых номеров. Это может внести противоречие, такое как повторение имени Peel в последней команде, как вы сможете увидеть из вывода показанного в Рисунке 14.5.
SELECT a.snum, sname, onum, 'Highest on', odate 
   FROM Salespeople a, Orders b 
   WHERE a.snum = b.snum 
   AND b.amt = 
     ( SELECT MAX (amt) 
	  FROM Orders c 
	  WHERE c.odate = b.odate ) 

   UNION 

SELECT a.snum, (sname, (onum, 'Lowest on', odat 
   FROM Salespeople a, Orders b 
   WHEREa.snum = b.snum 
   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   | 
  |                                                  | 
   ================================================== 
Рисунок 1.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. Следующий шаг должен включать то, как значения вводятся в таблицы и как таблицы создаются с самого начала. Как вы увидите, запросы иногда используются внутри других типов команд, также хорошо как и сами по себе.

РАБОТА С SQL

1. Создайте объединение из двух запросов которое показало бы имена, города, и оценки всех заказчиков. Те из них которые имеют поле rating=200 и более, должны кроме того иметь слова - " Высокий Рейтинг ", а остальные должны иметь слова " Низкий Рейтинг ".

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

3. Сформируйте объединение из трех запросов. Первый выбирает пол snum всех продавцов в San Jose; второй, пол cnum всех заказчиков в San Jose; и третий пол onum всех порядков на 3 Октября. Сохраните дубликаты между последними двумя запросами, но устраните любую избыточность вывода между каждым из их и самым первым. ( Примечание: в данных типовых таблицах, не содержится никакой избыточности. Это только пример. )

( См.
Приложение A для ответов. )

15. ВВОД, УДАЛЕНИЕ и ИЗМЕНЕНИЕ ЗНАЧЕНИЙ ПОЛЕЙ

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

КОМАНДЫ МОДИФИКАЦИИ ЯЗЫКА DML

Значения могут быть помещены и удалены из полей, трем командами языка DML ( Язык Манипулирования Данными ):
INSERT  (ВСТАВИТЬ), 
UPDATE (МОДИФИЦИРОВАТЬ), 
DELETE  (УДАЛИТЬ). 
Не смущайтесь, все они упоминались ранее в SQL, как команды модификации.

ВВОД ЗНАЧЕНИЙ

Все строки в SQL вводятся с использованием команды модификации INSERT. В самой простой форме, INSERT использует следующий синтаксис:
INSERT INTO <TABLE name>VALUES ( ,  . . .); 
Так, например, чтобы ввести строку в таблицу Продавцов, вы можете использовать следующее условие:
INSERT INTO Salespeople 
    VALUES (1001, 'Peel', 'London', .12); 
Команды DML не производят никакого вывода, но ваша программа должна дать вам некоторое подтверждение того что данные были использованы.

Им таблицы ( в нашем случае - Salespeople (Продавцы)), должно быть предварительно определено, в команде CREATE TABLE ( см. Главу 17 ), а каждое значение пронумерованное в предложении значений, должно совпадать с типом данных столбца, в который оно вставляется. В ANSI, эти значения не могут составлять выражений, что означает что 3 - это доступно, а выражение 2 + 1 - нет. Значения, конечно же, вводятся в таблицу в поименном порядке, поэтому первое значение с именем, автоматически попадает в столбец 1, второе в столбец 2, на так далее.

ВСТАВКА ПУСТЫХ УКАЗАТЕЛЕЙ (NULL)

Если вам нужно ввести пустое значение(NULL), вы вводите его точно так- же как и обычное значение. Предположим, что еще не имелось пол city для мистера Peel. Вы можете вставить его строку со значением=NULL в это поле, следующим образом:
INSERT INTO Salespeople 
  VALUES (1001, 'Peel', NULL, .12); 
Так как значение NULL - это специальный маркер, а не просто символьное значение, он не включается в одиночные кавычки.

ИМЕНОВАНИЕ СТОЛБЦА ДЛЯ ВСТАВКИ (INSERT)

Вы можете также указывать столбцы, куда вы хотите вставить значение имени. Это позволяет вам вставлять имена в любом порядке. Предположим что вы берете значения для таблицы Заказчиков из отчета выводимого на принтер, который помещает их в таком порядке: 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 должна отвечать следующим условиям:

* Она должна уже быть создана командой CREATE TABLE.

* Она должна иметь четыре столбца которые совпадают с таблицей

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

Общее правило то, что вставляемые столбцы таблицы, должны совпадать со столбцами выводимыми подзапросом, в данном случае, для всей таб- лицы Продавцов. 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 ДЛЯ МНОГИХ СТОЛБЦОВ

Однако, вы не должны, ограничивать себя модифицированием единственного столбца с помощью команды 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'; 

МОДИФИЦИРОВАНИЕ ПУСТЫХ(NULL) ЗНАЧЕНИЙ

Предложение 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.

РАБОТА С SQL

1. Напишите команду которая бы поместила следующие значения, в их нижеуказанном порядке, в таблицу Продавцов:
 
               city  - San Jose, 
               name - Bianco, 
               comm - NULL, 
               cnum  - 1100. 
2. Напишите команду которая бы удалила все порядки заказчика Clemens из таблицы Порядков.

3. Напишите команду которая бы увеличила оценку всех заказчиков в Риме на 100.

4. Продавец Serres оставил компанию. Переназначьте его заказчиков продавцу Motika.

( См.
Приложение A для ответов. )

16. ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С КОМАНДАМИ МОДИФИКАЦИИ

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

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

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

ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С INSERT

INSERT - это самый простой случай. Вы уже видели как вставлять результаты запроса в таблицу. Вы можете использовать подзапросы внутри любого запроса, который генерирует значения для команды INSERT тем же самым способом, которым вы делали это для других запросов - т.е. внутри предиката или предложения HAVING. Предположим, что мы имеем таблицу с именем SJpeople, столбцы которой совпадают со столбцами нашей таблицы Продавцов. Вы уже видели как заполнять таблицу подобно этой, заказчиками в городе, например, в San Jose:
 
                   INSERT INTO SJpeople 
                      SELECT * 
                      FROM Salespeople 
                      WHERE city = 'San Jose'; 
Теперь мы можем использовать подзапрос чтобы добавить к таблице SJpeople всех продавцов которые имеют заказчиков в San Jose, неза- висимо от того, находятся ли там продавцы или нет:
 
       INSERT INTO SJpeople 
          SELECT * 
             FROM Salespeople 
             WHERE snum = ANY 
               ( SELECT snum 
                    FROM Customers 
                    WHERE city = ' (San (Jose' ); 
 
Оба запроса в этой команде функционируют также как если бы они не являлись частью выражения INSERT. Подзапрос находит все строки для заказчиков в San Jose и формирует набор значений snum. Внешний запрос выбирает строки из таблицы Salespeople, где эти значения snum найдены. В этом примере, строки для продавцов Rifkin и Serres, которые назначены заказчикам в San Jose - Liu и Cisneros, будут вставлены в таблицу SJpeople.

НЕ ВСТАВЛЯЙТЕ ДУБЛИКАТЫ СТРОК

Последовательность команд в предшествующем разделе может быть проблематичной. Продавец 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. Это даст вам возможность определять некоторые довольно сложные критерии чтобы установить, какие строки будут удаляться, что важно, так как вы конечно же не захотите по неосторожности удалить нужную строку. Например, если мы закрыли наше ведомство в Лондоне, мы могли бы использовать следующий запрос чтобы удалить всех заказчиков назначенных к продавцам в Лондоне:
     DELETE 
	FROM Customers 
	WHERE snum = ANY 
	  ( SELECT snum 
	      FROM Salespeople 
	      WHERE city = 'London' ); 
Эта команда удалит из таблицы Заказчиков строки Hoffman и Clemens ( назначенных для Peel ), и Periera ( назначенного к 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

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.odate = b.odate )); 

СТОЛКНОВЕНИЕ С ОГРАНИЧЕНИЯМИ ПОДЗАПРОСОВ КОМАНДЫ DML

Неспособность сослаться к таблице задействованной в любом подзапросе из команды модификации (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 который обладает не самым понятным логическим порядком. Сначала мы сделали запрос таблицы которая уже заполнена данными. Потом мы показали как можно фактически помещать эти значения изначально. Но, как вы видите, полное ознакомление с запросами здесь неоценимо. Теперь, когда мы показали вам как заполнять значениями таблицы которые уже были созданы (по определению) , мы покажем(
со следующей главы) откуда появились эти таблицы.

РАБОТА С SQL

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

2. Напишите команду которая бы удаляла всех заказчиков не имеющих текущих порядков.

3. Напишите команду которая бы увеличила на двадцать процентов комиссионные всех продавцов имеющих общие текущие порядки выше чем $3,000.

( См.
Приложение A для ответов. )

21. ИЗМЕНЕНИЕ ЗНАЧЕНИЙ С ПОМОЩЬЮ ПРЕДСТАВЛЕНИЙ

Эта глава рассказывает о командах модификации ЯЗЫКА 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, следующие:

* Оно должно выводиться в одну и только в одну базовую таблицу.

* Оно должно содержать первичный ключ этой таблицы ( это технически не предписывается стандартом ANSI, но было бы неплохо придерживаться этого).

* Оно не должно иметь никаких полей, которые бы являлись агрегатными функциями.

* Оно не должно содержать DISTINCT в своем определении.

* Оно не должно использовать GROUP BY или HAVING в своем определении.

* Оно не должно использовать подзапросы ( это - ANSI_ограничение которое не предписано для некоторых реализаций )

* Оно может быть использовано в другом представлении, но это представле- ние должно также быть модифицируемыми.

* Оно не должно использовать константы, строки, или выражения значений ( например: comm * 100 ) среди выбранных полей вывода.

* Для INSERT, оно должно содержать любые пол основной таблицы которые имеют ограничение NOT NULL, если другое ограничение по умолчанию, не определено.

МОДИФИЦИРУЕМЫЕ ПРЕДСТАВЛЕНИЯ И ПРЕДСТАВЛЕНИЯ ТОЛЬКО_ЧТЕНИЕ.

Одно из этих ограничений то, что модифицируемые представления, фактически, подобны окнам в базовых таблицах. Они показывают кое-что, но не обязательно все, из содержимого таблицы. Они могут ограничивать определенные строки ( использованием предикатов), или специально именованные столбцы ( с исключениями ), но они представляют значения непосредственно и не выводит их информацию, с использованием составных функций и выражений. Они также не сравнивают строки таблиц друг с другом ( как в объединениях и подзапросах, или как с 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.

РАБОТА С 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 для ответов. )

22. КТО ЧТО МОЖЕТ ДЕЛАТЬ В БАЗЕ ДАННЫХ

В этой главе, вы обучитесь работе с привилегиями. Как сказано в Главе 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.

КОМАНДА 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 и REGERNCES в которых необязательно указывать имена столбцов. Привилегию 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; 

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

ИСПОЛЬЗОВАНИЕ АРГУМЕНТОВ ALL И PUBLIC

SQL поддерживает два аргумента для команды GRANT, которые имеют специальное значение: ALL PRIVILEGES (ВСЕ ПРИВИЛЕГИИ) или просто ALL и PUBLIC (ОБЩИЕ). ALL используется вместо имен привилегий в команде GRANT чтобы отдать все привилегии в таблице. Например, Diane может дать Stephen весь набор привилегий в таблице Заказчиков с помощью такой команды:

GRANT REFERENCES ON Salespeople TO Diane;

( привилегии UPDATE и REFERENCES естественно применяются ко всем столбцам. ) А это другой способ высказать ту же мысль:

GRANT ALL ON Customers TO Stephen;

PUBLIC - больше похож на тип аргумента - захватить все (catch-all), чем на пользовательскую привилегию. Когда вы предоставляете привилегии для публикации, все пользователи автоматически их получают. Наиболее часто, это применяется для привилегии SELECT в определенных базовых таблицах или представлениях которые вы хотите сделать доступными для любого пользователя. Чтобы позволить любому пользователю видеть таблицу Порядков, вы, например, можете ввести следующее:

GRANT SELECT ON Orders TO PUBLIC;

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

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

ПРЕДОСТАВЛЕНИЕ ПРИВИЛЕГИЙ С ПОМОЩЬЮ WITH GRANT OPTION

Иногда, создателю таблицы хочется чтобы другие пользователи могли получить привилегии в его таблице. Обычно это делается в системах, где один или более людей создают несколько (или все) базовые таблицы в ба- зе данных а затем передают ответственность за них тем кто будет фактически с ними работать. 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, таких как сохранение или восстановление изменений, создание ваших собственных имен для таблиц принадлежащих другим людям, и понимание что происходит когда различные пользователи пытаются обращаться к одному и тому же объекту одновременно.

РАБОТА С SQL

1. Передайте Janet право на изменение оценки заказчика.

2. Передайте Stephan право передавать другим пользователям право делать запросы в таблице Порядков.

3. Отнимите привилегию INSERT( ВСТАВКА) в таблице Продавцов у Claire и у всех пользователей которым она была предоставлена.

4. Передайте Jerry право вставлять или модифицировать таблицу Заказчиков с сохранением его возможности оценивать значения в диапазоне от 100 до 500.

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

( См.
Приложение A для ответов. )
Автор: Гавриленко Сергей Алексеевич
Прислал: Гавриленко Сергей Алексеевич

Зачастую встает вопрос, а как же использовать хранимую процедуру в запросе?
Хранимую процедуру в запросе использовать не получится. MSSQL Server 2000 такой функциональности не предоставляет. Есть несколько вариантов обхода это проблемы:
1. Оформить хранимую процедуру как функцию.
create function dbo.dataset ()
returns @out table (
	id int,
	name varchar(150)
) as begin
	insert @out(id, name)
	select EmployeeID, FirstName + ' ' + LastName
	from Northwind.dbo.Employees
	return
end
go

select *
from dbo.dataset() 
where name like '%King%'
Недостатки: в силу некоторых ограничений, накладываемых на синтаксис пользовательских функций, переписать хранимую процедуру в функцию иногда просто невозможно.

2. Получить набор через временную таблицу.
create procedure dbo.dataset_sp 
as begin
	set nocount on
	select EmployeeID, FirstName + ' ' + LastName
	from Northwind.dbo.Employees
end
go

create table #dataset(
	id int,
	name varchar(150)
)
insert #dataset exec dbo.dataset_sp

select *
from #dataset
where name like '%King%'

drop table #dataset
Недостатки:
  • таким образом можно получить только первый набор данных
  • невозможно "унаследовать" получение набора. При попытке получить таким образом набор из хранимой процедуры, которая в свою очередь получает набор из другой процедуры insert exec'ом, мы получим ошибку : An INSERT EXEC statement cannot be nested
  • Отзывы и оценки
    sysadm2000 26 августа 2005, 13:49 Оценка: N/A
    не та основная проблема во втором примере, что указал автор FAQ
    Основная проблема - что НАДО ТОЧНО ЗНАТЬ структуру возвращаемого рекордсета...
    А кто бы его знал... Если его знать - то зачем бы вообще возникала эта задача...
    Веселов Константин Евгеньевич 26 марта 2005, 17:44 Оценка: 3
    Вот еще вариант:

    EXEC sp_addlinkedserver '(local)';

    SELECT *
    FROM OPENQUERY([(local)], 'EXEC dbo.dataset_sp')
    Гавриленко Сергей Алексеевич 05 марта 2005, 12:22 Оценка: 4
    Сам же правлюсь:
    вместо "таким образом можно получить только первый набор данных" следует читать "Таким образом можно получить все наборы данных, возвращаемые процедурой, если они соответствуют структуре таблицы; если хоть один набор не соответствует, будет выдана соответствующая ошибка."

    Автор: BPMargolin, Neil Pike
    Прислал: cat2

    ====================
    Q. How can I code a dynamic varying ORDER BY statement in SQL Server?

    A. First let's illustrate the concept ...

    Using the "pubs" database that ships with SQL Server, let's say that we want to create a stored procedure that will allow the user to select the entire "authors" table sorted by "au_id", the author name, or ZIP code. To make it a bit more interesting, let's also support!the option of sorting the author's name either first name first, or last name first.

    So, in essence, we want to program a stored procedure that via, the value of a single input parameter, will enable any of the following four result sets to be generated. In short, we want to be able to execute...

    EXEC spAuthors 1 
    or 
    EXEC spAuthors 2 
    or 
    EXEC spAuthors 3 
    or 
    EXEC spAuthors 4 

    and get ...
    Result set #1 - sorted by author identification code ("au_id"):
    The code we will want to be executed is:
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_id 

    and this will create the following output:

    au_id au_fname au_lname
    ----------- -------- --------
    172-32-1176 Johnson White
    213-46-8915 Marjorie Green
    238-95-7766 Cheryl Carson

    . . .

    172-32-1176 Johnson White
    213-46-8915 Marjorie Green
    238-95-7766 Cheryl Carson

    . . .

    893-72-1158 Heather McBadden
    899-46-2035 Anne Ringer
    998-72-3567 Albert Ringer


    Result set #2 - sorted by author's name, first name first:
    The code should be:
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_fname, au_lname 

    which will produce:

    au_id au_fname au_lname
    ----------- -------- --------
    409-56-7008 Abraham Bennet
    672-71-3249 Akiko Yokomoto
    998-72-3567 Albert Ringer

    . . .

    846-92-7186 Sheryl Hunter
    724-80-9391 Stearns MacFeather
    807-91-6654 Sylvia Panteley


    Result set #3 - sorted by author's name, last name first:
    The code will be:
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_lname, au_fname 
    This code generates:

    au_id au_fname au_lname
    ----------- -------- --------
    409-56-7008 Abraham Bennet
    648-92-1872 Reginald Blotchet-Halls
    238-95-7766 Cheryl Carson

    . . .

    724-08-9931 Dirk Stringer
    172-32-1176 Johnson White
    672-71-3249 Akiko Yokomoto

    And finally, result set #4 - sorted by ZIP code:
    The code will be:
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY zip 
    With a result set of:

    au_id au_fname au_lname
    ----------- -------- --------
    807-91-6654 Sylvia Panteley
    527-72-3246 Morningstar Greene
    722-51-5454 Michel DeFrance

    . . .

    472-27-2349 Burt Gringlesby
    893-72-1158 Heather McBadden
    648-92-1872 Reginald Blotchet-Halls



    Okay, now that we have a firm idea of what we're looking for, let's see how we can go about creating a stored procedure with the flexibility we want.

    Our coding options include:

    I. Using IF ... THEN ... ELSE to execute one of four pre-programmed queries,
    II. Constructing the SQL statements dynamically, and using either the EXECUTE() function or sp_executesql system stored procedure to execute it,
    III. Using a CASE statement to choose the sequencing,
    IV. Using ANSI SQL-92 standard code suggested by renowned SQL Guru Joe Celko, and
    V. Using ANSI SQL-99 (SQL-3) code originated by the very gifted Richard Romley.

    Option I is what probably first comes to mind to most individuals. The stored procedure would probably look something like:

    USE pubs 
    GO 
    
    CREATE PROCEDURE dbo.spAuthors 
    @OrdSeq tinyint 
    AS 
    
    IF @OrdSeq = 1 
    BEGIN 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_id 
    END 
    
    ELSE IF @OrdSeq = 2 
    BEGIN 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_fname, au_lname 
    END 
    
    ELSE IF @OrdSeq = 3 
    BEGIN 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY au_lname, au_fname 
    END 
    
    ELSE IF @OrdSeq = 4 
    BEGIN 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY zip 
    END 
    
    GO 
    Each option has its advantages and disadvantages, so let's begin by critiquing this one.

    The advantages include:
    a) the code is straightforward, and easy to understand, and
    b) the SQL Server query optimizer is able to create an optimized query plan for each SELECT query, thus ensuring maximal performance.

    The primary disadvantage is that there are four separate SELECT queries that have to be maintained should the reporting requirements change.


    Option II is an alternative that will also be frequently suggested, particularly by those with experience with using dynamic queries in SQL Server.

    USE pubs 
    GO 
    
    CREATE PROCEDURE dbo.spAuthors 
    @OrdSeq tinyint 
    
    AS 
    DECLARE @SQLstmt varchar (255) 
    
    SELECT @SQLstmt = 'SELECT au_id, ' 
    SELECT @SQLstmt = @SQLstmt + 'au_fname, ' 
    SELECT @SQLstmt = @SQLstmt + 'au_lname ' 
    SELECT @SQLstmt = @SQLstmt + 'FROM authors ' 
    
    SELECT @SQLstmt = @SQLstmt + 
    CASE @OrdSeq 
    WHEN 1 THEN 'ORDER BY au_id' 
    WHEN 2 THEN 'ORDER BY au_fname, au_lname' 
    WHEN 3 THEN 'ORDER BY au_lname, au_fname' 
    WHEN 4 THEN 'ORDER BY zip' 
    END 
    
    EXEC (@SQLstmt) 
    GO 
    Note that in SQL Server 7.0, you can use the system stored procedure sp_executesql in place of the EXEC() function. Please refer to the SQL Server Books Online for the advantages of sp_executesql over the EXEC() function.

    While this is a perfectly good option, it does have two significant disadvantages. Perhaps the more important of the two is, that the user of the stored procedure must have appropriate permissions on any database objects referred to inside EXEC() or sp_executesql, in addition to an EXECUTE privilege on the stored procedure itself.

    Also, another possible disadvantage of this coding is that the SELECT statement, when placed inside the EXEC() function is not cached. Thus every invocation of the spAuthor stored procedure, when coded with a call to the EXEC() function, will result in SQL Server re-parsing the SELECT code, and generating a query plan anew. This is probably not a concern in most production environments, but it might be of importance in a high-performance OLTP shop. (Note that sp_executesql will cache the query plans.)


    Option III has garnered some support!on the Microsoft SQL Server newsgroups since it was first offered, although I believe that in practice it is perhaps the least flexibility of the 5 options being presented here. Nevertheless, it does lead us away from the EXEC() function and/or sp_executesql.

    USE pubs 
    GO 
    
    CREATE PROCEDURE dbo.spAuthors 
    @OrdSeq tinyint 
    
    AS 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY CASE @OrdSeq 
    WHEN 1 THEN au_id 
    WHEN 2 THEN au_fname + ' ' + au_lname 
    WHEN 3 THEN au_lname + ' ' + au_fname 
    WHEN 4 THEN zip 
    ELSE NULL 
    END 
    
    GO 
    It is easy to see why this is a very popular solution. At first glance, it seems to be the ideal solution. However it does suffer from one very serious flaw. The CASE construction evaluates to value of a specific data type. In this case, all four columns ... au_id, au_fname, au_lname and zip ... are character strings, and SQL Server will, when parsing the statement, look at the expressions after the THEN clause and construct a data type that can hold, without lose of precision, any of the individual expressions. In fact, the data type returned by the CASE construction in the above code will be varchar (61).

    However, this technique just won't stand up to the demands of sequencing by columns of significantly different data types.

    To see how fragile the code actually is, add before the WHEN 1 clause, the following:

    WHEN 0 THEN 6.44

    and use the following to call the stored procedure ...

    EXEC dbo.spAuthors 1


    The technique offered in Option IV was first posted by well-known SQL Guru Joe Celko in response to the technique in Option III. Joe participated in the creation of the ANSI SQL-92 standard, and thus is a strong supporter of ANSI SQL-92 compliant code. Joe frequently makes the point that code written to the SQL-92 standard is portable to any database that supports the standard.

    USE pubs 
    GO 
    
    CREATE PROCEDURE dbo.spAuthors 
    @OrdSeq tinyint 
    AS 
    
    SELECT au_id, au_fname, au_lname, 
    CASE @OrdSeq 
    WHEN 1 THEN au_id 
    WHEN 2 THEN au_fname + ' ' + au_lname 
    WHEN 3 THEN au_lname + ' ' + au_fname 
    WHEN 4 THEN zip 
    ELSE NULL 
    END AS OrdSeq 
    FROM authors 
    ORDER BY OrdSeq 
    
    GO 
    Note that this code does require an additional column (OrdSeq) in the result set so that the ORDER BY clause has something to "work" on. When Joe Celko posted the technique, there was criticism concerning the additional column. I'd offer as a thought that stored procedures should be invoked not by end-users, but by applications. The application can just ignore the "extraneous" column. Nevertheless, additional bytes are being pushed across a network, and that, thus, can be a performance consideration. It can also be argued that we have changed the definition of the problem to accommodate this solution. Nevertheless, I agree with Joe Celko, that, if portability of code is important, this solution is definitely worth considering.

    The careful reader might notice that the columns au_id, au_fname, au_lname and zip are all character strings, and might therefore conclude that the technique works only when with columns of similar data types. As Joe Celko pointed out however, the ANSI SQL-92 standard also supports the CAST function to transform one data type into another. Since all of the common data types are ultimately human readable, they can be converted into an alphanumeric format, and thus columns of the various numeric data types can also be used along with character string data types. (The CAST function was introduced into SQL Server beginning with version 7.0. A SQL Server 6.5 solution would have to use the well-known CONVERT function.)

    The authors table in pubs does not contain a column with a strictly numeric data type, so it is a bit difficult to illustrate. Let us assume however that the "zip" column in authors is actually defined as an integer data type rather than as char(5). In that case, the SELECT could be programmed ...

    SELECT au_id, au_fname, au_lname, 
    CASE @OrdSeq 
    WHEN 1 THEN au_id 
    WHEN 2 THEN au_fname + ' ' + au_lname 
    WHEN 3 THEN au_lname + ' ' + au_fname 
    WHEN 4 THEN RIGHT ('00000' + CAST (zip as char(5)), 5) 
    ELSE NULL 
    END AS OrderingSequence 
    FROM authors 
    ORDER BY OrderingSequence 
    In order for the sorting to work properly, we do have to be aware of, and take into account, the format of the output from the CAST function. In SQL Server, you can experiment and see for yourself that integer values cast to character format will result in left-aligned output. This will sort incorrectly, so we have to force a right-alignment. Because the ANSI SQL-92 standard is weak on string manipulation functions, we are forced to rely upon the SQL Server-specific RIGHT function to achieve this, thus breaking the portability of the code.


    The last piece of code, Option V, was originally posted by the very gifted Richard Romley. It is not ANSI SQL-92 compliant, but just might be with the SQL-99 (aka SQL-3) standard. It is my personal favorite.

    USE pubs 
    GO 
    
    CREATE PROCEDURE dbo.spAuthors 
    @OrdSeq tinyint 
    
    AS 
    SELECT au_id, au_fname, au_lname 
    FROM authors 
    ORDER BY 
    CASE @OrdSeq WHEN 1 THEN au_id ELSE NULL END, 
    CASE @OrdSeq WHEN 2 THEN au_fname + ' ' + au_lname ELSE NULL END, 
    CASE @OrdSeq WHEN 3 THEN au_lname + ' ' + au_fname ELSE NULL END, 
    CASE @OrdSeq WHEN 4 THEN zip ELSE NULL END 
    
    GO
    There are many similarities between this code and the code presented in options III and IV. However, Richard Romley has avoided the problems inherent with the fact that CASE can only return a value of one specific data type by breaking the ORDER BY into four separate CASE expressions. Using this construction, SQL Server can return an appropriate data type for each CASE expression, without ever getting tangled up trying to transform data types.

    By the way, the reason that this solution is not SQL-92 compliant is because the SQL-92 standard only permits ORDER BYs using a column, and not an expression. SQL Server has long supported ORDER BYs using an expression, and the SQL-99 appears to be ready to accept that extension.

    v2.02 2000.06.03
    Applies to SQL Server versions : 6.5, 7.0, 2000
    FAQ Categories : Application Design and Programming
    Authors : BPMargolin, Neil Pike
    Автор: Glory
    Прислал: Glory

    Наверное, одним из первых вопросов, возникающих у начинающих программистов на T-SQL, это вопрос "А как получить выборку из таблицы, имя которой занесено в переменную ?"
    Т.к. в T-SQL нет возможности использовать в некоторых частях запроса значения переменных, то единственным доступным решением является использование динамического запроса. Идея очень проста: в специально определнной переменной "собирается" строка запроса, которая должна выполняться. Далее эта строка запускается на выполнение. Запуск можно осуществить двумя способами
    - с помощью команды EXECUTE
    - с помощью системной процедуры sp_executesql.

    Выглядит это приблизительно так
    DECLARE @SQL varchar(8000), @table_name varchar(10)
    SET @SQL = 'SELECT * FROM ' + @TableName
    
    exec(@SQL)
    --или
    
    exec sp_executesql @SQL
    Обычно динамические запроса формируются внутри хранимых процедур, в которых по входным параметром составляется конкретная строка выполнения.

    I.Особенности динамического запроса
    1. Динамический запрос ВСЕГДА выполняется В ТОМ-ЖЕ КОННЕКТЕ и КАК ОТДЕЛЬНЫЙ ПАКЕТ(batch). Другими словами при использовании такого запроса,
    - вы ни имеете доступа к локальным переменным, объявленым до вызова динамического запроса (однако возможен доступ к объявленным ранее временным таблицам)
    - все временые таблицы и переменные, созданные во время выполнения команды exec, будут недоступны в вызывающей процедуре, т.к. будут удалены по окнончании пакета, открытого для exec.

    2. Динамический запрос ВСЕГДА выполняется с ПРАВАМИ ПОЛЬЗОВАТЕЛЯ, ВЫЗВАВШЕГО ПРОЦЕДУРУ, а не с правами владельца процедуры. Другими словами, если владельцем процедуры Procedure1 является User1, который имеет права к таблице Table1, то для пользователя User2 мало назначить права на выполнение процедуры Procedure1, если обращение в ней к таблице Table1 идет через динамический запрос. Придется давать ему соответствующие права и непосредственно для Table1.

    3. Компиляция запроса происходят непосредственно перед его вызовом. Т.е. обо все синтаксических ошибках вы узнаете только в этот момент.


    II.Особенности использования команда exec
    1. Команда exec поддерживает к качестве аргумента конкатенацю строк и/или переменных. НО не поддерживатеся конкатенация результатов выполнения функций, т.е. конструкции вида
    exec('SELECT * FROM ' + LEFT(@TableName, 10))
    запрещены к использованию.
    2. В команде нет входных/выходных параметров.



    III.Особенности использования процедуры sp_executesql
    1. Процедура НЕ поддерживает в качестве параметров конкатенацию строк и/или переменных.
    2. Текст запроса должен быть либо переменной типа NVARCHAR/NCHAR, либо такого же типа стринговой константой.
    3. Имеется возможность передачи параметров в выполняемый скрипт и получение выходных значений
    Последнее явно в документации не описано, поэтому вот несколько примеров

    В данном примере в динамический запрос передаются 4 переменные, три из которых яляюся выходными


    declare @var1 int, @var2 varchar(100), @var3 varchar(100), @var4 int
    declare @mysql nvarchar(4000)
    set @mysql = 'set @var1 = @var1 + @var4; set @var2 = ''CCCC''; set @var3 = @var3 + ''dddd'''
    set @var1 = 0
    set @var2 = 'BBBB'
    set @var3 = 'AAAA'
    set @var4 = 10
    
    select @var1, @var2, @var3
    exec sp_executesql @mysql, N'@var1 int out, @var2 varchar(100) out, @var3 varchar(100) out, @var4 int',   
    @var1 = @var1 out, @var2 = @var2 out, @var3 = @var3 out, @var4 = @var4
    select @var1, @var2, @var3



    В данном примере в динамическом запросе открывается курсор, который доступен в вызывающей процедуре через выходную переменную


    USE pubs
    declare @cur cursor
    exec sp_executesql N'set @curvar= cursor local for select top 10 au_id, au_lname, au_fname from authors open @curvar' , 
    N'@curvar cursor output ', @curvar=@cur output
    
    FETCH NEXT FROM @cur
    WHILE @@FETCH_STATUS = 0
    BEGIN
    FETCH NEXT FROM @cur
    END




    Резюме(более IMHO, чем обязательные требования)
    Динамический запрос очень полезная и иногда просто незаменимая вещь, НО способы его реализации и конкретно через вызов в отдельном пакете с правами пользователя, вызвавшего процедуру, принижают его практическое МАССОВОЕ применение.
    Glory
    Автор: MiCe
    Прислал:

    Создание Linked server
    EXEC sp_addlinkedserver 
            @server = 'FOX_ODBC', 
            @provider = 'MSDASQL', 
            @srvproduct = '',
            @provstr = 'Driver={Microsoft Visual FoxPro Driver}; 
    UID=;SourceDB=C:\;SourceType=DBF;Exclusive=No;BackgroundFetch=Yes;Col
    late=Russian;Null=No;Deleted=No'
    
    EXEC  sp_addlinkedserver 
            @server = 'FOX_OLEDB', 
            @provider = 'VFPOLEDB',
            @srvproduct = '',
            @datasrc ='z:\',
            @provstr = 'Collating Sequence=RUSSIAN'

    Примеры запросов
    select * from FOX_OLEDB...[db\medbf] -- относительно datasrc 
    select * from FOX_OLEDB...[\\srv\share\db\medbf]  -- UNC
    select * from FOX_OLEDB...[c:\db\medbf]  -- local path
    ---------------------------

    Так можно создавать и модифицировать dbf файлы.
    select *
    from OPENQUERY(FOXODBC,
    'select * from \\srv\buh\existdbf ;create dbf \\srv\buh\newdbf (id c(10),name c(50)) ' 
    ) a
    
    -- файл \\srv\buh\existdbf - пустышка!!!! но он должен существовать
    -- лично я использую специально созданный для этого пустой dbf
    -- собственно создание пррисзодит во второй команде
    
    Идея состоит в том, чтобы "под прикрытием" запроса к фиктивной таблице передать Linked server-у на выполнение другие инструкции.

    PS..
    вот перечень команд которые поддерживает 'VFPOLEDB'( для ODBC for Visual FoxPro некоторые не поддерживаются)....
    выдержка из из "хелпа" к VS .Net 7.0
    --------------------------------------------
    The Visual FoxPro OLE DB Provider supports the native Visual FoxPro language syntax for the following commands:

    CREATE TABLE - SQL Command
    Creates a table having the specified fields.
    DELETE - SQL Command
    Marks records for deletion.
    DROP TABLE Command
    Removes a table from the database specified with the data source and deletes it from disk.
    INSERT - SQL Command
    Appends a record to the end of a table that contains the specified field values.
    SELECT - SQL Command
    Retrieves data from one or more tables.
    UPDATE - SQL Command
    Updates records in a table with new values.
    The Visual FoxPro Language Reference contains detailed information about the following supported commands:

    ALTER TABLE - SQL Command
    Programmatically modifies the structure of a table.
    CREATE TABLE - SQL Command
    Creates a table having the specified fields.
    Using Data Definition Language (DDL)
    You cannot include DDL in the following places:

    In a batch SQL statement that requires a transaction
    Following a previously executed statement that required a transaction if not in auto-commit mode and if your application has not yet called SQLTransact.
    For example, if you want to create a temporary table, you should create the table before you begin the statement requiring a transaction. If you include the CREATE TABLE statement in a batch SQL statement that requires a transaction, the provider returns an error message.

    DELETE - SQL Command
    Marks records for deletion.
    DELETE TAG Command
    Removes a tag or tags from a compound index (.cdx) file.
    DROP TABLE Command
    Removes a table from the database specified with the data source and deletes it from disk.
    INDEX Command
    Creates an index file to display and access table records in a logical order.
    INSERT - SQL Command
    Appends a record to the end of a table that contains the specified field values.
    SELECT - SQL Command
    Retrieves data from one or more tables.
    The Visual FoxPro OLE DB Provider supports the native Visual FoxPro language syntax for this command.

    SET ANSI Command
    Determines how comparisons between strings of different lengths are made with the = operator in Visual FoxPro SQL commands.
    SET BLOCKSIZE Command
    Specifies how disk space is allocated for the storage of memo fields.
    SET COLLATE Command
    Specifies a collation sequence for character fields in subsequent indexing and sorting operations.
    SET DELETED Command
    Specifies whether records marked for deletion are processed and whether they are available for use in other commands.
    SET EXACT Command
    Specifies the rules for comparing two strings of different lengths.
    SET EXCLUSIVE Command
    Specifies whether table files are opened for exclusive or shared use on a network.
    SET NULL Command
    Determines how null values are supported by the ALTER TABLE - SQL, CREATE TABLE - SQL, and INSERT - SQL commands.
    SET PATH Command
    Specifies a path for file searches.
    Provider Remarks
    If you issue SET PATH in a stored procedure, it will be ignored by the following functions and commands: SELECT, INSERT, UPDATE, DELETE, and CREATE TABLE

    If you issue SET PATH in a stored procedure and do not subsequently set the path back to its original state, other connections to the database will use the new path (because SET PATH is not scoped to data sessions).

    If you want to create, select, or update tables in a directory other than that specified by the data source, specify the full path of the file with your command.

    SET REPROCESS Command
    Specifies how many times or for how long to lock a file or record after an unsuccessful locking attempt.
    SET UNIQUE Command
    Specifies whether records with duplicate index key values are maintained in an index file.
    UPDATE - SQL Command
    Updates records in a table with new values.
    --------------
    приведу еще синтаксис create table ( так как отличается от MS SQL DDL)

    Creates a table having the specified fields.

    CREATE TABLE | DBF TableName1 [NAME LongTableName] [FREE]
    (FieldName1 FieldType [(nFieldWidth [, nPrecision])]
    [NULL | NOT NULL] [CHECK lExpression1 [ERROR cMessageText1]]
    [DEFAULT eExpression1] [PRIMARY KEY | UNIQUE]
    [REFERENCES TableName2 [TAG TagName1]] [NOCPTRANS]
    [, FieldName2 ...] [, PRIMARY KEY eExpression2 TAG TagName2
    |, UNIQUE eExpression3 TAG TagName3]
    [, FOREIGN KEY eExpression4 TAG TagName4 [NODUP] REFERENCES TableName3 [TAG TagName5]]
    [, CHECK lExpression2 [ERROR cMessageText2]])| FROM ARRAY ArrayName
    Parameters
    TableName1
    Specifies the name of the table to create. The TABLE and DBF options are identical.
    NAME LongTableName
    Specifies a long name for the table. A long table name can be specified only when a database is open because long table names are stored in databases.
    Long names can contain up to 128 characters and can be used in place of short file names in the database.

    FREE
    Specifies that the table will not be added to an open database. FREE isn't required if a database isn't open.
    (FieldName1 FieldType [(nFieldWidth [, nPrecision])]
    Specifies the field name, field type, field width, and field precision (number of decimal places), respectively.
    A single table can contain up to 255 fields. If one or more fields allow null values, the limit is reduced by one to 254 fields.

    FieldType is a single letter indicating the field's data type. Some field data types require that you specify nFieldWidth or nPrecision, or both.

    The following table lists the values for FieldType and whether nFieldWidth and nPrecision are required.

    FieldType nFieldWidth nPrecision Description
    C n – Character field of width n
    D – – Date
    T – – DateTime
    N n d Numeric field of width n with d decimal places
    F n d Floating numeric field of width n with d decimal places
    I – – Integer
    B – d Double
    Y – – Currency
    L – – Logical
    M – – Memo
    G – – General

    nFieldWidth and nPrecision are ignored for D, T, I, Y, L, M, G, and P types. nPrecision defaults to zero (no decimal places) if nPrecision isn't included for the N or F types. nPrecision defaults to the number of decimal places specified by the SET DECIMAL setting if nPrecision isn't included for the B type.

    NULL
    Allows null values in the field. If one or more fields can contain null values, the maximum number of fields the table can contain is reduced by one, from 255 to 254.
    NOT NULL
    Prevents null values in the field.
    If you omit NULL and NOT NULL, the current setting of SET NULL determines if null values are allowed in the field. However, if you omit NULL and NOT NULL and include the PRIMARY KEY or UNIQUE clause, the current setting of SET NULL is ignored and the field defaults to NOT NULL.

    CHECK lExpression1
    Specifies a validation rule for the field. lExpression1 can be a user-defined function. Note that when a blank record is appended, the validation rule is checked. An error is generated if the validation rule doesn't allow for a blank field value in an appended record.
    ERROR cMessageText1
    Specifies the error message Visual FoxPro displays when the validation rule specified with CHECK generates an error. The message is displayed only when data is changed within a Browse window or Edit window.
    DEFAULT eExpression1
    Specifies a default value for the field. The data type of eExpression1must be the same as the field's data type.
    PRIMARY KEY
    Creates a primary index for the field. The primary index tag has the same name as the field.
    UNIQUE
    Creates a candidate index for the field. The candidate index tag has the same name as the field. For more information about candidate indexes, see Setting a Primary or Candidate Index.
    Note Candidate indexes (created by including the UNIQUE option in CREATE TABLE or ALTER TABLE – SQL) are not the same as indexes created with the UNIQUE option in the INDEX command. An index created with the UNIQUE option in the INDEX command allows duplicate index keys; candidate indexes do not allow duplicate index keys. See INDEX for additional information on its UNIQUE option.
    Null values and duplicate records are not permitted in a field used for a primary or candidate index. However, Visual FoxPro will not generate an error if you create a primary or candidate index for a field that supports null values. Visual FoxPro will generate an error if you attempt to enter a null or duplicate value into a field used for a primary or candidate index.

    REFERENCES TableName2 [TAG TagName1]
    Specifies the parent table to which a persistent relationship is established. If you omit TAG TagName1, the relationship is established using the primary index key of the parent table. If the parent table does not have a primary index, Visual FoxPro generates an error.
    Include TAG TagName1 to establish a relation based on an existing index tag for the parent table. Index tag names can contain up to 10 characters.

    The parent table cannot be a free table.

    NOCPTRANS
    Prevents translation to a different code page for character and memo fields. If the table is converted to another code page, the fields for which NOCPTRANS has been specified are not translated. NOCPTRANS can only be specified for character and memo fields. This will create what appears in the Table Designer as Character (binary) and Memo (binary) data types.
    The following example creates a table named MYTABLE containing two character fields and two memo fields. The second character field CHAR2 and the second memo field MEMO2 include NOCPTRANS to prevent translation.

    CREATE TABLE mytable (char1 C(10), char2 C(10) NOCPTRANS,;
    memo1 M, memo2 M NOCPTRANS)
    PRIMARY KEY eExpression2 TAG TagName2
    Specifies a primary index to create. eExpression2 specifies any field or combination of fields in the table. TAG TagName2 specifies the name for the primary index tag that is created. Index tag names can contain up to 10 characters.
    Because a table can have only one primary index, you cannot include this clause if you have already created a primary index for a field. Visual FoxPro generates an error if you include more than one PRIMARY KEY clause in CREATE TABLE.

    UNIQUE eExpression3 TAG TagName3
    Creates a candidate index. eExpression3 specifies any field or combination of fields in the table. However, if you have created a primary index with one of the PRIMARY KEY options, you cannot include the field that was specified for the primary index. TAG TagName3 specifies a tag name for the candidate index tag that is created. Index tag names can contain up to 10 characters.
    A table can have multiple candidate indexes.

    FOREIGN KEY eExpression4 TAG TagName4 [NODUP]
    Creates a foreign (non-primary) index, and establishes a relationship to a parent table. eExpression4 specifies the foreign index key expression and TagName4 specifies the name of the foreign index key tag that is created. Index tag names can contain up to 10 characters. Include NODUP to create a candidate foreign index.
    You can create multiple foreign indexes for the table, but the foreign index expressions must specify different fields in the table.

    REFERENCES TableName3 [TAG TagName5]
    Specifies the parent table to which a persistent relationship is established. Include TAG TagName5 to establish a relation based on an index tag for the parent table. Index tag names can contain up to 10 characters. If you omit TAG TagName5, the relationship is established using the parent table's primary index key by default.
    CHECK eExpression2 [ERROR cMessageText2]
    Specifies the table validation rule. ERROR cMessageText2 specifies the error message Visual FoxPro displays when the table validation rule is executed. The message is displayed only when data is changed within a Browse window or Edit window.
    FROM ARRAY ArrayName
    Specifies the name of an existing array whose contents are the name, type, precision, and scale for each field in the table. The contents of the array can be defined with the AFIELDS( ) function.
    Remarks
    The new table is opened in the lowest available work area, and can be accessed by its alias. The new table is opened exclusively, regardless of the current setting of SET EXCLUSIVE.

    If a database is open and you don't include the FREE clause, the new table is added to the database. You cannot create a new table with the same name as a table in the database.

    If a database isn't open when you create the new table, including the NAME, CHECK, DEFAULT, FOREIGN KEY, PRIMARY KEY, or REFERENCES clauses generates an error.

    Note that the CREATE TABLE syntax uses commas to separate certain CREATE TABLE options. Also, the NULL, NOT NULL, CHECK, DEFAULT, PRIMARY KEY and UNIQUE clause must be placed within the parentheses containing the column definitions.
    Автор: Glory
    Прислал:

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

    Отсюда и похожие методы решения

    Вариант 1. «Классический».

    SELECT TOP 100 * FROM MyTable 
    WHERE id NOT IN (SELECT TOP 100 id FROM MyTable ORDER BY id) ORDER BY id

    Главный недостаток этого метода в в том, что т.к. TOP n записей выбираются уже из конечного результата запроса, то проверка условия WHERE будет выполняться для каждой строки главного запроса. При этом, время выполнения этой проверки будет расти вместе с номером выбираемой порции(страницы). Если, например, таблица содержит 100 записей и необходимо выбирать данные порциями по 10 записей, то
    для 2-ой порции нужно будет будет проверять подзапрос из 10 записей
    для 3-ей порции нужно будет будет проверять подзапрос из 20 записей
    для 4-ой порции нужно будет будет проверять подзапрос из 30 записей и т.д.

    Недостатком также является невозможность задать значение для TOP с помощью переменной. Что ведет к необходимости использования динамического запроса.

    Достоинство метода в его универсальности, академичности. Он не требует специфики T-SQL, этот метод можно применить практически на любом SQL-сервере.


    Вариант 2 «Эффективный, специфический для T-SQL».

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

    CREATE PROCEDURE dbo.get_this_page (@rec_per_page int, @page_num int) AS
    
    SELECT identity(int, 1,1) AS RowNum, MyId AS OrigId INTO #tmp FROM mytable
    SELECT b.* FROM #tmp AS a
    INNER JOIN mytable AS b on a.OrigId = b.MyId 
    WHERE a.RowNum BETWEEN (@rec_per_page * @page_num + 1) 
       AND (@rec_per_page * (@page_num + 1))
    
    DROP TABLE #tmp
    Примечания.
    - предложенный вариант процедуры будет блокировать некоторые системные таблицы базу tempdb на все время выполнения 1-го запроса. Если время блокировки становиться неприемлимым, то необходимо отделить создание временной таблицы от заполнеения ее данными вот запрос таким образом
    CREATE TABLE #temp(RowNum int identity, OrigId int)
    INSERT INTO #temp(OrigId) SELECT MyId FROM mytable

    - Если, поле MyId было создано признаком «IDENTITY», то это поле в запросе необходимо «завернуть» в функцию «CONVERT», иначе будет сообщение об ошибке.

    Вариант 3 (от Cat2). "Постраничная выборка, использующая временные таблицы".
    Будет корректно работать только если на полях, по которым производится выборка построены индексы

    /*
    Создаем тестовую таблицу. По полям i1 и d строим уникальные индексы
    */
    set nocount on
    create table list (
    id int identity(1,1) primary key,
    i1 int,
    i2 int,
    d datetime
    )
    CREATE  INDEX ind_i ON list(i1)
    CREATE  INDEX ind_d ON list(d)
    go
    
    set nocount on
    declare @i int
    declare @m int
    set @i=1
    while @i<=30
    begin
    	insert into list (i1) values (null)
    	set @i=@i+1
    end
    
    /*
    Заполняем тестовую таблицу. В поля i1 и i2 заносим числа от 1 до 30, а в d даты с 1 по 30 января 
    2000 года
    */
    
    select id into #t from list
    while exists (select * from list where i1 is Null)
    begin
    	set @i=rand()*30+1
    	set @m=(select max(id) from #t)
    	update list 
    	set i1=@m,
    	i2=@m,
    	d=convert(datetime,'20020101',112)+@m-1
    	where id=@i and i1 is null
    	if @@rowcount>0
    	delete from #t where id = @m
    end
    drop table #t 
    
    go
    
    --Выбираем "строки" с 11 по 20
    
    
    /*
    Даже если выборка идет по первичному ключу его лучше указать.
    В противном случае возможны всякие неожиданности
    */
    SELECT identity(int,1,1) AS RowNum, convert(int,id) as id  
    into #t FROM list with (index(0))
    
    select t.RowNum as [По id],t.id,l.i1,l.i2,l.d from #t t
    join list l on l.id=t.id
    where RowNum between 11 and 20
    order by RowNum
    drop table #t
    go
    
    /*
    Выборка по полю, по которому нет индекса
    Этот код не будет работать так, как надо, несмотря на order by i2
    Подробднее об этом написано здесь - http://support.microsoft.com/default.aspx?scid=kb;en-us;273586
    */
    SELECT identity(int,1,1) AS RowNum, i2, convert(int,id) as id 
    into #t FROM list
    order by i2
    
    select t.RowNum  as [По i2],l.id,l.i1,t.i2,l.d 
    from #t t
    join list l on l.id=t.id
    where RowNum between 11 and 20
    order by RowNum
    drop table #t
    go
    
    /*
    Выборка по полю, по которому индекс построен
    Этот код делает то, что надо
    */
    SELECT identity(int,1,1) AS RowNum, i1, convert(int,id) as id i
    nto #t FROM list with (index(ind_i))
    
    select t.RowNum  as [По i1],t.id,t.i1,l.i2,l.d 
    from #t t
    join list l on l.id=t.id
    where RowNum between 11 and 20
    order by RowNum
    
    drop table #t
    go
    
    /*
    Выборка по дате в порядке убывания
    */
    SELECT identity(int,1,1) AS RowNum, d, convert(int,id) as id 
    into #t FROM list with (index(ind_d))
    order by d desc
    
    select t.RowNum as [По d],t.id,l.i1,l.i2,t.d
    from #t t
    join list l on l.id=t.id
    where RowNum between 11 and 20
    
    drop table #t
    go
    drop table list

    Вариант 4 (от Глеба Уфимцева). Использование курсора.

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

    1. Обращение к новой странице выборки не должно приводить к перезапросу всей выборки.
    2. Запрос страницы должен возвращать обычный привычный рекордсет.
    3. Инициация не должна быть чрезмерно долгой, чтобы запрос к таблице в 1000000 строк не утомил ожиданием.
    4. В любой момент должно быть разрешено выбрать любую страницу из выборки, в том числе и ранее выбранную.
    5. Страница должна содержать любое задаваемое кол-во строк.
    6. Страница должна начинаться с любой задаваемой строки по номеру.

    Решение, удовлетворяющее всем этим пожеланиям стразу, было найдено. Вот оно:

    1. Имеем запрос, который мы хотим выбирать постранично

    select * from BigTable
    мы его не запускаем, а переходим к шагу 2.

    2. Инициализируем таким образом:

    declare @handle int,@rows int
    exec sp_cursoropen @handle OUT, 'select * from BigTable',1, 1, @rows OUT
    select @handle, @rows

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

    3. Получаем нужную страницу из выборки:

    exec sp_cursorfetch @handle,16,@rowid,@rowcount

    Где в @handle подставляем сохраненное значение хендла, в @rowid подставляется номер строки, с которой начинается интересующая нас страница, а в @rowcount подставляется кол-во строк на данной странице.

    Шаг 3 повторяем столько сколько нужно раз.

    4. Освобождаем ресурсы после того, как страницы уже больше не понадобятся

    exec sp_cursorclose @handle
    Отзывы и оценки
    Timon 20 сентября 2005, 06:14 Оценка: 5
    Вариант с курсором вне конкуренции
    chenosov 24 августа 2005, 11:04 Оценка: 5
    Прекрасный способ 4.
    Жаль недокументированы эти процедуры. И зачем нужно MS это прятать?
    Hubbitus 10 августа 2005, 15:28 Оценка: 5
    4 вариант понравился больше всего, видится мне он самым быстрым и универсальным, реализовал его в качестве процедуры...

    Но может кто-то подскажет, как можно "убрать" пустой рекордсет от вызова sp_cursoropen?? Буду очень благодарен.
    meclect 26 июля 2005, 19:34 Оценка: N/A
    Забыто по крайней мере еще два варианта :)
    1. Классический модифицированный
    SELECT TOP 100 * FROM tt WHERE id in (SELECT TOP 100 ID FROM tt WHERE ID not in (
    SELECT top 100000 id FROM tt)) ORDER BY id asc
    Добавляется еще один вложенный селект, проверено, работает в два раза быстрее классического

    2. Извращенный
    SELECT * FROM tt INNER join
    (SELECT TOP 10 id FROM
    (select top 100000 id from tt ORDER BY id ASC) A
    ORDER BY id DESC) B ON B.id = tt.id
    ORDER BY tt.id ASC
    Смысл в том, что выбираем первый топ, переворачиваем, выбираем что надо и опять переворачиваем. Вместо INNER JOIN можно использовать IN, но медленнее
    sp 08 июля 2005, 16:59 Оценка: 4
    A почему до сих пор никто не рассматривал такой вариант ?

    CREATE PROCEDURE dbo.GetList
    @PageSize int,
    @PageNo int,
    @PageCount int out
    AS
    SET NOCOUNT ON

    DECLARE @id bigint
    DECLARE @recordcount int
    DECLARE @FirstRec int

    IF (@PageNo = 0) SET @PageNo=1
    SELECT @recordcount=count(ID) FROM Table
    SELECT @FirstRec = (@PageNo-1) * @PageSize + 1

    if (@FirstRec<=@recordcount)
    begin
    SET ROWCOUNT @FirstRec
    select @id = ID FROM Table ORDER BY ID DESC

    /* Get Table list */
    SET ROWCOUNT @PageSize
    SELECT
    <поля>
    FROM
    Table
    WHERE
    <условие>
    ORDER BY ID DESC
    end
    select @PageCount = CEILING(@recordcount / cast(@PageSize as real))

    RETURN @@ERROR
    GO
    Sergey 28 марта 2005, 10:48 Оценка: 5
    Автор: fima
    Прислал: Glory

    Q.
    Как организовать запрос к БД, чтобы он возвращал не только данные, но и порядковые номера строк в результирующем наборе ?

    A.

    Способов решить эту задачу несколько.
    use pubs
    set nocount on
    if exists (select * from sysobjects where type = 'U' and  name = 'test')
    begin
    	drop table test
    end
    /* создание таблицы для примера */
    create table test
    (
    	id_test int identity not null,
    	string char (7),
    	constraint pk_test primary key (id_test)
    )
    /* установка флага для занесения с определенными ид. */
    set identity_insert test on
    /* занесение тестовых значений с произвольными ид. */
    insert into test (id_test, string)
    values (1,'string1')
    insert into test (id_test, string)
    values (4,'string2')
    insert into test (id_test, string)
    values (12,'string3')
    insert into test (id_test, string)
    values (17,'string4')
    insert into test (id_test, string)
    values (29,'string5')
    insert into test (id_test, string)
    values (31,'string6')
    insert into test (id_test, string)
    values (42,'string7')
    insert into test (id_test, string)
    values (45,'string8')
    insert into test (id_test, string)
    values (61,'string9')
    /* отмена установки флага для занесения с определенными ид. */
    set identity_insert test off
    go
    /* способ №1, создание проекции. */
    if exists (select * from sysobjects where type = 'V' and name = 'ranked_table')
    begin
    	drop view ranked_table
    end
    go
    create view ranked_table (rank, id_test, sting)
    as
    	select (
    			select count(*) 
    			from test as test_2
    			where test_2.id_test <= test_1.id_test
    		) as rank,
    		test_1.id_test,
    		test_1.string
    	from test as test_1
    go
    select * 
    from ranked_table
    order by rank
    go
    /* способ №2 стандартный SQL */
    select count (test_2.id_test) as rank, test_1.id_test, test_1.string
    from test as test_1 inner join test as test_2 on
    	test_1.id_test >= test_2.id_test
    group by test_1.id_test, test_1.string
    order by rank
    go
    /* способ №3 стандартный SQL */
    select test_3.rank, test_3.id_test, test_3.string
    from (select test_1.id_test,
    		test_1.string,
    		(select count(*) 
    			from test as test_2
    			where test_2.id_test <= test_1.id_test
    		) as rank
    	from test as test_1) as test_3
    order by rank
    go
    /* способ №4, временная таблица с полем identity */
    create table #rank_table
    (
    	rank int identity not null,
    	id_test int null,
    	string char (7),
    	constraint pk_test primary key (rank)
    )
    go
    insert into #rank_table (id_test, string)
    select id_test, string
    from test
    order by id_test
    select * from #rank_table
    go
    /* способ №5, переменная типа table с полем identity */
    declare @rank_table table
    (
    	rank int identity not null,
    	id_test int null,
    	string char (7)
    )
    insert into @rank_table (id_test, string)
    select id_test, string
    from test
    order by id_test
    select * from @rank_table
    go
    /* способ №6, курсор */
    declare @rank int,
    	@id_test int,
    	@string char (7)
    declare rank_cursor cursor
    for select id_test, string
    	from test
    	order by id_test
    open rank_cursor
    fetch next from rank_cursor into @id_test, @string
    set @rank = 1
    while (@@fetch_status <> -1)
    begin
    	select @rank, @id_test, @string
    	set @rank = @rank + 1
    	fetch next from rank_cursor into @id_test, @string
    end
    close rank_cursor
    deallocate rank_cursor
    /* результат всех примеров
    rank        id_test     string  
    ----------- ----------- ------- 
    1           1           string1
    2           4           string2
    3           12          string3
    4           17          string4
    5           29          string5
    6           31          string6
    7           42          string7
    8           45          string8
    9           61          string9
     */


    Соответственно нужно выбрать подходящий Вам.
    Обратите внимание что пример №3 входит состовляющей частью в пример №1. Поэтому на примере №2 тоже можно построить проекцию. На мой взгляд, проекция на примере №2 наиболее оптимальна, вместе с примером №5. Выбор из №2 и №5 зависит от количества данных и пр.

    Пример №5 будет работать только на SQL 2000. На SQL 2000 лучше использовать переменые типа table вместо временных таблиц, если это возможно.

    Пример №6 на мой взгляд, наиболее неудачный и я привел его для общей эрудиции. Вроде привел всё что вспомнил. Если кто знает еще способы пишите.
    Отзывы и оценки
    ЕвгенийВ 23 августа 2005, 13:24 Оценка: 4
    На таблице из 2000000 строк, запрос с курсорами работает быстрее и много запроса с view/count.
    Автор: Glory
    Прислал:

    Если get_xml - это процедура, в которой запускается XML запрос, то ее результаты можно вывести во внешний файл таким образом

    DECLARE @result int
    DECLARE @OutputFileName varchar(150)
    DECLARE @cmd varchar( 150)
    
    Set @OutputFileName = 'c:\mysql.xml'
    
    Set @cmd = 'BCP "EXEC get_xml" queryout "' + @OutputFileName + '" -C ACP -c -r -T'
    
    EXEC @result = master..xp_cmdshell @cmd
    (BCP.EXE - это утилита, поставляемая с SQL сервером.)

    Примечание
    Eсли в запросе выбираются данные с кирилическими символами, то вместо аргументов '-C ACP -c' нужно использовать '-w -C1251'.


    Т.к. в результатах XML запроса отсутсвует пара тэгов <root></root>, то для того, чтобы полученный файл можно было просматривать в браузере эти теги нужно добавить "вручную". Это можно сделать следующими способами

    1. С помощью дополнительной таблицы/переменной типа таблица.
    CREATE PROCEDURE dbo.get_xml
    as
    SET NOCOUNT ON
    declare @t table(fake_id varchar(10) )
    
    select fake_id, name from @t  root full outer join
         (select top 10 name, id from sysobjects) AS sysobjects ON 1=1 for xml auto, elements

    В этом примере вы получите XML вида
    <root><Element1><Attribute1>Value<Attribute1/><Attribute2>Value"<Attribute2/>....<Element1/></root>


    2. С помощью for xml explicit

    CREATE PROCEDURE dbo.get_xml
    as
    SET NOCOUNT ON
    
    select	1 as Tag,
    	NULL as Parent,
    	NULL as [root!1],
    	NULL as [sysobjects!2!name],
    	NULL as [sysobjects!2!id]
    
    UNION all
    
    SELECT TOP 10 2,
    	1,
    	NULL,
    	name as [sysobjects!2!name],
    	id as [sysobjects!2!id]
    from sysobjects
    for xml explicit
    В этом примере вы получите XML вида
    <root><Element1 Attribute1="Value" Attribute2="Value"..../>....</root>


    Кроме того записать результаты XML запроса в файл можно с помощью простого VB Scripta

    On Error Resume Next
    Public cnn, rst, strXML, strConn
    Set cnn	= CreateObject("ADODB.Connection")
    Set rst	= CreateObject("ADODB.Recordset")
    
    strConn = "PROVIDER=MSDASQL;DRIVER={SQL Server};" & _
    	"SERVER=<yourserver>;" & _
    	"DATABASE=<yourdb>;" & _
    	"UID=<yourlogin>;" & _
    	"PWD=<yourpasswd>;" & _
    	"Network=DBMSSOCN;" & _
    	"Address=<ip-address,port-number>;" & _
    	"UseProcForPrepare=0;" & _
    	"AutoTranslate=No"
    
    With cnn
            .ConnectionString = strConn
            .Open strConn
    End With
    
    If cnn.State = 1 Then
        Set rst = cnn.Execute("SELECT * FROM [MYTABLE] FOR XML RAW")
        If rst.State = 1 Then
    		strXML = ""
    		While Not rst.EOF
        		strXML = strXML & rst.Fields(0).Value
        		rst.MoveNext
        	Wend
    		rst.Close
    	End If
    	cnn.Close
    End If
    WScript.Echo strXML
    Отзывы и оценки
    R1244 11 августа 2005, 08:42 Оценка: 5
    Хорошая вещь, но как сделать вывод во внешний файл из триггера, там без оператора GO скрипт не срабатывает.
    GO --Без него пустой файл создается и все подвисает
    DECLARE @result int
    DECLARE @OutputFileName varchar(150)
    DECLARE @cmd varchar( 150)
    Set @OutputFileName = 'c:\mysql.xml'
    Set @cmd = 'BCP "EXEC get_xml" queryout "' + @OutputFileName + '" -C ACP -c -r -T'
    EXEC @result = master..xp_cmdshell @cmd
    Автор: Glory
    Прислал: Glory

    Каким образом на T-SQL организовать запись во внешний файл.

    1-ый варинт
    DECLARE @result int
    
    EXEC @result = master..xp_cmdshell 'osql -S MYSQLSERVER -E -Q "SELECT TOP 10 * FROM pubs.dbo.authors" -b -o c:\myoutput.txt', no_output
    IF (@result = 0)
       PRINT 'Success'
    ELSE
       PRINT 'Failure'


    2-ой варинт
    DECLARE @FileName varchar(255), 
    @Text1 varchar(8000),
    @FS int, 
    @OLEResult int, 
    @FileID int, 
    @hr int,
    @source varchar(30), 
    @desc varchar (200)
    
    EXECUTE @OLEResult = sp_OACreate 'Scripting.FileSystemObject', @FS OUTPUT
    
    IF @OLEResult <> 0 
    BEGIN
    PRINT 'Scripting.FileSystemObject'
    GOTO Error_Handler
    END
    
    execute @OLEResult = sp_OAMethod @FS,'CreateTextFile',@FileID OUTPUT, 'c:\xxx.txt'
    IF @OLEResult <> 0 
    BEGIN
    PRINT 'CreateTextFile'
    GOTO Error_Handler
    END
    
    set @Text1 = 'blah-blah-blah' + char(0)
    execute @OLEResult = sp_OAMethod @FileID, 'WriteLine', NULL, @Text1
    IF @OLEResult <> 0 
    BEGIN
    PRINT 'WriteLine'
    GOTO Error_Handler
    END
    
    Print @Text1
    goto Done
    
    Error_Handler: 
    PRINT '*** ERROR ***'
    EXEC @hr = sp_OAGetErrorInfo null, @source OUT, @desc OUT
    SELECT hr = CONVERT (binary(4), @hr), source = @source, description = @desc
    
    Done:
    EXECUTE @OLEResult = sp_OADestroy @FileID
    EXECUTE @OLEResult = sp_OADestroy @FS



    3-ий и IMHO самый правильный вариант - DTS Package

    Примечание
    2-ой варианте по неизвестным мне причинам может не работать (кажется только в MS SQL7 и/или на WInNT4).
    Возможно проблема решается установкой последнего последней версией WSH.
    Возможно нет.