You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel项目中MySQL无限层级评论及回复排序问题

无限嵌套层级评论排序问题

问题背景

在Laravel项目中处理无限嵌套的评论及回复排序时遇到问题,需求是按created_at降序排列评论,同时保留回复的树形结构。单页对应commentable_id的评论数量可能超过500条。

表结构(简化)

comments表字段:
id | body | user_id | parent_id | commentable_id | created_at
根评论的parent_id为NULL。

当前使用的查询语句

WITH RECURSIVE child_comments (id, body, parent_id, created_at, path, depth) AS
(   
    SELECT id, body, parent_id, created_at,
        CAST(id AS CHAR(200)),
        0 as depth
    FROM comments
    WHERE 
        commentable_id = 150 
        AND parent_id is NULL
    UNION ALL 
    SELECT c.id, c.body, c.parent_id, c.created_at,
        CONCAT(cc.path, ', ', c.id),
        cc.depth + 1 
    FROM child_comments cc 
    JOIN comments c ON c.parent_id =cc.id
    WHERE c.commentable_id = 150
) SELECT * from child_comments
    ORDER BY
        CASE WHEN parent_id != 0 THEN path END,
        CASE WHEN parent_id is NULL THEN  `created_at` END DESC;

当前查询结果

+------+------------------+-----------+---------------------+---------------+-------+
| id   | body             | parent_id | created_at          | path          | depth |
+------+------------------+-----------+---------------------+---------------+-------+
|    7 | coment 3         |      NULL | 2024-05-09 12:13:07 | 7             |     0 |
|    2 | coment 2         |      NULL | 2024-05-02 21:07:29 | 2             |     0 |
|    1 | coment 1         |      NULL | 2024-05-02 17:28:42 | 1             |     0 |
|    3 | coment 1_1       |         1 | 2024-05-07 23:02:50 | 1, 3          |     1 |
|    5 | coment 1_1_1     |         3 | 2024-05-07 23:03:02 | 1, 3, 5       |     2 |
|    6 | coment 1_1_1_1   |         5 | 2024-05-07 23:05:26 | 1, 3, 5, 6    |     3 |
|    9 | coment 1_1_1_1_1 |         6 | 2024-05-09 12:14:06 | 1, 3, 5, 6, 9 |     4 |
|    4 | coment 1_2       |         1 | 2024-05-07 23:02:57 | 1, 4          |     1 |
|    8 | coment 2_1       |         2 | 2024-05-09 12:13:35 | 2, 8          |     1 |
+------+------------------+-----------+---------------------+---------------+-------+

期望结果

+----+-------------------+-----------+---------------------+---------------+-------+
| id | body              | parent_id | created_at          | path          | depth |
+----+-------------------+-----------+---------------------+---------------+-------+
|  7 | comment 3         |      NULL | 2024-05-09 12:13:07 | 7             |     0 |
|  2 | comment 2         |      NULL | 2024-05-02 21:07:29 | 2             |     0 |
|  8 | comment 2_1       |         2 | 2024-05-09 12:13:35 | 2, 8          |     1 |
|  1 | comment 1         |      NULL | 2024-05-02 17:28:42 | 1             |     0 |
|  3 | comment 1_1       |         1 | 2024-05-07 23:02:50 | 1, 3          |     1 |
|  5 | comment 1_1_1     |         3 | 2024-05-07 23:03:02 | 1, 3, 5       |     2 |
|  6 | comment 1_1_1_1   |         5 | 2024-05-07 23:05:26 | 1, 3, 5, 6    |     3 |
|  9 | comment 1_1_1_1_1 |         6 | 2024-05-09 12:14:06 | 1, 3, 5, 6, 9 |     4 |
|  4 | comment 1_2       |         1 | 2024-05-07 23:02:57 | 1, 4          |     1 |
+----+-------------------+-----------+---------------------+---------------+-------+

解决方案

当前查询的排序逻辑存在缺陷:根评论按created_at降序排列,但子评论未紧跟对应父评论,而是所有根评论排完后才集中显示子评论。要实现根评论降序、子评论嵌套跟随父评论的需求,需调整递归CTE的排序依据字段,并修改排序规则。

修改后的查询语句(保留原path格式)

WITH RECURSIVE child_comments (id, body, parent_id, created_at, sort_path, depth, root_created) AS
(   
    SELECT id, body, parent_id, created_at,
        CONCAT(LPAD(CAST(UNIX_TIMESTAMP(created_at) AS CHAR), 10, '0'), ',', id) AS sort_path,
        0 as depth,
        UNIX_TIMESTAMP(created_at) as root_created
    FROM comments
    WHERE 
        commentable_id = 150 
        AND parent_id is NULL
    UNION ALL 
    SELECT c.id, c.body, c.parent_id, c.created_at,
        CONCAT(cc.sort_path, ',', LPAD(CAST(UNIX_TIMESTAMP(c.created_at) AS CHAR), 10, '0'), ',', c.id),
        cc.depth + 1,
        cc.root_created
    FROM child_comments cc 
    JOIN comments c ON c.parent_id = cc.id
    WHERE c.commentable_id = 150
) 
SELECT 
    id, 
    body, 
    parent_id, 
    created_at,
    -- 还原原path格式(仅用于展示)
    SUBSTRING_INDEX(GROUP_CONCAT(SUBSTRING_INDEX(sort_path, ',', -1) ORDER BY depth), ',', depth+1) AS path,
    depth
from child_comments
-- 先按根评论时间降序,再按sort_path降序确保子评论跟随父评论
ORDER BY root_created DESC, sort_path DESC;

简化方案(无需保留原path格式,性能更优)

如果不需要展示原path字段,可直接用包含时间戳的排序路径实现需求,适合大数量评论场景:

WITH RECURSIVE child_comments (id, body, parent_id, created_at, sort_path, depth) AS
(   
    SELECT id, body, parent_id, created_at,
        CONCAT(LPAD(CAST(UNIX_TIMESTAMP(created_at) AS CHAR), 10, '0'), '-', id) AS sort_path,
        0 as depth
    FROM comments
    WHERE 
        commentable_id = 150 
        AND parent_id is NULL
    UNION ALL 
    SELECT c.id, c.body, c.parent_id, c.created_at,
        CONCAT(cc.sort_path, '-', LPAD(CAST(UNIX_TIMESTAMP(c.created_at) AS CHAR), 10, '0'), '-', c.id),
        cc.depth + 1 
    FROM child_comments cc 
    JOIN comments c ON c.parent_id = cc.id
    WHERE c.commentable_id = 150
) 
SELECT id, body, parent_id, created_at, depth
from child_comments
ORDER BY sort_path DESC;

逻辑说明

  1. 排序路径设计:将评论的时间戳(补零为固定长度)与id拼接成sort_path,确保排序时能按时间降序排列,同时让子评论的路径继承父评论的路径前缀,自然实现嵌套跟随。
  2. 排序规则:直接按sort_path降序,即可让根评论按时间从新到旧排列,子评论自动紧跟对应父评论,且子评论内部也按时间降序排列。

内容的提问来源于stack exchange,提问作者Ridderspoor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:55:53