MySQL:我可以进行左连接并只从连接表中拉出一行吗?

标签 mysql query-optimization

我为工作编写了一个自定义帮助台,它一直运行良好……直到最近。一个查询确实变慢了。现在大约需要 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;

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

已减慢查询的目标是从“票”表中提取所有字段,并从“解决方案”表中提取最新条目。这是我一直在使用的查询:

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 |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+

所以我正在寻找一种方法来提高我的查询效率,但仍能达到相同的目标。有什么想法吗?

最佳答案

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

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

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 的左连接表本身与 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))

关于MySQL:我可以进行左连接并只从连接表中拉出一行吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11131237/

相关文章:

mysql - 身份验证/授权 Entity Framework

mysql - 如何让这个SQL查询更加高效?

mysql - 如何在以下 mysql 查询的 where 子句中引用 'decider'?

PHP 如果大于(条件)则不起作用?

php - 对相关模型进行分页

mysql - 请解释一下这个 Shell 管道魔法 (... | tee >(tail -c1 >$PULSE) | bzip2 | ...) 是如何工作的?

mysql - 如何优化 MySQL 中涉及子查询的查询?

php - 加速mysql查询

mysql - 我应该注意 MySQL 中的 SQL 查询优化吗

mysql - 关于SQL语法的问题