Mysql 在一个查询中更新多行及其所有父行的多对多计数器缓存
考虑一个博客应用程序,它有一个帖子、类别表和一个链接帖子和一个或多个类别的查找表。类别是分层的。帖子可以分配给任何类别,而不仅仅是叶节点 类别表有一个Mysql 在一个查询中更新多行及其所有父行的多对多计数器缓存,mysql,many-to-many,Mysql,Many To Many,考虑一个博客应用程序,它有一个帖子、类别表和一个链接帖子和一个或多个类别的查找表。类别是分层的。帖子可以分配给任何类别,而不仅仅是叶节点 类别表有一个post\u count字段,用于缓存分配给特定类别的帖子数量。它还有MPTT的parent\u id、lft和rght列 但它还有一个under_post_count字段,用于缓存分配给它或其任何子类别的不同帖子的数量。这很有用,因此您可以显示类别的分层列表,其中包含分配给它的帖子数量,或其子项之一,并在其旁边 我的应用程序已经达到了这样的程度:
post\u count
字段,用于缓存分配给特定类别的帖子数量。它还有MPTT的parent\u id
、lft
和rght
列
但它还有一个under_post_count
字段,用于缓存分配给它或其任何子类别的不同帖子的数量。这很有用,因此您可以显示类别的分层列表,其中包含分配给它的帖子数量,或其子项之一,并在其旁边
我的应用程序已经达到了这样的程度:在使用类别创建帖子、编辑其类别或删除具有类别的帖子之后,我有一个新旧类别的类别ID列表,其post\u count
字段需要更新。我希望下一步可以做的是在一个查询中,更新所有已识别类别及其所有父类别下的字段,以及分配给每个类别或其任何子类别的不同帖子的数量
以下是为类别创建表和一些测试数据所需的SQL:
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`lft` int(11) DEFAULT NULL,
`rght` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`post_count` int(11) NOT NULL DEFAULT '0',
`under_post_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
CREATE TABLE `categories_posts` (
`category_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
PRIMARY KEY (`category_id`,`post_id`)
) ENGINE=MyISAM;
INSERT INTO `categories` (`id`, `parent_id`, `lft`, `rght`, `name`) VALUES
(1, NULL, 1, 8, 'Cat 1'),
(4, 1, 2, 3, 'Cat 1.1'),
(5, 1, 4, 5, 'Cat 1.2'),
(6, 1, 6, 7, 'Cat 1.3'),
(2, NULL, 9, 16, 'Cat 2'),
(7, 2, 10, 11, 'Cat 2.1'),
(8, 2, 12, 13, 'Cat 2.2'),
(9, 2, 14, 15, 'Cat 2.3'),
(3, NULL, 17, 24, 'Cat 3'),
(10, 3, 18, 19, 'Cat 3.1'),
(11, 3, 20, 21, 'Cat 3.2'),
(12, 3, 22, 23, 'Cat 3.3');
运行几次,为categories\u posts
表创建一些测试数据:
INSERT IGNORE INTO `categories_posts` (`category_id`, `post_id`)
SELECT `id`, CEILING(10 * RAND()) FROM `categories` ORDER BY RAND() LIMIT 6
任何人都能弄明白这一点,我们将非常感谢您的帮助。这里有几种剥猫皮的方法(假设5.1和触发器)
- 您可以从应用程序层更新所有内容
- 您可以从
触发对categories\u posts
的更新,并从post\u count
触发更新(级联)到categories
下的
- 最后,您可以从
categories\u post
进行反规范化,因为获取它应该相当简单且便宜
SELECT c.id, SUM(cc.post_count)
FROM categories c
LEFT JOIN categories cc ON c.lft <= cc.lft AND c.rght >= cc.rght
GROUP BY c.id;
将这两者结合起来,可以得到包括层次结构在内的计数
SELECT c.id, COUNT(*)
FROM categories c
LEFT JOIN categories cc ON c.lft <= cc.lft AND c.rght >= cc.rght
LEFT JOIN categories_posts cp ON cc.id = cp.post_id
GROUP BY c.id;
应该适用于post\u计数
由于mysql不喜欢在where部分中提到目标表,因此必须这样做
UPDATE categories LEFT JOIN
(SELECT c.id, COUNT(*) AS result
FROM categories c
LEFT JOIN categories cc ON c.lft <= cc.lft AND c.rght >= cc.rght
INNER JOIN categories_posts cp ON cc.id = cp.post_id
GROUP BY c.id) AS x ON categories.id = x.id
SET under_post_count = x.result
EDIT3
请注意:
- 无论数据的状态如何,上面的查询都将修复_post_count
和post_count
下的
如果数据访问层得到适当的抽象和保护,并且能够保证原子性,那么有些查询会更便宜——这些查询只会对状态中的适当记录执行post\u count=post\u count+/-1
(类似于下的)
如果您无法从应用程序级别可靠地模拟触发器,那么检查是否需要运行上述查询可能会更便宜(尽管mysql在这方面很好,但如果您希望不依赖数据库),或者采取一些策略,通常只增加/减少计数器,只定期重新计算数字李>
@Unreason,谢谢你的回复,很抱歉我没有在问题中详细说明,但是有没有一种不依赖触发器的方法可以做到这一点?也就是说,纯粹在SQL更新语句中?@neilcrookes,我用更新语句更新了问题。你应该能把他们连在一起。演出不会很精彩。我可以问一下为什么不触发吗?@Unreason,太棒了,非常感谢。最后一个问题(promise):我如何在posts表上添加范围条件,例如posts.status=‘published’?@neilcrokes,updated-有一个错误通过所有查询传播。顺便说一句,你没有回答-为什么不呢?通常情况下,数据库保持数据完整性比在应用程序级别保持数据完整性更好。@Unreason,太好了,非常感谢。没有触发器的原因是这个查询实际上是由我的应用程序中的ORM层实现的,它与dbms无关。此外,查询是在应用程序的一个组件中构建的,该组件可以应用于具有这种关系的任何数据。+1表示具有良好DDL和样本数据的定义良好的问题
UPDATE categories
SET post_count = (SELECT COUNT(*)
FROM categories_posts cp
WHERE cp.post_id = categories.id)
UPDATE categories LEFT JOIN
(SELECT c.id, COUNT(*) AS result
FROM categories c
LEFT JOIN categories cc ON c.lft <= cc.lft AND c.rght >= cc.rght
INNER JOIN categories_posts cp ON cc.id = cp.post_id
GROUP BY c.id) AS x ON categories.id = x.id
SET under_post_count = x.result
UPDATE categories LEFT JOIN
(SELECT c.id, COUNT(*) AS result
FROM categories c
LEFT JOIN categories cc ON c.lft <= cc.lft AND c.rght >= cc.rght
INNER JOIN categories_posts cp ON cc.id = cp.category_id
INNER JOIN posts p ON cp.post_id = p.id
WHERE p.status = 'published'
GROUP BY c.id) AS x ON categories.id = x.id
SET under_post_count = x.result,
post_count = (SELECT COUNT(*)
FROM categories_posts cp
WHERE cp.category_id = categories.id)