3

我正在用 PHP/MySQL 设计一个票务系统。每张票代表“票”表中的一行。用户可以为每张工单添加多个“评论”。

查看票证时,我只是在“评论”表上执行 SQL 查询,以选择链接到相应票证 ID 的所有评论

但是,现在我必须设计一个搜索页面,它返回任何匹配的票证并立即显示(部分,例如最后 5 个)他们的链接评论。

我通过执行我在单票视图页面中使用的相同 SQL 查询来实现这一点,但对搜索查询中的每个匹配行重复此操作。因此,如果您有 1500 个匹配行,这意味着“comments”表上有 1500 个 SQL 查询,是的,效率不高……

现在我想知道:有没有办法将评论表连接到门票表,但将评论表中的多行“压缩”成一列?

我正在考虑对评论行执行 CONCAT(),将它们作为逗号分隔值返回,然后我可以在 php 中再次explode() 以获取包含我对每张票的所有评论的数组。但这是最有效的方法还是有更好的方法?

编辑:请记住,还没有任何评论的票也应该由查询返回

4

3 回答 3

2

嗯,是的,很多人容易遇到的典型的“set-grouping”问题……

当我们谈论将条记录(评论)连接到一行(事件)时,使用 MySQL 可靠地(如考虑可变数量的行)执行此操作的唯一方法是使用GROUP_CONCAT()which 将跨一个或多个行的信息分组到一个单个分隔的字符串。

您还可以使用名为 的功能将信息分组到可变数量的实际PIVOT中,但不幸的PIVOT是,在 MySQL 中不可用。

无论哪种方式,您都需要一些应用程序逻辑(爆炸等)来格式化和显示每张工单的评论子集。

至于 SQL,您可以执行以下操作:

SELECT
    a.ticket_id,
    a.ticket_title,
    a.date_created,
    SUBSTRING_INDEX(GROUP_CONCAT(CONCAT(LEFT(b.comment_txt, 150), '...', ':::', b.date_posted) ORDER BY b.date_posted DESC SEPARATOR '|||'), '|||', 5) AS comment_list
FROM
    tickets a
LEFT JOIN
    comments b ON a.ticket_id = b.ticket_id
WHERE
    a.ticket_title LIKE '%search_term%'
GROUP BY
    a.ticket_id

除了...中的第 4 列之外,这里的所有内容都相当简单,SELECT所以让我们分解一下:

在最里面,我们有CONCAT(). 它的作用是将每个评论的字段连接在一起,以便您能够获得每个评论的多个属性(例如日期、实际文本,也许还有 ID 等)。

CONCAT()单独之后,单个评论可能看起来像:

Lorem Ipsum dolor sit amet consecteur...:::2012-06-21 00:00:00

:::是分隔explode()每个属性的分隔符之一。

向外移动,GROUP_CONCAT()然后将每一连接在一起。在这一点上,我们基本上是连接连接。ORDER BY b.date_posted此外,由于在函数内,最近的注释出现在字符串的开头。

评论列表可能如下所示:

Lorem Ipsum dolor sit amet consecteur...:::2012-06-21 00:00:00|||Cras aliquam neque quam, eget facilisis nulla...:::2012-06-18 00:00:00

|||是您用来分隔每条评论的分隔符。

再往前走,SUBSTRING_INDEX只选择前五个评论。由于我们按最近的优先排序评论,因此基本上只选择每张票中的五个最近的评论。

然后在您的 PHP 代码中,您可以大致执行以下操作:

foreach($tickets as $ticket)
    {
        // First check if the ticket has comments. Value will be NULL if not.
        if(!empty($ticket['comment_list']))
        {
            foreach(explode('|||', $ticket['comment_list']) as $comment)
            {
                $attributes = explode(':::', $comment);

                $comment_preview = $attributes[0]; // Get first attribute
                $date_posted = $attributes[1]; // Get second attribute
            }
        }
    }

我使用这些特定的分隔符是因为逗号可以出现在标题等字段中,并且您不希望脚本将字符串分隔在错误的位置。这种错误分隔的可能性是使用 的主要缺点之一GROUP_CONCAT(),因此您必须根据它们出现在字段值中的可能性来决定最好使用哪些分隔符。

于 2012-06-21T10:23:44.343 回答
2

Zane Bien 回答了这个CONCAT问题,我不会评论它,因为没有太多要补充的,但你也问这是否是最有效的方法,所以我会选择这个。

我真的不喜欢这个CONCAT解决方案。这不是 SQL 的哲学,你永远不知道用户会在评论区写什么。

就 SQL 而言,正确的做法是:

SELECT
    tickets.*,
    comments.*
FROM tickets
LEFT JOIN comments ON comments.ticket_id = tickets.id
WHERE tickets.title LIKE '%whatever%';

然后在 PHP 中,您只需遍历结果集并构建输出。请注意,LEFT JOIN 将正确地返回票证而不加评论。

如果你感到有点不安——但不应该!- 在每条线路上都有票信息,您可以将查询拆分为 2:

SELECT
    tickets.*
FROM tickets
WHERE tickets.title LIKE '%whatever%';

SELECT
    comments.*
FROM comments
INNER JOIN tickets ON comments.ticket_id = tickets.id
WHERE tickets.title LIKE '%whatever%';

这意味着在应用程序方面需要做更多的工作,因为您必须将评论链接回票证。

我的建议:进行多合一查询。

于 2012-06-21T11:38:23.667 回答
1

评论必须有工单 ID 的记录才能关联,因此您应该能够从工单中获取 ID 并使用 JOIN 来获取评论。

我正在用 Wordpress DB 做一些非常相似的事情,所以我只能通过那个布局。在 wordpress 中,帖子有一个与评论相关的 ID。

所以你应该能够用我认为的一个查询来做到这一点。也就是说,如果表格的设计允许这种类型的关联——它应该这样做。

SELECT tickets.ID, tickets.title, tickets.content, comments.content 
FROM tickets
INNER JOIN comments ON comments.ticket_id = tickets.ID
WHERE tickets.content LIKE '%the search term%'
ORDER BY comments.comment_date 

我只掌握了 SQL 的功能,所以这里的其他人可能会以正确的方式纠正我,但我认为它应该可以工作。此查询的实际搜索元素仅用于示例,我不知道您实际搜索的是什么。

修改:

$query = "SELECT tickets.ID ticket_id, tickets.title title, tickets.content content
          FROM tickets
          WHERE tickets.content LIKE '%the search term%'
          AND tickets.title LIKE '%the search term%'";

$tickets = $db->fetch_array($query);


foreach ($tickets as $ticket) {
    echo "<h1>$ticket['title']</h1>";
    echo "<h1>$ticket['content']</h1>";

    $query = "SELECT comments.title title, comments.content content
              FROM comments
              WHERE comments.ID = {$ticket['ticket_id']}
              ORDER BY comments.date DESC
              LIMIT 5";

    $comments = $db->fetch_array($query); 

    if ($comments) {

       echo "<div class="comments">";

       foreach($comments as $comment) {
          echo "<div class="comment">";
          echo "<h1>$comment['title']</h1>";
          echo "<h1>$comment['content']</h1>";
          echo "</div>";
       }

       echo "</div>";

    }
}
于 2012-06-21T09:39:12.740 回答