Kodokon kodokon.com

Подзапросы: WHERE, FROM, IN и EXISTS

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

9 мин · 3 вопросов

Открыть этот урок в Kodokon

Подзапрос - это запрос в скобках внутри другого. Одной инструкцией он отвечает на вопросы вроде «заказы выше среднего» или «клиенты хотя бы с одним заказом». Запусти этот скрипт в sqliteonline.com или sqlite3, прежде чем начать.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  amount REAL NOT NULL
);

INSERT INTO customers (id, name) VALUES
  (1, 'Alice'), (2, 'Karim'),
  (3, 'Mina'), (4, 'Paulo');

INSERT INTO orders (id, customer_id, amount)
VALUES
  (1, 1, 49.90), (2, 1, 15.00),
  (3, 2, 120.50), (4, 2, 80.00),
  (5, 3, 9.90), (6, 1, 200.00);
Набор данных: четыре клиента, шесть заказов.

Помещённый в WHERE, скалярный подзапрос должен вернуть одно значение: одну строку, один столбец. Движок вычисляет его первым, а затем использует как обычную константу. Простым WHERE amount > AVG(amount) так сделать нельзя - в SQL это запрещено.

SQL
SELECT id, customer_id, amount
FROM orders
WHERE amount > (
  SELECT AVG(amount) FROM orders
);
Заказы выше среднего чека (79.22).

IN сравнивает столбец с множеством значений, которые вернул подзапрос. EXISTS проверяет, вернёт ли коррелированный подзапрос (он ссылается на c.id, столбец внешнего запроса) хотя бы одну строку: движок останавливается, как только найдено первое совпадение.

SQL
SELECT name FROM customers
WHERE id IN (
  SELECT customer_id FROM orders
  WHERE amount > 100
);

SELECT c.name FROM customers AS c
WHERE EXISTS (
  SELECT 1 FROM orders AS o
  WHERE o.customer_id = c.id
);
IN: крупные покупатели. EXISTS: активные клиенты.

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

SQL
SELECT ROUND(AVG(t.total), 2) AS avg_basket
FROM (
  SELECT customer_id, SUM(amount) AS total
  FROM orders
  GROUP BY customer_id
) AS t;
Средний общий чек на активного клиента: 158.43.

Проверка знаний

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

  1. Сколько значений должен вернуть подзапрос, использованный в amount > (...)?
    • Ровно одно
    • По одному на каждую строку внешней таблицы
    • Сколько угодно
  2. В чём главное преимущество EXISTS перед IN?
    • Он может вернуть несколько столбцов
    • Он останавливается на первой найденной строке, не материализуя всё множество
    • Он работает без подзапроса
  3. Что всегда должен нести подзапрос в FROM, чтобы быть переносимым везде?
    • Псевдоним, например AS t
    • Конструкцию ORDER BY
    • Внутреннюю точку с запятой