Getting most recent row per group in MySQL [duplicate] - mysql

This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
Closed 3 years ago.
I am trying to get all most recent Paper.material's for each Book.isbn. I am using left to get the base of each Book.isbn, since they can have a suffix that indicates the revision, for example : 'X1726748384Z1' or 'X1837943875Z2' etc.. I tried over partition by but it didn't work because MySQL 5.7 doesn't support this. How can I make this work?
GOAL
For each Book.isbn as X:
select left(Book.isbn, 11) BaseISBN, Paper.material, Book.date
from Book
join Page on Book.id = Page.book_id
join Paper on Page.id = Paper.page_id
where Book.name = 'world'
and left(Book.isbn, 11) = X <--
and Page.name = 'test'
order by Book.date desc
limit 1
QUERY
select left(Book.isbn, 11) BaseISBN, Paper.material, max(Book.date)
from Book
join Page on Book.id = Page.book_id
join Paper on Page.id = Paper.page_id
where Book.name = 'world'
and left(Book.isbn, 11) in('X1726748384', 'X1837943875')
and Page.name = 'test'
group by left(Book.isbn, 11);
RETURNS
| Book.isbn | Paper.material | max(Book.date) |
|-------------|----------------|----------------------|
| X1726748384 | 10134248300B | 2018-01-01T00:00:00Z |
| X1837943875 | 10985782343F | 2021-01-01T00:00:00Z |
etc...
SHOULD RETURN
| Book.isbn | Paper.material | max(Book.date) |
|-------------|----------------|----------------------|
| X1726748384 | 10985782343E | 2018-01-01T00:00:00Z |
| X1837943875 | 10985782343H | 2021-01-01T00:00:00Z |
etc...
SQL FIDDLE
And DDLs of same:
CREATE TABLE `Book` (
`id` INT NOT NULL AUTO_INCREMENT,
`isbn` varchar(255),
`name` varchar(255),
`date` DATETIME,
PRIMARY KEY (`id`)
);
CREATE TABLE `Page` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` varchar(255),
`book_id` INT NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `Paper` (
`id` INT NOT NULL AUTO_INCREMENT,
`material` varchar(255),
`page_id` INT NOT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `Page` ADD CONSTRAINT `Page_fk0` FOREIGN KEY (`book_id`) REFERENCES `Book`(`id`);
ALTER TABLE `Paper` ADD CONSTRAINT `Paper_fk0` FOREIGN KEY (`page_id`) REFERENCES `Page`(`id`);
INSERT INTO `Book` (`isbn`, `name`, `date`) VALUES
('X1234234403', 'hello', '2016-01-01'),
('X1726748384', 'world', '2017-01-01'),
('X1726748384Z1', 'world', '2018-01-01'),
('X1837943875', 'world', '2019-01-01'),
('X1837943875Z1', 'world', '2020-01-01'),
('X1837943875Z2', 'world', '2021-01-01');
INSERT INTO `Page` (`name`, `book_id`) VALUES
('bla', 1),
('test', 2),
('test', 3),
('test', 4),
('test', 5),
('test', 6);
INSERT INTO `Paper` (`material`, `page_id`) VALUES
('10134248300A', 1),
('10134248300B', 2),
('10134248300C', 2),
('10985782343D', 3),
('10985782343E', 3),
('10985782343F', 4),
('10985782343G', 5),
('10985782343H', 6);

'Etc.' is not very useful when trying to understand what should and shouldn't be included in a result.
Nevertheless, perhaps you're after something a bit like this...
SELECT b.*
, z.material
FROM book b
JOIN
( SELECT LEFT(isbn,11) isbn11
, MAX(date) date
FROM book
WHERE
( isbn LIKE 'X1726748384%'
OR isbn LIKE 'X1837943875%'
)
AND name = 'world'
GROUP
BY isbn11
) x
ON x.isbn11 = LEFT(b.isbn,11)
AND x.date = b.date
JOIN page y
ON y.book_id = b.id
JOIN paper z
ON z.page_id = y.id
WHERE y.name = 'test';
+----+---------------+-------+---------------------+--------------+
| id | isbn | name | date | material |
+----+---------------+-------+---------------------+--------------+
| 3 | X1726748384Z1 | world | 2018-01-01 00:00:00 | 10985782343D |
| 3 | X1726748384Z1 | world | 2018-01-01 00:00:00 | 10985782343E |
| 6 | X1837943875Z2 | world | 2021-01-01 00:00:00 | 10985782343H |
+----+---------------+-------+---------------------+--------------+
3 rows in set (0.00 sec)

Related

MySQL Correct Query to Obtain Sum and Count of Columns

I have the following query.
SELECT
b.id, b.prid, b.title,
p.fullname,
m.enroll, m.point,
SUM(m2.enroll) enrolls, COUNT(m2.point) points,
COUNT(DISTINCT r.id) reviewal
FROM books b
JOIN person p ON p.id = b.prid
LEFT JOIN promotes m ON m.bkid = b.id AND m.prid = p.id
LEFT JOIN promotes m2 ON m2.bkid = b.id
LEFT JOIN reviews r ON r.bkid = b.id
GROUP BY b.id
ORDER BY b.id ASC;
**Output**
| id | prid | title | fullname | enroll | point | enrolls | points | reviewal |
| --- | ---- | --------- | -------- | ------ | ----- | ------- | ------ | -------- |
| 1 | 1 | Physics | Jade | 1 | 2 | 1 | 1 | 0 |
| 2 | 2 | Chemistry | Jack | 1 | 1 | 4 | 4 | 2 |
| 3 | 3 | Maths | John | 1 | | 1 | 1 | 1 |
However, the query is returning wrong results for the enrolls and points columns. The correct results for the two columns are:
| id | enrolls | points |
| --- | ------- | ------ |
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 1 |
What should be the correct query?
Table structure and the data (DB FIDDLE):
CREATE TABLE `books` (
`id` int(10) UNSIGNED NOT NULL,
`prid` int(10) UNSIGNED NOT NULL,
`title` varchar(100) NOT NULL
);
INSERT INTO `books` (`id`, `prid`, `title`) VALUES
(1, 1, 'Physics'), (2, 2, 'Chemistry'), (3, 3, 'Maths');
CREATE TABLE `person` (
`id` int(10) UNSIGNED NOT NULL,
`fullname` varchar(100) NOT NULL
);
INSERT INTO `person` (`id`, `fullname`) VALUES
(1, 'Jade'), (2, 'Jack'), (3, 'John');
CREATE TABLE `promotes` (
`bkid` int(11) UNSIGNED NOT NULL,
`prid` int(11) UNSIGNED NOT NULL,
`enroll` tinyint(1) DEFAULT 1,
`point` tinyint(1) DEFAULT NULL
);
INSERT INTO `promotes` (`bkid`, `prid`, `enroll`, `point`) VALUES
(1, 1, 1, 2), (2, 1, 1, 3), (2, 2, 1, 1),
(3, 1, NULL, 0), (3, 2, 0, NULL), (3, 3, 1, NULL);
CREATE TABLE `reviews` (
`id` int(10) NOT NULL,
`prid` int(10) UNSIGNED NOT NULL,
`bkid` int(10) UNSIGNED NOT NULL,
`review` varchar(500) NOT NULL
);
INSERT INTO `reviews` (`id`, `prid`, `bkid`, `review`) VALUES
(1, 1, 2, 'Nice'), (2, 1, 3, 'Thinking'), (3, 2, 2, 'Try it');
ALTER TABLE `books` ADD PRIMARY KEY (`id`),
ADD KEY `book_prid_fk` (`prid`);
ALTER TABLE `person` ADD PRIMARY KEY (`id`);
ALTER TABLE `promotes` ADD PRIMARY KEY (`bkid`,`prid`),
ADD KEY `promotes_prid_fk` (`prid`);
ALTER TABLE `reviews` ADD PRIMARY KEY (`id`),
ADD KEY `reviews_prid_fk` (`prid`), ADD KEY `reviews_bkid_fk` (`bkid`);
ALTER TABLE `books`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
ALTER TABLE `person`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
ALTER TABLE `reviews`
MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
ALTER TABLE `books`
ADD CONSTRAINT `book_prid_fk` FOREIGN KEY (`prid`) REFERENCES `person` (`id`);
ALTER TABLE `promotes`
ADD CONSTRAINT `promotes_bkid_fk` FOREIGN KEY (`bkid`) REFERENCES `books` (`id`),
ADD CONSTRAINT `promotes_prid_fk` FOREIGN KEY (`prid`) REFERENCES `person` (`id`);
ALTER TABLE `reviews`
ADD CONSTRAINT `reviews_bkid_fk` FOREIGN KEY (`bkid`) REFERENCES `books` (`id`),
ADD CONSTRAINT `reviews_prid_fk` FOREIGN KEY (`prid`) REFERENCES `person` (`id`);
The enroll column in the promotes table may contain a value from null, 0, or 1. Similarly, the point column value could be either null or 1-5.
Try doing the aggregation on a subquery, because the aggregation comes after the JOIN condition , that's why your values multiplies.
Try:
SELECT b.id,
b.prid,
b.title,
p.fullname,
m.enroll,
m.point,
m2.enrolls,
m2.points,
COUNT(DISTINCT r.id) reviewal
FROM books b
JOIN person p ON p.id = b.prid
LEFT JOIN promotes m ON m.bkid = b.id AND m.prid = p.id
LEFT JOIN (select bkid,
SUM(enroll) enrolls,
COUNT(point) points
from promotes
group by bkid
) as m2 ON m2.bkid = b.id
LEFT JOIN reviews r ON r.bkid = b.id
GROUP BY b.id
ORDER BY b.id ASC
https://dbfiddle.uk/Q-cPIMoD

MySql: how to get the desired result

I've a table like this:
CREATE TABLE `base_build_floor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`build_no` varchar(64) NOT NULL,
`build_name` varchar(64) DEFAULT NULL,
`floor_no` varchar(64) DEFAULT NULL,
`floor_name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
)
and insert some data:
INSERT INTO `base_build_floor` VALUES ('41', 'BUILD40210011', 'A', null, null);
INSERT INTO `base_build_floor` VALUES ('42', 'BUILD40210012', 'B', null, null);
INSERT INTO `base_build_floor` VALUES ('43', 'BUILD40210013', 'C', null, null);
INSERT INTO `base_build_floor` VALUES ('44', 'BUILD40210013', 'C', 'FLOOR40210002', 'C1');
INSERT INTO `base_build_floor` VALUES ('45', 'BUILD40210013', 'C', 'FLOOR40210003', 'C2');
INSERT INTO `base_build_floor` VALUES ('46', 'BUILD40210012', 'B', 'FLOOR40210004', 'B1');
the table is about a build-floor table, first you should make a building, then, a building can has no or some floors. the A building has no floor, the B building has one floor named B1, the C building has two floors named C1 and C2, I want to get the result as below:
41 BUILD40210011 A null null
44 BUILD40210013 C FLOOR40210002 C1
45 BUILD40210013 C FLOOR40210003 C2
46 BUILD40210012 B FLOOR40210004 B1
it means that, if a building has no floors, then get it, while if a building has any one floor, the building itself should not be got, so how to write the mysql?I've tried to use Subquery but doesn't work
I've try like this :
SELECT
b.*
FROM
base_build_floor b
WHERE
b.floor_no IS NOT NULL
OR (
b.floor_no IS NULL
AND b.build_no NOT IN (
SELECT
GROUP_CONCAT(nostr)
FROM
(
SELECT
concat("'", f.build_no, "'") as nostr
FROM
base_build_floor f
WHERE
f.floor_no IS NOT NULL
GROUP BY
f.build_no
) t
)
)
but I get all the data
With NOT EXISTS:
select t.* from base_build_floor t
where t.floor_no is not null
or not exists (
select 1 from base_build_floor
where build_no = t.build_no and floor_no is not null
)
See the demo.
Results:
| id | build_no | build_name | floor_no | floor_name |
| --- | ------------- | ---------- | ------------- | ---------- |
| 41 | BUILD40210011 | A | | |
| 44 | BUILD40210013 | C | FLOOR40210002 | C1 |
| 45 | BUILD40210013 | C | FLOOR40210003 | C2 |
| 46 | BUILD40210012 | B | FLOOR40210004 | B1 |
This query would be much simpler if you had normalized tables. Ideally, you would have a buildings table with building id, no, and name, and a floors table with building id, floor no, and floor name. Then you could just join the two tables. Since that's not the case, we can basically extract the building and floor sub-tables from the main one and join them like this:
SELECT
b.build_no,
b.build_name,
f.floor_no,
f.floor_name
FROM
(SELECT DISTINCT build_no, build_name
FROM base_build_floor) b
LEFT OUTER JOIN
(SELECT *
FROM base_build_floor
WHERE floor_no IS NOT NULL) f ON b.build_no = f.build_no

Get Users available based on validation of status, id, and time spread across 3 tables

Lets say I want to pull a list of users(From TableA) who:
are currently within Status = 2
and
currently not in TableB,
OR
are in TableB.UserId AND (TableB.AppId > 1year) by comparing the dates between TableC.id's CreatedDate and with the CurrentDate.
Current Schema Setup...
TableA (UserID)
--------------------------------------
id | fName | lName | Status | CreatedDate
1 | John | Doe | 2 | 2017-03-02 06:31:15.482
2 | Marry | Jane | 2 | 2017-05-03 16:43:56.937
3 | William | Thompson | 4 | 2017-06-15 13:12:32.219
4 | Timothy | Limmons | 2 | 2017-09-27 01:52:42.842
TableB
--------------------------------------
id | AppID | UserID | CreatedDate
1 | 2 | 1 | 2019-04-16 23:21:56.099
2 | 3 | 4 | 2019-08-03 04:32:18.472
TableC (AppID)
--------------------------------------
id | Title | CreatedDate
1 | ToDo List | 2017-03-09 22:45:12.907
2 | Magic Marshmellows | 2018-11-14 07:01:04.050
3 | Project Falcon | 2019-07-23 14:22:44.837
The info above should pull users from TableA with the id's of 1 and 2.
Marry has not been paired with an App, and is therefor available
John is paired with the App Magic Marshmellows, but the project began over 1 year ago and is therefor available
The following info should NOT pull users with the id's of 3 and 4.
William is a status of 4 (not 2) and is therefor NOT available.
Timothy is paired with the App Project Falcon, and this app began within a year from the current DateTime (12/15/2019)... and is therefor NOT available
I need something like...
Select *
FROM
[TableA] a
WHERE
a.Status = 2
IF
TableB.UserID NOT CONTAINS a.id
ELSE IF
TableB.UserID = a.id
AND WHERE
TableB.AppID = TableC.id
AND WHERE
TableC.CreatedDate is less than 1 year old from Current Date
I'm just not sure how to go about using the right syntax for this. Any help would be appreciated.
P.S. If there is a better title for this complicated question, please let me know.
IN MYSQL you would do a query like this.
CREATE TABLE UserID
(`id` int, `fName` varchar(7), `lName` varchar(8), `Status` int, `CreatedDate` Date)
;
INSERT INTO UserID
(`id`, `fName`, `lName`, `Status`, `CreatedDate`)
VALUES
(1, 'John', 'Doe', 1, '2017-03-02 06:31:15.482'),
(2, 'Marry', 'Jane', 2, '2017-05-03 16:43:56.937'),
(3, 'William', 'Thompson', 4, '2017-06-15 13:12:32.219'),
(4, 'Timothy', 'Limmons', 2, '2017-09-27 01:52:42.842')
;
✓
✓
CREATE TABLE TableB
(`id` int, `AppID` int, `UserID` int, `CreatedDate` Date)
;
INSERT INTO TableB
(`id`, `AppID`, `UserID`, `CreatedDate`)
VALUES
(1, 2, 1, '2019-04-16 23:21:56.099'),
(2, 3, 4, '2019-08-03 04:32:18.472')
;
✓
✓
CREATE TABLE APPID
(`id` int, `Title` varchar(18), `CreatedDate` Date)
;
INSERT INTO APPID
(`id`, `Title`, `CreatedDate`)
VALUES
(1, 'ToDo List', '2017-03-09 22:45:12.907'),
(2, 'Magic Marshmellows', '2018-11-14 07:01:04.050'),
(3, 'Project Falcon', '2019-07-23 14:22:44.837')
;
✓
✓
SELECT u.*
From UserID u LEFT JOIN TableB b ON u.id = b.UserID
LEFT JOIN APPID a ON b.APPID = a.id
WHERE Status = 2
AND (u.id NOT IN (SELECT UserID FROM TableB)
OR (u.id IN (SELECT UserID FROM TableB) AND a.CreatedDate > NOW() - INTERVAL 1 YEAR));
id | fName | lName | Status | CreatedDate
-: | :------ | :------ | -----: | :----------
4 | Timothy | Limmons | 2 | 2017-09-27
2 | Marry | Jane | 2 | 2017-05-03
SELECT * FROM APPID WHERE `CreatedDate` > NOW() - INTERVAL 1 YEAR
id | Title | CreatedDate
-: | :------------- | :----------
3 | Project Falcon | 2019-07-23
db<>fiddle here
SELECT
a.*
FROM
TableA AS a
LEFT JOIN (
SELECT
b.UserID
FROM
TableB AS b
INNER JOIN TableC AS c ON (
b.AppID = c.id
)
WHERE
c.CreatedDate >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
) AS s ON (
a.id = s.UserID
)
WHERE
a.Status = 2
AND s.UserID IS NULL
OR
SELECT
a.*
FROM
TableA AS a
LEFT JOIN TableB AS b ON (
b.UserID = a.id
)
LEFT JOIN TableC AS c ON (
c.id = b.AppID
)
WHERE
a.Status = 2
AND (
b.UserID IS NULL
OR c.CreatedDate < DATE_SUB(NOW(), INTERVAL 1 YEAR)
)
OR
SELECT
a.*
FROM
TableA AS a
WHERE
a.Status = 2
AND NOT EXISTS (
SELECT
*
FROM
TableB AS b
INNER JOIN TableC AS c ON (
b.AppID = c.id
)
WHERE
b.UserID = a.id
AND c.CreatedDate >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
)
=>
id fName lName Status CreatedDate
1 John Doe 2 2017-03-02
2 Marry Jane 2 2017-05-03
CREATE TABLE IF NOT EXISTS `TableA` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fName` varchar(7) DEFAULT NULL,
`lName` varchar(8) DEFAULT NULL,
`Status` int(11) DEFAULT NULL,
`CreatedDate` date DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `Status` (`Status`,`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
DELETE FROM `TableA`;
INSERT INTO `TableA` (`id`, `fName`, `lName`, `Status`, `CreatedDate`) VALUES
(1, 'John', 'Doe', 2, '2017-03-02'),
(2, 'Marry', 'Jane', 2, '2017-05-03'),
(3, 'William', 'Thompson', 4, '2017-06-15'),
(4, 'Timothy', 'Limmons', 2, '2017-09-27');
CREATE TABLE IF NOT EXISTS `TableB` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`AppID` int(11) DEFAULT NULL,
`UserID` int(11) DEFAULT NULL,
`CreatedDate` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `AppID` (`AppID`),
KEY `UserID` (`UserID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
DELETE FROM `TableB`;
INSERT INTO `TableB` (`id`, `AppID`, `UserID`, `CreatedDate`) VALUES
(1, 2, 1, '2019-04-16'),
(2, 3, 4, '2019-08-03');
CREATE TABLE IF NOT EXISTS `TableC` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Title` varchar(18) DEFAULT NULL,
`CreatedDate` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `CreatedDate` (`CreatedDate`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
DELETE FROM `TableC`;
INSERT INTO `TableC` (`id`, `Title`, `CreatedDate`) VALUES
(1, 'ToDo List', '2017-03-09 22:45:12'),
(2, 'Magic Marshmellows', '2018-11-14 07:01:04'),
(3, 'Project Falcon', '2019-07-23 14:22:44');
UPDATED: WHERE condition fixed

GROUP BY + HAVING ignore row

Basically what I wanted is that I can select all the race records with record holder and best time. I looked up about similar queries and managed to find 3 queries that were faster than the rest.
The problem is it completely ignores the race the userid 2 owns the record of.
These are my tables, indexes, and some sample data:
CREATE TABLE `races` (
`raceid` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`raceid`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`userid` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`userid`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `race_times` (
`raceid` smallint(5) unsigned NOT NULL,
`userid` mediumint(8) unsigned NOT NULL,
`time` mediumint(8) unsigned NOT NULL,
PRIMARY KEY (`raceid`,`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `races` (`raceid`, `name`) VALUES
(1, 'Doherty'),
(3, 'Easter Basin Naval S'),
(5, 'Flint County'),
(6, 'Fort Carson'),
(4, 'Glen Park'),
(2, 'Palomino Creek'),
(7, 'Tierra Robada');
INSERT INTO `users` (`userid`, `name`) VALUES
(1, 'Player 1'),
(2, 'Player 2');
INSERT INTO `race_times` (`raceid`, `userid`, `time`) VALUES
(1, 1, 51637),
(1, 2, 50000),
(2, 1, 148039),
(3, 1, 120516),
(3, 2, 124773),
(4, 1, 101109),
(6, 1, 89092),
(6, 2, 89557),
(7, 1, 77933),
(7, 2, 78038);
So if I run these 2 queries:
SELECT rt1.raceid, r.name, rt1.userid, p.name, rt1.time
FROM race_times rt1
LEFT JOIN users p ON (rt1.userid = p.userid)
JOIN races r ON (r.raceid = rt1.raceid)
WHERE rt1.time = (SELECT MIN(rt2.time) FROM race_times rt2 WHERE rt1.raceid = rt2.raceid)
GROUP BY r.name;
or..
SELECT rt1.*, r.name, p.name
FROM race_times rt1
LEFT JOIN users p ON p.userid = rt1.userid
JOIN races r ON r.raceid = rt1.raceid
WHERE EXISTS (SELECT NULL FROM race_times rt2 WHERE rt2.raceid = rt1.raceid
GROUP BY rt2.raceid HAVING MIN(rt2.time) >= rt1.time);
I receive correct results as shown below:
raceid | name | userid | name | time |
-------+----------------------+--------+----------+--------|
1 | Doherty | 2 | Player 2 | 50000 |
3 | Easter Basin Naval S | 1 | Player 1 | 120516 |
6 | Fort Carson | 1 | Player 1 | 89092 |
4 | Glen Park | 1 | Player 1 | 101109 |
2 | Palomino Creek | 1 | Player 1 | 148039 |
7 | Tierra Robada | 1 | Player 1 | 77933 |
and here is the faulty query:
SELECT rt.raceid, r.name, rt.userid, p.name, rt.time
FROM race_times rt
LEFT JOIN users p ON p.userid = rt.userid
JOIN races r ON r.raceid = rt.raceid
GROUP BY r.name
HAVING rt.time = MIN(rt.time);
and the result is this:
raceid | name | userid | name | time |
-------+----------------------+--------+----------+--------|
3 | Easter Basin Naval S | 1 | Player 1 | 120516 |
6 | Fort Carson | 1 | Player 1 | 89092 |
4 | Glen Park | 1 | Player 1 | 101109 |
2 | Palomino Creek | 1 | Player 1 | 148039 |
7 | Tierra Robada | 1 | Player 1 | 77933 |
As you can see, race "Doherty" (raceid: 1) is owned by "Player 2" (userid: 2) and it is not shown along with the rest of race records (which are all owned by userid 1). What is the problem?
Regards,
Having is a post filter. The query gets all the results, and then further filters them based on having. The GROUP BY compacting the rows based on the group, which gives you the first entry in each set. Since player 1 is the first entry for race 1, that's the result that is being processed by the HAVING. It is then filtered out because its time does not equal the MIN(time) for the group result.
This is why the other ones you posted are using a sub-query. My personal preference is for the first example, as to me it's slightly easier to read. Performance wise they should be the same.
While it's not a bad idea to try and avoid sub queries in the where clause, this is mostly valid when you can accomplish the same result with a JOIN. Other times it's not possible to get the result with a JOIN and a sub query is required.

MySQL: Version control get newest revisions

I have implemented a version control with mysql (and php).
It looks like the following:
categories
-------------
| id | name |
|----|-------
| 1 | a
| 2 | b
| 3 | c
| 4 | d
-------------
revisions
----------------------
| id | cid | current |
|----|-----|----------
| 1 | 1 | 1 |
| 2 | 1 | NULL |
| 3 | 2 | NULL |
| 4 | 3 | 1 |
| 5 | 4 | NULL |
| 6 | 4 | 1 |
----------------------
Each category has multiple revisions assigned to it. A revision is set, if "current" is set to 1 (only one revision per category can be 1, all others are NULL). What I want now is to get every category, which has new reivisions (note, once a revision is submitted it is not set to current immedeately. this is done by a e.g. a moderator). My problem is now getting also each category, which only has one revision with current IS NULL
Therefore the expected result would be:
-------------
| category.id
|------------
| 1
| 2
-------------
all the best
edit://
My current solution is:
SELECT categories.* FROM categories
JOIN revisions as t1 ON
(
t1.cid = categories.id
t1.current IS NULL
)
JOIN revisions as t2 ON
(
t2.id != t1.id
t2.current IS NOT NULL
t2.cid = categories.id
)
WHERE t1.id > t2.id
i made an SQL Fiddle Demo with a different table structure that will fit your needs
CREATE TABLE `categories` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(1) NOT NULL DEFAULT '',
`revision_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO categories
(`id`, `name`, `revision_id`)
VALUES
(1, 'a', 1),
(2, 'b', null),
(3, 'c', 4),
(4, 'd', 6),
(5, 'e', 7),
(6, 'f', 9)
;
CREATE TABLE `revisions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`category_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO revisions
(`id`, `category_id`)
VALUES
(1, 1),
(2, 1),
(3, 2),
(4, 3),
(5, 4),
(6, 4),
(7, 5),
(8, 6),
(9, 6)
;
Foreign Key Relations
ALTER TABLE `categories`
ADD CONSTRAINT `fk_cat_rev`
FOREIGN KEY (`revision_id`)
REFERENCES `revisions` (`id`) ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE `revisions`
ADD CONSTRAINT `fk_rev_cat`
FOREIGN KEY (`category_id`)
REFERENCES `categories` (`id`) ON UPDATE CASCADE ON DELETE CASCADE;
Check Relations on DELETE
DELETE FROM revisions WHERE id = 7;
DELETE FROM categories WHERE id = 6;
SELECT
cid
FROM (
SELECT
c.id cid,
MAX( r.id ) mrid,
c.revision_id rid
FROM
categories c
JOIN
revisions r ON r.category_id = c.id
GROUP BY
c.id ) tmp
WHERE
COALESCE( mrid, 0 ) <> COALESCE( rid, 0 );
SQL Fiddle DEMO