【问题标题】:Optimizing Query. Want to pick the last record without using max in sub-query优化查询。想要在子查询中不使用 max 的情况下选择最后一条记录
【发布时间】:2011-10-20 23:23:58
【问题描述】:

这是我的查询:

SELECT B.RECORDID, A.ITEMCODE, A.ITEMNAME, A.STOCKINHAND, B.SALEPRICE 
FROM ITEMMASTER A, STOCKENTRY B 
WHERE A.ITEMID = B.ITEMID 
  AND RECORDID = (SELECT MAX(RECORDID) FROM STOCKENTRY 
                  WHERE ITEMID = A.ITEMID) 
  AND A.STOCKINHAND > 0 
  AND B.SALEPRICE > 0 
  AND B.INVOICEDATE IS NOT NULL 
ORDER BY A.ITEMNAME, B.INVOICEDATE;

表 B (StockEntry) 可能包含一条或多条记录,而表 A (ItemMaster) 肯定只有一行对应于该 ItemID。

如果我删除 WHERE 子句中的子查询,它会显示一行或多行。我觉得通过 WHERE 子句中的子查询选择 max(RecordID) 会减慢查询速度。我在 RecordID、InvoiceDate、ItemID 上确实有索引,但 MySQL 日志仍然显示此查询执行不佳。由于某种原因,我无法更改列顺序

有没有更好的方法来优化这个查询?

【问题讨论】:

  • 不确定MySQL 中的索引是否类似于SQL Server,但对于SQL Server,我会在Stockentry 上为ItemID, RecordID 添加一个覆盖索引,这应该会大大加快子选择的速度。
  • @marc - 他在最后一段的旁边提到了MySQL
  • 您有当前查询的平均时间吗?
  • @ace:这是我在 dba.stackexchange.com 上发布查询日志的问题的链接:dba.stackexchange.com/questions/4358/…

标签: mysql sql greatest-n-per-group query-performance


【解决方案1】:

它可能很慢,因为它为外部查询的每一行运行一个相关子查询。有两种解决方案往往运行效率更高。

一种是使用派生表,它使用子查询,但它只执行一次子查询来准备派生表。

SELECT B.RECORDID, A.ITEMCODE, A.ITEMNAME, A.STOCKINHAND, B.SALEPRICE 
FROM ITEMMASTER A
JOIN STOCKENTRY B ON A.ITEMID = B.ITEMID
JOIN (SELECT ITEMID, MAX(RECORDID) AS MAXRECORDID 
      FROM STOCKENTRY GROUP BY ITEMID) M
  ON (M.ITEMID, M.MAXRECORDID) = (B.ITEMID, B.RECORDID)
WHERE A.STOCKINHAND > 0 
  AND B.SALEPRICE > 0 
  AND B.INVOICEDATE IS NOT NULL 
ORDER BY A.ITEMNAME, B.INVOICEDATE;

