Модераторы: skyboy
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Суммируется не то, что надо, DISTINCT, SUM, JOIN, GROUP 
V
    Опции темы
JEEN
Дата 18.3.2016, 01:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 8.3.2008

Репутация: 1
Всего: 1



Здравствуйте. Имеется запрос, который должен выводить счета, попутно считая общую сумму по всем наименованиям и оплаченную сумму.

Например, имеем 2 счета:
1) Счет №1 (2 наименования на общую сумму 2000 руб.), был 1 платеж на всю сумму.
2) Счет №2 (1 наименование на сумму 500 руб.), по ошибке было 2 платежа по 500 руб.

Должно вывести так:
Счет №1 | 2 услуги | 1 платеж | 2000 руб. (сумма счета) | 2000 руб. (оплачено)
Счет №2 | 1 услуга | 2 платежа | 500 руб.   (сумма счета) | 1000 руб. (оплачено)

А сейчас выводит так:
Счет №1 | 2 услуги | 1 платеж | 2000 руб. (сумма счета) | 4000 руб. (оплачено)
Счет №2 | 1 услуга | 2 платежа | 500 руб.  (сумма счета)  | 1000 руб. (оплачено)

Ошибка в том, что он почему то количество услуг (наименований) умножил на сумму счета.

Запрос:
Код

SELECT i.num, 
SUM(p.sum) as payments_sum,  
SUM(DISTINCT d.cost * d.amount) as cost_sum
FROM invoices i
LEFT JOIN payments p ON p.invoice_id = i.id 
LEFT JOIN details d ON d.invoice_id = i.id 
GROUP BY i.id


Либо так:
Счет №1 | 2 услуги | 1 платеж | 2000 руб. (сумма счета) | 2000 руб. (оплачено)
Счет №2 | 1 услуга | 2 платежа | 500 руб.   (сумма счета) | 500 руб. (оплачено)

Если добавить еще один DISTINCT, то теперь он не учитывает 2 платежа по счету.

Запрос:
Код

SELECT i.num, 
SUM(DISTINCT p.sum) as payments_sum,  
SUM(DISTINCT d.cost * d.amount) as cost_sum
FROM invoices i
LEFT JOIN payments p ON p.invoice_id = i.id 
LEFT JOIN details d ON d.invoice_id = i.id 
GROUP BY i.id


Упрощенная структура базы данных выглядит так:
user posted image

Это сообщение отредактировал(а) JEEN - 18.3.2016, 01:09
PM MAIL   Вверх
JEEN
Дата 18.3.2016, 01:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 8.3.2008

Репутация: 1
Всего: 1



Попробовал с подзапросами сделать, результат на первый взгляд верный, но может быть возможно сделать это в один запрос?

Код

SELECT 
i.num, 
(select Sum(d.cost * d.amount) from details d WHERE d.invoice_id = i.id) cost_sum, 
(select Sum(p.sum) from payments p WHERE p.invoice_id = i.id) payments_sum
FROM invoices i


Это сообщение отредактировал(а) JEEN - 18.3.2016, 01:43
PM MAIL   Вверх
Akina
Дата 18.3.2016, 09:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 453



Цитата(JEEN @  18.3.2016,  02:00 Найти цитируемый пост)
Ошибка в том, что он почему то количество услуг (наименований) умножил на сумму счета.

Ну это ТВОЯ ошибка. Которая проистекает из непонимания того, как выполняется JOIN. У тебя две записи в payments и две в details - в результате после JOIN ты получишь четыре объединённых записи. Отсюда и кратное увеличение суммы.

Цитата(JEEN @  18.3.2016,  02:22 Найти цитируемый пост)
Попробовал с подзапросами сделать, результат на первый взгляд верный

Интересно, как это он дал верный результат БЕЗ ГРУППИРОВКИ?

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

Более адекватным будет запрос типа (пишу от руки, могу накосячить)
Код

SELECT i0.*, COALESCE(is.si,0) cost_sum, COALESCE(ip.sp) payment_sum
FROM
invoices i0
LEFT JOIN
(
  SELECT i1.invoice_id, SUM(d.cost * d.amount) si
  FROM details d, invoices i1
  WHERE d.invoice_id = i1.id
  GROUP BY i1.invoice_id
) is ON i0.id=is.invoice_id
LEFT JOIN
(
  SELECT i2.invoice_id, SUM(p.sum) sp
  FROM payments p, invoices i2
  WHERE p.invoice_id = i2.id
  GROUP BY i2.invoice_id
) ip ON i0.id=ip.invoice_id



--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
tzirechnoy
Дата 18.3.2016, 10:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Цитата
верное, но скорее всего медленное


Да большынство СУБД ужэ научилось всё это правильно преобразовывать в единый план. Более того, шансы оптимизатору ошыбиться как раз большэ при наличии группировок во вложэнных запросах, из-за которых он не можэт произвольно менять начальную таблицу.

Впрочем, при LEFT JOin это так и так ему сложно.

В итоге -- тривиальный план с проходом по таблицэ invoices и индэксной выборкой из payments и details будет правильным чуть менее чем всегда, так что за скорость особенно можно не волноваться.
PM MAIL   Вверх
Akina
Дата 18.3.2016, 10:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 453



Цитата(tzirechnoy @  18.3.2016,  11:32 Найти цитируемый пост)
большынство СУБД ужэ научилось

К сожалению, ТС не озвучил СУБД, не говоря уж о версии. 


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
tzirechnoy
Дата 18.3.2016, 10:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Цитата
DISTINCT d.cost * d.amount


Да, за такое использование DISTINCT надо сразу расстреливать по-моему.

Разные значения стоимости позицыи вообще никак не относятся к разным позицыям. Ну, то есть у одной позицыи стоимость будет одинакова, конечно, но вот у разных она тожэ можэт быть одинаковой.

Правильное приближэние -- поделить sum() на количество "неправильных" повторений, т.е. количество записей из другой таблицы, которые были использованы в данной группе. В частности, sum(d.cost*d.amount) / count(DISTINCT p.id), и sum(p.sum) / count(DISTINCT d.id). Единственно, возникнет проблема с LEFT JOIN -- когда записей в другой таблицэ нет, но ведь одно повторение осталось! Так что потребуется вместо count(DISTINCT p.id) взять что-нибудь вроде count(DISTINCT COALESCE(p.id, -1)) чтобы всегда NULL было заменено на (невозможное в других случаях) значение, и, таким образом, это повторение данных посчиталось, ну или написать CASE для замены 0 на 1: CASE WHEN count(DISTINCT p.id) = 0 THEN 1 ELSE count(DISTINCT p.id) END. И, естественно, тожэ самое для другой таблицы.

Добавлено через 3 минуты и 31 секунду
Цитата
К сожалению, ТС не озвучил СУБД


MySQL, надо думать, кто ещё позволит в SELECT указать колонку, которой в GROUP BY не было?
PM MAIL   Вверх
JEEN
Дата 18.3.2016, 11:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 8.3.2008

Репутация: 1
Всего: 1



Всем спасибо за помощь. Вчера еще перенес свой код на рабочий проект, работает он без группировки отлично, точно так, как я написал во втором сообщении.
БД MySQL 5.5.47
PM MAIL   Вверх
Akina
Дата 18.3.2016, 11:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 453



tzirechnoy, вот видишь... а ты 
Цитата(tzirechnoy @  18.3.2016,  11:32 Найти цитируемый пост)
большынство СУБД

Вот когда он до 5,7 обновится - тогда ещё на что-то можно будет надеяться.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Составление SQL-запросов | Следующая тема »


 




[ Время генерации скрипта: 0.1460 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.