Функция СУММ дважды добавляет записи

У меня есть 2 записи суммы в моей базе данных, и я должен использовать функцию суммы, чтобы отобразить общую сумму, но она показывает и суммирует дважды

Я пробовал другую функцию суммирования в том же запросе, и она работает, но когда я использую ее во второй раз, она суммируется дважды.

$sql = "SELECT 
                mm.idno as idno,
                TRIM(UPPER(CONCAT(mm.fname, ' ', mm.mname, ' ', mm.lname))) as fullname, 
                TRIM(UPPER(mm.branchname)) as branchname, 
                TRIM(UPPER(mm.address)) as outlet_address, 
                SUM(dr.totalamt) as total_amount,
                SUM(drr.totalamt) as return_amount  
        FROM 8_membermain mm
            LEFT JOIN 8_directsalessummary dr ON mm.idno = dr.idno
            LEFT JOIN 8_drreturnsummary drr ON dr.idno = drr.idno
        WHERE mm.status > 0 AND dr.status = 1
        AND dr.ispaid = 'Full Payment'
        AND dr.trandate BETWEEN '".$date1."' AND '".$date2."' 
        AND drr.trandate BETWEEN '".$date1."' AND '".$date2."'";

SUM(dr.totalamt) as total_amount работает нормально. Единственная проблема SUM(drr.totalamt) as return_amount

При использовании SUM() / COUNT() / AVG() во второй/третьей таблицах в соединении результат будет/может умножаться, потому что соединения будут/могут генерировать несколько записей, когда отношения являются «один ко многим» или «многие ко». -many, которые затем будут агрегированы. Вы также можете использовать SUM(DISTINCT ..), но ответ @TimBiegeleisen более эффективен и должен быть лучше для производительности, поскольку этот метод не требует разделения общего объединенного набора результатов, что действительно требуется SUM(DISTINCT ..).

Raymond Nijland 02.07.2019 12:31

@RaymondNijland использование SUM(DISTINCT...) в поле суммы не является хорошей идеей, поскольку вполне возможно, что будет несколько строк с одинаковым значением, которые все должны быть включены в сумму. Смотрите мой ответ для альтернативы подзапросам.

ysth 02.07.2019 12:46
"использование SUM(DISTINCT...) в поле суммы не является хорошей идеей, так как вполне возможно, что будет несколько строк с одинаковым значением, которое должно быть включено в сумму" @ysth очень верно, использование SUM(DISTINCT ..) для данных, где это может быть связано, не является хорошей идеей..
Raymond Nijland 02.07.2019 13:01

Я согласен @RaymondNijland. Я пытался использовать DISTINCT, и для загрузки требуется время, что является очень плохой практикой. Спасибо тебе, друг.

Tristan Ross 02.07.2019 13:09
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
4
958
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема с вашим подходом заключается в том, что вы дважды присоединяетесь к первый, а затем агрегируете. SUM(dr.totalamt) работает, потому что включает только первое соединение. Но SUM(drr.totalamt) неверно, потому что вы пересчитываете из-за второго соединения. Чтобы это исправить, следует выполнять агрегации в отдельных подзапросах, а затем присоединяться к этим подзапросам:

SELECT
    mm.idno,
    TRIM(UPPER(CONCAT(mm.fname, ' ', mm.mname, ' ', mm.lname))) AS fullname,
    TRIM(UPPER(mm.branchname)) AS branchname,
    TRIM(UPPER(mm.address)) AS outlet_address, 
    COALESCE(dr.totalamt, 0) AS total_amount,
    COALESCE(drr.totalamt, 0) AS return_amount
FROM 8_membermain mm
LEFT JOIN
(
    SELECT idno, SUM(totalamt) AS totalamt
    FROM 8_directsalessummary
    WHERE status = 1 AND ispaid = 'Full Payment' AND
        trandate BETWEEN ? AND ?
    GROUP BY idno
) dr
    ON mm.idno = dr.idno
LEFT JOIN
(
    SELECT idno, SUM(totalamt) AS totalamt
    FROM 8_drreturnsummary
    WHERE trandate BETWEEN ? and ?
    GROUP BY idno
) drr
    ON mm.idno = drr.idno
WHERE
    mm.status > 0;

