【问题标题】:MySQL With Recursive带递归的 MySQL
【发布时间】:2019-09-02 17:14:39
【问题描述】:

我需要返回我的类别以及每个部门的商品数量,父类别必须从子类别继承每个类别的产品数量。

类别

+-------------+----------+--------+ |编号 |姓名 | parent_id | +-------------+----------+--------+ | 1 |电子产品 |空 | | 2 |电视 | 1 | | 3 |管 | 2 | | 4 |液晶显示器 | 2 | | 5 |等离子 | 2 | | 6 |便携式电子产品 | 1 | | 7 | MP3 播放器 | 6 | | 8 |闪存 | 7 | | 9 | CD 播放器 | 6 | | 10 | 2 路收音机 | 6 | +-------------+----------+--------+

产品

+-------------+----------+--------+ |编号 |产品 |类别 ID | +-------------+----------+--------+ | 1 |液晶电视 32" | 4 | | 2 |液晶电视 45" | 4 | | 3 |电视管 29" | 3 | | 3 | IPOD | 7 | +-------------+----------+--------+

预期结果

+-------------+----------+------------ + |编号 |姓名 |等级|数量 | +-------------+----------+------------ + | 1 |电子产品 | 1 | 4 | | 2 |电视 | 2 | 3 | | 3 |管 | 3 | 1 | | 4 |液晶显示器 | 3 | 2 | | 5 |便携式电子产品 | 2 | 1 | | 6 | MP3 播放器 | 3 | 1 | +-------------+----------+------------ +

我需要使用with递归,因为速度比使用嵌套要快很多

WITH RECURSIVE category_path (id, name, level, parent_id) AS
(
SELECT id, name, 1 level, parent_id
 FROM categories
  WHERE parent_id IS NULL
 UNION ALL
 SELECT c.id, c.name, level + 1, c.parent_id
  FROM category_path AS cp 
   JOIN categories AS c
    ON cp.id = c.parent_id
)
SELECT * FROM category_path

Time: 0.020s

使用嵌套

SELECT
     parent.id,     
   parent.name,
     parent.parent_id,
   COUNT(departaments.product_id) 
FROM
   categories AS node 
   INNER JOIN
      categories AS parent 
      ON node.lft BETWEEN parent.lft AND parent.rgt 
   INNER JOIN
      departaments 
      ON node.id = departaments.categorie_id 
GROUP BY
   parent.id 
ORDER BY
   node.lft;

Time: 1.510s

【问题讨论】:

    标签: mysql mariadb


    【解决方案1】:

    首先编写一个查询来获取每个类别的产品数量。这很简单:

    with products_per_category as (
      select c.id, count(p.id) as pcount
      from categories c
      left join products p on p.category_id = c.id
      group by c.id
    )
      select *
      from products_per_category
      order by id
    

    db-fiddle

    然后编写递归CTE,生成transitive closure

    with recursive rcte as (
      select c.id, c.id as ancestor_id
      from categories c
      union all
      select r.id, c.parent_id
      from rcte r
      join categories c on c.id = r.ancestor_id
    )
      select *
      from rcte
      order by id, ancestor_id
    

    结果会是这样的:

    | id  | ancestor_id |
    | --- | ----------- |
    | 1   | 1           |
    | 2   | 1           |
    | 2   | 2           |
    ...
    | 9   | 1           |
    | 9   | 6           |
    | 9   | 9           |
    | 10  | 1           |
    | 10  | 6           |
    | 10  | 10          |
    

    db-fiddle

    这就像你得到从根节点到每个类别的路径。例如。对于9,路径是1->6->9

    如果您先通过ancestor_id订购,您将获得:

    | id  | ancestor_id |
    | --- | ----------- |
    | 1   | 1           |
    ...
    | 10  | 1           |
    | 2   | 2           |
    | 3   | 2           |
    | 4   | 2           |
    | 5   | 2           |
    | 3   | 3           |
    ...
    

    db-fiddle

    在这里您可以看到,类别 2 (ancestor_id=2) 具有子类别 (id) 2,3,4,5。请注意,每个类别都有自己的子类别。这将使下一步更简单。

    现在我们只需要加入两个 CTE 并总结产品数量:

    with recursive products_per_category as (
      select c.id, count(p.id) as pcount
      from categories c
      left join products p on p.category_id = c.id
      group by c.id
    ), rcte as (
      select c.id, c.id as ancestor_id
      from categories c
      union all
      select r.id, c.parent_id
      from rcte r
      join categories c on c.id = r.ancestor_id
      where c.parent_id is not null
    )
      select
        c.id,
        c.name,
        sum(p.pcount) as quantity
      from rcte r
      join categories c on c.id = r.ancestor_id
      left join products_per_category p on p.id = r.id
      group by c.id
    

    结果:

    | id  | name                 | quantity |
    | --- | -------------------- | -------- |
    | 1   | ELECTRONICS          | 4        |
    | 2   | TELEVISIONS          | 3        |
    | 3   | TUBE                 | 1        |
    | 4   | LCD                  | 2        |
    | 5   | PLASMA               | 0        |
    | 6   | PORTABLE ELECTRONICS | 1        |
    | 7   | MP3 PLAYERS          | 1        |
    | 8   | FLASH                | 0        |
    | 9   | CD PLAYERS           | 0        |
    | 10  | 2 WAY RADIOS         | 0        |
    

    db-fiddle

    如果您想删除空类别 (quantity = 0),只需将所有 LEFT JOIN 替换为 INNER JOIN。

    更新

    要获取level,您可以在外部SELECT 中使用子查询:

    (select count(*) from rcte r2 where r2.id = c.id) as level
    

    db-fiddle

    【讨论】:

    • 谢谢。它按预期完美运行,速度超快时间:0.073ms
    • 嗯.. 你真的是说0.073ms = 73 microseconds?或者是0.073s = 73ms
    • 我需要子类继承父类的属性,比如我想列出所有有group_1的类,但是子类必须继承这个属性。并且子类别只有在其父类别处于活动状态时才会出现。 db-fiddle
    猜你喜欢
    • 1970-01-01
    • 2011-03-13
    • 1970-01-01
    • 2015-05-05
    • 2013-01-17
    • 2010-09-21
    • 2012-08-02
    • 2023-03-12
    相关资源
    最近更新 更多