MySQL 使用子选择的查询需要使用连接或存在

MySQL query using subselects needs to use joins or exists

随着时间的推移,我得到了这个查询,它甚至与其他查询等合并,所以它变得一团糟。

现在执行需要的时间太长了。我尝试使用 EXPLAIN EXTENDED 并添加我可以添加的任何索引/键,但出于某种原因我没有做任何帮助。

我很确定原因是所有子 select 都因为 mysql 必须在内存中创建临时 table 并对其执行非索引查找table 每一行(至少,这是我一直在阅读的内容)。

我一直在阅读有关子查询、联接和使用 exists 来尝试优化这件事的信息,但我只是不了解解决此问题的最佳方法。

有没有什么方法可以使用连接或使用 exists 来替换一些子查询并使此查询更快 运行?

查询:

SELECT 
      s.*, 
      case when m_id.make_name is not null 
                then m_id.make_name
           when m.make_name is not null 
                then m.make_name
                else s.brand end as brandname
   FROM 
      services as s 
         left join makelist as m_id 
            on cast(s.brand as unsigned) = m_id.id 
         left join makelist as m 
            on s.brand = m.make_name 
   WHERE 
          s.is_delete = 'n' 
      and UNIX_TIMESTAMP(s.`date`) >= 1420070400 
      and UNIX_TIMESTAMP(s.`date`) <= 1451563199 
      and s.service_id in ( select ticket_id
                               from messages
                               where edit_id = 0 
                                 and waiting = 1 
                                 and message_id not in ( select edit_id
                                                            from messages
                                                            where edit_id != 0 ) 
                          ) 
      or service_id in ( select ( select m3.ticket_id
                                     from messages m3 
                                     where m88.edit_id = m2.message_id ) as ticket_id 
                            from 
                               messages m88 
                            where m88.edit_id in ( select t11.edit_id
                                                      from 
                                                         ( select max(`datetime`) as newdate 
                                                             from messages
                                                             where edit_id != 0 
                                                             group by edit_id ) as t22, 
                                                         messages as t11 
                                                      where t11.`datetime` = t22.newdate 
                                                        and `waiting` = 1 ) 
                       ) 
     and s.service_id in ( select ticket_id
                              from messages
                              where edit_id = 0 
                                and warning = 1 
                                and message_id not in ( select edit_id
                                                           from messages
                                                           where edit_id != 0 )
                         ) 
      or service_id in ( select 
                               ( select m33.ticket_id
                                    from messages m33 
                                    where m888.edit_id = m22.message_id ) as ticket_id
                            from messages m888 
                            where m888.edit_id in ( select t111.edit_id 
                                                       from ( select max(`datetime`) as newdate 
                                                                 from messages
                                                                 where edit_id != 0 
                                                                 group by edit_id ) as t222, 
                                                            messages as t111 
                                                       where t111.`datetime` = t222.newdate
                                                        and `warning = 1 ) 
                       ) 
   order by 
      s.`date` desc 
   limit 
      0, 10

还有...数据样本...

table:消息

CREATE TABLE IF NOT EXISTS `messages` (
  `message_id` int(10) NOT NULL AUTO_INCREMENT,
  `employee_id` int(10) NOT NULL,
  `admin_id` int(10) NOT NULL,
  `ticket_id` int(10) NOT NULL,
  `message` text NOT NULL,
  `status` char(1) NOT NULL COMMENT 'r=read, u=unread',
  `datetime` datetime NOT NULL,
  `warning` tinyint(1) NOT NULL DEFAULT '0',
  `waiting` tinyint(1) NOT NULL DEFAULT '0',
  `edit_id` int(10) NOT NULL,
  PRIMARY KEY (`message_id`),
  KEY `message_id` (`message_id`),
  KEY `edit_id` (`edit_id`),
  KEY `ticket_id` (`ticket_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=197 ;



INSERT INTO `messages` (`message_id`, `employee_id`, `admin_id`, `ticket_id`, `message`, `status`, `datetime`, `warning`, `waiting`, `edit_id`) VALUES
(189, 18, 0, 4049, 'Ordered battery ', 'u', '2015-06-02 13:14:38', 0, 1, 0),
(190, 18, 0, 4069, 'Ordered Ram', 'u', '2015-06-04 09:17:57', 0, 0, 0),
(191, 18, 0, 4069, 'Ordered Ram', 'u', '2015-06-04 09:18:43', 0, 1, 0),
(192, 18, 0, 4068, 'Ordered Hard Drive', 'u', '2015-06-04 13:40:13', 0, 1, 0),
(193, 1, 0, 3712, 'customer called just now and said data was missing from last time it was here, i informed her that we keep backups for a month (not 4) and that was definitely gone, and that her screen was still going blank, and i informed her she needed to drop it by for free test. she said her daughter has it in another county and it will be a while before she can bring it in. ', 'u', '2015-06-06 09:59:27', 1, 0, 0),
(194, 18, 0, 4089, 'Ordered Keyboard ', 'u', '2015-06-09 09:51:33', 0, 1, 0),
(195, 18, 0, 4103, 'Battery  PA3817u-1BRS....  or Jack 0..  customer said will bring it back next week. ', 'u', '2015-06-11 16:53:16', 0, 0, 0),
(196, 18, 0, 4105, 'Ordered Screen ', 'u', '2015-06-12 11:26:09', 0, 1, 0);

table: 名单

CREATE TABLE IF NOT EXISTS `makelist` (
  `id` int(255) NOT NULL AUTO_INCREMENT,
  `make_name` varchar(255) NOT NULL,
  `make_desc` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=31 ;

INSERT INTO `makelist` (`id`, `make_name`, `make_desc`) VALUES
(1, 'Acer', ''),
(2, 'Apple', ''),
(3, 'ASUS', ''),
(4, 'Compaq', ''),
(5, 'Dell', ''),
(6, 'Gateway', ''),
(7, 'HP', ''),
(8, 'IBM', ''),
(9, 'Lenovo', ''),
(10, 'Sony', ''),
(11, 'Toshiba', ''),
(27, 'Microsoft', ''),
(26, 'Printer Only', ''),
(25, 'Custom', ''),
(23, 'eMachine', ''),
(24, 'MSI', ''),
(30, 'Panasonic', ''),
(28, 'Samsung', '');

table:服务

CREATE TABLE IF NOT EXISTS `services` (
  `service_id` int(10) NOT NULL AUTO_INCREMENT,
  `employee_id` int(10) NOT NULL,
  `customer_id` int(10) NOT NULL,
  `name` varchar(255) NOT NULL,
  `date` datetime NOT NULL,
  `phone` text NOT NULL,
  `alternate_phone` text NOT NULL,
  `email` varchar(50) NOT NULL,
  `brand` varchar(50) NOT NULL,
  `model` varchar(50) NOT NULL,
  `serial_tag` varchar(50) NOT NULL,
  `password` varchar(25) NOT NULL,
  `type` char(1) NOT NULL,
  `emergency` char(1) NOT NULL,
  `symptoms` varchar(100) NOT NULL,
  `left_items` text NOT NULL,
  `employee_note` text NOT NULL,
  `is_delete` char(1) NOT NULL DEFAULT 'n' COMMENT 'y=yes, n=no',
  `pickedup` tinyint(1) NOT NULL,
  `pickup_time` datetime NOT NULL,
  `how_paid` varchar(255) NOT NULL DEFAULT 'NA',
  `on_call_list` tinyint(1) NOT NULL,
  `call_list_note` mediumtext NOT NULL,
  `exclude` tinyint(1) NOT NULL DEFAULT '0',
  `paymentAmount` decimal(7,2) NOT NULL,
  `typeother` varchar(255) NOT NULL,
  `na_reason` varchar(255) NOT NULL,
  PRIMARY KEY (`service_id`),
  KEY `service_id` (`service_id`),
  KEY `employee_id` (`employee_id`),
  KEY `customer_id` (`customer_id`),
  KEY `is_delete` (`is_delete`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4121 ;


INSERT INTO `services` (`service_id`, `employee_id`, `customer_id`, `name`, `date`, `phone`, `alternate_phone`, `email`, `brand`, `model`, `serial_tag`, `password`, `type`, `emergency`, `symptoms`, `left_items`, `employee_note`, `is_delete`, `pickedup`, `pickup_time`, `how_paid`, `on_call_list`, `call_list_note`, `exclude`, `paymentAmount`, `typeother`, `na_reason`) VALUES
(4118, 18, 0, 'custnameone', '2015-06-12 13:36:00', '(111) 111-1442', '', '', 'Other:::Packard Bell', 'MS2290', '', 'pass', 'l', '', '::diagnostics::', 'power_cord::', 'Will not turn on.. ', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', ''),
(4119, 18, 0, 'custnametwo', '2015-06-12 15:51:00', '(111) 111-9390', '(111) 111-8207 cell', 'email@yahoo.com', '11', 'Satellite  L675', '', '', 'l', 'n', ':virus:::', '::', 'Clean up.. Virus\r\n', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', ''),
(4120, 18, 0, 'custnamethree', '2015-06-12 17:57:00', '(111) 111-1455', '', 'email@yahoo.com', '10', 'Vaio E - Sve151D11L', '', '1234', 'l', 'n', ':virus:diagnostics::', 'power_cord::', 'Will not boot to windows ', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', '');

在请求更多详细信息后更新:

此查询列出了服务 table 中的所有记录,并通过 PHP 动态生成。服务 table 中的每条记录都可以附加 1 条或多条消息,通过 services.service_id = messages.ticket_id 链接。当有人发布消息(或编辑消息)时,他们可以 select 将其标记为 "warning" and/or "waiting"。此查询正在提取将警告或等待设置为 1 的消息的工单。

然而,又一层洋葱皮剥开了,我们来到了消息编辑。消息编辑存储在相同的 table 中,区别在于消息编辑没有 ticket_id 而是有 edit_id 等于原始消息的 message_id。因此,查询必须找到工单,找到与工单关联的消息,找出这些消息是否有编辑,并确定哪一个是消息的最新当前版本,以及消息的当前版本是否标有警告或等待。因此,混乱繁琐的查询。

处理 makelist table 和品牌的东西只是为了完整性,因为开始工作很棘手,想要确保包含任何解决方案。在这种情况下它并不是真正相关的,但是 makelist/brand 的东西基本上是根据品牌列中存储在服务 table 中的品牌 ID 来查找品牌名称。

无论您对此答案提供什么评论,我都会继续帮助您尝试修改,但在您原来的评论中描述太多了post。

您正在查看给定 UNIX 时间范围内的服务,但您在子 select 中的 service_id 上的限定符正在查看所有消息。那么,您是否只对首先符合相关时间范围的门票感兴趣?

您的复杂 WHERE 子句(缩写...)

WHERE 
          s.is_delete = 'n' 
      and UNIX_TIMESTAMP(s.`date`) >= 1420070400 
      and UNIX_TIMESTAMP(s.`date`) <= 1451563199 
      and s.service_id in ... (sub-qualify 1)
       or service_id in ... (sub-qualify 2)
      and s.service_id in ... (sub-qualify 3)
       or service_id in ... (sub-qualify 4)

实际上是 运行 针对所有消息(根据子资格实例 1-4)。

首先仅在日期范围内对原始票证 (message_id's) 进行预查询,然后对其合格的子消息进行编辑以查找最大日期与整个日期。

这是我想出的一些东西,我会试着描述一下,你消化一下。

SELECT
      s2.*,
      COALESCE( m_id.make_name, COALESCE( m.make_name, s2.brand )) as brandname
   from
      ( SELECT
              m.ticket_id,
              SUM( case when edits.edit_id = 0 then 0 else 1 end ) as NumberOfEdits,
              SUM( m.waiting + coalesce( edits.waiting, 0 ) ) as WaitingMsgs,
              SUM( m.warning + coalesce( edits.warning, 0 )) as WarningMsgs,
              SUM( m.waiting 
                 + m.warning 
                 + coalesce( edits.waiting, 0 )
                 + coalesce( edits.warning, 0 ) ) as WaitOrWarnCount,
              MAX( case when edits.waiting = 1 then edits.`datetime` else null end ) as LatestWaitingDate,
              MAX( case when edits.warning = 1 then edits.`datetime` else null end ) as LatestWarningDate,
              MAX( case when edits.waiting = 1 then edits.message_id else null end ) as LatestWaitingMsgID,
              MAX( case when edits.warning = 1 then edits.message_id else null end ) as LatestWarningMsgID
           from
              services as s 
                 LEFT JOIN messages m
                    ON s.service_id = m.ticket_id
                    LEFT JOIN messages edits
                       ON m.message_id = edits.edit_id
           WHERE 
                  s.is_delete = 'n' 
              and UNIX_TIMESTAMP(s.`date`) >= 1420070400 
              and UNIX_TIMESTAMP(s.`date`) <= 1451563199
           GROUP BY
              m.ticket_id ) PreQual

         JOIN services s2
            ON PreQual.ticket_id = s2.service_id

            LEFT JOIN makelist as m_id 
               ON CAST(s2.brand as unsigned) = m_id.id 
            LEFT JOIN makelist as m 
               ON s2.brand = m.make_name 

         LEFT JOIN messages origMsg
            ON PreQual.ticket_id = origMsg.ticket_id

         LEFT JOIN messages waitMsg
            ON PreQual.LatestWaitingMsgID = waitMsg.Message_ID

         LEFT JOIN messages warnMsg
            ON PreQual.LatestWaarningMsgID = warnMsg.Message_ID

   where 
         (     PreQual.NumberOfEdits = 0 
           AND PreQual.WaitOrWarnCount > 0 )
      OR
         waitMsg.message_id > 0
      OR
         warnMsg.message_id > 0

第一个 FROM 源实际上是您感兴趣的 unix 日期范围和状态内的那些服务票证的子 select。它仅左连接到消息 table对于该服务票证 ID。然后根据对给定服务票证的原始消息的任何编辑,将该主要消息左连接到自身。

现在,如果每张票可以有多条消息,我将通过 SUM(case/when) 进行简单计数(如果有任何编辑与消息关联)。接下来,我将根据原始票据消息或任何标记为 "Waiting" 的编辑消息获取 sum(),因此这样一来,如果有任何等待消息,我就会预先知道。同样检查原始消息或任何编辑的任何警告。对于笑容,我还总结了每张服务票的任何等待或警告相关消息的总数。

接下来,我将获得最大 date/time 标记,用于与等待或警告相关票证相关的任何可能的编辑。

最后,我为每个适用的特定工单 if/when 获取相应等待或警告的最新消息 ID 值。我正在使用这个值,因为它是服务编辑票的直接消息,与所有其他服务票无关。

因此,在您开始的日期/状态中,所有这些都汇总到每个最初合格的服务 "Ticket_ID" 的一行中。

现在,额外加入。在这里,我在合格的票证 PreQual 结果上重新加入服务 table,因此我不必为外部查询部分重新应用 unix date/time...我已经有了票证 ID。然后我将 LEFT JOINs 加入原始消息,无论适用于每张票的最新等待消息和警告消息。

终于可以套用整体的WHERE子句了,需要您根据需要确认或调整。

我对您感兴趣的工单的第一个条件是那些没有等待或警告的待处理编辑的服务工单,但原始消息至少是等待或警告状态。

第二个标准(或)是如果有一个编辑状态为等待的条目(已经从预审的总和case/when中获得资格,我已经知道它的状态是等待)

第三个条件(或)同样适用于具有警告的编辑状态(同样,来自预审查询的总和 case/when)。

因此,如果您的数据是一年或更长时间,并且您只是在当前日期/周(或其他)范围内查找票证,那么您只考虑这些消息和编辑,而不是所有内容的整个历史.

同样,您可能需要最终确定您想要的内容,但我认为我非常接近...

最后添加...

不知道消息的上下文,无论最后一条消息是什么,都取代了之前的所有消息,这也可以显着减少您的问题,所以请稍后澄清。

如果给定的产品需要维修,它会得到一个工单 ID 和默认消息,表明一个人 "WAITING" 是要取货的单位。发生了一些事情并且对原始消息进行了编辑,因此创建了一个 EDIT 条目并指示了警告,所以现在,您有原始的等待条目,以及作为警告的后续条目。在联系客户并解决警告后,产品完成其服务并进行另一次编辑并关闭工单,因此 FINAL 编辑对等待或警告没有任何价值。在这种情况下,LAST EDIT,无论任何先前的消息或对先前消息的编辑 "wins" 总体状态......票证已完成。

同样,如果工单以等待开始,然后针对警告进行编辑,则警告(现在是最新的)是状态的主要考虑因素。

如果这个最新的场景更好地描述了服务票操作的工作流程,请确认,我将修改查询以进一步简化。