【问题标题】:MySQL optimize complicate query NOT IN (2 SELECT WITH JOIN) WHEREMySQL 优化复杂查询 NOT IN (2 SELECT WITH JOIN) WHERE
【发布时间】:2014-10-28 13:20:42
【问题描述】:

我有以下疑问:

SELECT driver_id, first_name, last_name
    FROM drivers
    WHERE driver_id NOT IN 
    (SELECT DISTINCT w.driver_id from waybills w 
            JOIN drivers d ON d.driver_id = w.driver_id 
            WHERE  w.waybill_owner = 1 
            AND w.waybill_status = 'dispatched'
            AND w.delivery_date = '2014-10-28')
    AND driver_id NOT IN 
    (SELECT DISTINCT wm.driver_id from waybill_movements wm 
            JOIN drivers d ON d.driver_id = wm.driver_id 
            WHERE  wm.movement_owner = 1 
            AND wm.delivery_date = '2014-10-28')
    AND status = 'active'
    AND driver_owner = 1
    ORDER BY last_name ASC

如何优化这个查询?

查询运行良好并返回预期结果,但我的问题是查询是否可以优化。

非常感谢您的时间和帮助。

更新:

而且,是的,我有这些索引:

运单(waybill_owner, waybill_status, w.delivery_date) waybill_movements (wm.movement_owner, delivery_date) 驱动程序(驱动程序 ID 主键和驱动程序(状态,驱动程序所有者)

表结构不需要优化

没想到会有这么多答案。谢谢大家。

【问题讨论】:

  • 如果查询运行良好且结果正确,为什么还要进一步优化?您正在解决一个并非真正问题的问题。
  • 查询非常复杂,“解释”使用文件排序,许多临时表意味着使用大量内存和磁盘空间,因此需要优化。
  • 那你为什么这么说Query works well and return expected results你说效果很好,这通常意味着它运行得很快并且不会抛出错误。
  • 编写复杂的 SQL 查询很容易。优化查询需要专家技能。我要的。
  • @Kris,显然可以对此查询进行一些改进以进行优化,而无需进入 EXPLAIN 并打扰索引(我也强烈鼓励 OP 确保为更快的查询做好准备) .

标签: mysql select join


【解决方案1】:

这取决于is 的定义是什么。您可能希望将这些 WHERE IN 子查询重写到您的 FROM 子句中,然后针对该子句运行现有查询,并查看 MySQL explain 是否为此提供了不同的执行路径和统计信息。

毫无疑问,因为您几乎必须在现有查询中点击您的drivers 表三次才能获得您想要的结果。这完全是不必要的开销。

SELECT d.driver_id, d.first_name, d.last_name
FROM drivers d
    LEFT OUTER JOIN waybills w ON
        d.driver_id = w.driver_id  AND
        w.waybill_owner = 1 AND
        w.waybill_status = 'dispatched' AND
        w.delivery_date = '2014-10-28'
    LEFT OUTER JOIN waybill_movements wm ON
        d.driver_id = wm.driver_id AND
        wm.movement_owner = 1 AND
        wm.delivery_date = '2014-10-28')
WHERE
    w.driver_id IS NULL AND
    wm.driver_id IS NULL AND
    d.status = 'active' AND
    d.driver_owner = 1
ORDER BY last_name ASC  

您的ORDER BY 也很昂贵,因为它们总是如此。如果不需要,最好将其删除。

您可能希望通过解释运行@StuartLC 建议的 SQL 和这个 SQL,并查看 MySQL 更喜欢其中一个。你可能会发现他的结果更好,但有时它是一个硬币翻转。 MySQL 通常对派生表的速度不是特别快,因此尽可能多地塞进传统的连接中可以产生更好的结果。但这完全取决于表的大小,它们是否有适当的索引以及所有有趣的东西。

【讨论】:

  • 谢谢。如果我使用解释你的查询对我来说似乎更好。 3个简单的查询:第一:使用where;使用索引;使用文件排序,第二:使用where,第三:使用where;不存在
  • 很难知道数据库将如何处理您发送给它的 SQL,但如果可以的话,我通常会尽量避免派生表。另外,我刚刚切换查询以在 waybill.driverid 和 wabill_movements.driverid 上使用IS NULL。我第一次尝试时就倒退了。
【解决方案2】:

我通常发现进行左连接和查找表结果的 NULL 更容易,并且可以很好地利用可以创建的索引。

The drivers table I would have an index ON (driver_owner, status, driver_id)
Your waybill table, index ON(waybill_owner, driver_id, delivery_date, waybill_status)
waybill_movements, index ON(movement_owner, driver_id, delivery_date )


