【问题标题】:MySQL: Link multiple records from table B, onto single record of table A in one rowMySQL:将表 B 中的多条记录链接到表 A 的一行中的单个记录上
【发布时间】:2012-06-23 12:11:29
【问题描述】:

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

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

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

我通过执行我在单个工单视图页面中使用的相同 SQL 查询来完成这项工作,但对搜索查询中的每个匹配行重复此操作。所以如果你有 1500 个匹配的行,这意味着 'cmets' 表上有 1500 个 SQL 查询,是的,不是很有效......

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

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

编辑:请记住,我们也应该通过查询返回还没有任何 cmets 的票证

【问题讨论】:

  • 您能否发布您的两个表的结构以及您希望为每张工单检索comments 表中的哪些字段?

标签: php mysql sql


【解决方案1】:

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 将在没有 cmets 的情况下正确返回票证。

如果您感到有点不安 - 但不应该! - 每行都有票务信息,您可以将查询拆分为 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%';

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

我的建议:多合一查询。

【讨论】:

  • 哇,这就是我要找的东西!非常感谢!我确实会进行多合一查询并在 PHP 中对多个票行进行排序。
  • 这也是我即将建议的替代解决方案。当您遍历结果集时,它涉及更多的应用程序逻辑来检测ticket_id 中的更改。好处是您不必担心分隔符风险。缺点是即使您只需要 5 个最近的 cmets,您也必须在应用程序代码中遍历每个票证的 all cmets。当然,您可以按日期订购 cmets,但在获得前五个后,您必须 continue; 直到 ticket_id 值发生变化。
  • 也许有些票包含 800+ cmets?这可能会稍微减慢你的速度。我根据标题准确地回答了这个问题(“多条记录在一行中”),但我认为这种解决方案更好,并且是可行的方法。完全消除应用程序在某些输入上中断的风险的好处超过了可能多一点渲染时间的缺点。
  • 如果commentstickets 都有自己的id 列,您的一体化查询将返回两个id 列。
  • 我可以在这个查询中放一个 WHERE 语句,它可以根据输入日期、最后 x 等过滤掉 cmets...
【解决方案2】:

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

我正在使用 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>";

    }
}

【讨论】:

  • 刚刚意识到我无缘无故地多次拉票... Oliver 的想法是正确的,通过先获取票证来分解查询,然后使用票证 ID 获取 cmets。除非你也需要搜索 cmets 的内容!
  • 如果这样做只会显示 1 个 cmets.content 字段,仅包含一条评论
  • 第一个查询是错误的,我只是把它留在里面以显示工作原理和我被误导的逻辑。
  • 您可以从第一个查询中取出 group by 以获取所有 cmets 并按日期和日期排序,但一次性完成所有操作会降低灵活性
  • 您所做的最后一次编辑实际上就是我现在所做的,每次都为您循环的每张票查询 cmets,但这会导致每张票有 1 个 SQL 查询。如果您的搜索查询有 1500 行结果集,您最终将执行 1500 个其他 SQL 查询来显示所有 cmets,这确实有效,但不是很干净或高效。
【解决方案3】:

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

当我们谈论加入多条记录(cmets)到一行(事件)时,使用 MySQL 可靠地(如考虑可变数量的行)执行此操作的唯一方法是使用 @ 987654321@ 会将一行或多行的信息组合成一个分隔字符串。

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

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

对于 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

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

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

CONCAT() 之后,一条评论可能看起来像:

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

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

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

评论列表可能如下所示:

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 仅选择前五个 cmets。由于我们是按最近的顺序排列 cmets,因此基本上只选择每张票中最近的五个 cmets。

然后在你的 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() 的主要缺点之一,因此您必须根据它们出现在字段值中的可能性来决定最好使用哪些分隔符。

【讨论】:

  • 感谢您建议 Zane,这就是我在问题中所指的内容,但感谢您将其写得更干净。问题是,这个工单系统将用于开发人员和高级支持台之间的交互,因此 cmets 必然会到处包含代码片段。我认为有人将我的分隔符字符串放入评论只是时间问题。如果有人能提出更好的解决方案,我将等待,如果没有,我会接受这个答案。谢谢!
  • 您可以将评论文本值存储为 Base64,这意味着如果您使用 .这不是有效的 Base64 字符。您还可以推出自己的编码,过滤掉您正在使用的任何分隔符。
猜你喜欢
  • 2022-06-18
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
  • 2019-05-05
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多