← Назад к вопросам

Как сделать выборку из двух таблиц помимо JOIN?

2.0 Middle🔥 152 комментариев
#Базы данных и SQL

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы выборки из двух таблиц без использования JOIN

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

1. Подзапросы (Subqueries)

Подзапросы позволяют встраивать один запрос в другой, используя результаты внутреннего запроса во внешнем.

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

SELECT 
    u.id,
    u.name,
    (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u
WHERE u.active = 1;

Здесь подзапрос выполняется для каждой строки основной таблицы, используя значение u.id.

Подзапросы в WHERE

SELECT *
FROM products p
WHERE p.category_id IN (
    SELECT id FROM categories c WHERE c.active = 1
);

Подзапросы в FROM (производные таблицы)

SELECT 
    u.name,
    stats.total_amount
FROM users u
CROSS JOIN (
    SELECT 
        user_id,
        SUM(amount) as total_amount 
    FROM orders 
    GROUP BY user_id
) stats
WHERE u.id = stats.user_id;

2. UNION и UNION ALL

Эти операторы объединяют результаты двух или более SELECT-запросов по вертикали (столбцы должны совпадать по типам).

-- UNION удаляет дубликаты
SELECT id, name, 'user' as type FROM users
UNION
SELECT id, title as name, 'product' as type FROM products;

-- UNION ALL сохраняет все строки (включая дубликаты)
SELECT id, name FROM active_users
UNION ALL
SELECT id, name FROM archived_users;

Важное отличие: UNION выполняет сортировку для удаления дубликатов, что может быть медленнее, чем UNION ALL.

3. Кросс-соединение (CROSS JOIN) с фильтрацией

Хотя технически это JOIN, подход отличается от обычных INNER/LEFT JOIN:

SELECT 
    u.*,
    p.*
FROM users u, products p
WHERE u.id = p.created_by_id 
AND p.price > 1000;

Это устаревший синтаксис, но он до сих пор поддерживается. Современная запись - FROM users u CROSS JOIN products p WHERE ....

4. EXISTS и NOT EXISTS

Эти операторы проверяют существование строк в подзапросе:

-- Пользователи с заказами
SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.user_id = u.id 
    AND o.status = 'completed'
);

-- Пользователи без заказов
SELECT *
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

5. Применение в PHP с отдельными запросами

Иногда эффективнее выполнить два отдельных запроса и объединить результаты на уровне приложения:

<?php
// Первый запрос - получаем пользователей
$users = $pdo->query("SELECT id, name FROM users LIMIT 100")->fetchAll(PDO::FETCH_KEY_PAIR);

// Второй запрос - получаем заказы для этих пользователей
$userIds = array_keys($users);
$placeholders = implode(',', array_fill(0, count($userIds), '?'));

$orders = $pdo->prepare("SELECT user_id, COUNT(*) as order_count 
                         FROM orders 
                         WHERE user_id IN ($placeholders) 
                         GROUP BY user_id");
$orders->execute($userIds);
$orderData = $orders->fetchAll(PDO::FETCH_KEY_PAIR);

// Объединяем данные в PHP
foreach ($users as $userId => $userName) {
    $users[$userId] = [
        'name' => $userName,
        'order_count' => $orderData[$userId] ?? 0
    ];
}
?>

6. Временные таблицы и CTE (Common Table Expressions)

-- Использование CTE (доступно в MySQL 8.0+, PostgreSQL, SQL Server)
WITH user_orders AS (
    SELECT user_id, COUNT(*) as total_orders
    FROM orders
    GROUP BY user_id
)
SELECT 
    u.*,
    COALESCE(uo.total_orders, 0) as order_count
FROM users u
LEFT JOIN user_orders uo ON u.id = uo.user_id;

-- Временная таблица (более универсальный способ)
CREATE TEMPORARY TABLE temp_user_stats AS
SELECT user_id, SUM(amount) as total_spent
FROM orders
GROUP BY user_id;

SELECT u.*, tus.total_spent
FROM users u
LEFT JOIN temp_user_stats tus ON u.id = tus.user_id;

Критерии выбора метода

  1. Производительность:

    • EXISTS часто быстрее IN для больших наборов данных
    • Отдельные запросы + обработка в PHP могут быть эффективны при правильном кэшировании
  2. Читаемость:

    • Подзапросы в SELECT ухудшают читаемость
    • CTE делают сложные запросы более структурированными
  3. Гибкость:

    • Отдельные запросы дают больше контроля на уровне приложения
    • UNION позволяет работать с разнородными данными
  4. Совместимость:

    • CTE не поддерживаются в старых версиях MySQL
    • Временные таблицы работают почти везде

Рекомендация: Для сложных бизнес-логик часто оптимальным является комбинированный подход - часть объединения на уровне БД, часть на уровне приложения, с учетом специфики данных и требований к производительности.

Как сделать выборку из двух таблиц помимо JOIN? | PrepBro