MySQL: Version control get newest revisions - mysql

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

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

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

Getting most recent row per group in MySQL [duplicate]

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)

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.

Getting stock balance

I have 3 tables as follows:
products:[product_id,product_name]
stock:[product_id,date,qty_received]
sales:[product_id,date,qty_sold]
I'm trying to display the balance of all products by subtracting qty_received - qty_sold.
My query is:
SELECT DISTINCT p.product_id, p.product_name,
IFNULL(sum(qty_received),0)-IFNULL(sum(qty_sold),0) as balance FROM products p
LEFT JOIN stock st ON (p.product_id=st.product_id)
LEFT JOIN sales sa ON (p.product_id=sa.product_id)
GROUP BY p.product_id
With the above query I'm not getting the correct balance.
Could you please help.
Additional Information:
Following are a simplified structure for my tables
Products table:
CREATE TABLE IF NOT EXISTS `products` (
`product_id` varchar(15) NOT NULL,
`product_name` varchar(20) NOT NULL,
UNIQUE KEY `product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Dumping data for table products
INSERT INTO `products` (`product_id`, `product_name`) VALUES
('111', 'Product One'),
('222', 'Product Two'),
('333', 'Product Three'),
('444', 'Product Four');
Stock table:
CREATE TABLE IF NOT EXISTS `stock` (
`stock_id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`qty_received` int(11) NOT NULL,
PRIMARY KEY (`stock_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=112 ;
-- Dumping data for table stock
INSERT INTO `stock` (`stock_id`, `product_id`, `qty_received`) VALUES
(1, 111, 5),
(2, 222, 10),
(3, 333, 4),
(4, 444, 6),
(5, 111, 2),
(6, 222, 7),
(7, 111, 3),
(8, 111, 3);
Sales Table
CREATE TABLE IF NOT EXISTS `sales` (
`sales_id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`qty_sold` int(11) NOT NULL,
PRIMARY KEY (`sales_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
-- Dumping data for table sales
INSERT INTO `sales` (`sales_id`, `product_id`, `qty_sold`) VALUES
(1, 111, -1),
(2, 222, -3),
(3, 333, -2),
(4, 111, -3),
(5, 222, -1),
(6, 222, -4);
The Result I'm getting is:
+------------+--------------+---------+
| product_id | product_name | balance |
+------------+--------------+---------+
| 111 | Product One | 10 |
| 222 | Product Two | 35 |
| 333 | Product Three| 2 |
| 444 | Product Four | 6 |
+------------+--------------+---------+
Correct balance I'm expecting is
+------------+--------------+---------+
| product_id | product_name | balance |
+------------+--------------+---------+
| 111 | Product One | 9 |
| 222 | Product Two | 9 |
| 333 | Product Three| 2 |
| 444 | Product Four | 6 |
+------------+--------------+---------+
Finally got a working sql for this solution , but not sure if this is the best way doing this. I'm very concerned about the performance, since these tables got large amount of records.
SELECT p.product_id,p.product_name,
IFNULL(sum(st.qty_received),0)+IFNULL(sold,0) as balance FROM products p
LEFT JOIN stock st ON (p.product_id=st.product_id)
LEFT JOIN (SELECT sa.product_id,sum(sa.qty_sold) as sold FROM sales sa
GROUP BY sa.product_id) as t1 ON (t1.product_id=p.product_id)
GROUP BY product_id
Anyone can suggest a better sql (or change in schema) for better speed/performance to get stock balance?
First, add p.product_name to GROUP BY clause.
Second, remove DISTINCT - it's unnecessary with GROUP BY.
UPDATE
I've tried a couple of queries and found that this one looks like the simplest:
SELECT
p.product_id,
p.product_name,
COALESCE(SUM(qty), 0) as balance
FROM products p
LEFT JOIN (
SELECT product_id, qty_received AS qty FROM stock st UNION ALL
SELECT product_id, qty_sold FROM sales st
) AS T
ON p.product_id = T.product_id
GROUP BY
p.product_id,
p.product_name
Also, I think it would be a good idea to make data type of product_id in all your tables the same. It's varchar in products and int in sales/stock.
I did a Fiddle for you