使用MySQL订购无限级分层结果集

时间:2019-01-10 11:33:44

标签: mysql sql hierarchy

我正在研究无限级别的评论-答复系统。并且成功获取父子相对数据行,但是我在订购该行时遇到了麻烦。

这用于创建结果:https://stackoverflow.com/a/5291159/4894502

我得到这样的结果;

comment_id     comment      parent_id depth rel_path    rating
    38      Com 1               0       0      0           0
    39      Com 2               0       0      0           10
    40      Com 3               0       0      0           0
    41      Com 1-1             38      1      0/38        0
    42      Com 2-1             39      1      0/39        0
    44      Com 2-2             39      1      0/39        2
    46      Com 3-1             40      1      0/40        0
    43      Com 2-1-1           42      2      0/39/42     0
    47      Com 2-2-1           44      2      0/39/44     0
    45      Com 2-1-1-1         43      3      0/39/42/43  0

但是排序是有问题的,期望的排序是每个子项必须在其父项之下,而子项必须根据某个参数在其中进行排序,例如rating。如此理想的结果;

comment_id     comment      parent_id depth rel_path    rating
    38      Com 1               0       0      0           0
    41      Com 1-1             38      1      0/38        0
    39      Com 2               0       0      0           10
    42      Com 2-1             39      1      0/39        0
    43      Com 2-1-1           42      2      0/39/42     0
    45      Com 2-1-1-1         43      3      0/39/42/43  0
    44      Com 2-2             39      1      0/39        2
    47      Com 2-2-1           44      2      0/39/44     0
    40      Com 3               0       0      0           0
    46      Com 3-1             40      1      0/40        0

或者这样(+按评分顺序)

comment_id     comment      parent_id depth rel_path    rating
    39      Com 2               0       0      0           10
    44      Com 2-2             39      1      0/39        2
    47      Com 2-2-1           44      2      0/39/44     0
    42      Com 2-1             39      1      0/39        0
    43      Com 2-1-1           42      2      0/39/42     0
    45      Com 2-1-1-1         43      3      0/39/42/43  0
    38      Com 1               0       0      0           0
    41      Com 1-1             38      1      0/38        0
    40      Com 3               0       0      0           0
    46      Com 3-1             40      1      0/40        0

Db小提琴上的示例 https://www.db-fiddle.com/f/uk3ZDLdD8N5tvhzb9S6rXC/1

示例表:

