SQL SUM() Calculates only one row in sub query - mysql

I'm making statistic for 5 tables. I have made the example with one client data.
loan
id | status
------------
1454 | payed
payment schedule
id | loan_id | user_client_id
-----------------------------
1456 | 1454 | 3113
payment_schedule_row
id | payment_schedule_id | payment | payment_date
---------------------------------------------------
5013 | 1456 | 32 | 2013-11-06
5014 | 1456 | 32 | 2013-12-06
5015 | 1456 | 32 | 2013-01-05
5016 | 1456 | 32 | 2013-02-04
5017 | 1456 | 32 | 2013-03-06
5018 | 1456 | 32 | 2013-04-05
5019 | 1456 | 32 | 2013-05-05
5020 | 1456 | 32 | 2013-06-04
5021 | 1456 | 32 | 2013-07-04
5022 | 1456 | 32 | 2013-08-03
5023 | 1456 | 32 | 2013-09-02
5014 | 1456 | 32 | 2013-10-02
payment_schedule_cover
id | payment_schedule_id | date | sum
----------------------------------------------
2282 | 1456 | 2013-11-08 | 34
3054 | 1456 | 2013-12-07 | 40
3776 | 1456 | 2013-01-04 | 38
4871 | 1456 | 2013-02-06 | 49
5954 | 1456 | 2013-03-06 | 40
7070 | 1456 | 2013-04-25 | 49
9029 | 1456 | 2013-05-21 | 52
10377 | 1456 | 2013-06-20 | 30
10391 | 1456 | 2013-06-21 | 30
10927 | 1456 | 2013-07-07 | 60
payment_schedule_delay
id | payment_schedule_row_id | start_date | end_date | delay
----------------------------------------------------------------
1135 | 5013 | 2013-11-07 | 2013-11-08 | 0.07
1548 | 5014 | 2013-12-07 | 2013-12-07 | 0.03
2628 | 5016 | 2014-02-05 | 2014-02-06 | 0.01
And the query is :
SELECT period, loan_sum, covers, delay
FROM
(SELECT MAX(EXTRACT(YEAR_MONTH FROM psc.date)) AS period,
(SELECT SUM(psr2.payment) FROM payment_schedule_row AS psr2 WHERE psr.payment_schedule_id = psr2.payment_schedule_id) AS loan_sum,
(SELECT SUM(psc2.sum) FROM payment_schedule_cover AS psc2 WHERE psc.payment_schedule_id = psc2.payment_schedule_id) AS covers,
(SELECT SUM(psd2.delay) FROM payment_schedule_delay AS psd2 WHERE psr.id = psd2.payment_schedule_row_id) AS delay
FROM loan
INNER JOIN payment_schedule AS ps ON ps.loan_id = loan.id
INNER JOIN payment_schedule_row AS psr ON psr.payment_schedule_id = ps.id
INNER JOIN payment_schedule_cover AS psc ON psc.payment_schedule_id = ps.id
WHERE loan.status = 'payed'
GROUP BY ps.id) AS sum_by_id
GROUP BY period
Result for the query:
period | loan_sum | covers | delay
-----------------------------------
201407 | 384 | 422 | 0.07
Everything is right except the delay. It should be 0.11 (0.07 + 0.03 + 0.01)
So I have been trying to find the error from the query for days now. Maybe someone can tell me what I'm doing wrong.
Sqlfiddle link: http://sqlfiddle.com/#!2/21585/2

SELECT period, loan_sum, covers, delay
FROM
(SELECT MAX(EXTRACT(YEAR_MONTH FROM psc.date)) AS period,
(SELECT SUM(psr2.payment) FROM payment_schedule_row AS psr2 WHERE psr.payment_schedule_id = psr2.payment_schedule_id) AS loan_sum,
(SELECT SUM(psc2.sum) FROM payment_schedule_cover AS psc2 WHERE psc.payment_schedule_id = psc2.payment_schedule_id) AS covers,
(SELECT SUM(psd2.delay) FROM payment_schedule_delay AS psd2 WHERE psr.id = psd2.payment_schedule_row_id) AS delay
FROM loan
INNER JOIN payment_schedule AS ps ON ps.loan_id = loan.id
INNER JOIN payment_schedule_row AS psr ON psr.payment_schedule_id = ps.id
INNER JOIN payment_schedule_cover AS psc ON psc.payment_schedule_id = ps.id
INNER JOIN payment_schedule_delay AS psd ON psr.id = psd.payment_schedule_row_id
WHERE loan.status = 'payed'
GROUP BY ps.id) AS sum_by_id
GROUP BY period

