Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Dwie sumy, dwóch tabel w jednym zapytaniu
Forum PHP.pl > Forum > Bazy danych > MySQL
dawnthief
Witajcie, jestem tu nowy i mam pewien problem.
Dane są dwie tabele o tej samej strukturze:

fm_expenses i fm_income
w expenses przechowywane są dane o wydatkach a w income o dochodach

ID int(11)
SUMA float(10,2)
DATA timestamp
CATEGORY int(3)
USERID int(11)


Chcę zliczyć sumę fm_income jako dochody a fm_expenses jako wydatki
robię to tak:
  1. SELECT SUM(fm_expenses.SUMA) AS wydatki, SUM(fm_income.SUMA) AS dochody FROM fm_expenses LEFT JOIN fm_income ON fm_expenses.USERID = fm_income.USERID WHERE fm_expenses.USERID=1 AND fm_income.USERID=1

i wychodzą bzdury. Kosmiczne cyfry, zdaję sobie sprawę że skaszaniona relacja tu jest, ale za to zwraca to wszystko w ładnym formacie (dwa pola, wydatki i dochody).
Wykombinowałem jescze jeden sposób:
  1. SELECT SUM(SUMA) AS dochody FROM fm_income WHERE USERID=1 UNION SELECT SUM(SUMA) AS wydatki FROM fm_expenses WHERE USERID=1

Zwraca prawidłowe liczby ale wszystko w polu dochody i jako dwa rekordy.

Jak to ugryźć?
mmmmmmm
Tak to jest jak te dane się trzyma w dwóch różnych tabelach... Bez sensu.
  1. SELECT w.USERID, dochody, wydatki FROM (SELECT USERID, Sum(SUMA) AS wydatki FROM fm_expenses GROUP BY USERID) w LEFT JOIN (SELECT USERID, Sum(SUMA) AS dochody FROM fm_income GROUP BY USERID) d ON d.USERID=w.USERID WHERE w.USERID=1
trueblue
mmmmmmm,
Twoje pytanie zwróci błędny wynik jeśli użytkownik będzie dochód bez wydatku.


  1. SELECT userid,SUM(suma*(1-ABS(SIGN(typ-1)))) AS wydatek,SUM(suma*(1-ABS(SIGN(typ-2)))) AS dochod FROM
  2. (SELECT w.USERID, w.SUMA, 1 AS typ FROM fm_expenses AS w LEFT JOIN fm_income ON fm_income.USERID=w.USERID
  3. UNION ALL
  4. SELECT d.USERID,d.SUMA,2 AS typ FROM fm_income AS d LEFT JOIN fm_expenses ON fm_expenses.USERID=d.USERID
  5. ) AS tmp GROUP BY userid
dawnthief
Rozwiązanie które podał mmmmmmm podaje jak najbardziej prawidłowe wyniki, ma tylko jeden mankament, nie zwraca nic jeżeli dla danego usera (USERID) nie ma żadnego rekordu w wydatkach.
Rozwiązanie trueblue działa nawet jeśli nie ma rekordów w wydatkach lub dochodach ale niestety podaje nieprawidłowe sumy sad.gif


SQL wyrzucił mi takie sumy , pierwsze to rozwiązanie mmmmmmm, drugie to rozwiązanie trueblue
wydatek dochod
2600.47 3316.95
1814.06999796629 1996.94998836517
trueblue
Podałem zapytanie dla wszystkich userów.
Spróbuj tak:
  1. SELECT userid,SUM(suma*(1-ABS(SIGN(typ-1)))) AS wydatek,SUM(suma*(1-ABS(SIGN(typ-2)))) AS dochod FROM
  2. (SELECT w.USERID, w.SUMA, 1 AS typ FROM fm_expenses AS w LEFT JOIN fm_income ON fm_income.USERID=w.USERID WHERE w.userid=1
  3. UNION ALL
  4. SELECT d.USERID,d.SUMA,2 AS typ FROM fm_income AS d LEFT JOIN fm_expenses ON fm_expenses.USERID=d.USERID WHERE d.userid=1
  5. ) AS tmp
