性能不佳的Mysql子查询 - 我可以把它变成一个Join吗?

时间:2010-10-28 15:49:40

标签: sql mysql

我有一个导致性能不佳的子查询问题......我认为可以使用连接重写子查询,但是我很难绕过它。

查询的要点是这样的: 对于EmailAddress和Product的给定组合,我需要获得一个不是最新的ID列表....这些订单将在表格中标记为“过时”,这将只留下aa的最新订单给出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

4 个答案:

答案 0 :(得分:5)

使用:

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

答案 1 :(得分:2)

<强>咆哮: OMG小马的答案给出了你所要求的 - 用连接重写。但我不会太兴奋,你的性能杀手是电子邮件地址的内部联接,我认为,这不是特别选择性的,然后你的数据库需要筛选所有那些寻找订单日期MAX的行。 / p>

这对于MySQL来说实际上意味着要做一个文件排序(你可以发布EXPLAIN SELECT ....?)。

现在,如果mysql可以访问包含emailaddressproductorderdate的索引,特别是在MyISAM上,可以更有效地确定MAX(orderdate)(和不,在每个列上都有一个索引与在所有列上都有一个复合索引不同。如果我试图优化该查询,我会打赌。

除了这个咆哮之外,这是我not the latest from a category的版本(我不认为它会更好,但它不同,你应该测试性能;由于缺少子查询,它可能会更快)< / p>

我的尝试(未经测试)

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在emailaddresscategoryorderdate上的综合索引应该已经包含每个类别的最大值,最小值和数量,而且应该很便宜。

答案 2 :(得分:1)

我的MySQL有点生疏(我已经习惯了MSSQL),但这是我最好的猜测。可能需要在GROUP BYHAVING子句中进行一些调整。此外,我从您的重复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

答案 3 :(得分:0)

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)上创建一个索引,以便快速工作。

另请注意,它会返回产品中MAX(orderdate)的重复项(如果有)。