SELECT period, loan_sum, covers, delay
FROM
(SELECT MAX(EXTRACT(YEAR_MONTH FROM psc.date)) AS period,
(SELECT SUM(psr2.payment) FROM payment_schedule_row AS psr2 WHERE psr.payment_schedule_id = psr2.payment_schedule_id) AS loan_sum,
(SELECT SUM(psc2.sum) FROM payment_schedule_cover AS psc2 WHERE psc.payment_schedule_id = psc2.payment_schedule_id) AS covers,
(SELECT SUM(psd2.delay) FROM payment_schedule_delay AS psd2 WHERE psr.id IN /* IN operator will allow for multiple values like psr.id IN (5013,5014,5016) */ psd2.payment_schedule_row_id) AS delay
FROM loan
INNER JOIN payment_schedule AS ps ON ps.loan_id = loan.id
INNER JOIN payment_schedule_row AS psr ON psr.payment_schedule_id = ps.id
INNER JOIN payment_schedule_cover AS psc ON psc.payment_schedule_id = ps.id
WHERE loan.status = 'paid'
GROUP BY ps.id) AS sum_by_id
GROUP BY period
Change = to IN in the line where you are summarizing the delay values.

I finally got an answer from a MySQL forum. The answer what fixed my problem was:
... there are problems ...
The Group By operator in the subquery does not see aggregation inside the correlated sub-subquery sums. Those sums need to be moved out a level.
There's no aggregation for the outer query's Group By to group; it just functions as an Order By
A query like select a,b,c sum(d) ... group by a can return arbitrary results for b and c unless a strictly 1:1 relationship holds between a and each of b and c, which looks unlikely to be the case in your subquery.
Correlated subqueries are inefficient, as yours illustrate with their two-stage joins
The delay correlated subquery doesn't join to anything
So move correlated subquery logic to the FROM clause, join the delay query, touch up the Group By clause, and we have ...
select psc.period, psc.sum, psr.payments, sum(psd.delay) as delay
from loan
join payment_schedule as ps on ps.loan_id = loan.id
join(
select payment_schedule_id, sum(payment) as payments
from payment_schedule_row
group by payment_schedule_id
) as psr on psr.payment_schedule_id = ps.id
join (
select payment_schedule_id, sum(sum) as sum, max( extract(year_month from date) ) as period
from payment_schedule_cover
group by payment_schedule_id
) psc on ps.id = psc.payment_schedule_id
join payment_schedule_row psr2 on ps.id = psr2.payment_schedule_id
join (
select payment_schedule_row_id, sum(delay) as delay
from payment_schedule_delay
group by payment_schedule_row_id
) as psd on psr2.id = psd.payment_schedule_row_id
where loan.status = 'payed'
group by psc.period, psc.sum, psr.payments;

Related

MySQL select N latest rows for each product from 3 relational tables

