优化mysql递归连接查询

时间:2016-03-19 09:46:43

标签: php mysql sql query-optimization self-join

我有两张表ps_productps_category。产品表在本地数据库中有大约146690行,在远程数据库中有196000行,btreeid_product上有id_default_category索引。类别表在btreeid_category列上有大约851行和id_parent索引。

这些类别最多可分层次达到6级,我需要所有类别的所有产品。因此,如果产品53属于67类,而67类是子类别50,它是43的子类别......依此类推,直到1,即根类别,我将得到 53 - > 67-> 50- GT; 43-> 20→; 1→空

我已经找到了一个mysql查询,它使自己加入ps_category 6次来获取数据,并且在本地数据库上大约需要 0.8秒并且 - 5秒通过网络执行。有什么方法可以优化吗?查询:

    SELECT 
    p.id_product, c.id_category, c1.id_category, c2.id_category, c3.id_category, c4.id_category, c5.id_category, c6.id_category
FROM `ps_category` c
    left join ps_product p on p.id_category_default = c.id_category
    left join ps_category c1 on c1.id_category = c.id_parent  
    left join ps_category c2 on c2.id_category = c1.id_parent  
    left join ps_category c3 on c3.id_category = c2.id_parent  
    left join ps_category c4 on c4.id_category = c3.id_parent
    left join ps_category c5 on c5.id_category = c4.id_parent  
    left join ps_category c6 on c6.id_category = c5.id_parent

1 个答案:

答案 0 :(得分:0)

尝试的一个选择是使用闭包表。

CREATE TABLE category_closure (
    `a_catagory_id` SMALLINT UNSIGNED NOT NULL,
    `d_category_id` SMALLINT UNSIGNED NOT NULL,
    PRIMARY KEY (`a_category_id`,`d_category_id`)
) ENGINE=InnoDB

此表记录层次结构中各级别类别之间的关系。 a_category_id指的是关系中的祖先,而d_category_id指的是后代。

为了使表正常工作,必须将每个类别作为其自己的祖先和后代输入到表中。

INSERT INTO category_closure
(a_category_id, d_category_id)
SELECT
id_category,
id_category
FROM ps_category

然后,您可以在每个类别的id_parent列中输入您已知的关系。

INSERT INTO category_closure
(a_category_id, d_category_id)
SELECT
id_parent,
id_category
FROM ps_category
WHERE id_parent IS NOT NULL

最后,你需要连接点。在do..while循环中运行以下SELECT,只要从中返回行,将这些行插入闭包表并继续循环。

SELECT
cc1.a_category_id,
cc2.d_category_id
FROM category_closure cc1
INNER JOIN category_closure cc2
ON cc2.a_category_id = cc1.d_category_id
LEFT OUTER JOIN category_closure missing_cc
ON missing_cc.a_category_id = cc1.a_category_id
AND missing_cc.d_category_id = cc2.d_category_id
WHERE missing_cc.a_category_id IS NULL

这个查询的作用是它需要所有现有关系,并找到应该存在的内容。例如,您有以下链:

53> 67> 50

这意味着您将从前两个INSERT中获得以下记录: (50,50) (67,67) (53,53) (50,67) (67,53) (和其他人)。

现在,我们需要的是(50,53),因为53是50的后代。在上面的SELECT查询中,cc1将匹配(50,67)记录。 cc2将匹配(67,53)记录。这意味着missing_cc正在尝试与来自cc1a_category_id)的50和来自cc2d_category_id)的53匹配。

由于最初不存在这样的记录,SELECT语句将返回这两行,您可以插入它们,然后重复。这一次,进一步向上发展。您不需要(或您的程序)知道有多少层,只需继续直到SELECT找不到更多结果。

最后,一旦构建了闭包表,就可以选择相关信息:

SELECT 
p.id_product,
c.id_category,
GROUP_CONCAT(cc.a_category_id) AS parent_category_ids
FROM ps_category c
LEFT OUTER JOIN ps_product p
ON p.id_category_default = c.id_category
LEFT OUTER JOIN category_closure cc
ON cc.d_category_id = c.id_category
AND cc.a_category_id != c.id_category
GROUP BY c.id_category, p.id_product

这将选择所有类别,每个类别中的所有产品,然后为每个组合提供以逗号分隔的祖先类别列表。

现在,这实际上是相当多的重复信息,因为您可以将类别及其祖先与产品分开运行,但它实际上归结为您打算如何使用这些数据,是否可以分开查询与否。

注意:如果要添加和删除类别,则在删除所有当前关闭条目后,每次执行此操作时都必须重复此过程。有更好的方法可以做到这一点,而不是这里显示的,这样的方法甚至可以让你放弃id_parent列的使用(特别是如果层次结构是非循环的),但这样的事情超出了这个范围问题

这个答案至少可以让你尝试一些东西,而不必改变你的应用程序或任何现有数据。