Определите единицу наблюдения
В таблице заказов одна строка может соответствовать заказу, а в таблице позиций одна строка описывает отдельный товар внутри него. Соединение по идентификатору заказа создаёт строку для каждой подходящей пары записей. Если у заказа три позиции, его реквизиты появятся в результате трижды. Это нормальное поведение соединения, а не ошибка базы. Проблема начинается, когда аналитик суммирует повторённую полную стоимость заказа как стоимость каждой позиции. Перед написанием запроса полезно записать словами: считаю заказы, товарные позиции или покупателей.
Проверьте арифметику на маленьком примере
Пусть первый заказ имеет сумму сто и две позиции, второй сумму двести и одну позицию. В таблице заказов общий итог равен трёмстам. После соединения с позициями суммы заказов будут сто, сто и двести. Их сумма составит четыреста. Добавление DISTINCT к суммированию не является универсальным исправлением: два разных заказа могут иметь одинаковую стоимость, и тогда удаление повторяющихся значений потеряет законную часть выручки. Исправлять нужно уровень расчёта: суммировать заказы до соединения либо заранее свести дочерние записи к одной строке на заказ.
Счётчик должен соответствовать вопросу
COUNT(*) считает строки результата. COUNT(поле) считает строки, где выбранное поле не равно NULL. После LEFT JOIN эти показатели могут различаться: заказ без подходящих позиций сохранится, а поля присоединённой таблицы получат NULL. Чтобы посчитать найденные позиции, обычно нужен их непустой идентификатор, а не все строки соединения. Для подсчёта заказов пригодится COUNT(DISTINCT order_id), если именно order_id однозначно определяет заказ в рассматриваемых данных. Но исправленный счётчик заказов ещё не доказывает правильность остальных агрегатов в том же запросе.
Проверяйте условия и промежуточные результаты
Фильтр WHERE применяется к строкам до группировки, а HAVING ограничивает уже сформированные группы. Кроме того, условие в WHERE на поле правой таблицы может убрать строки без совпадения после LEFT JOIN. Поэтому полезно отдельно посмотреть несколько заказов с позициями и без них. Базовая проверка уникальности идентификатора выглядит так: SELECT order_id, COUNT(*) FROM orders GROUP BY order_id HAVING COUNT(*) > 1;. Если запрос вернул строки, нужно выяснить, являются ли они дублями, версиями записи или признаком неправильно выбранного ключа. Удалять их автоматически нельзя.
Попробуйте на практике
Проверьте расчёт суммы заказов на маленьком вымышленном наборе данных, который можно разобрать вручную.
- Запишите два заказа с суммами сто и двести; первому назначьте две позиции, второму одну.
- Вручную выпишите результат соединения заказов с позициями по идентификатору заказа.
- Посчитайте число строк, число разных заказов и сумму повторившихся сумм заказов.
- Выберите способ сохранить одну строку на заказ для расчёта выручки и проверьте итог триста.
- Добавьте третий заказ без позиций и объясните, как изменятся INNER JOIN, LEFT JOIN и разные варианты COUNT.
Как проверить результат. Вы можете объяснить происхождение каждой строки и каждого слагаемого. Итог совпадает с ручным расчётом, а способ исправления работает и при одинаковой стоимости разных заказов.
Частые вопросы
Если число строк после JOIN выросло, запрос обязательно неверен?
Нет. При связи одного заказа с несколькими позициями рост ожидаем. Важно, соответствует ли полученная детализация рассчитываемой метрике.
Можно ли довериться LIMIT при проверке суммы?
Нет. Небольшая выборка помогает исследовать строки, но сама по себе не подтверждает итог по всем данным. Контрольные суммы и проверки ключей выполняют на нужном полном наборе.
Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.