dawnthief
Wyrzuca błąd
  1. #1140 - Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause


a co robi te
  1. suma*(1-ABS(SIGN(typ-1)))

?


Właśnie sobie uświadomiłem jak mało wiem o SQLu...
trueblue
Dodaj na końcu całości GROUP BY userid.

  1. suma*(1-ABS(SIGN(typ-1)))

Umożliwia emulację tabeli przestawnej.
dawnthief
Tak teraz błędem nie rzuca, ale wciąż podaje niewłaściwe sumy.
trueblue
Na 100% jesteś pewien, że wynik nie jest poprawny?
Ja testuję na, co prawda, paru rekordach, ale jest ok.

EDIT: Powinno być UNION ALL, choć nie wiem czy to jest przyczyną wyniku, który uznajesz za błędny.
dawnthief
Teraz już kompletnie odwaliło
  1. SELECT userid,SUM(suma*(1-ABS(SIGN(typ-1)))) AS wydatek,SUM(suma*(1-ABS(SIGN(typ-2)))) AS dochod FROM
  2. (SELECT w.USERID, w.SUMA, 1 AS typ FROM fm_expenses AS w LEFT JOIN fm_income ON fm_income.USERID=w.USERID WHERE w.userid=2
  3. UNION ALL
  4. SELECT d.USERID,d.SUMA,2 AS typ FROM fm_income AS d LEFT JOIN fm_expenses ON fm_expenses.USERID=d.USERID WHERE d.userid=2
  5. ) AS tmp GROUP BY USERID


Wrzucam takie zapytanie (zmieniłem userid z 1 na 2 , bo chciałem sprawdzić na małej ilości danych)
mam w dochodach dwa rekordy SUMA=3.00 co powinno dawać 6
a w wydatkach jeden rekord SUMA=2.00 a drugi SUMA=2.50 co powinno dawać 4.50

Otrzymuję taki wynik:
wydatek: 9
dochod: 12
trueblue
Niepotrzebne te LEFT JOIN.

Dla USERID=1
  1. SELECT userid,SUM(suma*(1-ABS(SIGN(typ-1)))) AS wydatek,SUM(suma*(1-ABS(SIGN(typ-2)))) AS dochod FROM
  2. (SELECT USERID,SUMA, 1 AS typ FROM fm_expenses
  3. UNION ALL
  4. SELECT USERID,SUMA,2 AS typ FROM fm_income
  5. ) AS tmp WHERE userid=1 GROUP BY userid
dawnthief
Działa! wszystko cacy, punkcik powędrował.
Teraz jescze pare pytań żebym bezmyślnie nie kopiował i wklejał
  1. SELECT userid,SUM(suma*(1-ABS(SIGN(typ-1)))) AS wydatek,SUM(suma*(1-ABS(SIGN(typ-2)))) AS dochod FROM
  2. (SELECT USERID,SUMA, 1 AS typ FROM fm_expenses
  3. UNION ALL
  4. SELECT USERID,SUMA,2 AS typ FROM fm_income
  5. ) AS tmp WHERE userid=1 GROUP BY userid


- Skąd się bierze te 1 i 2 w tych Selectach po sumie ?
- SUM(suma*(1-ABS(SIGN(typ-1)))) - jak to dokładnie działa, po co i dlaczego?
trueblue
Wydatek ma typ=1, dochód=2.

Dla typ=1 jeśli rekord ma typ=1, będzie suma*1, inaczej suma*0, analogicznie dla typ=2.
Można to zastąpić taką konstrukcją:
  1. SUM(suma*(IF(typ=1,1,0))) AS wydatek,SUM(suma*(IF(typ=2,1,0)))

Krótko: wartość suma wpada do sumowania wydatku jeśli jest wydatkiem, jeśli nie jest, nie wpada. Analogicznie dla dochodu.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.