Коррелированные подзапросы, EXISTS и NOT EXISTS

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

Матрица поддержки

SQL-шаблон Поддержка в YQL Что использовать
Некоррелированный подзапрос во FROM или IN Поддерживается Используйте подзапрос непосредственно
Некоррелированный EXISTS (SELECT ...) Поддерживается; возвращает true, если подзапрос содержит хотя бы одну строку Используйте непосредственно, если условие не зависит от строки внешнего запроса
Некоррелированный NOT EXISTS (SELECT ...) Поддерживается; возвращает true, если подзапрос пуст Используйте непосредственно, если условие не зависит от строки внешнего запроса
EXISTS с обращением к внешней колонке Не поддерживается LEFT SEMI JOIN или INNER JOIN с уникальными ключами справа
NOT EXISTS с обращением к внешней колонке Не поддерживается LEFT ONLY JOIN
Коррелированный скалярный подзапрос или подзапрос с агрегацией Не поддерживается Предварительно вычислите результат и используйте JOIN

EXISTS

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

SELECT client_id, name
FROM clients
WHERE EXISTS (
    SELECT 1
    FROM orders
);

Следующий распространённый SQL-запрос не поддерживается, поскольку внутренний запрос обращается к внешнему псевдониму c:

-- Не поддерживается в YQL.
SELECT c.client_id, c.name
FROM clients AS c
WHERE EXISTS (
    SELECT 1
    FROM orders AS o
    WHERE o.client_id = c.client_id
);

Чтобы вернуть клиента, только если существует хотя бы один подходящий заказ, используйте LEFT SEMI JOIN:

SELECT c.client_id, c.name
FROM clients AS c
LEFT SEMI JOIN orders AS o
ON o.client_id = c.client_id;

LEFT SEMI JOIN возвращает только колонки левой стороны. Несколько подходящих строк справа не дублируют строку слева, поэтому такое преобразование сохраняет семантику проверки существования.

В качестве альтернативы используйте INNER JOIN, предварительно удалив повторяющиеся ключи с правой стороны:

$order_clients = (
    SELECT client_id
    FROM orders
    GROUP BY client_id
);

SELECT c.client_id, c.name
FROM clients AS c
INNER JOIN $order_clients AS o
ON o.client_id = c.client_id;

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

Для одного неопционального ключа запрос можно также переписать через IN:

SELECT c.client_id, c.name
FROM clients AS c
WHERE c.client_id IN (
    SELECT o.client_id
    FROM orders AS o
);

NOT EXISTS

Некоррелированный NOT EXISTS возвращает результат, обратный EXISTS: true, если подзапрос пуст, и false, если подзапрос содержит хотя бы одну строку. Его результат также не зависит от строки внешнего запроса.

Чтобы вернуть клиента, только если подходящих заказов нет, используйте LEFT ONLY JOIN:

SELECT c.client_id, c.name
FROM clients AS c
LEFT ONLY JOIN orders AS o
ON o.client_id = c.client_id;

Коррелированные подзапросы с агрегацией

Предварительно вычислите агрегат и присоедините его к внешней таблице:

$last_order = (
    SELECT client_id, MAX(created_at) AS last_order_at
    FROM orders
    GROUP BY client_id
);

SELECT c.client_id, o.last_order_at
FROM clients AS c
LEFT JOIN $last_order AS o
ON o.client_id = c.client_id;

См. также