Коррелированные подзапросы, 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;