Now i have this code which return latest record for each product. But i don't know how to modify this to get for example 3 latest rows for each product.
I want to compare latest product prices and i need few latest rows of each.
shops
id | shopId
-----------
1 | 2345
2 | 6573
products
id | shopId | title | active | pDateAdded | pDateUpdate
---------------------------------------------------------------------------
18 | 1 | Honda | 1 | 2021-03-07 01:56:34 | 2021-03-07 04:36:34
19 | 2 | Subaru | 1 | 2021-03-07 03:43:34 | 2021-03-08 04:36:34
20 | 1 | VW | 1 | 2021-03-07 07:21:34 | 2021-03-09 04:36:34
21 | 2 | Ford | 0 | 2021-03-07 11:37:34 | 2021-03-10 04:36:34
prices
id | shopId | productId | price | dDateAdded
-----------------------------------------------------
224 | 1 | 18 | 2385 | 2021-03-09 12:39:57
225 | 2 | 19 | 1523 | 2021-03-09 13:14:44
226 | 1 | 20 | 5489 | 2021-03-09 17:32:18
227 | 1 | 18 | 2256 | 2021-03-10 18:22:13
228 | 2 | 19 | 1600 | 2021-03-10 21:33:21
229 | 1 | 20 | 5321 | 2021-03-10 14:15:56
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
231 | 2 | 19 | 1666 | 2021-03-11 17:31:49
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
This command return only 1 latest record from prices table for every product from products table for specific shopId
SELECT s.*, c.*, d.*
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN (
SELECT productId, MAX(dDateAdded) MaxDate
FROM prices
GROUP BY productId
) MaxDates
ON MaxDates.productId = c.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id AND MaxDates.MaxDate = d.dDateAdded
WHERE s.id = ".$shopId."
For example if shopId=1 this command get only that records (I omitted here the data from the other tables that are retrieved):
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
But i want to get for example 2 latest records for every product where shopId=1, so the records which i want to get:
(shops)id | (shops)shopId | title | active | price | dDateAdded
1 | 2345 | Honda | 1 | 2256 | 2021-03-10 18:22:13
1 | 2345 | Honda | 1 | 2137 | 2021-03-10 14:15:56
1 | 2345 | VW | 1 | 5321 | 2021-03-11 05:55:25
1 | 2345 | VW | 1 | 5001 | 2021-03-11 20:18:01
To select N latest rows needs to allocate row number and to filter by N rows. However, the ROW_NUMBER function is not supported in MySQL 5.7.
So that you need to simulate the ROW_NUMBER function like the follwing:
You can get the desired result by adding subquery with row number to your query like the below:
DB Fiddle
SELECT
s.id,
s.shopId,
c.title,
c.active,
d.price,
d.dDateAdded
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id
--
LEFT JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY p1.id, p1.shopId, p1.productId, p1.dDateAdded
) AS w
ON d.id=w.id
--
WHERE
s.id = 1 AND
w.row_num <= 2
DB Fiddle
SELECT
id,
shopId,
productId,
price,
dDateAdded
FROM (
SELECT p1.*,
(
SELECT COUNT(*)+1 FROM prices p2
WHERE
p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
) row_num
FROM prices p1
) p
WHERE
shopId = 1 AND
row_num <= 2
ORDER BY id
DB Fiddle
SELECT p.* FROM prices p
INNER JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY
p1.id,
p1.shopId,
p1.productId,
p1.dDateAdded
) w
ON p.id=w.id
WHERE
p.shopId = 1 AND
w.row_num <= 2
ORDER BY p.id
Other way using a variable

MySQL 2 tables UNION

