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

时间:2015-06-18 08:05:58

标签: mysql sql performance join subquery

我已经随着时间的推移添加了这个查询,甚至与其他查询等合并,所以它已经变得非常混乱。

现在执行需要花费太长时间。我尝试使用EXPLAIN EXTENDED并添加任何索引/键,但我没有做任何帮助。

我很确定原因是所有的子选择,因为mysql必须在内存中创建一个临时表,并在该表上为每一行执行非索引查找(至少,'我正在阅读的内容。

我一直在阅读子查询,联接和使用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

并且......数据样本......

表:消息

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.... $39 or Jack $100..  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);

表:makelist

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', '');

表:服务

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', '', '');

在请求更多细节后更新:

此查询列出了services表中的所有记录,并通过PHP动态生成。服务表中的每条记录都可以附加一条或多条消息,通过services.service_id = messages.ticket_id链接。当有人发布消息(或编辑一封消息)时,他们可以选择将其标记为&#34;警告&#34;和/或&#34;等待&#34;。此查询正在提取包含警告或等待设置为1的消息的票证。

然而,洋葱的另一层剥了皮,我们来编辑消息。消息编辑存储在同一个表中,区别在于消息编辑没有ticket_id,而是具有和edit_id等于原始消息的message_id。因此,查询必须找到故障单,查找与故障单关联的消息,查明这些消息是否已编辑,并确定哪一个是消息的最新当前版本,以及消息的当前版本是否标记为警告或等待。因此,麻烦的麻烦查询。

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

1 个答案:

答案 0 :(得分:3)

无论您对此答案提出什么评论,我都会继续帮助您尝试修改,但在原始帖子的评论中描述的内容太多了。

您正在查看给定UNIX时间范围内的服务,但是子选择中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&#39; 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日期范围和状态中的那些服务票据的子选择。它仅对该服务票证ID进行LEFT-JOINED到消息表。然后,根据对给定服务票证的原始消息的任何编辑,该主要消息将自动LEFT-JOINED。

现在,如果每个票证可以有多个消息,我通过SUM(大小写/何时)进行简单计数,如果任何编辑与消息相关联。接下来,我根据ORIGINAL TICKET消息得到一个sum()或任何EDIT消息标记为&#34; Waiting&#34;,所以这样,如果有任何等待消息,我就知道了。同样检查任何警告,同时查看原始消息或任何编辑。对于笑脸,我还总结了每张服务票的任何等待或警告相关消息的总数。

接下来,我将获得与等待或警告相关故障单相关联的任何可能编辑的最大日期/时间戳。

最后,如果/适用的话,我会根据特定故障单获取相应等待或警告的最新MESSAGE ID VALUE。我正在使用此值,因为IT是服务编辑票证的直接消息,与所有其他服务票证无关。

因此,对于每个最初合格的服务而言,所有这些都汇总到一行&#34; Ticket_ID&#34;在您开始的日期/状态内。

现在,额外加入。在这里,我将加入返回合格票证PreQual结果的服务表,因此我不必重新应用外部查询部分的unix日期/时间......我已经拥有票证ID。然后,我将LEFT JOIN转到原始消息,无论是适用于每个车票的最新等待消息和警告消息。

最后,现在我可以应用整体WHERE子句,您需要根据需要进行确认或调整。

我感兴趣的门票的第一个标准是没有等待或警告的待处理编辑的服务票证,但原始消息至少是等待或警告状态。

第二个标准(OR&#39; d)是如果有一个编辑状态为WAITING的条目(已经从总和案例中获得资格/何时获得等级,我已经知道它的状态正在等待)< / p>

第三个标准(OR&#39; d)同样适用于具有警告的编辑状态(同样,来自总和大小/等时查询的情况)。

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

同样,你可能需要最终确定你想要的东西,但我认为我非常接近......

一个最后的补充......

如果不知道最后一条消息是什么,不知道消息的上下文,取代所有先前消息,这可能会显着减少您的问题,所以请在之后澄清。

如果给定的产品用于服务,它会获得一个票证ID和一个人的默认消息&#34; WAITING&#34;为了获取该单位。发生了一些事情,并对原始消息进行了编辑,因此创建了一个EDIT条目并指示了警告,所以现在,您有原始的等待条目,并且后续作为警告。联系客户并解决警告后,产品将完成其服务并进行另一次编辑并关闭故障单,因此FINAL编辑对等待或警告没有任何价值。在这种情况下,最后编辑,无论先前的任何消息或编辑先前的消息&#34;赢得&#34;整体状态......门票已经完成。

同样,如果故障单以WAITING开始,然后对WARNING进行编辑,则WARNING(现在是最新的)是状态的主要考虑因素。

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