mysql - 使用子选择的MySQL查询需要使用联接或存在

标签 mysql sql performance join subquery

随着时间的推移,我已经添加了该查询,甚至与其他查询合并,等等,因此变得一团糟。

立即执行需要很长时间。我尝试使用EXPLAIN EXTENDED并添加了所有可能的索引/键,但是由于某种原因我没有做任何帮助。

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

我一直在阅读子查询,联接和使用存在来尝试优化此问题的方法,但我只是不了解执行此操作的最佳方法。

有什么方法可以使用联接或使用现有方法替换某些子查询并使此查询运行得更快?

查询:

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


表格:制作清单

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




索取更多详细信息后更新:

该查询列出了服务表中的所有记录,并通过PHP动态生成。服务表中的每个记录都可以附加1条或多条消息,并通过services.service_id = messages.ticket_id进行链接。当某人发布一条消息(或编辑一条消息)时,他们可以选择将其标记为“警告”和/或“正在等待”。该查询将拉起那些具有警告或等待设置为1的消息的故障单。

但是,洋葱的另一层剥了皮,我们进行了消息编辑。消息编辑存储在同一表中,不同之处在于消息编辑没有ticket_id,而具有和原始消息的message_id相同的edit_id。因此,查询必须查找票证,查找与票证关联的消息,找出这些消息是否具有编辑内容,并确定哪个消息是该消息的最新当前版本,以及该消息的当前版本是否标记有“警告或等待。因此,杂乱而繁琐的查询。

处理makelist表和品牌的工作只是为了确保完整性,因为开始工作很棘手,并且想要确保所包括的任何解决方案都是如此。在这种情况下,这并不是很相关,但是制造清单/品牌资料基本上是根据存储在“品牌”列中的服务表中的品牌ID查找品牌名称的。

最佳答案

无论您对此答案提供什么意见,我都会继续帮助您进行修订,但是对于您对原始帖子的评论中所描述的内容太多了。

您正在查看给定UNIX时间范围内的服务,但是子选择中service_id上​​的限定符正在查看ALL消息。那么,您仅对首先有资格进入相关时间范围的门票感兴趣吗?

您复杂的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)进行预查询,并对其合格的子消息进行编辑以查找最大日期与整个日期。

这是我想出的东西,将尝试描述,以便您摘要。

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左联接到消息表中。然后,基于对给定服务凭单的原始消息的任何编辑,将该主消息向左添加。

现在,如果每个票证可以有多条消息,那么如果有任何编辑与该消息相关联,我将通过SUM(案例/时间)进行简单计数。接下来,我将基于原始票证消息或任何EDIT消息被标记为“等待”的结果得到sum(),这样,我就预先知道是否有任何等待消息。同样,检查是否有警告,同时查看原始消息或任何编辑内容。对于笑容,我还对每个服​​务凭单汇总了任何WAITING或WARNING关联消息的总数。

接下来,我将获取与等待或警告相关票证相关的任何可能编辑的最大日期/时间戳。

最后,如果适用,我将获得针对特定票证的相应等待或警告的最新MESSAGE ID VALUE。我正在使用THIS值,因为它是服务编辑票证的直接消息,而与所有其他服务票证无关。

因此,所有这些都将在开始的日期/状态内,按原始合格的服务“ Ticket_ID”汇总到一行。

现在,其他联接。在这里,我将重新连接到合格票证PreQual结果上的服务表,因此不必为外部查询部分重新应用UNIX日期/时间...我已经有了票证ID。然后,无论适用于每个票证的最新等待消息和警告消息如何,我都将保留到原始消息。

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

我感兴趣的故障单的第一个标准是那些没有等待或警告的挂起编辑的服务故障单,但是原始消息至少是处于等待或警告状态。

第二个条件(或)是,如果存在一个编辑状态为WAITING的条目(已从总和中获得资格/资格预审时,我已经知道其状态正在等待)

第三个条件(OR'd)同样适用于具有警告(同样,从总和情况/当进行资格预审时)的警告的编辑状态。

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

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

最后加一...

如果不知道消息的上下文(无论最后一条消息是什么),将取代之前的所有消息,这也可以显着减少您的问题,因此请在之后进行澄清。

如果给定的产品正在维修中,它将获得票证ID和默认消息,表明有人正在“等待”要取货的设备。发生了某些情况,并对原始消息进行了编辑,因此创建了EDIT条目并显示了警告,因此,现在您具有原始的wait条目,并且将后续操作作为警告。与客户联系并解决警告后,产品结束其服务,并进行了另一次编辑并关闭了凭单,因此“最终”编辑对于等待或警告没有任何意义。在这种情况下,无论任何先前的消息或对先前消息的编辑,“最后编辑”都会赢得总体状态。票证已完成。

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

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

关于mysql - 使用子选择的MySQL查询需要使用联接或存在,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30909747/

相关文章:

mysql - 如何返回 2 COUNT()

mysql - 如何从数据库mysql中选择特定列中具有相同且最新值的所有行

sql - 具有 SUM 聚合的 Postgres CASE 条件评估不需要的 ELSE 部分

sql-server - SQL Server 内存优化表 - 与临时表相比性能较差

javascript - 为什么循环数组时 for..in 比 for 循环更快

php - 如何将2个php文件中的数据提交到mySQL

php - Yii 单元测试 CDbException

Mysql 抛出一个错误

sql - 从电子邮件地址获取用户名/帐户名

sql - Postgres 一贯偏爱嵌套循环连接而不是合并连接