MySQL: SUM() with JOIN returns incorrect values

后端 未结 2 858
逝去的感伤
逝去的感伤 2021-01-06 07:36

I am trying to fetch a SUM() for each user in a table, but MySQL is returning the wrong values.

This is how it should look (http://sqlfiddle.com/#!2/7b988/4/0)

2条回答
  •  一生所求
    2021-01-06 08:02

    Try this query:

    SELECT bl.user_id, SUM( ph.amount ) PAIDOUT
    FROM (
       SELECT distinct blocks.user_id 
       FROM blocks
       WHERE confirms > 520
    ) bl
    LEFT JOIN  payout_history ph
    ON bl.user_id = ph.user_id
    GROUP BY ph.user_id
    ;
    

    SQLFiddle --> http://sqlfiddle.com/#!2/7b988/48



    --- EDIT --- an explanation how the query works (or rather why your query doesn't work) ----

    Looking at expected results it seems that the query should calculate a sum of amount column for each user_id, but only for those user_id, that are also in the blocks table, and have a blocks.confirms value grather than 520.
    A simple join (also left outer join) cannot work in this case, because the blocks table can contain many records for the same user_id, for example a query that returns rows for only user_id=110 gives the following results:

    SELECT *
    FROM blocks
    WHERE confirms > 520
          AND user_id = 110;
    
    + ------- + ------------ + ----------- + ------------- +
    | id      | user_id      | reward      | confirms      |
    + ------- + ------------ + ----------- + ------------- +
    | 0       | 110          | 20.89832115 | 521           |
    | 65174   | 110          | 3.80357075  | 698           |
    | 65204   | 110          | 4.41933060  | 668           |
    | 65218   | 110          | 4.69059801  | 654           |
    | 65219   | 110          | 4.70222521  | 653           |
    | 65230   | 110          | 4.82805490  | 642           |
    | 65265   | 110          | 5.25058079  | 607           |
    | 65316   | 110          | 6.17262650  | 556           |
    + ------- + ------------ + ----------- + ------------- +
    

    The straigh join (and LEFT/RIGHT outer join) works in this way, that takes each record from the first joinded table, and pair this record (combine it) with all rows from the other joinded table thet meet the join condition.

    In our case the left join produces a below resultset:

    SELECT *
    FROM blocks
    LEFT JOIN payout_history
    ON blocks.user_id = payout_history.user_id
    WHERE confirms > 520
        AND blocks.user_id = 110;
    + ------- + ------- + ----------- + -------- + --- + ------- + ----------- +
    | id      | user_id | reward      | confirms | id  | user_id | amount      |
    + ------- + ------- + ----------- + -------- + --- + ------- + ----------- +
    | 0       | 110     | 20.89832115 | 521      | 1   | 110     | 20.898319   |
    | 65174   | 110     | 3.80357075  | 698      | 1   | 110     | 20.898319   |
    | 65204   | 110     | 4.41933060  | 668      | 1   | 110     | 20.898319   |
    | 65218   | 110     | 4.69059801  | 654      | 1   | 110     | 20.898319   |
    | 65219   | 110     | 4.70222521  | 653      | 1   | 110     | 20.898319   |
    | 65230   | 110     | 4.82805490  | 642      | 1   | 110     | 20.898319   |
    | 65265   | 110     | 5.25058079  | 607      | 1   | 110     | 20.898319   |
    | 65316   | 110     | 6.17262650  | 556      | 1   | 110     | 20.898319   |
    + ------- + ------- + ----------- + -------- + --- + ------- + ----------- +
    

    and now if we add SUM( amount ) .... GROUP BY user_id, MySql will calucate a sum of all amount values from the above resultset ( 8 rows * 20.898 = ~ 167.184 )

    SELECT blocks.user_id, sum( amount)
    FROM blocks
    LEFT JOIN payout_history
    ON blocks.user_id = payout_history.user_id
    WHERE confirms > 520
        AND blocks.user_id = 110
    GROUP BY blocks.user_id;
    + ------------ + ----------------- +
    | user_id      | sum( amount)      |
    + ------------ + ----------------- +
    | 110          | 167.186554        |
    + ------------ + ----------------- +
    



    As you see in this case the join doesn't give us desired results - we need something named a semi join - below are different variants of semi joins, give them a try:

    SELECT bl.user_id, SUM( ph.amount ) PAIDOUT
    FROM (
       SELECT distinct blocks.user_id 
       FROM blocks
       WHERE confirms > 520
    ) bl
    LEFT JOIN  payout_history ph
    ON bl.user_id = ph.user_id
    GROUP BY ph.user_id
    ;
    
    
    SELECT ph.user_id, SUM( ph.amount ) PAIDOUT
    FROM payout_history ph
    WHERE ph.user_id IN (
         SELECT user_id FROM blocks
         WHERE confirms > 520
      )
    GROUP BY ph.user_id
    ;
    
    SELECT ph.user_id, SUM( ph.amount ) PAIDOUT
    FROM payout_history ph
    WHERE EXISTS (
         SELECT 1 FROM blocks bl
         WHERE bl.user_id = ph.user_id
            AND bl.confirms > 520
      )
    GROUP BY ph.user_id
    ;
    

提交回复
热议问题