MYSQL Count not correct - mysql

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

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

i want to show an one particular blog have how much likes and how much comment using sql query

Blog table:
| bid | btitle |
| 29 | ...... |
| 38 | ...... |
likes table:
| lid | bid |
| 1 | 29 |
| 2 | 29 |
| 3 | 29 |
| 4 | 38 |
| 5 | 38 |
comment table
| commid | bid |
| 1 | 29 |
| 2 | 29 |
| 3 | 38 |
I had tried the following query but that will not work for me:
SELECT blog.bid,blog.btitle,COUNT(likes.lid) AS likecnt,COUNT(comment.comid) AS commentcnt FROM blog,likes,comment WHERE blog.bid=likes.bid AND blog.bid=comment.bid GROUP BY blog.bid
i want output like:
| bid | btitle | likecnt | commentcnt |
| 29 | ...... | 3 | 2 |
| 38 | ...... | 2 | 1 |
You can do left join with separate aggregation :
select b.bid, b.btitle,
coalesce(l.likecnt, 0) as likecnt,
coalesce(c.commentcnt, 0) as commentcnt
from blog b left join
(select l.bid, count(*) as likecnt
from likes l
group by l.bid
) l
on l.bid = b.bid left join
(select c.bid, count(*) as commentcnt
from comment c
group by c.bid
) c
on c.bid = l.bid;
If you want only matching bids the use INNER JOIN instead of LEFT JOIN & remove COALESCE().
Under many circumstances, correlated subqueries may be the fastest solution:
select b.bid, b.btitle,
(select count(*) from likes l where l.bid = b.bid) as num_likes,
(select count(*) from comment c where c.bid = b.bid) as num_comments
from blog b;
When is this a win performance wise. First, you want indexes on likes(bid) and comments(bid). With those indexes, it might be the fastest approach for your query.
It is particularly better if you have a where clause filtering the blogs in the outer query. It only has to do the counts for the blogs in the result set.
Use proper joins and count DISTINCT values because multiple joins increase the number of returned rows:
SELECT b.bid, b.btitle,
COUNT(DISTINCT l.lid) AS likecnt,
COUNT(DISTINCT c.comid) AS commentcnt
FROM blog b
LEFT JOIN likes l ON b.bid = l.bid
LEFT JOIN comment c ON b.bid = c.bid
GROUP BY b.bid, b.btitle
See the demo.
I use LEFT joins just in case there are no comments or likes for a post.
Results:
| bid | btitle | likecnt | commentcnt |
| --- | ------ | ------- | ---------- |
| 29 | ...... | 3 | 2 |
| 38 | ...... | 2 | 1 |

Only GROUP_CONCAT active functions

I have four tables, a clients, persons, client_functions and functions table.
I wrote this query:
SELECT
P.number,
P.first_name
GROUP_CONCAT(F.description) AS Functions
FROM clients AS C
LEFT JOIN persons AS P ON P.id=C.id
LEFT JOIN client_functions as CF ON CF.client_id=C.id
LEFT JOIN functions AS F ON F.id=CF.function_id
WHERE P.person_type = 'client' AND P.company_id = 3
GROUP BY
P.number,
P.first_name
In my GROUP_CONCAT() i only want to group F.description if CF.archived = 0. Does anybody has an idea on how i can put a condition on the GROUP_CONCAT?
Current query results in:
--------------------------------------------
| 93 | Jan Lochtenberg | PV,PV,PV,PV |
| 94 | Chris van Eijk | VP-I,VP,PV |
| 95 | Gertrude Irene | VP-I,PV,PV,PV |
| 96 | Wiekert Jager | VP-I,PV |
| 97 | Antonius Kode | VP,VP-I,VP |
| 98 | HansLelie | PV,PV,PV |
---------------------------------------------
But i only want to see the active functions
--------------------------------------------
| 93 | Jan Lochtenberg | PV |
| 94 | Chris van Eijk | VP-I,VP,PV |
| 95 | Gertrude Irene | VP-I,PV |
| 96 | Wiekert Jager | VP-I,PV |
| 97 | Antonius Kode | VP,VP-I,VP |
| 98 | HansLelie | PV |
---------------------------------------------
Your where is undoing some of your left joins. In fact, you don't need the clients table at all. Then you can put the filtering condition on functions in the ON clause:
SELECT P.number, P.first_name, P.last_name,
GROUP_CONCAT(F.description) AS Functions
FROM persons P LEFT JOIN
client_functions CF
ON CF.client_id = p.id LEFT JOIN
functions F
ON F.id = CF.function_id AND cf.archived = 0
WHERE P.person_type = 'client' AND P.company_id = 3
GROUP BY P.number, P.first_name, P.last_name;
In my GROUP_CONCAT() i only want to group F.description if CF.archived = 0
Translated to SQL:
GROUP_CONCAT(IF(CF.archived = 0, F.description, NULL))
The GROUP_CONCAT() function ignores the NULL values. It returns, however, NULL if there isn't any not-NULL value to work with.

