[英]Poorly performing Mysql subquery — can I turn it into a Join?
我有一个导致性能不佳的子查询问题......我认为子查询可以使用连接重写,但我很难绕过它。
查询的要点是这样的:对于给定的EmailAddress和Product的组合,我需要得到一个不是最新的ID列表....这些订单将在表格中标记为“过时”只留下给定的EmailAddress和Product组合的最新订单......(这有意义吗?)
表定义
CREATE TABLE `sandbox`.`OrderHistoryTable` (
`id` INT( 11 ) NOT NULL AUTO_INCREMENT ,
`EmailAddress` VARCHAR( 100 ) NOT NULL ,
`Product` VARCHAR( 100 ) NOT NULL ,
`OrderDate` DATE NOT NULL ,
`rowlastupdated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
PRIMARY KEY ( `id` ) ,
KEY `EmailAddress` ( `EmailAddress` ) ,
KEY `Product` ( `Product` ) ,
KEY `OrderDate` ( `OrderDate` )
) ENGINE = MYISAM DEFAULT CHARSET = latin1;
询问
SELECT id
FROM
OrderHistoryTable AS EMP1
WHERE
OrderDate not in
(
Select max(OrderDate)
FROM OrderHistoryTable AS EMP2
WHERE
EMP1.EmailAddress = EMP2.EmailAddress
AND EMP1.Product IN ('ProductA','ProductB','ProductC','ProductD')
AND EMP2.Product IN ('ProductA','ProductB','ProductC','ProductD')
)
重复'IN'语句的解释
13 bob@aol.com ProductA 2010-10-01
15 bob@aol.com ProductB 2010-20-02
46 bob@aol.com ProductD 2010-20-03
57 bob@aol.com ProductC 2010-20-04
158 bob@aol.com ProductE 2010-20-05
206 bob@aol.com ProductB 2010-20-06
501 bob@aol.com ProductZ 2010-20-07
我的查询结果应为| 13 | | 15 | | 46 | | 57 |
这是因为,在列出的订单中,这4个已被同一类别的产品的新订单“取代”。 该“类别”包含产品A,B,C和D.
订单ID 158和501基于查询在其各自的类别中不显示其他订单。
最终查询基于以下接受的答案:我最终使用了以下查询而没有子查询,并且获得了大约3倍的性能(从90秒下降30秒)。 我现在还有一个单独的“组”表,我可以枚举组成员,而不是在查询本身中拼写出来...
SELECT DISTINCT id, EmailAddress FROM (
SELECT a.id, a.EmailAddress, a.OrderDate
FROM OrderHistoryTable a
INNER JOIN OrderHistoryTable b ON a.EmailAddress = b.EmailAddress
INNER JOIN groups g1 ON a.Product = g1.Product
INNER JOIN groups g2 ON b.Product = g2.Product
WHERE
g1.family = 'ProductGroupX'
AND g2.family = 'ProductGroupX'
GROUP BY a.id, a.OrderDate, b.OrderDate
HAVING a.OrderDate < MAX(b.OrderDate)
) dtX
采用:
SELECT a.id
FROM ORDERHISTORYTABLE AS a
LEFT JOIN (SELECT e.EmailAddress,
e.product,
MAX(OrderDate) AS max_date
FROM OrderHistoryTable AS e
WHERE e.Product IN ('ProductA','ProductB','ProductC','ProductD')
GROUP BY e.EmailAddress) b ON b.emailaddress = a.emailaddress
AND b.max_date = a.orderdate
AND b.product = a.product
WHERE x.emailaddress IS NULL
AND a.Product IN ('ProductA','ProductB','ProductC','ProductD')
Rant: OMG小马的答案给出了你要求的东西 - 用连接重写。 但我不会太兴奋,你的性能杀手是电子邮件地址的内部联接,我认为,这不是特别选择性的,然后你的数据库需要筛选所有那些寻找订单日期最大值的行。
这对于MySQL来说实际上意味着要做一个文件排序(你可以发布EXPLAIN SELECT ....?)。
现在,如果mysql可以访问包含emailaddress
, product
和orderdate
的索引,特别是在MyISAM上可以更有效地确定MAX(orderdate)(并且不会,每个列上的索引都不同于在所有列上都有一个复合索引。 如果我试图优化该查询,我会打赌。
除了这个咆哮之外,我的版本not the latest from a category
版本(我不认为它会更好,但它是不同的,你应该测试性能;它可能因为缺少子查询而更快)
我的尝试 (未经测试)
SELECT DISTINCT
notlatest.id,
notlatest.emailaddress,
notlatest.product,
notlatest.orderdate
FROM
OrderHistoryTable AS notlatest
LEFT JOIN OrderHistoryTable AS EMP latest ON
notlatest.emailaddress = latest.emailaddress AND
notlatest.orderdate < latest.orderdate AND
WHERE
notlatest.product IN ('ProductA','ProductB','ProductC','ProductD') AND
latest.product IN ('ProductA','ProductB','ProductC','ProductD') AND
latest.id IS NOT NULL
评论:
- 如果类别中只有一条记录,则不会显示
- 再次索引应该加快上述速度
实际上,这可能是(可能)一个很好的例子,说明数据标准化将如何提高性能 - 您的产品意味着产品类别,但产品类别不会存储在任何地方,从长远来看IN测试将无法维护。
此外,通过创建产品类别,您可以直接在其上编制索引 。
如果产品在类别上编入索引,那么类别上的联接性能应该更好,然后对按值(而不是类别)索引的产品进行测试。 (实际上MyISAM的emailaddress
, category
, orderdate
的综合索引应该已经包含每个类别的最大,最小和计数,这应该是便宜的)。
我的MySQL有点生疏(我已经习惯了MSSQL),但这是我最好的猜测。 它可能需要在GROUP BY
和HAVING
子句中进行一些调整。 此外,我从您的重复IN语句中假设您希望产品在两个表中都匹配。 如果不是这种情况,我会调整查询。
SELECT a.id
FROM OrderHistoryTable a
INNER JOIN OrderHistoryTable b
ON a.Product = b.Product AND
a.Employee = b.Employee
WHERE a.Product IN ('ProductA','ProductB','ProductC','ProductD')
GROUP BY a.id, a.OrderDate, b.OrderDate,
HAVING b.OrderDate < MAX(a.OrderDate)
编辑:删除无关的AND
。
SELECT *
FROM (
SELECT product, MAX(OrderDate) AS md
FROM OrderHistoryTable
WHERE product IN ('ProductA','ProductB','ProductC','ProductD')
GROUP BY
product
) ohti
JOIN orderhistorytable oht
ON oht.product = ohti.product
AND oht.orderdate <> ohti.md
在OrderHistoryTable (product, orderdate)
上创建一个索引OrderHistoryTable (product, orderdate)
以便快速工作。
另请注意,如果有的话,它将返回产品中MAX(orderdate)
重复项。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.