I ask for your understanding because I can not speak English well.
There are 4 tables.
sensor (PK: sensor)
sensor | service
1 | 1
2 | 2
equipment (PK: id, FK: sensor)
id | equip | sensor |
1 | 8 | 1 |
2 | 8 | 1 |
3 | 8 | 2 |
4 | 7 | 2 |
A (PK: AUTO INCREMENT, UNIQUE: id, time, FK: id)
id | time | temperature
1 | 1027 | 30
1 | 1028 | 30
1 | 1029 | NULL
1 | 1030 | 60
1 | 1101 | 999
B (PK: AUTO INCREMENT, UNIQUE: id, time, FK: id)
id | time | temperature
2 | 1027 | 40
2 | 1029 | 50
2 | 1030 | NULL
2 | 1031 | 59
I want the following results.
time | A_temperature | B_temperature
1027 | 30 | 40
1028 | 30 | NULL
1029 | NULL | 50
1030 | 60 | NULL
1031 | NULL | 59
So I made the following query:
SELECT DISTINCT COALESCE(A.time, B.time) time, A.temperature AS A_temperature, B.temperature AS B_temperature
FROM equipment AS equip
JOIN sensor sen
ON sen.sensor = equip.sensor
AND sen.service = 1
JOIN A
ON A.id = equip.id
AND (A.time>= '1027' AND A.time<= '1031')
LEFT JOIN B
ON B.id = equip.id
AND (B.time>= '1027' AND B.time<= '1031')
AND B.time= A.time
WHERE equip.equip = 8
UNION
SELECT DISTINCT COALESCE(B.time, A.time) time, A.temperature AS A_temperature, B.temperature AS B_temperature
FROM equipment AS equip
JOIN sensor sen
ON sen.sensor = equip.sensor
AND sen.service = 1
LEFT JOIN B
ON B.id = equip.id
AND (B.time>= '1027' AND B.time<= '1031')
AND B.time= A.time
JOIN A
ON A.id = equip.id
AND (A.time>= '1027' AND A.time<= '1031')
WHERE equip.equip = 8
ORDER BY time ASC;
But I did not get the results I wanted.
time | A_temperature | B_temprature
1027 | 30 | NULL
1027 | NULL | 40
1028 | 30 | NULL
1028 | NULL | NULL
1029 | NULL | 50
....
After executing the above query, A.time and B.time are separated and the result is output. I want to combine these at the same time. If time is null, we want to put it in non-null time.
I think you can simplify the answer from your previous question by creating a subquery which just selects all the distinct time values from tables A and B. Then you can separately JOIN the A and B tables to it and GROUP BY the time values, using MAX to aggregate the temperatures since the values will be either valid or NULL, which MAX will ignore:
SELECT t.time, MAX(A.temperature) AS A_temperature, MAX(B.temperature) AS B_temperature
FROM equipment e
JOIN sensor s ON s.sensor = e.sensor AND s.service = 1
JOIN (SELECT time FROM A UNION SELECT time FROM B) t
LEFT JOIN A on A.id = e.id AND A.time = t.time
LEFT JOIN B on B.id = e.id AND B.time = t.time
WHERE t.time BETWEEN 1027 AND 1031
GROUP BY t.time
ORDER BY t.time
Output:
time A_temperature B_temperature
1027 30 40
1028 30
1029 50
1030 60
1031 59
Demo

Sum is not done if joined table is empty with MySQL

I have the following tables structure and trying to make a report from these:
___BillableDatas
|--------|------------|---------|--------------|------------|
| BIL_Id | BIL_Date |BIL_Rate | BIL_Quantity | BIL_Status |
|--------|------------|---------|--------------|------------|
| 1 | 2018-03-01 | 105 | 1 | charged |
| 2 | 2018-03-01 | 15 | 2 | notcharged |
| 3 | 2018-03-01 | 5 | 1 | notcharged |
|--------|------------|---------|--------------|------------|
___SalesTaxes
|--------|--------------|------------|
| STX_Id | STX_TaxeName | STX_Amount |
|--------|--------------|------------|
| 8 | Tax 1 | 5.000 |
| 9 | Tax 2 | 15.000 |
|--------|--------------|------------|
STX_Amount is a percentage.
___ApplicableTaxes
|-----------|-----------|
| ATX_BILId | ATX_STXId |
|-----------|-----------|
| 1 | 8 |
| 1 | 9 |
|-----------|-----------|
ATX_BILId is the item ID link with ___BillableDatas.
ATX_STXId is the tax ID link with ___SalesTaxes.
I need to get to sum of the items per day
- without tax
- with tax
So mething like this:
|------------------|---------------|------------|
| BIL_RateNonTaxed | BIL_RateTaxed | BIL_Status |
|------------------|---------------|------------|
| 105.00 | 126.00 | charged | <- Taxes #8, #9 applicable
| 35.00 | 35.00 | notcharged | <- No taxes here
|------------------|---------------|------------|
Explications on the totals:
105 = 105*1 -- (total of the charged item multiply by the quantity)
35 = (15*2)+5 -- (total of the notcharged items multiply by the quantity)
126.00 = 105+(105*(5+15)/100)
35.00 = as no taxe, put the non taxed value.
My last try was this one:
SELECT BIL_Status
, SUM(BIL_Rate*BIL_Quantity) BIL_RateNonTaxed
, IFNULL(SUM((BIL_Rate*BIL_Quantity)+(BIL_Rate*BIL_Quantity*total_sales_tax/100)), SUM(BIL_Rate*BIL_Quantity)) BIL_RateTaxed
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY ATX_BILId
) x
GROUP
BY BIL_Status
This query works just when each item has a linked taxe (case of my item #1). When item has no linked taxes (item #2 and #3), the sum is not made.
Please see this SQLFiddle to help you if needed:
http://sqlfiddle.com/#!9/433a3f/2
The only one error with the link is I should have 35 and not 30.
Thanks.
The subquery was grouping by the wrong thing. You are grouping by ATX_BILId, but I think you really wanted to get all the unique billable data bill_ids. At least the following query returns what you expected. The only difference is changing "GROUP BY ATX_BILId" to "GROUP BY BIL_Id"
SELECT BIL_Status
, SUM(BIL_Rate*BIL_Quantity) BIL_RateNonTaxed
, IFNULL(SUM((BIL_Rate*BIL_Quantity)+(BIL_Rate*BIL_Quantity*total_sales_tax/100)), SUM(BIL_Rate*BIL_Quantity)) BIL_RateTaxed
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY BIL_Id
) x
GROUP
BY BIL_Status
Link to SQL Fiddle
In general, when you are troubleshooting this type of query, the first thing to do is examine the returned rows without the group bys. It becomes easier to see the problem when you run the following query.
SELECT BIL_Status,
BIL_Rate,
BIL_Quantity,
total_sales_tax
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY ATX_BILId
) x