How to simplify a complex mysql full outer join

I am trying to create a full outer join in Mysql. I found several answers to the basic question, and I'm using "union" to make it work. However, I was unable to get the syntax correct without resorting to creating a few temporary tables. I've tried to generate the query without the tables, but I was never able to get the results to include the entries with a null partner_id.
Here is a reduced set of the data, already filtered by meeting_id:
+-----+---------+--------+------------+------------+
| pid | first | gender | meeting_id | partner_id |
+-----+---------+--------+------------+------------+
| 2 | Vicki | F | 74 | NULL |
| 54 | Fazal | M | 74 | 4 |
| 4 | Lisa | F | 74 | 54 |
| 10 | Rod | M | 74 | 57 |
| 57 | Kellee | F | 74 | 10 |
| 11 | Jake | M | 74 | 55 |
| 55 | Rosa | F | 74 | 11 |
| 47 | Ralph | M | 74 | 46 |
| 46 | Holly | F | 74 | 47 |
| 40 | Wes | M | 74 | 12 |
| 12 | Lori | F | 74 | 40 |
| 5 | Richard | M | 74 | 6 |
| 6 | Rita | F | 74 | 5 |
| 15 | John | M | 74 | 16 |
| 16 | Corie | F | 74 | 15 |
+-----+---------+--------+------------+------------+
My original query looked like this:
set #mtg=74;
select
a.pid,
concat(a.first, ' ', a.last) as guy,
a.issub as guysub,
b.pid,
concat(b.first, ' ', b.last) as gal,
b.issub as galsub,
b.partner_id
from
scheduled_players a
left outer join
scheduled_players b
on a.partner_id = b.pid
where
a.gender = 'M' and a.meeting_id = #mtg and b.meeting_id = #mtg
union
select
a.pid,
concat(a.first, ' ', a.last) as guy,
a.issub as guysub,
b.pid,
concat(b.first, ' ', b.last) as gal,
b.issub as galsub,
b.partner_id
from
scheduled_players a
left outer join
scheduled_players b
on b.partner_id = a.pid
where
a.gender = 'M' and a.meeting_id = #mtg and b.meeting_id = #mtg
;
That query did not return the single entry with a null partner_id. I read a number of answers on StackOverflow and it seemed as if the where clause could cause the outer join to revert to an inner join. In my case, I did not see how this could happen, but to test this, I decided to create temporary tables to contain the 'where' clause elements. I needed to create 2 temporary tables for each of the 'guys' and 'gals', since I had the tables 2 times in the query. The results are here:
set #mtg=74;
create temporary table if not exists
meeting_guys as select * from scheduled_players
where meeting_id = #mtg and gender='M';
create temporary table if not exists
meeting_gals as select * from scheduled_players
where meeting_id = #mtg and gender='F';
create temporary table if not exists
meeting_guys2 as select * from scheduled_players
where meeting_id = #mtg and gender='M';
create temporary table if not exists
meeting_gals2 as select * from scheduled_players
where meeting_id = #mtg and gender='F';
select
a.pid,
concat(a.first, ' ', a.last) as guy,
a.issub as guysub,
b.pid,
concat(b.first, ' ', b.last) as gal,
b.issub as galsub,
b.partner_id
from
meeting_guys a
left outer join
meeting_gals b
on a.partner_id = b.pid
union
select
a.pid,
concat(a.first, ' ', a.last) as guy,
a.issub as guysub,
b.pid,
concat(b.first, ' ', b.last) as gal,
b.issub as galsub,
b.partner_id
from
meeting_guys2 a
right outer join
meeting_gals2 b
on b.partner_id = a.pid
;
It turned out this worked, and I received the results I was expecting (I removed the last names since these are real people):
+------+---------+--------+------+--------+--------+------------+
| pid | guy | guysub | pid | gal | galsub | partner_id |
+------+---------+--------+------+--------+--------+------------+
| 54 | Fazal | 0 | 4 | Lisa | 0 | 54 |
| 10 | Rod | 0 | 57 | Kellee | 0 | 10 |
| 11 | Jake | 0 | 55 | Rosa | 0 | 11 |
| 47 | Ralph | 0 | 46 | Holly | 0 | 47 |
| 40 | Wes | 0 | 12 | Lori | 0 | 40 |
| 5 | Richard | 0 | 6 | Rita | 0 | 5 |
| 15 | John | 0 | 16 | Corie | 0 | 15 |
| NULL | NULL | NULL | 2 | Vicki | 0 | NULL |
+------+---------+--------+------+--------+--------+------------+
I was able to get the results I was looking for, but I don't understand why the previous query did not work. Fortunately, I have a working solution, but I'd really like to find out if there is a better, more optimal way.
Firstly to point out that this is untested so you might just need to tweak it but you sound more than capable of fixing the odd error. If you do need me to clarify why I did something or you need me to fix something, just say the word.
To explain why your first attempt eliminated the null records unexpectedly, you are right that it is your where clause that is doing it. for the left join, instead of a.meeting_id = #mtg and b.meeting_id = #mtg you would use `a.meeting_id = #mtg and (b.meeting_id = #mtg or b.meeting_id is null)' obviously for the right join you would check for the null in the left table.
As for an alternate solution, I have used a temp table to limit the result set to just the matching meeting_id's early (for performance) in case your table is large, and then I filter for M/F in the derived tables.
Hope it helps you.
set #mtg=74;
create temporary table if not exists
meeting as
select
pid,
concat(first, ' ', last) as full_name,
issub,
partner_id,
meeting_id,
gender
from scheduled_players
where meeting_id = #mtg;
select
M.pid,
M.full_name as guy,
M.issub as guysub,
F.pid,
F.full_name as gal,
F.issub as galsub,
F.partner_id
from
(select * from meeting where gender = 'M') M
left outer join (select * from meeting where gender = 'F') F
on M.partner_id = F.pid
UNION
select
M.pid,
M.full_name as guy,
M.issub as guysub,
F.pid,
F.full_name as gal,
F.issub as galsub,
F.partner_id
from
(select * from meeting where gender = 'M') M
right outer join (select * from meeting where gender = 'F') F
on F.partner_id = M.pid
EDIT
If performance isn't an issue then maybe it is just simpler to forget the temp table altogether and refer to the table directly in the derived tables as;
select concat(first, ' ', last) as full_name, * from scheduled_players where gender = 'M' and meeting_id = #mtg
select concat(first, ' ', last) as full_name, * from scheduled_players where gender = 'F' and meeting_id = #mtg
You could also create a single temp table and then insert and update that in separate queries.
Whatever works for you at the end of the day.