CREATE TABLE `comment` (
  `comment_id` int(11) NOT NULL,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `depth` int(4) NOT NULL DEFAULT '0',
  `comment` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `rating` int(4) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

INSERT INTO `comment` (`comment_id`, `parent_id`, `depth`, `comment`, `rating`) VALUES
(42,39, 1,'Com 2-1', 0),
(41,38, 1,'Com 1-1', 0),
(40,0, 0,'Com 3', 0),
(39,0, 0,'Com 2', 20),
(38,0, 0,'Com 1', 0),
(43,42, 2,'Com 2-1-1', 0),
(44,39, 1,'Com 2-2', 2),
(45,43, 3,'Com 2-1-1-1', 0),
(46,40, 1,'Com 3-1', 0),
(47,44, 2,'Com 2-2-1', 0);

ALTER TABLE `comment`
  ADD PRIMARY KEY (`comment_id`),
  ADD UNIQUE KEY `comment_id` (`comment_id`);

ALTER TABLE `comment`
  MODIFY `comment_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=48;

程序(与上面链接中的答案几乎相同):

delimiter #

create procedure comment_hier
(
in param_parent_id smallint unsigned
)


BEGIN

DECLARE v_done TINYINT unsigned default 0;
DECLARE v_depth SMALLINT unsigned default 0;

CREATE TEMPORARY TABLE hier(
   comment_id int(11) unsigned, 
   parent_id int(11) unsigned,  
   depth int(99) unsigned default 0,
   relation_path varchar(360) default 0
)engine = memory;

INSERT INTO hier SELECT comment_id, parent_id, v_depth, 0 FROM comment a WHERE a.parent_id = param_parent_id;

CREATE TEMPORARY TABLE tmp engine=memory SELECT * FROM hier;

while not v_done do

    if exists( select 1 from hier h inner join comment a on h.comment_id = a.parent_id and h.depth = v_depth) then

    insert into hier 
    select a.comment_id, a.parent_id, v_depth + 1, CONCAT_WS('/', relation_path, a.parent_id) from comment a 
    inner join tmp t on a.parent_id = t.comment_id and t.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;

select  
    a.comment_id,
    a.comment as comment,
    a.parent_id as parent_id,
    h.depth,
    h.relation_path, 
    a.rating as rating 
from
    hier h
left join comment a on h.comment_id = a.comment_id
ORDER BY h.depth, a.comment_id;

DROP TEMPORARY TABLE if exists hier;
DROP TEMPORARY TABLE if exists tmp;

END #

运行:

delimiter ;

call comment_hier(0);

我已经工作了几个小时,但无法解决。谢谢。

1 个答案:

答案 0 :(得分:3)

要实现不同的排序(仅按顶级注释的等级,仅按层次结构),您需要将所有排序条件添加到hier表中作为新的层次结构路径。我对您的分层排序路径进行了重组,以同时包含路径和最终注释ID,以便每个初始注释都没有0而是39(或3840)作为其第一部分。这使得层次排序变得微不足道。

要按等级排序,必须构造rating/comment_id的每个路径元素。这使得按顺序(按线程中的相对等级)然后按comment_id进行排序变得很简单。

结果代码为

模式(MySQL v5.7)

CREATE TABLE `comment` (
  `comment_id` int(11) NOT NULL,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `depth` int(4) NOT NULL DEFAULT '0',
  `comment` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `rating` int(4) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

INSERT INTO `comment` (`comment_id`, `parent_id`, `depth`, `comment`, `rating`) VALUES
(42,39, 1,'Com 2-1', 0),
(41,38, 1,'Com 1-1', 0),
(40,0, 0,'Com 3', 0),
(39,0, 0,'Com 2', 20),
(38,0, 0,'Com 1', 0),
(43,42, 2,'Com 2-1-1', 0),
(44,39, 1,'Com 2-2', 2),
(45,43, 3,'Com 2-1-1-1', 0),
(46,40, 1,'Com 3-1', 0),
(47,44, 2,'Com 2-2-1', 0);

ALTER TABLE `comment`
  ADD PRIMARY KEY (`comment_id`),
  ADD UNIQUE KEY `comment_id` (`comment_id`);

ALTER TABLE `comment`
  MODIFY `comment_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=48;

#CREATING PROCEDURE
#---------------------------------------------------------------------#
drop procedure if exists comment_hier;

delimiter $$

create procedure comment_hier
(
in param_parent_id smallint unsigned
)

BEGIN

DECLARE v_done TINYINT unsigned default 0;
DECLARE v_depth SMALLINT unsigned default 0;

CREATE TEMPORARY TABLE hier(
   comment_id int(11) unsigned, 
   root_id int(11) unsigned,  
   parent_id int(11) unsigned,  
   depth int(99) unsigned default 0,
   relation_path varchar(360) default 0,
   relation_rating_path varchar(360) default 0
)engine = memory;

INSERT INTO hier SELECT comment_id, comment_id as root_id, parent_id, v_depth, comment_id, concat_ws('/',  a.rating, comment_id) FROM comment a WHERE a.parent_id = param_parent_id;

CREATE TEMPORARY TABLE tmp engine=memory SELECT * FROM hier;

while not v_done do

    if exists( select 1 from hier h inner join comment a on h.comment_id = a.parent_id and h.depth = v_depth) then

    insert into hier 
    select a.comment_id, t.root_id, a.parent_id, v_depth + 1, CONCAT_WS('/', relation_path, a.comment_id), CONCAT_WS('/', relation_rating_path,  a.rating, a.comment_id) from comment a 
    inner join tmp t on a.parent_id = t.comment_id and t.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;

#SELECT STATEMENT
#----------------------------------------------------------------------------------#
select  
    a.comment_id,
    a.comment as comment,
    a.parent_id as parent_id,
    h.depth,
    h.relation_path
    , h.relation_rating_path
    , a.rating as rating
    , h.root_id
from
    hier h
left join comment a on h.comment_id = a.comment_id
-- for tree-like ordering
-- order by relation_path
-- for rating ordering of the comment
order by relation_rating_path desc, comment_id
    ;

DROP TEMPORARY TABLE if exists hier;
DROP TEMPORARY TABLE if exists tmp;

END $$

delimiter ;

查询#1

call comment_hier(0);

| comment_id | comment     | parent_id | rating | depth | relation_path | relation_rating_path | root_id |
| ---------- | ----------- | --------- | ------ | ----- | ------------- | -------------------- | ------- |
| 47         | Com 2-2-1   | 44        | 0      | 2     | 39/44/47      | 20/39/2/44/0/47      | 39      |
| 44         | Com 2-2     | 39        | 2      | 1     | 39/44         | 20/39/2/44           | 39      |
| 45         | Com 2-1-1-1 | 43        | 0      | 3     | 39/42/43/45   | 20/39/0/42/0/43/0/45 | 39      |
| 43         | Com 2-1-1   | 42        | 0      | 2     | 39/42/43      | 20/39/0/42/0/43      | 39      |
| 42         | Com 2-1     | 39        | 0      | 1     | 39/42         | 20/39/0/42           | 39      |
| 39         | Com 2       | 0         | 20     | 0     | 39            | 20/39                | 39      |
| 46         | Com 3-1     | 40        | 0      | 1     | 40/46         | 0/40/0/46            | 40      |
| 40         | Com 3       | 0         | 0      | 0     | 40            | 0/40                 | 40      |
| 41         | Com 1-1     | 38        | 0      | 1     | 38/41         | 0/38/0/41            | 38      |
| 38         | Com 1       | 0         | 0      | 0     | 38            | 0/38                 | 38      |

View on DB Fiddle