sum two rows and order by date / total

2019-09-06 01:07发布

问题:

need some help to build a query, this is my current scheme:

users:
+----+------------+
| id |  username  |
+----+------------+
| 1  |    rob     |
| 2  |    john    |
| 3  |    jane    | <--- jane never has donated
| 4  |    mike    | 
+----+------------+

donations:
+--------------------+------------+
|   uid   |  amount  |   date     |
+---------+----------+------------+
|    1    |    20    | 2013-10-10 | 
|    2    |    5     | 2013-10-03 | 
|    2    |    50    | 2013-09-25 |
|    2    |    5     | 2013-10-01 |
|    4    |    100   | 2012-10-01 | <-- past year
+---------+----------+------------+


Result I want:
+---------+-------------+---------+-------------+---------------+----------+ 
|    id   |   username  | amount  |   monthly   |  totalamount  |   total  |
+---------+-------------+---------+-------------+ --------------+----------+
|    1    |     rob     |   20    |      1      |       20      |     1    |
|    2    |     john    |   60    |      3      |       60      |     3    |
|    3    |     jane    |   0     |      0      |       0       |     0    |
|    4    |     mike    |   0     |      0      |       100     |     1    | 
+---------+-------------+-----------------------+---------------+----------+

This is my query:

SELECT
  u.*,
  COALESCE(sum(d.amount), 0) amount,
  COUNT(d.uid) monthly, 
  COUNT(d.amount) as Total, <-- need to get sum all time donations and number of times donated
FROM users u
LEFT JOIN donations d
  ON u.id = d.uid
    AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
GROUP BY u.id ORDER BY u.id ASC

So i need to add 2 different sums from same data.

EDIT: http://sqlfiddle.com/#!2/20a974/9 schema and data

How I can do this?

回答1:

For this we need to filter the data on the select and not on the join.

Remove this condition:

AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))

and add this to the select:

SUM (CASE WHEN (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END) as monthly

Edit:

whole query:

SELECT users.id, users.username, 
  COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN donations.amount ELSE 0 END), 0) monthly_sum, 
  COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END), 0) monthly_amount, 

  COALESCE(sum(donations.amount), 0) total_sum, 
  count(*) total_amount

from users
left join donations
  on donations.uid = users.id
group by users.id, users.username

http://sqlfiddle.com/#!2/20a974/20/0



回答2:

For me the easiest way to think about the separately grouped information is to put it into separate queries and then just join the results back together. This is not likely to be the most efficient, but it helps to get something working.

select auo.id, auo.username, 
  coalesce(monthly_count, 0), coalesce(monthly_total, 0),
  coalesce(total, 0), coalesce(total_amount, 0)
from aaa_users auo
left join  (
   select au.id as id, count(adm.amount) as monthly_count, SUM(adm.amount) as monthly_total
   from aaa_users au join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-30
   group by au.id
) as monthly on monthly.id = auo.id
left join (
   select au.id as id, count(ady.amount) total, SUM(ady.amount) as total_amount
   from aaa_users au join aaa_donations ady on au.id = ady.uid and ady.donate_date >  getDate()-450
   group by au.id 
) as yearly on yearly.id = auo.id

As @CompuChip said, it's cleaner to just join to the donations table twice, but I have something wrong in my join logic as the values for john are getting duplicated. I think there would need to be a donations.id column to prevent the monthly and total donations from being combined. Anyway, here's an example even though it isn't working correctly

select au.id, au.username, 
    count(adm.amount), SUM(adm.amount) as monthly_total, 
    count(ady.amount), SUM(ady.amount) as total_amount
from aaa_users au 
left outer join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-60
left outer join aaa_donations ady on au.id = ady.uid and ady.donate_date > getDate()-450
group by au.id, au.username
order by au.id, au.username


回答3:

You can do another join to donations, giving it a different alias: LEFT JOIN donations d2 on d2.uid = u.id. Then sum over d2.amount for the last two fields, e.g.

SELECT u.*, 
  COALESCE(sum(d.amount), 0) amount, 
  COUNT(d.uid) monthly, 
  COUNT(d.amount) as Total, 

  COALESCE(sum(d2.amount), 0) amountAll, 
  COUNT(d2.uid) monthlyAll,
  COUNT(d2.amount) as TotalAll

  FROM users u
     LEFT JOIN donations d ON u.id = d.uid AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
     LEFT JOIN donations d2 ON u.id = d2.uid 

 GROUP BY u.id ORDER BY u.id ASC