在MySQL中查询至少有一个子节点满足约束1并且所有子节点都满足约束2的所有行

时间:2019-04-04 16:22:31

标签: mysql sql database query-optimization

我有一个MySQL数据库,其中的表表示可能的拼车路线。这三个相关的表是拼车表(基数约为200万),拼车停车表(基数约为1100万)和行程表(基数约为300K)。行程代表从位置A移至位置B的请求。拼车代表汽车通过在多个位置接送用户并将其在多个位置下车而一次完成多个行程的可能路线。以下是示例: 拼车:

+------------+-----------+
| carpool_id | completed |
+------------+-----------+
|          1 |         0 |
|          2 |         0 |
|          3 |         1 |
+------------+-----------+

拼车停车:

+------------+---------+---------+
| carpool_id | trip_id |  type   |
+------------+---------+---------+
|          1 |       1 | pickup  |
|          1 |       2 | pickup  |
|          1 |       2 | dropoff |
|          1 |       1 | dropoff |
|          2 |       2 | pickup  |
|          2 |       3 | pickup  |
|          2 |       3 | dropoff |
|          2 |       2 | dropoff |
|          3 |       3 | pickup  |
|          3 |       4 | pickup  |
|          3 |       4 | dropoff |
|          3 |       3 | dropoff |
+------------+---------+---------+

行程:

+---------+------------+---------------+--------------+
| trip_id | carpool_id |    status     | pickup_date  |
+---------+------------+---------------+--------------+
|       1 | NULL       | 'INITIAL'     | '2019-04-01' |
|       2 | NULL       | 'INITIAL'     | '2019-04-02' |
|       3 | 3          | 'IN_PROGRESS' | '2019-04-03' |
|       4 | 3          | 'INITIAL'     | '2019-04-03' |
+---------+------------+---------------+--------------+

trip.pickup_date有一个索引。 目标是获得所有满足以下条件的拼车:

at least one trip has a pickup_date later than a specified date
AND 
(the carpool is completed OR 
(all trips have status in ('INITIAL', 'WAITING') AND have a NULL carpool_id))

在上面的示例中,如果指定的pickup_date为“ 2019-04-02”,则将是拼车1和拼车3。拼车2将不会返回,因为行程3已经是拼车的一部分并且是“ IN_PROGRESS”。

我有一个有效的查询,但是由于carpool_stop表中的行数众多,所以对于过去的一天,指定的领取日期需要10分钟才能完成。

SELECT carpool.*
  FROM (
     SELECT carpool_stop.carpool_id
        FROM trip
        JOIN carpool_stop ON carpool_stop.trip_id = trip.trip_id
        JOIN carpool      ON carpool.carpool_id = carpool_stop.carpool_id
        WHERE trip.pickup_date >= '2019-04-02'
        GROUP BY carpool.carpool_id
  ) AS inner_query
  JOIN carpool      ON carpool.carpool_id = inner_query.carpool_id
  JOIN carpool_stop ON carpool_stop.carpool_id = carpool.carpool_id
  JOIN trip         ON trip.trip_id = carpool_stop.trip_id
  GROUP BY carpool.carpool_id
  HAVING (sum(CASE WHEN (trip.status NOT IN ('INITIAL', 'WAITING') OR trip.carpool_id IS NOT NULL) 
                   THEN 1 
                   ELSE 0 
                   END) = 0 
         OR carpool.completed = 1)

我希望有一种方法可以更快地编写此查询,例如一分钟或更短的时间。

2 个答案:

答案 0 :(得分:0)

我假设已为picking_date列建立索引。如果不是,那么无论您做什么查询都会很慢。

要记住的主要事情是,大多数行都是历史记录(trip.pickup_date <'2019-04-02')。因此,您想要的是一个仅选择最近行程的查询(或子查询),然后围绕该查询构建其余查询。

您只是通过内部查询做到了这一点,所以我想有一个正确的主意。那为什么慢呢?没有为picking_date编制索引,或者以使MySQL无法使用该索引的方式编写查询。 (MySQL的EXPLAIN command可以显示这种情况是否发生。)

有多种方法可以简化查询。只是几个:

  1. 我认为内部查询不需要连接到拼车表中-尽管我不希望由此而带来巨大的提速。
  2. 您可以尝试将整个内容编写为两个SQL语句,然后使用UNION。 (这也摆脱了OR,有时可能会有所帮助。)
  3. 有一些摆脱GROUP BY ..HAVING的方法可能会或可能不会有帮助。

或者:在我看来,该查询返回的是已完成的拼车,以及尚未开始的拼车。相反,测试中间的所有拼车可能会更简单(即拼车尚未完成;但是至少有一个行程的状态已被选择或稍后)。如果尝试此操作,请将结果与慢速查询进行比较,以得出确保它们返回相同的结果。可能有些模糊的状态需要处理。

答案 1 :(得分:0)

仅基于标题:

SELECT ...
    FROM ...
    WHERE     EXISTS( SELECT 1 FROM ... WHERE ... )      -- at least 1 child
      AND NOT EXISTS( SELECT 1 FROM ... WHERE NOT ... )  -- all (ie, none fail)

如果需要帮助,请提供SHOW CREATE TABLE