Упорядочение иерархического набора результатов бесконечного уровня с MySQL - PullRequest
0 голосов
/ 10 января 2019

Я работаю на бесконечной системе комментариев-ответов. И получение строк родительских и относительных данных успешно, но у меня проблемы с упорядочением этих строк.

Используется для создания результатов: 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 Fiddle https://www.db -fiddle.com / ж / 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 голосов
/ 10 января 2019

Для достижения разных порядков (по рейтингу комментария верхнего уровня, только по иерархии) вам нужно будет добавить все критерии сортировки в вашу таблицу hier в качестве новых иерархических путей. Я реструктурировал ваш путь иерархической сортировки, включив в него как путь, так и идентификатор окончательного комментария, чтобы каждый начальный комментарий не содержал 0, а 39 (или 38 или 40) в качестве первого компонента. Это делает иерархическое упорядочение тривиальным.

Для упорядочения по рейтингу вы должны построить каждый элемент пути из 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      |

Просмотр на БД Fiddle

...