Latest Group By MYSQL + link Table [duplicate]

This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
GROUP BY characteristic in mysql [closed]
(1 answer)
Closed 9 years ago.
I'm experiencing trouble with the a linking table and retrieving the lastUsed values.
Linking table:
link_devices_user
id | deviceId | shopId |
1 | 359 | 46 |
2 | 1339 | 46 |
3 | 1328 | 45 |
4 | 882 | 46 |
system_devices
id | carId | registerId | lastUsed |
359 | 350 | regi1 | 2014-01-03 09:00:00 |
1339 | 350 | regi2 | 2013-01-03 09:00:00 |
1328 | 160 | regi3 | 2012-01-03 09:00:00 |
882 | 150 | regi4 | 2014-01-03 08:59:00 |
Now I need to retrieve the latest unique carId from system_devices that is connected to shopId 46.
So in this case, the results should be.
882 | 150 | regi4 | 2014-01-03 08:59:00 |
359 | 350 | regi1 | 2014-01-03 09:00:00 |
I now have the following query. This gives me the unique carId but not the latest unique carId. What should i change?
SELECT system_devices.id,
carId,
FROM link_devices_user
INNER JOIN system_devices
ON link_devices_user.deviceId = system_devices.id
WHERE link_devices_user.shopid = '46'
GROUP BY system_devices.carId
ORDER BY system_devices.lastUsed DESC
Try this:
SELECT s.*
FROM system_devices s LEFT JOIN system_devices s1
ON (s.carId = s1.carId AND s.lastUsed < s1.lastUsed)
INNER JOIN link_devices_user ld ON s.id = ld.deviceId
WHERE (s1.id IS NULL) AND (ld.shopId = 46)
ORDER BY carId
http://sqlfiddle.com/#!2/158d9/4
You can check the SQL fiddle sample provided that gives the results required.
Try this:
SELECT A.id, A.carId, A.registerId, A.lastUsed
FROM (SELECT sd.id, sd.carId, sd.registerId, sd.lastUsed
FROM system_devices sd
INNER JOIN link_devices_user ld ON sd.id = ld.deviceId
WHERE ld.shopId = 46
ORDER BY sd.id, sd.carId DESC
) AS A
GROUP BY A.id
OR
SELECT sd.id, sd.carId, sd.registerId, sd.lastUsed
FROM system_devices sd
INNER JOIN link_devices_user ld ON sd.id = ld.deviceId
INNER JOIN (SELECT id, MAX(carId) carId FROM system_devices GROUP BY id) A ON A.id = sd.id AND A.carId = sd.carId
WHERE ld.shopId = 46
ORDER BY sd.id

