Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
УИС Лабораторные работы.doc
Скачиваний:
8
Добавлен:
06.12.2018
Размер:
377.86 Кб
Скачать

Лабораторная работа № 4 Создание запросов с помощью sql

Цель работы: Освоить приемы создания запросов к БД с помощью структурированного языка запросов SQL в Microsoft Access.

Сведения из теории

Простейший запрос в SQL имеет следующую структуру:

SELECT * FROM S;

SELECT Фамилия, Город_П FROM S;

где SELECT – оператор выбора;

* – специальный символ, обозначающий все данные;

FROM ключевое слово («из»);

Sназвание таблицы.

Фамилия, Город_П – выводимая информация (имена столбцов).

Оператор SELECT часто выглядит значительно сложнее за счет применения дополнительных операторов.

Оператор DISTINCT позволяет не выбирать из таблиц повторяющиеся группы записей. Аргумент DISTINCT это «отличие».

SELECT DISTINCT П# FROM SP;

Оператор ALL имеет противоположный эффект, т.е. дублирование строк вывода сохранится. ALL – это пояснительный, а не действующий аргумент.

SELECT ALL П# FROM SP;

Оператор WHERE накладывает условие на выбор объектов. Команда SELECT выбирает только те строки, для которых условие выбора имеет значение True (истина).

SELECT * FROM S WHERE Город_П = ‘Киев’;

Аналогично осуществляется выборка и из связанных таблиц. Например, выведем Фамилии поставщиков, города изготовления детали и количество в поставках детали Р4:

SELECT S.Фамилия, P.Город_Д, SP.Количество

FROM S, P, SP