另一种解决方案是使用 排除连接 来查找 B 中的行,这样不存在具有相同 itemid 和更大 recordid 的其他行。使用正确的索引(例如,(ITEMID,RECORDID)上的复合索引,这应该会表现得很好。

SELECT B.RECORDID, A.ITEMCODE, A.ITEMNAME, A.STOCKINHAND, B.SALEPRICE 
FROM ITEMMASTER A
JOIN STOCKENTRY B ON A.ITEMID = B.ITEMID 
LEFT OUTER JOIN STOCKENTRY B2
  ON B.ITEMID = B2.ITEMID AND B.RECORDID < B2.RECORDID
WHERE B2.ITEMID IS NULL 
  AND A.STOCKINHAND > 0 
  AND B.SALEPRICE > 0 
  AND B.INVOICEDATE IS NOT NULL 
ORDER BY A.ITEMNAME, B.INVOICEDATE;

这类问题在 Stack Overflow 上经常出现。我在问题中添加了greatest-n-per-group 标签,以便您查看其他案例。


回复@RPK 的评论:

我自己不使用 MySQL QB,而且该应用程序已经更改了很多次,我无法建议如何使用它。但是在 mysql 监视器(命令行)中,我使用 EXPLAINPROFILING 的组合来给我统计信息。

但是,您发表了关于不修改(或创建?)索引的评论。这会阻碍您的优化尝试。

【讨论】:

  • +1,使用新的连接语法并删除每一行的相关子查询。
  • @Bill:您的第一个查询中有一个更正。在 WHERE 你离开 AND 之后。
  • @Bill:在这两个查询中,您推荐哪一个?是否有任何查询分析器工具可以用来获取实际统计信息?我正在使用 MySQL 查询浏览器,但它缓存了结果,所以我没有得到实际的时间。
  • 见上面对我的回答的修正。
  • @RPK:取决于 RDBMS 的内部实现。当您将 TOP 与 LIMIT 进行比较时,您是在谈论 Microsoft SQL Server 与 MySQL,这是两种完全不同的技术。它们每个都可能以不同的方式进行优化,因此它们可能比使用 MAX(RecordID) 更慢或更快。索引的存在和存储引擎的选择也使优化有所不同。所以没有简单的答案。
【解决方案2】:

如果查询被频繁使用并且性能仍然是一个问题,您可以创建一个包含项目最后一条记录 id 的表,并使用 ITEMMASTER 表上的触发器使其保持最新。

【讨论】:

    【解决方案3】:

    我的建议是创建一个视图

    CREATE VIEW `STOCKENTRY_V` AS 
    SELECT ITEMID,MAX(RECORDID) AS RECORDID
    FROM STOCKENTRY
    GROUP BY ITEMID;
    

    您可以对 2 个表 + 视图进行简单连接。我对它的执行速度很感兴趣。

    SELECT B.RECORDID, A.ITEMCODE, A.ITEMNAME, A.STOCKINHAND, B.SALEPRICE 
    FROM ITEMMASTER A, STOCKENTRY B, STOCKENTRY_V C
    WHERE A.ITEMID = B.ITEMID AND AND B.ITEMID = C.ITEMID
      AND B.RECORDID = C.RECORDID
      AND A.STOCKINHAND > 0 
      AND B.SALEPRICE > 0 
      AND B.INVOICEDATE IS NOT NULL 
    ORDER BY A.ITEMNAME, B.INVOICEDATE;
    

    【讨论】:

      【解决方案4】:

      TOP 是特定于数据库的;你可能想使用 MySQL 替代品ORDER BY ... DESC LIMIT 1

      This SO post 很好地概述了跨数据库实现 LIMIT 概念的不同方法。

      【讨论】:

      • 真的。限制 1 在 MySQL 中有效。但我无法确定查询的确切性能,因为查询结果可能已被缓存。
      • LIMIT 也是特定于数据库的。
      • FWIW, TOP 受 Sybase 和 Microsoft SQL Server 支持。 MySQL、PostgreSQL 和 SQLite 支持LIMIT
      【解决方案5】:

      尝试使用TOP 1 ... ORDER BY .. DESC,如下所示:

      SELECT B.RECORDID, A.ITEMCODE, A.ITEMNAME, A.STOCKINHAND, B.SALEPRICE 
      FROM ITEMMASTER A, STOCKENTRY B 
      WHERE A.ITEMID = B.ITEMID 
        AND RECORDID = (SELECT top 1 RECORDID FROM STOCKENTRY 
                        WHERE ITEMID = A.ITEMID
                        order by RECORDID desc) 
        AND A.STOCKINHAND > 0 
        AND B.SALEPRICE > 0 
        AND B.INVOICEDATE IS NOT NULL 
      ORDER BY A.ITEMNAME, B.INVOICEDATE;
      

      【讨论】:

      • 我在MySql标签添加到问题之前回答,TOP不是MySql语法
      • @KM:MySQL 无法识别“Top 1”。
      • @RPK,请参阅我之前的评论,尝试类似:(select RECORDID FROM STOCKENTRY limit 0,1 WHERE ITEMID = A.ITEMID order by RECORDID desc)Bill Karwin's answer 会更好地执行很多。最好在一个查询中最终确定所有“最大值”并加入它们,而不是每次为每一行找到最大值。
      猜你喜欢
      • 2012-06-24
      • 1970-01-01
      • 2020-07-07
      • 2015-07-24
      • 2011-09-14
      • 2018-01-06
      • 1970-01-01
      • 2019-12-10
      • 1970-01-01
      相关资源
      最近更新 更多