1

表结构:

CREATE TABLE IF NOT EXISTS `categories` (
  `id` smallint(4) unsigned NOT NULL AUTO_INCREMENT,
  `parentid` smallint(4) unsigned DEFAULT NULL,
  `category` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `parentid` (`parentid`,`category`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ;

INSERT INTO `categories` (`id`, `parentid`, `category`) VALUES
(1, NULL, 'A'),
(2, NULL, 'B'),
(3, 1, 'A.1'),
(4, 1, 'A.2'),
(5, 1, 'A.3'),
(6, 1, 'A.4'),
(7, 3, 'A.1.1'),
(8, 3, 'A.1.2'),
(9, 3, 'A.1.3');

代码“callcategorycount”函数是调用“categorycount”过程以便在select语句中使用:

DELIMITER $$

CREATE FUNCTION callcategorycount(id SMALLINT(4)) RETURNS SMALLINT(4) BEGIN
    CALL categorycount(id, @categorycount);
    RETURN @categorycount;
END$$

CREATE PROCEDURE categorycount(IN categoryid SMALLINT(4), OUT categorycount SMALLINT(4)) BEGIN
    DROP TEMPORARY TABLE IF EXISTS results;
    DROP TEMPORARY TABLE IF EXISTS temp1;
    DROP TEMPORARY TABLE IF EXISTS temp2;

    CREATE TEMPORARY TABLE temp1 AS
        SELECT DISTINCT id, parentid FROM categories WHERE parentid = categoryid;

    CREATE TEMPORARY TABLE results AS
        SELECT id, parentid FROM temp1;

    WHILE ( SELECT COUNT(*) FROM temp1 ) DO
        CREATE TEMPORARY TABLE temp2 AS
            SELECT DISTINCT id, parentid FROM categories WHERE parentid IN (SELECT id FROM temp1);

        INSERT INTO results SELECT id, parentid FROM temp2;
        DROP TEMPORARY TABLE IF EXISTS temp1;

        CREATE TEMPORARY TABLE temp1 AS
            SELECT id, parentid FROM temp2;

        DROP TEMPORARY TABLE IF EXISTS temp2;
    END WHILE;

    SELECT COUNT(*) INTO categorycount FROM results;

    DROP TEMPORARY TABLE IF EXISTS results;
    DROP TEMPORARY TABLE IF EXISTS temp1;
END$$

DELIMITER ;

对于输出(执行时间:1.7886 秒):

SELECT id, parentid, category, callcategorycount(id) AS subcategories FROM categories;

|id|parentid|category|subcategories
|1|NULL|A|7
|2|NULL|B|0
|3|1|A.1|3
|4|1|A.2|0
|5|1|A.3|0
|6|1|A.4|0
|7|3|A.1.1|0
|8|3|A.1.2|0
|9|3|A.1.3|0

如何优化“categorycount”程序?

4

2 回答 2

0

您可以执行以下操作,尽管我建议您仔细检查我的逻辑,因为它完成得很快。

将计数器字段添加到类别表

-- TABLE

drop table if exists categories;
create table categories
(
cat_id smallint unsigned not null auto_increment,
name varchar(255) not null,
parent_cat_id smallint unsigned null,
child_counter smallint unsigned not null default 0,
primary key categories_pk (cat_id),
key categories_parent_idx (parent_cat_id)
)
engine = innodb;

创建用于插入类别的存储过程

-- SPROCS

drop procedure if exists insert_category;
delimiter #

create procedure insert_category
(
in p_name varchar(255),
in p_parent_cat_id smallint unsigned
)
proc_main:begin

declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;

  insert into categories (name, parent_cat_id) values (p_name, p_parent_cat_id);

  if p_parent_cat_id is null then
    leave proc_main;
  end if;

  -- work out the parents

  create temporary table hier(
    parent_cat_id smallint unsigned, 
    cat_id smallint unsigned, 
    depth smallint unsigned default 0
  )engine = memory;

  insert into hier select parent_cat_id, cat_id, v_depth from categories where cat_id = p_parent_cat_id;

  /* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

  create temporary table tmp engine=memory select * from hier;

  while not v_done do

    if exists( select 1 from categories c
        inner join tmp on c.cat_id = tmp.parent_cat_id and tmp.depth = v_depth) then

        insert into hier 
            select c.parent_cat_id, c.cat_id, v_depth + 1 from categories c
            inner join tmp on c.cat_id = tmp.parent_cat_id and tmp.depth = v_depth;

        set v_depth = v_depth + 1;          

        truncate table tmp;
        insert into tmp select * from hier where depth = v_depth;

    else
        set v_done = 1;
    end if;

  end while;

  -- update parent counts

  update categories c
  inner join hier h on c.cat_id = h.cat_id
  set
   c.child_counter = c.child_counter + 1;

  drop temporary table if exists hier;
  drop temporary table if exists tmp;

end proc_main #

delimiter ;

测试和结果

truncate table categories;

call insert_category('A',null); -- 1
call insert_category('B',null);

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |             0 |
|      2 | B    |          NULL |             0 |
+--------+------+---------------+---------------+
2 rows in set (0.00 sec)

call insert_category('C',1); -- 3
call insert_category('D',1);
call insert_category('E',1); -- 5
call insert_category('F',1);

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |             4 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             0 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             0 |
|      6 | F    |             1 |             0 |
+--------+------+---------------+---------------+
6 rows in set (0.00 sec)

call insert_category('G',3); -- 7
call insert_category('H',3);
call insert_category('I',3); -- 9

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |             7 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             3 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             0 |
|      6 | F    |             1 |             0 |
|      7 | G    |             3 |             0 |
|      8 | H    |             3 |             0 |
|      9 | I    |             3 |             0 |
+--------+------+---------------+---------------+
9 rows in set (0.00 sec)

附加测试

call insert_category('K',7); -- 10

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |             8 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             4 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             0 |
|      6 | F    |             1 |             0 |
|      7 | G    |             3 |             1 |
|      8 | H    |             3 |             0 |
|      9 | I    |             3 |             0 |
|     10 | K    |             7 |             0 |
+--------+------+---------------+---------------+
10 rows in set (0.00 sec)

call insert_category('L',9); 
call insert_category('M',10); 

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |            10 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             6 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             0 |
|      6 | F    |             1 |             0 |
|      7 | G    |             3 |             2 |
|      8 | H    |             3 |             0 |
|      9 | I    |             3 |             1 |
|     10 | K    |             7 |             1 |
|     11 | L    |             9 |             0 |
|     12 | M    |            10 |             0 |
+--------+------+---------------+---------------+
12 rows in set (0.00 sec)

call insert_category('N',7); 

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |            11 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             7 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             0 |
|      6 | F    |             1 |             0 |
|      7 | G    |             3 |             3 |
|      8 | H    |             3 |             0 |
|      9 | I    |             3 |             1 |
|     10 | K    |             7 |             1 |
|     11 | L    |             9 |             0 |
|     12 | M    |            10 |             0 |
|     13 | N    |             7 |             0 |
+--------+------+---------------+---------------+
13 rows in set (0.00 sec)


call insert_category('O',5); 

select * from categories;
+--------+------+---------------+---------------+
| cat_id | name | parent_cat_id | child_counter |
+--------+------+---------------+---------------+
|      1 | A    |          NULL |            12 |
|      2 | B    |          NULL |             0 |
|      3 | C    |             1 |             7 |
|      4 | D    |             1 |             0 |
|      5 | E    |             1 |             1 |
|      6 | F    |             1 |             0 |
|      7 | G    |             3 |             3 |
|      8 | H    |             3 |             0 |
|      9 | I    |             3 |             1 |
|     10 | K    |             7 |             1 |
|     11 | L    |             9 |             0 |
|     12 | M    |            10 |             0 |
|     13 | N    |             7 |             0 |
|     14 | O    |             5 |             0 |
+--------+------+---------------+---------------+
14 rows in set (0.00 sec)

完整脚本在这里: http: //pastie.org/5649953

希望它对您有所帮助或指向最佳解决方案。

于 2013-01-08T19:38:24.703 回答
0

试试这个查询

SELECT 
      T2.*,
      IFNULL(c.SubCount,0) as SUBCAOUNT
FROM (
    SELECT
        @r AS _id,
        (SELECT @r := parentid FROM categories WHERE id = _id) AS parentid,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := 9, @l := 0) vars,
        categories m
    WHERE @r <> 0) T1
JOIN categories T2
ON T1._id = T2.id
LEFT JOIN (
            SELECT 
                COUNT(id) as SubCount, 
                parentid 
            FROM categories
            GROUP BY parentid) as c ON c.parentid = T1._id
ORDER BY T1.lvl DESC;

在这个查询中,我使用 9 作为参数,因为它是 A 的最终深度。

输出

ID  PARENTID    CATEGORY    SUBCAOUNT
1   (null)      A           4
3   1           A.1         3
9   3           A.1.3       0   

SQL 小提琴演示

编辑:试试这个查询。

SELECT 
      T2.*,
      SUM(c.SubCount) as SUBCAOUNT
FROM (
    SELECT
        @r AS _id,
        (SELECT @r := parentid FROM categories WHERE id = _id) AS parentid,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := 9, @l := 0) vars,
        categories m
    WHERE @r <> 0) T1
JOIN categories T2
ON T1._id = T2.id
LEFT JOIN (
            SELECT 
                IFNULL(COUNT(id),0) as SubCount, 
                parentid 
            FROM categories
            GROUP BY parentid) as c ON c.parentid = T1._id

ORDER BY T1.lvl DESC;

输出

ID  PARENTID    CATEGORY    SUBCAOUNT
9   3           A.1.3       7   

演示

更多编辑

这是你如何做到的

SELECT
      c.id,
      c.category,
      IFNULL(Total,0) as No_Of_Children
FROM categories as c
LEFT JOIN (
            SELECT
                 id,
                 COUNT(parentid) as Total,
                 parentid
            FROM categories
            GROUP BY parentid
) as lc ON lc.parentid = c.id;

输出

ID  CATEGORY    NO_OF_CHILDREN
1   A           4
2   B           0
3   A.1         3
4   A.2         0
5   A.3         0
6   A.4         0
7   A.1.1       0
8   A.1.2       0
9   A.1.3       0

这是

演示

于 2013-01-05T19:38:15.157 回答