WHERE ((S.П# = SP.П#) AND (P.Д# = SP.Д#) AND (P.Д# = P4));

Кроме равенства можно использовать и другие операторы сравнения:

  • > – больше;

  • < – меньше;

  • >= – больше или равно;

  • <= – меньше или равно;

  • <> – не равно.

Оператор NOT. Это оператор логического отрицания. Часто он бывает очень эффективен при формировании условия выбора. Например, надо выбрать поставщиков, проживающих не в Киеве, или имеющих статус, отличный от 20:

SELECT Фамилия, Город_П FROM S

WHERE NOT ((Город_П = ‘Киев’) AND (Статус = 20));

Оператор IN определяет набор (массив) значений, с элементами которого будет сравниваться текущее поле записи:

SELECT Фамилия, Город_П FROM S

WHERE Город_П IN (‘Киев’, ‘Минск’);

Оператор BETWEEN позволяет указать, что значение должно находиться в указанном диапазоне значений:

SELECT Название, Вес FROM P

WHERE Bec BETWEEN 10 AND 15;

SELECT Фамилия, Статус FROM S

WHERE Фамилия BETWEEN ‘А’ AND ‘И’ ;

Следует отметить, что в выборку попадут только Борисов и Андреев, а фамилия ‘Иванов’ находится уже за пределами указанного диапазона.

Оператор LIKE работает с полями типа CHAR или VARCHAR, т.е. строковыми постоянной или переменной длины. Он позволяет с помощью специальных символов задавать шаблоны (маски) похожих записей. Знак подчеркивания “_” в шаблоне замещает любой одиночный символ, а знак процента % – любую последовательность символов. Например, маске ‘b_t’ удовлетворяют записи ‘bit’, ‘bat’, а маске ‘%ров%’ – записи ‘Макаров’, ’Коровников’, ‘Костров’.

Если надо найти строку, содержащую символ шаблона (процент или знак подчеркивания), следует определить любой одиночный символ как Escape-символ и использовать его в шаблоне оператора LIKE непосредственно перед символом ‘%’ или ‘_’. Это будет означать, что следующий за Escape-символом символ обычный, а не служебный. Если Escape-символ сам должен входить в шаблон как обычный символ, то его указывают дважды. Можно в этом случае в качестве Escape-символа выбрать другой символ. Рассмотрим примеры:

SELECT * FROM TStudent

WHERE Адрес LIKE ‘%@_%’ESCAPE‘@’;

Здесь символ @ определен как Escape-символ. В маске символ @ указывает на то, что следующий за ним символ подчеркивания должен содержаться в строке адреса.

SELECT Название FROM TFirma

WHERE Название LIKE ‘%@_@@%’ESCAPE‘@’;

Эта запись в маске означает, что в названии фирмы должна присутствовать комбинация символов ‘_@’.

Оператор IS NULL. Если в какое-либо поле в таблице значение не вводилось, то оно имеет NULL-значение. Это не 0 и не пробел, а просто отметка, которую программа БД приписывает данному полю. NULL-значение может присваиваться любому полю БД, независимо от типа данных. Оно означает неизвестное значение. При сравнении с NULL-значением любого другого значения результат неизвестен. При неизвестном результате сравнения строка, содержащая NULL-значение, в выборку не включается. Использование перед результатом сравнения оператора NOT ничего не меняет, результат не становится истиной, т.к. до его отрицания он и не был ложью. Часто требуется отличать строки, не удовлетворяющие условию, от строк, содержащих NULL-значения. Для этой цели используется оператор IS, который используется совместно с ключевым словом NULL.

Для нахождения всех записей в таблице TStudent, в которых не введена прописка, следует сделать запрос:

SELECT ФИО FROM TStudent

WHERE Propiska IS NULL;

Для нахождения всех записей в таблице TStudent, в которых прописка введена, следует сделать запрос:

SELECT ФИО FROM TStudent

WHERE Propiska IS NOT NULL;

или

SELECT ФИО FROM TStudent

WHERE NOT Propiska IS NULL;

Агрегатные функции (функции агрегирования).

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

  • COUNT – подсчет количества записей (не NULL значений полей);

  • SUMподсчет арифметической суммы всех значений поля;

  • AVG – усреднение всех выбранных значений данного поля;

  • MAX – нахождение наибольшего из всех выбранных значений;

  • MIN – нахождение наименьшего из всех выбранных значений.

Агрегатные функции используются подобно именам полей в предложении SELECT запроса, но с одним исключением, они берут имена полей как аргументы. С функциями SUM и AVG могут использоваться только числовые поля. С функциями COUNT, MAX и MIN могут использоваться как числовые, так и строковые поля.

Предположим, что у нас имеется БД, содержащая информацию о работе токарей:

TWork

ID

Фамилия

Объем

День

Цена

Брак

1

1

3

1

1000

1

2

2

10

1

800

12

3

3

6

1

1000

2

4

4

4

1

500

2

5

1

2

2

500

3

6

3

3

2

500

0

7

1

5

3

500

0

8

4

4

3

500

0

9

1

5

3

1000

2

10

3

4

2

500

1

TFIO TDay

ID

Фамилия

1

Иванов

2

Петров

3

Сидоров

4

Гришин

ID

День

1

понедельник

2

среда

3

пятница


Рассмотрим действия агрегатных функций на этих примерах:

SELECT SUM (Объем) FROM TWork; (Результат 46)

SELECT AVG (Объем) FROM TWork; (Результат 4,6)

SELECT MAX (TWork.Объем) FROM TWork, TDay

WHERE (TWork.День=TDay.ID) AND (TDay.День LIKE ’п%’); (Результат 10)

SELECT MIN (TWork.Объем) FROM TWork, TFIO

WHERE (TWork.Фамилия=TFIO.ID) AND

(TFIO.Фамилия = ‘Иванов’); (Результат 2)

SELECT COUNT (TWork.ID) FROM TWork, TFIO

WHERE (TWork.Фамилия=TFIO.ID) AND

(TFIO.Фамилия LIKE ’%ов’); (Результат 8)

Если параметром COUNT является звездочка («любая запись»), то в результате подсчитывается общее количество строк. При этом будут включены как пустые записи (NULL), так и дубликаты других записей, поэтому часто результат превышает COUNT для отдельного поля:

SELECT COUNT(*) FROM TWork, TFIO

WHERE (TWork.Фамилия=TFIO.ID) AND

(TFIO.Фамилия LIKE ‘%ов’); (Результат 8)

Наложение требования DISTINCT позволяет сузить расчетный диапазон, увеличить надежность расчетов, т.к. в реальных БД могут присутствовать дубликаты записей.

SELECT COUNT(DISTINCT День) FROM TWork; (Результат 3)

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

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

SELECT SUM(TWork.Объем*TWork.Цена) FROM TWork, TDay

WHERE (TWork.День=TDay.ID) AND (TDay.День=’среда’);

(Результат 33000)

Найдем наибольшую долю брака при работе токаря Сидорова.

STLECT MAX(TWork.Брак/( TWork.Брак+ TWork.Объем))

FROM TWork, TFIO WHERE

(TWork.Фамилия=TFIO.ID) AND (TFIO.Фамилия=‘Сидоров’);

(Результат 0,33)

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

В простейших случаях группой является вся таблица, среди всех записей которой производится действие агрегирования:

SELECT MAX(Брак) FROM TWork;

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

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

Упорядочивание данных ORDER BY.

По определению реляционной БД, таблицы – это неупорядоченные наборы данных. По этой причине последовательность записей в результате выполнения запроса заранее неизвестна (произвольна). Для получения определенной последовательности записей или просто значений поля необходимо использовать директиву ORDER BY. При этом данные упорядочиваются либо по возрастанию (ASC), либо по убыванию (DESC).

SELECT * FROM TFIO ORDER BY Фамилия ASC;

SELECT * FROM TWork ORDER BY ID DESC;

Упорядочивание данных может быть и вложенным. Например, получим таблицу о величине брака (Фамилия – День - Брак)

SELECT TFIO.Фамилия, TDay.День, TWork.Брак

FROM TWork, TFIO, TDay

WHERE (TWork.Фамилия=TFIO.ID) AND (TWork.День=TDay.ID)

ORDER BY TFIO.Фамилия ASC, TDay.День DESC, TWork.Брак ASC;

ORDER BY можно использовать сразу с любым количеством столбцов, но эти столбцы в обязательном порядке должны быть указаны в наборе столбцов правее ключевого слова SELECT. Например, нельзя использовать такую команду:

SELECT Фамилия FROM TFIO ORDER BY ID ASC;

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

Группировка данных GROUP BY.

Порой требуется не просто упорядочить записи, но и произвести их группировку, в частности, для использования агрегатных функций для всех выделенных групп записей. Директива GROUP BY позволяет определять подмножества значений, для которых применяется агрегатная функция. Например, найдем максимальное количество деталей, произведенных каждым токарем за весь диапазон времени:

SELECT TFIO.Фамилия, MAX(TWork.Объем)

FROM TWork, TFIO

WHERE (TWork.Фамилия=TFIO.ID)

GROUP BY TFIO.Фамилия;

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

SELECT TFIO.Фамилия, TDay. День, AVG(TWork.Брак)

FROM YWork, TFIO, TDay

WHERE (TFIO.ID=TWork.Фамилия) AND (TDay.ID=TWork.День)

GROUP BY TFIO.Фамилия, TDay.День

Директива HAVING.

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

SELECT TFIO.Фамилия, AVG(TWork.Брак)

FROM TWork, TFIO

WHERE (TFIO.ID=TWork.Фамилия) AND (AVG(TWork.Брак)>5)

GROUP BY TFIO.Фамилия;

Дело в том, что проверка выполнения условия выполняется для одиночной записи, а функция AVG предполагает обработку группы записей. Такая задача решается с помощью директивы HAVING:

SELECT TFIO.Фамилия, AVG(TWork.Брак)

FROM TWork, TFIO

WHERE (TFIO.ID=TWork.Фамилия)

GROUP BY TFIO.Фамилия

HAVING AVG(TWork.Брак)>5;

Аргументы директивы HAVING подчиняются тем же правилам, что и аргументы директивы WHERE. Единственное принципиальное отличие состоит в том, что в директиве HAVING нельзя обрабатывать данные, полученные не в результате агрегирования, т.е. те данные, которые внутри группы записей могут иметь не одно единственное значение. По этой причине следующая команда не будет выполнена:

SELECT TFIO.Фамилия, MAX(TWork.Брак)

FROM TWork, TFIO

WHERE (TFIO.ID=TWork.Фамилия)

GROUP BY TFIO.Фамилия

HAVING (TWork.Объем=4);

В этом примере группа определяется только фамилией токаря, поэтому в отдельных записях группы значение поля TWork.Объем может иметь несколько различных значений. Однако поле, определяющее группу, может использоваться в директиве HAVING, т.к. внутри группы записей нет неоднозначности значения этого поля. Например, получим минимальное количество отбракованных деталей, получившееся в течение рабочих смен токарей Иванова и Сидорова:

SELECT TFIO.Фамилия, MIN(TWork.Брак)

FROM TWork, TFIO

WHERE (TFIO.ID=TWork.Фамилия)

GROUP BY TFIO.Фамилия

HAVING TFIO.Фамилия IN (‘Иванов’, ‘Сидоров’);

Стандартом ANSI SQL запрещается вложенное агрегирование, т.е. когда в качестве аргумента агрегатной функции выступает другая агрегатная функция:

SELECT TDay.День, MAX(SUM(TWork.Брак))

FRIOM TWork, TDay

WHERE (TDay.ID=TWork.День)

GROUP BY TDay.День;

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

Форматирование запроса.

Большинство основанных на SQL баз данных предоставляют специальные возможности, позволяющие форматировать вывод запросов. Всего имеется две особенности, оговоренных стандартом SQL, которые позволяют производить форматирование данных.

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

SELECT TDay.День, TWork.Брак/2

FROM TDay, TWork

WHERE (TDay.ID=TWork.День);

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

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

SELECT TDay.День, ‘Бракованных деталей = ’, TWork.Брак/2

FROM TDay, TWork

WHERE (TDay.ID=TWork.День);

Действия по форматированию данных внутри запроса используются только в весьма устаревших СУБД. Во всех современных СУБД запрос не является частью интерфейса программы – он лишь средство их получения.

Работа со строками.

Современный стандарт ASNI SQL декларирует возможность использования набора функций для форматирования строковых или символьных данных. В качестве параметров этих функций могут выступать строковые поля или строковые константы. Рассмотрим лишь некоторые из них

Конкатенация.

Сцепление строк выполняется в соответствии с особенностями синтаксиса SQL.

SELECT ФИО + ‘ ’ + Город FROM TЖитель;

SELECT ФИО || ‘ ’ || Город FROM TЖитель;

Замена комбинаций символов.

Заменим буквосочетание ‘перьм’ на ‘Пермь’ в столбце Адрес нашей таблицы

SELECT Адрес, REPLACE (Адрес, ‘перьм’, ‘Пермь’)

FROM TЖитель;

Выделение подстроки в строке.

Выделим первые три символа ФИО таблицы TЖитель.

SELECT ФИО + ‘-’ + SUBSTRING(ФИО FROM 1 FOR 3) FROM TЖитель;

SELECT ФИО || ‘-’|| SUBSTR(ФИО, 1, 3) FROM TЖитель;

Иванов – Ива

Замещение значения NULL.

Для замены NULL-значений используется функция NVL.

SELECT NVL(ФИО, ‘Забыли внести!’) FROM TЖитель;

Работа с числами.

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

ABS(a) – модуль числа;

ROUND(a) – округление;

SQRT(a) – квадратный корень;

SIGN(a) – знак числа;

POVER(a,b) – возведение в степень ();

EXP(a) – экспонента;

SIN(a), COS(a), TAN(a) – тригонометрические функции.

Работа с датами.

В каждой реализации SQL имеется свой формат даты: 12/15/05; #15.12.05#, “Dec 15, 2005”, “15-DEC-05” и др. Однако имеется единый набор функций для работы с этой информацией.

Значение текущей даты можно узнать как GETDATE() или SYSDATE.

К датам можно добавлять дни, месяцы и годы.

DATEADD(DAY или MONTH или YEAR, сколько, ИмяСтолбца);

Функции преобразования.

Т.к. математические функции работают только с числами, следует уметь преобразовывать символьные данные в числовые. Классический SQL таких возможностей не имеет, но в диалектах языка могут встретиться функции CSTR(n), VAL(s), STR(n), CDATE(s). Подробности о работе этих функций можно узнать только в документации.

Маски полей.

При разработке больших запросов или в случаях дублирования имен полей в разных таблицах очень удобно пользоваться масками (“псевдонимами”) столбцов. В дальнейшем программа, использующая результат этого запроса, будет обращаться к набору данных по FIO и Age, а не по именам полей.

SELECT Фамилия FIO, ГодРожд Age FROM TЖитель;

Маски таблиц.

Стандарт ANSI SQL поддерживает возможность использования масок таблиц, которые указываются в строке FROM за именами физических таблиц. Эти маски используются вместо имен таблиц в предложениях SELECT и WHERE.

SELECT TF.Фамилия, TD.День, AVG(TW.Брак)

FROM TWork TW, TFIO TF, TDay TD

WHERE (TF.ID=TW.Фамилия) AND (TD.ID=TW.День)

GROUP BY TFIO.Фамилия, TW.День;

Иногда маски таблиц в литературе называют псевдонимами. Это одно и то же. Псевдонимы таблиц действуют только во время выполнения данной команды. После завершения оператора SELECT обращение к таблицам TW, TD и TF будет признано ошибочным. Механизм псевдонимов работает и с одной таблицей. Например, на основании ненормализованной таблицы TWork (т.е. таблицы, из которой фамилии и дни не вынесены в дочерние таблицы) получим все пары токарей, которые работали в один день, и объем работы первого токаря был больше объема работы второго:

SELECT T1.Фамилия, T2.Фамилия, ‘работали в’, T1.День

FROM TWork T1, TWork T2

WHERE (T1.День=T2.День) AND (T1.Объем>T2.Объем);

Использование подзапросов.

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

SELECT ‘Всего сделано = ’, SUM(Объем)

FROM TWork

WHERE

(День = (SELECT ID

FROM TDay

WHERE День LIKE ‘%д%’)) AND

(Фамилия = (SELECT ID

FROM TFIO

WHERE Фамилия=’Иванов’));

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

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

SELECT *

FROM TWork

WHERE (Объем>

(SELECT AVG(Объем)

FROM TWork

WHERE День=‘пятница’));

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

SELECT *

FROM TWork

WHERE (День IN

(SELECT День

FROM TWork

WHERE День=‘%к’));

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

SELECT Фамилия, День, Объем

FROM TWork T1

WHERE (Объем >

(SELECT AVG(Объем)

FROM TWork T2

WHERE (T2.Фамилия=T1.Фамилия)));

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

Специальные возможности запросов.

Некоторые директивы запроса могут обращаться к полям набора данных не по их наименованиям, а по порядковому номеру. В случае разработки больших запросов это может оказаться полезным. Пусть имеется таблица TРабота:

TРабота

Фамилия

Сделал

День

Иванов

Петров

Сидоров

Ломакин

Петров

Петров

10

3

4

11

3

6

Понедельник

Понедельник

Понедельник

Вторник

Вторник

Среда

Запрос

SELECT Фамилия, Сделал, День FROM TРабота

ORDER BY 1,3;

выдаст нам следующую таблицу:

Фамилия

Сделал

День

Иванов

Ломакин

Петров

Петров

Петров

Сидоров

10

11

3

3

6

4

Понедельник

Вторник

Вторник

Понедельник

Среда

Понедельник

Однако следующий запрос будет некорректен:

SELECT * FROM TРабота ORDER BY 2;

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

SELECT Фамилия, AVG(Сделал) FROM TРабота

GROUP BY 1;

Получим таблицу:

Фамилия

Иванов

Ломакин

Петров

Сидоров

10

11

4

4

А вот этот запрос будет некорректным. Почему?

SELECT * FROM T_Работа

GROUP BY 1,3;

(Не указаны явно имена полей вывода.)

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

T_Города

ID

Город

1

2

3

4

Москва

Архангельск

Сочи

Томск

Т_Рейсы

ID

ID_Город

Время

1

2

3

4

5

6

1

1

3

1

4

1

11-00

15-00

14-00

19-00

11-40

21-00

Такую проблему можно решить с помощью агрегатной функции COUNT:

SELECT DISTINCT Город

FROM T_Города Т1

WHERE (( SELECT COUNT(T_Рейсы.ID_Город)

FROM T_Рейсы

WHERE (T1.ID=T_Рейсы.ID_Город) = 0);

Но такой запрос – не лучший вариант. Для этих целей лучше подходит оператор EXISTS. Этот оператор выдает значение “Trueили “False”, т.е. булево выражение. Используя в качестве аргумента подзапрос, этот оператор оценивает его как истинный, если он генерирует выходные данные, а в противном случае как ложный.

SELECT Город

FROM T_Город ТГ

WHERE NOT EXISTS (SELECT *

FROM T_Рейсы

WHERE TГ.ID = T_Рейсы.ID_Город);

В отдельных реализациях SQL имеется функция ANY, которая тоже в качестве аргумента имеет подзапрос. Она возвращает какое-либо значение из набора данных, сформированного подзапросом. По своему применению эта функция близка к EXISTS. Например, в результате этого запроса будет выведена таблица, в которую войдут данные обо всех врачах, зарплата которых больше зарплаты любого из учителей:

SELECT *

FROM TВрач

WHERE (Зарплата>ANY(SELECT Зарплата

FROM TУчитель));