Оператор SELECT

Оператор SELECT относится к подразделу языка манипулирования данными DML (Data Manipulation Language). Его можно назвать самым сложным и мощным оператором SQL. Он позволяет выбирать данные из одной или более таблиц на основании условий соединения, фильтровать данные, группировать их и вычислять агрегатные функции по группам, упорядочивать данные по различным критериям, вычислять и комбинировать данные.

Результатом выборки является выходной набор данных — множество строк одинаковой структуры, состав которых задан в списке выбора оператора SELECT.

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

SELECT [DISTINCT | ALL] <выходное поле> [, <выходное поле>]
    FROM [<соединяемые источники>]
    [WHERE <условие выборки>]
    [GROUP BY <условие группировки>
    [HAVING <условие выборки>]]
    [UNION [DISTINCT | ALL] <другой набор данных>]
    [ORDER BY <выражение для порядка выборки>]
    [OFFSET <n> {ROW | ROWS}] [FETCH {FIRST | NEXT} [<m>] {ROW | ROWS} ONLY]]

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

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

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

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

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

Предложение UNION дает возможность объединить в выходном наборе данных несколько таблиц с одинаковой структурой.

Предложение ORDER BY задает упорядоченность выходного набора данных. Здесь также можно указать количество строк, которое должно быть помещено в результирующий набор данных (OFFSET, FETCH).

Рассмотрим несколько простых примеров оператора SELECT.

SELECT * FROM COUNTRY;

Выбирает все записи и все столбцы из таблицы COUNTRY.

SELECT CURRENCY FROM COUNTRY WHERE COUNTRY='Russia';

Выбирает только поле CURRENCY (валюта) из таблицы стран COUNTRY, причем только те строки, название стран в которых удовлетворяют условию в предложении WHERE. В данном случае будет выбрана единственная строка и единственное поле, т.е. фактически одно значение.

SELECT COUNT(*) FROM CUSTOMER;

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

SELECT E.FULL_NAME FROM EMPLOYEE E ORDER BY E.FULL_NAME;

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

Список выбора

Список полей содержит одно или более выражений, разделённых запятыми. Результатом каждого выражения является значение соответствующего поля в наборе данных команды SELECT. Исключением является выражение * («звёздочка»), которое возвращает все поля отношения.

Синтаксис:

SELECT [ALL | DISTINCT] <поле>, <поле>, ... FROM T

, где <поле> может быть:

  • ТАБЛИЦА.ПОЛЕ
  • ПРОЦЕДУРА.ВЫХОДНОЕ_ПОЛЕ
  • NULL
  • NEXT VALUE FOR
  • Конструкция CASE
  • Выражение
  • Константа
  • Любое выражение, возвращающее единственное значение

Вместо ТАБЛИЦА можно указать псевдоним, представление.

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

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

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

В начало списка полей могут быть добавлены ключевые слова DISTINCT или ALL:

  • DISTINCT удаляет дубликаты строк: то есть, если две или более записи содержат одинаковые значения во всех соответствующих полях, только одна из этих строк будет включена в результирующий набор данных.
  • ALL включает все строки в результирующий набор данных. ALL включено по умолчанию и поэтому редко используется: явное указание поддерживается для совместимости со стандартом SQL.

Примеры списков выбора:

SELECT * FROM RDB$DATABASE;
SELECT CURRENT_USER FROM RDB$DATABASE;

Выбирает все поля из таблицы RDB$DATABASE. Особенность данной таблицы в том, что в ней существует всегда одна единственная запись. Часто ее используют чтобы просто вычислить значения какой-либо функции. При этом сама запись фактически не нужна, как показано во втором случае.

SELECT CUSTOMER.CUSTOMER, CUSTOMER.PHONE_NO, CITY FROM CUSTOMER;

Выбирает поля из таблицы CUSTOMER используя полное имя поля.

SELECT LAST_NAME, SALARY * 12 AS ANNUAL_SALARY FROM EMPLOYEE;

Запрос, в котором используется выражение в списке выбора. При этом для каждой строки результата столбец SALARY будет умножен на 12.

SELECT iif(CITY IS NULL, 'Муром', CITY) FROM CUSTOMER;

Тоже выражение, но с использованием встроенной функции iif, которая имеет три аргумента: логический и два значения. Если первый аргумент равен TRUE, то функция возвращает второй аргумент, в противном случае третий. Может быть использована любая функция, в том числе определенная пользователем.

SELECT UPPER(COUNTRY) FROM COUNTRY;

Пример приведения строкового поля к верхнему регистру. Это поле в результатирующем наборе данных будет иметь значения только в верхнем регистре.

Ограничения выборки

Позволяет получить части строк из упорядоченного набора.

Синтаксис:

SELECT ... FROM ...
    [ORDER BY <список сортировки>]
    [OFFSET <число строк> {ROW | ROWS}]
    [FETCH {FIRST | NEXT} [<число строк>] {ROW | ROWS} ONLY]

<число строк> - может быть выражением, возвращающим целое число, в том числе подзапросом.

Предложение OFFSET указывает, какое количество строк необходимо пропустить. Предложение FETCH указывает, какое количество строк необходимо получить.

Предложения OFFSET и FETCH могут применяться независимо уровня вложенности выражений запросов.

Рассмотрим несколько примеров.

Следующий запрос возвращает все строки кроме первых 10, упорядоченных по столбцу COL1:

SELECT * FROM T1 ORDER BY COL1 OFFSET 10 ROWS

В этом примере возвращается первые 10 строк, упорядоченных по столбцу COL1:

SELECT * FROM T1 ORDER BY COL1 FETCH FIRST 10 ROWS ONLY

