MySQL avg() and count() in one statement with group by - mysql

today I'm fighting with MySQL: I've got two tables, that contain records like that (actually there are more columns, but I don't think it's relevant):
Table Metering:
id, value
1000, 0.117
1000, 0.689
1001, 0.050
...
Table Res (there is no more than one record per id in this table):
id, number_residents
1001, 2
...
I try to get results in the following format:
number_residents, avg, count(id)
2, 0.1234, 456
3, 0.5678, 567
...
In words: I try to find out the average of the value-fields with the same number_residents. The id-field is the connection between the two tables. The count(id)-column should show how many ids have been found with that number_residents. The query I could come up with was the following:
select number_residents,count(distinct Metering.id),avg(value)
from Metering, Res
where Metering.id = Res.id
group by number_residents;
The results look like what I searched for but when I tried to validate them I became insecure. I tried it without the distinct at first but that leads to too high values in the count-column of the results.
Is my statement right to get what I want? I thought it might have to to something with the order of execution like asked here, but I actually can't find any official documentation on that...
Thanks for helping!

Judging by the table names, Res is the "parent" table and Metering us the "child" table - that is there are 0-n meterings for each residence.
You have use "old school" joins (and I mean old - the join syntax has been around for 25 years now), which are inner joins, meaning residences without meterings won't participate in the results.
Use an outer join:
select
number_residents,
count(distinct r.id) residences_count,
avg(value) average_value
from Res r
left join Metering m on m.id = r.id
group by number_residents
Although meterings.id = res.id, with a left join counting them may produce different results: I've changed the count to count residences, which for a left join means residences that don't have meterings still count.
Now, nulls (which are what you get from a left-joined table that doesn't have a matching row) don't participate in avg() - either for the numerator or denominator, if you want residences without any meterings to count when calcukating the average (as if they have a single zero metering for the purposes of dividing the total value), use this query:
select
number_residents,
count(distinct r.id) residences_count,
sum(value) / count(r.id) average_value
from Res r
left join Metering m on m.id = r.id
group by number_residents
Because res.id is never null, count(r.id) counts the number of meterings plus 1 for every residence without any meterings.

Related

MySQL Left-Join vs Join with subquery return different results

I wrote 2 queries expecting them to yield same results, yet they turned out to be different.
I would like to ask why they return different results?
I am more confident that the 1st query returns what I want, so how should I amend the 2nd query? Thx!
1st SQL query:
SELECT
Product.*,
Status.*,
Price.*
FROM Product
LEFT JOIN Status
ON Product.MarketplaceId = Status.ListingId
LEFT JOIN Price
ON Product.ProductId = Price.Id
LIMIT 15;
2nd SQL query:
SELECT
Product.*,
Status.*,
Price.*
FROM Product
LEFT JOIN Status
ON Product.MarketplaceId IN
(
SELECT ListingId FROM Status
)
LEFT JOIN Price
ON Product.ProductId IN
(
SELECT Id FROM Price
)
LIMIT 15;
Without seeing the data, I don't understand why a different result. However, if you intend to have only one per product, I would change the second query to use DISTINCT. If the subquery returns multiple rows for whatever the condition, it will return that many rows even if a single product.
Don't use IN ( SELECT ... ) if there is an alternative; it is often slower.
Don't use LEFT JOIN if the matching row in the 'right' table will always be there. It confuses readers.
The reason for different results is the lack of ORDER BY. (As Akina mentioned.) Removing the LIMIT would probably cause the two queries to deliver all the same rows, though probably in a different order.

SQL How to Count table for each fields

I have 3 tables which is:
Courses
courses_id
name
QnAs
qna_id
student_id
courses_id
name
question
Students
student_id
name
Now I'm trying to count how many qna's there are for each courses. How do i make the query?
I've tried doing this :
SELECT (SELECT COUNT(qna_id) AS Expr1
FROM QnAs) AS Count
FROM QnAs AS QnAs_1 CROSS JOIN
Courses
GROUP BY Courses.courses_id
It does counts how many QnA's there are but not for each Courses
The output i got is each Courses names and QnAs count number but what i want is the QnA's number for each of the Courses
It seems you merely want to aggregate QNAs by course ID:
select courses_id, count(*)
from qnas
group by courses_id
order by courses_id;
Along with the course names:
select c.course_id, c.name, coalesce(q.cnt, 0) as qna_count
from courses c
left join
(
select courses_id, count(*) as cnt
from qnas
group by courses_id
) q on q.course_id = c.course_id
order by c.course_id;
Why not just use GROUP BY?
SELECT q.courses_id, COUNT(qna_id) as cnt
FROM QnAs q
GROUP BY q.courses_id;
This is not an answer, but just an explanation what your query does.
In your own query you first cross join all QnAs with all courses for no apparent reason, thus getting all possible combinations. So with two courses, each with three QNAs (that makes six QNAs in total), you'd construct 2 x 6 = 12 rows.
For each of these rows you select the total number of rows in the QNA table, which is six in above example. So you'd select 12 rows, all showing the number 6.
But then you group by course ID, thus ending up with two rows only in my example. You should apply an aggregate function on your subquery, e.g. MAX or SUM, but you don't, which makes your query invalid (because you are dealing with many rows, but treat this as if it were a single value). MySQL however silently applies ANY_VALUE, so your query becomes:
SELECT
ANY_VALUE( (SELECT COUNT(*) FROM QnAs) ) AS Count
FROM QnAs AS QnAs_1
CROSS JOIN Courses
GROUP BY Courses.courses_id;
I hope this explanation helps you understand how joins and aggregation work. You may want to set ONLY_FULL_GROUP_BY mode (https://dev.mysql.com/doc/...) in order to have MySQL report the syntax error instead of silently "fixing" the query by applying ANY_VALUE.

MySQL Outer Join Giving Max Join Size Error

I thought I knew how to do a simple outer join, but it appears that I am wrong. I am new to MySQL, but I do have Oracle experience.
I have two tables that I want to query. The first table is a members table. The second table is called purchases. Purchases contains a row for each item a member purchases.
The members table contains a little more than 2700 rows. The purchases table contains a little less than 130,000 rows.
I eventually want to get a list of all members with a count of their unique item purchases. Here is my query:
select mem.member_id
,mem.name
,count(distinct pur.item_id)
from members mem
left outer join purchases pur on mem.member_id = pur.member_id
I get the following error when I execute the query:
1104 - The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET SQL_MAX_JOIN_SIZE=# if the SELECT is okay
The MAX Join Size is currently set to 7 million.
What am I not understanding here?
Your query looks fine, but if that's obviously failing, you might try the following
select
m.member_id,
m.`name`,
coalesce( cnts.UniqItems, 0 ) as UniqItems
from
members m
left join ( select p.member_id, count( distinct p.item_id ) as UniqItems
from purchases p
group by p.member_id ) cnts
on m.member_id = cnts.member_id
After writing, I think the problem may be the reserved word "NAME" for the column and should probably just need to be wrapped in tic marks to differentiate the column vs reserved word.

MySQL Left Joins

EDIT: OK, think I need to be clearer - I'd like the result to show all the 'names' that appear in the table acme, against the counts (if any) from the results table. Hope that makes sense?
Having a huge issue and my brain isn't working as it should.
All I want to do is, in a single statement via a join, count the number of rows for a common field.
SELECT name, COUNT(name) as Count FROM acme
SELECT name, COUNT(name) as Total FROM results
I'm sure it should be something like this...
SELECT acme.name, COUNT(acme.name) As Count,
COUNT(results.name) as Total
FROM acme
LEFT JOIN results ON acme.name = results.name
GROUP BY name
ORDERY BY name
But it doesn't bring back the correct counts.
Thoughts, where am I going wrong...this, I know, will be very very obvious.
H.
From your feedback, this will get what you want. You need to FIRST get unique names / counts from the "ACME" file first... THEN join that to the results table for count of records from that, otherwise, you would end up with a Cartesian result of counts. If ACME had Name "X" 5 times and Results had "X" 20 times, your total would be 100. The query below will actually result with a single row showing "X", 5, 20 which is what it appears you are looking for.. (for however many names exist in ACME).
I've changed to a LEFT join in case there are names in the ACME table that DO NOT exist in the RESULTS table, it won't drop them from your final answer
select
JustACME.Name,
JustACME.NameCount,
COALESCE( COUNT( * ), 0 ) as CountFromResultsTable
from
( select a.Name
count(*) as NameCount
from
acme a
group by
a.Name ) JustACME
LEFT JOIN results r
on JustACME.Name = r.Name
group by
JustACME.Name
It looks like it's because of the join, it's screwing with your counts. Try running the join with SELECT * FROM... and look at the resulting table. The problem should be obvious from there. =D
Yes, your join (inner or outer, doesn't matter) is messing with your results.
In fact, it is likely returning the product of rows with the same name, rather than the sum.
What you want to do is sum the rows from the first table, sum the rows from the second table, and join that.
Like this:
Select name, a.count as Count, r.count as Total
From (select name, count(*) from acme group by name) a
Left join (select name, count(*) from results group by name) r using (name)
I do not see why you forbid using two statements this just complicates everything.
The only reason I see for this is to get the two results into one answer.
I do not know if the latter would work but I would try this:
SET #acount = (SELECT count(DISTINCT name) FROM acme);
SELECT count(DISTINCT name) as Total, #acount as Count FROM results
I would post this as one query and (hopefully) get back the correct results. Let me note, that it is not clear from you question if you want to know how often every name doubles or if you want to count unique names.

Multiple mysql joins

I have three tables that I need get information from, 1 table has the information in and the other two hold information that i need to count.
so the first tables structure is:
tbl_img
img_id
img_name
tbl_comments
comment_id
img_id
comment
tbl_vote
vote_id
logo_id
I want the results to have the count of comments and votes that relate to each logo.
I have a bit of the query which is for the count of comments, but have no idea for the syntax for the second join.
SELECT l.img_id, l.img_name, COUNT(c.comment_id) AS comment_count
FROM tbl_images as l
LEFT OUTER JOIN tbl_comments AS c USING (img_id);
Can anyone help?
how about this :
SELECT l.img_id, l.img_name,
(SELECT COUNT(*) FROM tbl_comments c WHERE i.img_id = c.img_id ) AS comment_count,
(SELECT COUNT(*) FROM tbl_vote v WHERE i.img_id = v.img_id ) AS vote_count
FROM tbl_images i
Sounds like you need two queries for this: One for counting the votes, and one for counting the comments.
As far as I know, COUNT counts result rows, and joins create result rows to display all allowed permutations of joined tables.
Assuming you have I entries, each with J comments and K votes, you would receive J*K rows for each entry after joins, and COUNTs would both return that J*K instead of the correct amount.
I do not remember if you can do inner queries in MySQL, but that would be the way to go.
(See #Kevin Burtons answer)