К заполнителям ? вам нужно будет привязать два значения дат $date1 и $date2. Но дополнительное объяснение того, как использовать подготовленные операторы в PHP, кажется слишком широким, и проблема с логикой вашего запроса кажется более серьезной проблемой.

Привет. теперь он работает, и я попытался добавить $data = array($date1,$date2,$date1,$date2); $query = $this->db->query($sql,$data); на дату, но не работает. Прости. я просто новичок

Tristan Ross 02.07.2019 11:19

@TristanRoss Если у вас все еще есть дополнительные проблемы с синтаксисом PHP, вы можете открыть новый вопрос.

Tim Biegeleisen 02.07.2019 11:24

извините за захват вашего принятия; Я сказал, что это правильный способ сделать это. хотя с mariadb 10.2.1+/mysql 8.0+ в идеале вы должны использовать общее табличное выражение для мм и ограничить записи dr/drr теми, которые имеют отношение

ysth 02.07.2019 13:34
Ответ принят как подходящий

Судя по вашему описанию проблемы, ваш запрос соответствует двум строкам dr и одной строке drr. Если бы было более одной строки drr, общее количество dr также было бы неправильным. Это связано с тем, что соединения возвращают все возможные комбинации соединяемых таблиц. Например, если было две строки dr, которые мы назовем A и B, и три строки drr C, D и E, результаты (до группировки в одну строку из-за использования суммы без группировки по ) было бы:

A and C
A and D
A and E
B and C
B and D
B and E

При суммировании столбца dr каждое из значений A и B встречается по три раза, что приводит к тройной сумме. А при суммировании столбца drr каждое из значений C, D и E встречается по два раза, что приводит к удвоению суммы.

Правильным решением для этого является объединение подзапросов для dr и drr, как показано в ответе Тима. Но иногда, когда задействовано очень мало таблиц и очень мало записей в каждой группе для большинства из них, и таблицы имеют первичные ключи или какой-либо другой уникальный столбец, вы можете просто разделить каждую сумму на количество строк в каждой другой таблице (или 1, если строк не было):

SELECT
    ...
    SUM(dr.totalamt)/GREATEST(1,COUNT(DISTINCT drr.primarykey)) as total_amount,
    SUM(drr.totalamt)/GREATEST(1,COUNT(DISTINCT dr.primarykey)) as return_amount

Еще пара комментариев к вашему запросу:

Похоже, вы намереваетесь поставить GROUP BY mm.idno в конце; без этого ваш запрос возвращает данные для полей, связанных с mm, для произвольной записи mm, которая соответствует вашим критериям where, и описанный выше трюк не будет работать правильно, если на самом деле ваш запрос находит несколько записей mm.

Вы используете левое соединение, но затем в предложении where указываете условия, которые будут ложными, если строка не будет найдена; это фактически делает ваши левые соединения внутренними соединениями. Если вы хотите найти строки мм даже без строки dr или drr, вам нужно переместить условия в соединение, например:

LEFT JOIN 8_directsalessummary dr ON mm.idno = dr.idno
    AND dr.status = 1
    AND dr.ispaid = 'Full Payment'
    AND dr.trandate BETWEEN ...

Кроме того, вы присоединяетесь к drr, используя dr.idno; если вы хотите найти записи drr, даже если записи dr нет, вы должны вместо этого использовать mm.idno:

LEFT JOIN 8_drreturnsummary drr ON mm.idno = drr.idno
    AND drr.trandate BETWEEN ...

Отличная идея. +1 за это, так как я никогда раньше не видел этого трюка с «неумножением». на возможном огромном объединенном наборе результатов

Raymond Nijland 02.07.2019 13:08

@RaymondNijland даже с миллионами+, если для каждого не так много записей group by, не будет такого большого снижения производительности.

ysth 02.07.2019 13:26
"даже с миллионами+, если для каждой группы не так много записей по" Да, невозможно узнать, возможно ли это здесь или нет ... Пока COUNT(DISTINCT ...) не нужно использовать внутреннюю (дисковую) временную таблицу, вы, скорее всего, будете в порядке, используя это, по крайней мере, если топикстартер использует первичный ключ индексированный столбец, который вы предложили. Если MySQL необходимо использовать временную таблицу на диске, он получит (возможно, огромное) снижение производительности.
Raymond Nijland 02.07.2019 13:35

Другие вопросы по теме