【问题标题】:MySQL: Can I do a left join and pull only one row from the join table?MySQL:我可以进行左连接并从连接表中仅拉出一行吗?
【发布时间】:2012-06-23 06:41:57
【问题描述】:

我为工作编写了一个自定义帮助台,它一直运行良好……直到最近。一个查询真的变慢了。现在大约需要14秒!以下是相关表格:

CREATE TABLE `tickets` (
  `id` int(11) unsigned NOT NULL DEFAULT '0',
  `date_submitted` datetime DEFAULT NULL,
  `date_closed` datetime DEFAULT NULL,
  `first_name` varchar(50) DEFAULT NULL,
  `last_name` varchar(50) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `description` text,
  `agent_id` smallint(5) unsigned NOT NULL DEFAULT '1',
  `status` smallint(5) unsigned NOT NULL DEFAULT '1',
  `priority` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `date_closed` (`date_closed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `solutions` (
  `id` int(10) unsigned NOT NULL,
  `ticket_id` mediumint(8) unsigned DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `hours_spent` float DEFAULT NULL,
  `agent_id` smallint(5) unsigned DEFAULT NULL,
  `body` text,
  PRIMARY KEY (`id`),
  KEY `ticket_id` (`ticket_id`),
  KEY `date` (`date`),
  KEY `hours_spent` (`hours_spent`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

当用户提交工单时,它会进入“工单”表。然后,当代理解决问题时,他们会记录他们采取的行动。每个条目都进入“解决方案”表。换句话说,门票有很多解决方案。

变慢的查询的目标是从“tickets”表中提取所有字段,以及从“solutions”表中提取最新条目。这是我一直在使用的查询:

SELECT tickets.*,
    (SELECT CONCAT_WS(" * ", DATE_FORMAT(solutions.date, "%c/%e/%y"), solutions.hours_spent, CONCAT_WS(": ", solutions.agent_id, solutions.body))
    FROM solutions
    WHERE solutions.ticket_id = tickets.id
    ORDER BY solutions.date DESC, solutions.id DESC
    LIMIT 1
) AS latest_solution_entry
FROM tickets
WHERE tickets.date_closed IS NULL
OR tickets.date_closed >= '2012-06-20 00:00:00'
ORDER BY tickets.id DESC

以下是“latest_solution_entry”字段的示例:

6/20/12 * 1337 * 1: I restarted the computer and that fixed the problem. Yes, I took an hour to do this.

在 PHP 中,我将“latest_solution_entry”字段拆分并正确格式化。

当我注意到运行查询的页面速度变慢了方式时,我在没有子查询的情况下运行了查询,而且速度非常快。然后我在原始查询上运行了EXPLAIN 并得到了这个:

+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
| id | select_type        | table     | type  | possible_keys | key       | key_len | ref                 | rows  | Extra                       |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
|  1 | PRIMARY            | tickets   | index | date_closed   | PRIMARY   | 4       | NULL                | 35804 | Using where                 |
|  2 | DEPENDENT SUBQUERY | solutions | ref   | ticket_id     | ticket_id | 4       | helpdesk.tickets.id |     1 | Using where; Using filesort |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+

因此,我正在寻找一种方法来提高查询效率,同时仍能实现相同的目标。有什么想法吗?

【问题讨论】:

    标签: mysql query-optimization


    【解决方案1】:

    当您在 SELECT 子句中有一个内联视图时,它必须为每一行执行该选择。我发现在这种情况下最好在 FROM 子句中放置一个内联视图,而不是执行一次选择。

    SELECT t.*, 
           Concat_ws(" * ", Date_format(s.date, "%c/%e/%y"), s.hours_spent, 
           Concat_ws(":", s.agent_id, s.body)) 
    FROM   tickets t 
           INNER JOIN (SELECT solutions.ticket_id,
                              Max(solutions.date) maxdate 
                       FROM   solutions 
                       GROUP  BY solutions.ticket_id) last_solutions 
                   ON t.id = last_solutions.ticket_id
           INNER JOIN (SELECT solutions.ticket_id,
                              solutions.date,
                              Max(solutions.id) maxid 
                       FROM   solutions 
                       GROUP  BY solutions.ticket_id,
                                solutions.date) last_solution
                  ON last_solutions.ticket_id = last_solution.ticket_id 
                     and last_solutions.maxDate = last_solution.Date
           INNER JOIN solutions s 
                   ON last_solution.maxid = s.id
    WHERE  t.date_closed IS NULL 
            OR t.date_closed >= '2012-06-20 00:00:00' 
    ORDER  BY t.id DESC 
    

    注意:您可能需要根据需要将其设为 LEFT 联接

    【讨论】:

    • 这接近我所需要的。谢谢!不过,我认为ON t.id = last_solution.maxid 是错误的,因为tickets.idsolutions.ticket_id 相关,而不是solutions.id。另一个问题是我原始查询中的ORDER BY solutions.date DESC, solutions.id DESC 非常关键。代理可以修改他们提交解决方案的日期,因此他们可以将所有解决方案设置为相同的日期/时间,或过去的日期等。所以我想离开最近的日期在前,然后是最高的 ID。不只是最高的ID。那还有可能吗?非常感谢!
    • 我修复了第一个被破坏的ON,并使用两个子查询来获取第一个日期和 ID
    • 谢谢!我在 phpMyAdmin 中尝试了两次该查询,但我的浏览器两次都没有响应。我在上面运行了EXPLAIN,看起来它做了很多工作。为了让您了解我正在使用什么,“tickets”表有大约 32K 行,“solutions”表有大约 40K 行。有什么我做错了吗?非常感谢您的帮助!
    • 嗯,这不是很多行。由于双重聚合,这个查询很可能更糟。如果solutions.date 有一个索引以及solutions.id 和solutions.ticket 会有所帮助。这也可能有助于您的原始查询。在那之后,我没有想法抱歉。
    【解决方案2】:

    试试这个:

    SELECT *
    FROM (
      -- for each ticket get the most recent solution date
      SELECT ticket_id, MAX(solutions.date) as date
      FROM solutions
      GROUP BY ticket_id
    ) t
    JOIN tickets ON t.ticket_id = tickets.id
    WHERE tickets.date_closed IS NULL OR tickets.date_closed >= '2012-06-20 00:00:00'
    ORDER BY tickets.id DESC
    

    请注意,如果票证包含同一日期的 2 个解决方案,您的结果集中将有重复记录。您将需要另一个连接来删除这些重复项或使用绝对序列(如序列)(递增主键)。

    【讨论】:

    • 这将无法正常工作。当您不按 Mysql 中的字段(在本例中为日期)分组时,MySQL 返回组中的第一个值。所以除非第一个值也是最大值,否则它将被过滤掉。在这个simple demonstration 中,应该是两条记录时只返回一条记录
    • 你是对的。事实上,我相信我给出的 SQL 严格来说是不合法的。我已经替换了我的答案。
    【解决方案3】:

    让我总结一下我的理解:您想选择每张票及其最后的解决方案。

    我喜欢对这类问题使用以下模式,因为它避免了子查询模式,因此在需要性能的地方相当不错。缺点是有点难理解:

    SELECT
      t.*,
      s1.*
    FROM tickets t
    INNER JOIN solutions s1 ON t.id = s1.ticket_id
    LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id AND s2.id > s1.id
    WHERE s2.id IS NULL;
    

    为了更好地理解,我只写了模式的核心。

    关键是:

    • solutions 表的 LEFT JOIN 与自身的 s1.ticket_id = s2.ticket_id 条件:它模拟 GROUP BY ticket_id

    • 条件s2.id > s1.id:它是“我只想要最后一个解决方案”的SQL,它模拟MAX()。我假设在您的模型中,the last 表示with the greatest id,但您可以在此处使用日期条件。请注意,s2.id < s1.id 将为您提供第一个解决方案。

    • WHERE 子句s2.id IS NULL:最奇怪但绝对必要的...只保留您想要的记录。

    试试看告诉我:)

    编辑 1: 我刚刚意识到第二点假设过于简单化了问题。这让它变得更加有趣:p 我正在尝试看看这种模式如何与您的date, id 订购一起使用。

    编辑 2: 好的,稍加改动后效果很好。 LEFT JOIN 的条件变为:

    LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id
      AND (s2.date > s1.date OR (s2.date = s1.date AND s2.id > s1.id))
    

    【讨论】:

    • 如此接近!谢谢你的帮助!最后一件事是我需要没有解决方案的票仍然出现。只是在这种情况下latest_solution_entry 字段应该为空。
    • 好的,知道了。我刚刚将INNER JOIN 更改为LEFT JOIN。我还在检查一些东西,但会回来报告。
    • 奇怪的是,今天运行原始查询只需要大约 0.2136 秒(而不是 14 秒)。我用新查询(你的)试了一下,大约需要 0.0026 秒,大约是原始查询时间的 1.2%! 好多了!我不确定是什么导致它昨天需要 14 秒,但希望通过这个新查询,我们现在的状态会好得多。非常感谢!
    • 如果表中没有自动递增的 ID,您将如何调整此解决方案?
    【解决方案4】:

    根据目的,我给出一个想法:

    SELECT DISTINCT s1.ticket_id, t.*,  s1.*
    FROM tickets t
    LEFT JOIN solutions s1 ON t.id = s1.ticket_id
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-21
      • 2013-02-18
      • 2013-05-16
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      相关资源
      最近更新 更多