MYSQL Count not correct

SELECT
b.cID,
b.ID,
Count(r.userRead) AS readCount,
COUNT(DISTINCT r.userID) AS UserCount,
Count(c.userDownload) AS downloadCount,
COUNT(DISTINCT c.userID) AS userDownloadCount
FROM
book AS b
INNER JOIN book_event AS r ON r.bookID=s.ID AND r.bookRead = 1
INNER JOIN book_event as c ON c.bookID=s.ID AND c.bookDownload = 1
WHERE
b.cID = 1011
GROUP BY
b.ID
ORDER BY
b.ID DESC
this SQL query output (count's problem)
+-----------+-----+-----------+-----------------+--------------+-------------------+
| cID | ID | readCount | UserCount | downloadCount| userDownloadCount |
+-----------+-----+-----------+-----------------+--------------+-------------------+
| 1011 | 278 | 3168 | 67 | 3168 | 19 |
| 1011 | 272 | 9918 | 122 | 9918 | 41 |
| 1011 | 241 | 31694 | 99 | 31694 | 38 |
+-----------+-----+-----------+-----------------+--------------+-------------------+
3 rows in set
real value
+-----------+-----+-----------+-----------------+--------------+-------------------+
| cID | ID | readCount | UserCount | downloadCount| userDownloadCount |
+-----------+-----+-----------+-----------------+--------------+-------------------+
| 1011 | 278 | 133 | 67 | 24 | 19 |
| 1011 | 272 | 174 | 122 | 57 | 41 |
| 1011 | 241 | 299 | 99 | 106 | 38 |
+-----------+-----+-----------+-----------------+--------------+-------------------+
book_event (table)
+-----+--------+----------+--------------+
| ID | userID | userRead | userDownload |
+-----+--------+----------+--------------+
| 278 | 5169 | 1 | 0 |
| 278 | 5169 | 0 | 1 |
| ... | .... | . | . |
| 278 | 5628 | 1 | 0 |
| 278 | 5162 | 1 | 0 |
+-----+--------+----------+--------------+
I need to get the count grouped on two columns. readCount and downloadCount columuns is not correct but UserCount, userDownloadCount columuns value is correct.
how can i fix this problem?
This is because you have multiple read and download events on the same book, so your query is generating a cross product of events.
A good way to fix this is to aggregate the pieces of information separately. However, your query offers an easier solution. Just join to the book_event table once and then count the different events.
SELECT b.cID, b.ID,
sum(be.bookRead) as readCount,
count(distinct case when be.bookRead = 1 then be.userId end) as UserCount,
sum(be.userDownload) as downloadCount,
count(distinct case when be.userDownload = 1 then be.userId end) as userDownloadCount
FROM book AS b INNER JOIN
book_event be
on be.bookID = s.ID
WHERE b.cID = 1011
GROUP BY b.ID, b.cid
ORDER BY b.ID DESC
I added b.cid to the group by clause. It is good form to include all non-aggregated values in the SELECT clause in the GROUP BY. Other databases enforce this, and the rule is standard SQL.
I think your query is technically wrong (the group by syntax), but it is unrelated to your problem and works on MySQL.
The most common cause of count issues is not correctly accounting for nulls - but I don't think this is your problem either.
try this
SELECT
b.cID,
b.ID,
sum(distinct coalesce(r.userRead,0)) AS readCount,
count(DISTINCT r.userID) AS UserCount,
sum(distinct coalesce(c.userDownload,0)) AS downloadCount,
count(DISTINCT c.userID) AS userDownloadCount
FROM
book AS b
left JOIN book_event AS r ON r.bookID=s.ID AND r.bookRead = 1
left JOIN book_event as c ON c.bookID=s.ID AND c.bookDownload = 1
WHERE
b.cID = 1011
GROUP BY
b.cID, b.ID
ORDER BY
b.ID DESC