问题
I have two tables:
Points ->
id bigint(20) NO PRI NULL auto_increment
created_at datetime NO NULL
ip varchar(255) NO NULL
item_id bigint(20) NO MUL NULL
updated_at timestamp YES NULL
and Items ->
id bigint(20) NO PRI NULL auto_increment
author varchar(255) NO NULL
created_at datetime NO NULL
description varchar(255) NO NULL
link varchar(255) NO NULL
source varchar(255) NO NULL
title varchar(180) NO NULL
url_encoded varchar(255) NO UNI NULL
updated_at timestamp YES NULL
I want to join them hopefully in one query so I will get item.*
and the total of how many points are relative to them. I also want to do this only for the items that has any points created for them in the last 24 hours.
This is my query so far:
SELECT `items`.*, COUNT(points.item_id) as points
FROM `items`
INNER JOIN `points` ON `items`.`id` = `points`.`item_id`
WHERE `points`.`created_at` > '2013-03-16 16:00:14'
ORDER BY points DESC
LIMIT 30;
Unfortunately it gives me only one row when it should be two with two points when it should be one. In my database there is two items and one point for each of them. Please help me fix this and understand how I can improve my query to get both the results.
回答1:
You need to use GROUP BY
to explain what groupings to count based on. Without GROUP BY
you just get a single group of the entire result set, as you saw.
In standard SQL it is necessary to include in the GROUP BY
clause every non-aggregate expression that's included in the SELECT
clause, but MySQL lets you get away with not doing this, allowing an expression like the following. (At least, when not in strict mode; I'm not sure if strict mode strengthens this requirement to match standard SQL)
SELECT `items`.*, COUNT(1) AS points
FROM `items` INNER JOIN `points` ON `items`.`id` = `points`.`item_id`
WHERE ...
GROUP BY `items`.`id`
Assuming that items.id
is the primary key of this table, and so it won't appear in more than one row of items
, this should have the desired effect.
Once you introduce GROUP BY
it's important to understand the difference between the WHERE
and HAVING
clauses. The former applies the condition before the group and aggregates are applied, while the latter applies afterwards. This means you must use HAVING
if you want to do a conditional based on that count; the WHERE
clause in your initial example will apply before the aggregate, so the result will be the count of points created after the given date.
回答2:
SELECT i.*, count(*) AS point_ct
FROM items i
JOIN points p ON p.item_id = i.id
GROUP BY i.id
HAVING MAX(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 day)
ORDER BY point_ct DESC
LIMIT 30;
I quote the manual concerning the GROUP BY clause:
MySQL extends the use of
GROUP BY
to permit selecting fields that are not mentioned in theGROUP BY
clause. If you are not getting the results that you expect from your query, please read the description ofGROUP BY
found in
Section 12.17, “Functions and Modifiers for Use with GROUP BY Clauses”.
Standard SQL would require to list all ungrouped SELECT
items in GROUP BY
.
However, the standard also defines that "functionally dependent" columns are covered automatically. Since we are grouping by id
which is the primary key, this ends up to be standard SQL after all (except for LIMIT 30
, which would be FETCH FIRST 30 ROWS ONLY
in standard SQL).
来源:https://stackoverflow.com/questions/15464564/join-two-tables-mysql-one-to-many-relationship