在1个查询中更新多行及其所有父项的多对多计数器缓存

时间:2010-11-25 13:58:53

标签: mysql many-to-many

考虑一个博客应用程序,其中包含用于将帖子与一个或多个类别相关联的帖子,类别和查找表的表格。类别是分层的。帖子可以分配给任何类别,而不仅仅是叶子节点。

类别表有一个post_count字段,用于缓存分配给特定类别的帖子数。对于MPTT,它还有parent_idlftrght列。

但它还有一个under_post_count字段,用于缓存分配给它或其任何子类别的不同帖子的数量。这非常有用,因此您可以显示分类的分层列表,其中包含分配给它的帖子数,或其中一个子,旁边

我的应用已达到这样的程度,即在使用类别创建帖子后,或者编辑了类别或删除了类别的类别后,我会列出新旧类别的类别ID列表,其{{1字段需要更新。我希望接下来可以做的是单个查询,更新所有已识别类别的post_count字段及其所有父母,其中包含分配给每个类别或其中任何类别的不同帖子的数量子

这是创建表所需的SQL和类别的一些测试数据:

under_post_count

运行此几次以为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_posts

任何人都可以理解这一点,你的帮助会非常感激吗?

1 个答案:

答案 0 :(得分:3)

有很多方法可以让猫在这里(假设5.1和触发器)

  • 您可以从应用层

  • 更新所有内容
  • 您可以从post_count触发categories_posts的更新,并从under_post_count

  • 触发更新(级联)到categories
  • 最后,您可以从categories_posts

  • 触发所有更新

同样取决于实际的类别数量,您可能不需要对under_post_count进行非规范化,因为使用

获取它应该相当简单且便宜
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_posts cp ON c.id = cp.post_id 
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;

修改

从上面构建更新语句不应该那么难

UPDATE categories 
SET post_count = (SELECT COUNT(*) 
                  FROM categories_posts cp 
                  WHERE cp.post_id = categories.id)

适用于post_count

under_post_count的情况不同,因为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

<强> EDIT2
实际上,上述所有查询都存在错误 - 每当我加入类别和帖子时,我应该加入cc.id = cp.category_id而不是cp.post_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.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)

<强> EDIT3
只有几点说明:

  • 上述查询将修复under_post_countpost_count,无论数据的状态如何,
  • 如果您的数据访问层被正确抽象,保护并且如果您可以确保原子性,那么查询会更便宜 - 这些查询只会对状态中的相应记录执行post_count = post_count +/- 1(类似于{{1} }),
  • 如果您无法从应用程序级别可靠地模拟触发器,检查是否需要运行上述查询可能仍然更便宜(即使mysql在这方面相当不错,但如果您想要与DB无关),或采用一些策略,通常只是增加/减少计数器,只是定期重新计算数字。