Getting stock balance - mysql

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

Related

How to show multiple results in single columns whith subquery

I have orders table where i show information about order number, products, and customer.
I need to show list of product names and their qty in column products separated or in new row like below in table:
Expected result
id | order_number | customer | address | total | products
------------------------------------------------------------------------------------
1 | OR00123 | Tom Helks | Test 221 | 1233,2 | 1x iphone
| 2x samsung
| 3x someproduct
Order table
id | order_number | customer_id | total | status_id
------------------------------------------------------------------------------------
1 | OR00123 | 1 | 1233,2 | 1
OrderProducts table
id | order_id | product_id | qty |
------------------------------------------------------
1 | 1 | 5 | 1 |
2 | 1 | 2 | 2 |
3 | 1 | 6 | 3 |
Product table
id | name | price
----------------------------------------
5 | iphone | 1231
2 | samsung | 2322
6 | someproduct | 432
What i try.
I get above expected result but only dont do subquery for listing products and qty for order.
SELECT
order.number,
customer.name,
customer.adress,
SUM(order_products.qty * product.price) as total,
# subquery here
(
SELECT p.name FROM products p WHERE order_products.product_id = p.id
) as products
FROM.orders as order
INNER JOIN customer on customer.customer_id= customer.id
INNER JOIN order_products on order.id = order_products.order_id
ORDER BY dok.created_at;
My imagination stopped and I believe that the problem is in the sub-query, but I can't see it at the moment.
Thanks
You need to join all tables and GROUP BY the unique values.
The GROUP_CONCAT has to be used with then CONCAT of the wanted values
CREATE TABLE orders
(`id` int, `order_number` varchar(7), `customer_id` int, `total` varchar(6), `status_id` int)
;
INSERT INTO orders
(`id`, `order_number`, `customer_id`, `total`, `status_id`)
VALUES
(1, 'OR00123', 1, '1233,2', 1)
;
CREATE TABLE orders_product
(`id` int, `order_id` int, `product_id` int, `qty` int)
;
INSERT INTO orders_product
(`id`, `order_id`, `product_id`, `qty`)
VALUES
(1, 1, 5, 1),
(2, 1, 2, 2),
(3, 1, 6, 3)
;)
Records: 3 Duplicates: 0 Warnings: 0
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1
CREATE TABLE product
(`id` int, `name` varchar(11), `price` int)
;
INSERT INTO product
(`id`, `name`, `price`)
VALUES
(5, 'iphone', 1231),
(2, 'samsung', 2322),
(6, 'someproduct', 432)
;
Records: 3 Duplicates: 0 Warnings: 0
SELECT
order_number, `customer_id`, `total`,GROUP_CONCAT(CONCAT( op.`qty`,'x ',p.`name`) SEPARATOR ' ') products
FROM orders o
JOIN orders_product op ON o.`id` = op.`order_id`
JOIN product p ON op.`product_id` = p.`id`
GROUP BY order_number, `customer_id`, `total`
order_number
customer_id
total
products
OR00123
1
1233,2
1x iphone 2x samsung 3x someproduct
fiddle

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 Left join with grouping and date

As you can see on this sqlfiddle, I have this schema:
CREATE TABLE reviews
(`id` int(11) NOT NULL AUTO_INCREMENT,
`shop_id` int(11),
`order_id` char(255),
`product_id` char(32),
`review_time` int(11),
PRIMARY KEY (`id`)
)
;
INSERT INTO reviews
(`shop_id`, `order_id`, `product_id`, `review_time`)
VALUES
('10', '100', '1000', '1466190000'),
('10', '100', '1000', '1466276400'),
('10', '100', '1000', '1466462800'),
('20', '800', '8000', '1466249200')
;
CREATE TABLE tags
(`id` int(11) NOT NULL AUTO_INCREMENT,
`shop_id` int(11),
`order_id` char(255),
`product_id` char(32),
`tag_time` INT(11) NULL,
PRIMARY KEY (`id`)
)
;
INSERT INTO tags
(`shop_id`, `order_id`, `product_id`, `tag_time`)
VALUES
('10', '100', '1000', '1466449200'),
('10', '100', '1000', NULL),
('10', '100', '3000', NULL),
('20', '800', '8000', '1469449200')
;
I need to get statistics by date showing how many reviews I have per date and how many were tagged and how many were not. I'm using this query:
SELECT
DATE_FORMAT(FROM_UNIXTIME(r.`review_time`), "%d.%m.%Y") AS review_submited_on,
r.`shop_id`,
COUNT(*) as total_orders,
COUNT(*) as tagged_orders
FROM
reviews AS r
LEFT JOIN tags as t
ON r.`shop_id` = t.`shop_id` AND
r.`order_id` = t.`order_id` AND
r.`product_id` = t.`product_id`
WHERE
t.`tag_time` IS NOT NULL
GROUP BY r.`shop_id`, r.`order_id`, r.`product_id`
ORDER BY review_submited_on ASC
UPDATE
The expected result would look like this:
| review_submited_on | shop_id | total_orders | tagged_orders |
|--------------------|---------|--------------|---------------|
| 17.06.2016 | 10 | 3 | 1 |
| 18.06.2016 | 20 | 1 | 1 |
I created this sqlfiddle for demo.
Thanks for any help :)
Try this, and let me know if that is something you want.
SELECT review_submited_on, shop_id, total_orders, IFNULL(tagged_orders, 0) tagged_orders
FROM
(SELECT shop_id, COUNT(DISTINCT shop_id, order_id, product_id) total_orders, DATE_FORMAT(FROM_UNIXTIME(review_time), "%d.%m.%Y") AS review_submited_on
FROM reviews
GROUP BY shop_id) review_counter
LEFT JOIN
(SELECT shop_id, COUNT(DISTINCT shop_id, order_id, product_id) tagged_orders
FROM tags
WHERE tag_time IS NOT NULL
GROUP BY shop_id) tag_counter
USING (shop_id)
Result
| review_submited_on | shop_id | total_orders | tagged_orders |
|--------------------|---------|--------------|---------------|
| 17.06.2016 | 10 | 1 | 1 |
| 18.06.2016 | 20 | 1 | 1 |

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