SELECT 
      d1.driver_id, 
      d1.first_name, 
      d1.last_name
   FROM 
      drivers d1
         LEFT JOIN waybills w
            ON d1.driver_id = w.driver_id
            AND d1.driver_owner = w.waybill_owner
            AND w.waybill_status = 'dispatched'
            AND w.delivery_date = '2014-10-28'
         LEFT JOIN waybill_movements wm 
            ON d1.driver_id = wm.driver_id 
            AND d1.driver_owner = wm.movement_owner
            AND wm.delivery_date = '2014-10-28'
   where
          d1.driver_owner = 1
      AND d1.status = 'active'
      AND w.driver_ID IS NULL
      AND wm.driver_id IS NULL
   ORDER BY 
      d1.last_name ASC

通过为运单和运单包含“IS NULL”,您只会得到那些没有匹配记录的。

另外,看到你的 waybill_owner = 1……这和 driver_owner = 1 也是巧合吗?如果运单所有者应该始终与司机所有者相同,那么我会将运单表上的索引更改为首先具有 waybill_owner,然后是 driver_id,然后是其余的并将连接更改为基于 drivers.driver_owner = waybills。 waybill_owner(类似于 waybill_movements)

根据反馈修改

修改了索引和查询,以基于相同的drivers.driver_owner表加入运单表,以防止可能存在的任何奇怪的可能性导致跨所有者匹配的错误结果。

【讨论】:

  • 不是巧合,司机属于一家公司,运单和机芯属于同一家公司。感谢您的提醒!
  • @AdrianP.,修改后的索引和查询,如答案底部所述。
  • 您的查询与 JNevill 答案的解释相同。唯一的区别是 LEFT OUTER JOIN 与 LEFT JOIN 我已经修改了索引,它们正是您所要求的。
  • @AdrianP.,是的,但是他的原版和我的原版是不同的,我的版本显然更注重数据并抓住运单所有者,这很重要……他的版本仍然很难-编码为#1。
  • 是的。这个更好。谢谢。
【解决方案3】:

假设你已经看过索引

  • waybills(waybill_owner, waybill_status, w.delivery_date)
  • waybill_movements (wm.movement_owner, delivery_date)
  • drivers(driver_id) - 大概是主键?可能还有drivers(status, driver_owner)

我想到了另外两个改进

  • 两个子查询都没有实际使用 Joined 驱动程序表。假设这是故意的,您可以删除连接(如果目的不是过滤根本没有任何驱动程序的运单/移动),或者如果这是一个可为空的外键,则只过滤 where driver_id IS NOT NULL李>
  • 如果合并两个子查询,则可以执行单个NOT IN

SELECT driver_id, first_name, last_name
    FROM drivers
    WHERE driver_id NOT IN 
    (
      SELECT DISTINCT w.driver_id
         from waybills w 
            WHERE  w.waybill_owner = 1 
            AND w.waybill_status = 'dispatched'
            AND w.delivery_date = '2014-10-28'
      UNION
      SELECT DISTINCT wm.driver_id 
        from waybill_movements wm 
            WHERE  wm.movement_owner = 1 
            AND wm.delivery_date = '2014-10-28'
    )
    AND status = 'active'
    AND driver_owner = 1
    ORDER BY last_name ASC

【讨论】:

  • 您的查询看起来比 JNevill 好,但首先要解释:PRIMARY 使用 where;使用索引;使用文件排序,第二个:DEPENDENT SUBQUERY 使用 where;使用临时,第三个:DEPENDENT UNION 使用 where;使用临时,第四次:UNION RESULT 无键。你怎么看?谢谢。
【解决方案4】:
SELECT d.driver_id, d.first_name, d.last_name 
FROM drivers AS d
LEFT JOIN waybills AS w 
    ON d.driver_id = w.driver_id 
    AND w.waybill_owner = 1
    AND w.waybill_status = 'dispatched'
    AND w.delivery_date = '2014-10-28'
LEFT JOIN waybill_movements AS wm 
    ON d.driver_id = wm.driver_id
    AND wm.movement_owner = 1 
    AND wm.delivery_date = '2014-10-28'
WHERE w.driver_id IS NULL 
AND wm.driver_id IS NULL
AND d.status = 'active'
AND d.driver_owner = 1
ORDER BY d.last_name ASC

【讨论】:

  • 我看不出你所拥有的有什么问题,这与我所提供的相同,但只是向发布者转储一个查询而没有任何解释并不能帮助他们理解如何以及为什么有些东西有效,更不用说索引来帮助了。顺便说一句,我不是对你的答案投反对票,而是为了平衡它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多