Использование предложений OFFSET и FETCH в производной таблице, результат которой ограничивается ещё раз во внешнем запросе.

SELECT * FROM (
        SELECT * FROM T1
        ORDER BY COL1 DESC 
        OFFSET 1 ROW FETCH NEXT 10 ROWS ONLY
    ) a
ORDER BY a.COL1 FETCH FIRST ROW ONLY

Выражение FROM

Выражение FROM определяет источники, из которых будут отобраны данные. В простейшей форме, это может быть единственная таблица или представление. Однако источниками также могут быть хранимая процедура, производная таблица или общее табличное выражение (CTE). Различные виды источников могут комбинироваться с использованием разнообразных видов соединений (JOIN).

Синтаксис использования:

SELECT ... FROM <источник> [[AS] <псевдоним>] [<joins>] [...]

где <источник>:

  • таблица
  • представление
  • селективная хранимая процедура
  • производная таблица
  • общее табличное выражение

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

Если вы дадите таблице или представлению псевдоним, то вы должны везде использовать этот псевдоним, а не имя таблицы, при обращении к именам столбцов.

Например, следующие запросы корректны:

SELECT LAST_NAME FROM CUSTOMER;
SELECT CUSTOMER.LAST_NAME FROM CUSTOMER;
SELECT LAST_NAME FROM CUSTOMER C;
SELECT C.LAST_NAME FROM CUSTOMER C;

А этот нет, т.к. таблице назначен псевдоним, а в списке выбора поле указано с использованием имени таблицы:

SELECT CUSTOMER.LAST_NAME FROM CUSTOMER C;

Селективная хранимая процедура — это объект БД, записанный на языке PSQL и возвращающий записи с одним или несколькими столбцами. Выходные параметры селективной хранимой процедуры с точки зрения команды SELECT соответствуют полям обычной таблицы.

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

SELECT name, az, alt 
    FROM visible_stars('Brugge', current_date, '22:30')
    WHERE alt >= 20

Производная таблица — это корректная команда SELECT, заключённая в круглые скобки, опционально обозначенная псевдонимом таблицы и псевдонимами полей.

Синтаксис использования производной таблицы:

(запрос) [[AS] <псевдоним производной таблицы>] [(<псевдоним столбца 1>, ...)]

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

Пример, демонстрирующий использование псевдонима производной таблицы и списка псевдонимов столбцов (оба опциональные):

SELECT DBINFO.DESCR, DBINFO.DEF_CHARSET 
    FROM (SELECT * FROM RDB$DATABASE) 
        DBINFO (DESCR, REL_ID, SEC_CLASS, DEF_CHARSET)
  • Производные таблицы могут быть вложенными;

  • Производные таблицы могут быть объединениями и использоваться в объединениях. Они могут содержать агрегатные функции, подзапросы и соединения, и сами по себе могут быть использованы в агрегатных функциях, подзапросах и соединениях. Они также могут быть хранимыми процедурами или запросами из них. Они могут иметь предложения WHERE, ORDER BY и GROUP BY, указания FIRST, SKIP или ROWS и т.д.;

  • Каждый столбец в производной таблице должен иметь имя. Если этого нет по своей природе (например, потому что это — константа), то надо в обычном порядке присвоить псевдоним или добавить список псевдонимов столбцов в спецификации производной таблицы;

  • Список псевдонимов столбцов опциональный, но если он присутствует, то должен быть полным (т.е. он должен содержать псевдоним для каждого столбца производной таблицы);

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

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

Предположим, что у нас есть таблица COEFFS, которая содержит коэффициенты для ряда квадратных уравнений, которые мы собираемся решить. Она может быть определена примерно так:

CREATE TABLE coeffs (
    a DOUBLE PRECISION NOT NULL,
    b DOUBLE PRECISION NOT NULL,
    c DOUBLE PRECISION NOT NULL,
    CONSTRAINT chk_a_not_zero CHECK (a <> 0)
)

В зависимости от значений коэффициентов a, b и c, каждое уравнение может иметь ноль, одно или два решения. Мы можем найти эти решения с помощью одноуровневого запроса к таблице COEFFS, однако код такого запроса будет громоздким, а некоторые значения (такие, как дискриминанты) будут вычисляться несколько раз в каждой строке.

SELECT
    IIF ((B*B - 4*A*C) >= 0, (-B - SQRT(B*B - 4*A*C)) / (2*A), NULL) AS SOL_1,
    IIF ((B*B - 4*A*C) > 0, (-B + SQRT(B*B - 4*A*C)) / (2*A), NULL) AS SOL_2
FROM COEFFS

Если использовать производную таблицу, то запрос можно сделать гораздо более элегантным:

SELECT
    IIF (D >= 0, (-B - SQRT(D)) / DENOM, NULL) AS SOL_1,
    IIF (D > 0, (-B + SQRT(D)) / DENOM, NULL) AS SOL_2
FROM
    (SELECT B, B*B - 4*A*C, 2*A FROM COEFFS) (B, D, DENOM)

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

SELECT
    A, B, C,
    IIF (D >= 0, (-B - SQRT(D)) / DENOM, NULL) SOL_1,
    IIF (D > 0, (-B + SQRT(D)) / DENOM, NULL) SOL_2
FROM
    (SELECT A, B, C, B*B - 4*A*C AS D, 2*A AS DENOM FROM COEFFS)

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

Контрольные вопросы

  1. Как исключить дубликаты строк результата SELECT?
  2. Что может указываться в списке выбора?
  3. Каким образом можно ограничить выбираемые строки?
  4. Для чего применяются псевдонимы источник выборки?
  5. Что такое “производная таблица”?