Grouping from highest to lowest

I have a table like this:
+---------+--------------+---------+
| visitty | specialty | doctors |
+---------+--------------+---------+
| 1 | oncology | 3611 |
| 1 | neurology | 1931 |
| 1 | rheumatology | 1471 |
| 0 | oncology | 35 |
| 0 | rheumatology | 28 |
| 0 | neurology | 20 |
+---------+--------------+---------+
The above table was created by ordering the field doctors
Now, I'm trying to get the following result:
+---------+--------------+---------+
| visitty | specialty | doctors |
+---------+--------------+---------+
| 1 | oncology | 3611 |
| 0 | oncology | 35 |
| 1 | neurology | 1931 |
| 0 | neurology | 20 |
| 1 | rheumatology | 1471 |
| 0 | rheumatology | 28 |
+---------+--------------+---------+
Is there any way to do this?
In reply to Adil Miedl ... these were the criteria used in the query:
(PS: I think it can be a little confusing to underestand without the referenced tables)
SELECT
su.visitty, cs.specialty, COUNT(*) doctors
FROM
contacts c
INNER JOIN contact_groups ccgc ON c.id_contact = ccgc.id_contact
AND ccgc.status = 1
INNER JOIN groups ccg ON ccg.id_ccenter_groups = ccgc.id_ccenter_groups
AND ccg.status = 2
INNER JOIN distribuition ccd ON ccd.id_ccenter_groups = ccg.id_ccenter_groups
AND ccd.status = 2
INNER JOIN cds_contacts sc ON c.id_cds_account = sc.id_cds_account
LEFT JOIN cds_contacts_territories AS sct ON sc.id_contact = sct.id_contact
INNER JOIN cds_usuarios_territories AS sut ON sut.id_territory = sct.id_territory
INNER JOIN cds_usuarios AS su ON su.id_user = sut.id_user
INNER JOIN contact_specialties cs ON c.id_contact = cs.id_contact
and cs.status = 1
and cs.srsmain = 'Y'
WHERE
c.contact_type = 'Doctor'
AND ccd.release_date BETWEEN '2013-01-01 00:00:00' AND '2013-12-31 23:59:59'
GROUP by visitty , cs.specialty
ORDER BY doctors DESC;
You seem to want to order the specialties by the total of doctors in them. The query needs to calculate this value before doing the ordering:
select t.visitty, t.specialty, t.doctors
from table t join
(select specialty, sum(doctors) as numdoctors
from table t
group by specialty
) tsum
on t.specialty = tsum.specialty
order by tsum.doctors desc, tsum.specialty, t.doctors desc;