查询MySQL中至少一个孩子满足约束1且所有孩子满足约束2的所有行

Posted

技术标签:

【中文标题】查询MySQL中至少一个孩子满足约束1且所有孩子满足约束2的所有行【英文标题】:Query for all rows where at least one child satisfies constraint 1 and all children satisfy constraint 2 in MySQL 【发布时间】:2019-04-04 16:22:30 【问题描述】:

我有一个 mysql 数据库,其中的表格代表可能的拼车路线。三个相关表是拼车表(基数约为 200 万)、carpool_stop 表(基数约为 1100 万)和行程表(基数约为 300K)。旅行代表从位置 A 移动到位置 B 的请求。拼车代表汽车通过在多个位置接载用户并在多个位置下车来一次完成多次旅行的可能路线。以下是示例: 拼车:

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

carpool_stop:

+------------+---------+---------+
| 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 分钟才能完成指定的pickup_date,该日期只是过去一天。

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)

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

【问题讨论】:

【参考方案1】:

我假设pickup_date 列已编入索引。如果不是,那么无论您做什么查询都会很慢。

要记住的主要事情是,大多数行都是历史记录(trip.pickup_date

您的内心查询就是这样做的,所以我想说有正确的想法。那为什么慢呢?要么picking_date 没有被索引,要么你的查询是以一种混淆MySQL 使用该索引的方式编写的。 (如果发生这种情况,MySQL 的 EXPLAIN command 可以显示。)

有一些方法可以简化查询。就几个:

    我不认为内部查询需要加入 carpool 表——不过我不希望有很大的加速。 您可以尝试将整个内容编写为两个 SQL 语句,然后使用 UNION。 (这也摆脱了 OR,这有时会有所帮助。) 有一些方法可以摆脱 GROUP BY .. 可能有帮助也可能没有帮助。

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

【讨论】:

感谢您的回复。我编辑了原始帖子以指出我确实有一个关于pickup_date 的索引。您对第 1 点是正确的,它并没有太大的不同。通过查看解释和实验,我尝试使用 EXISTS 重写它,但是查询的其他部分运行缓慢。我认为当前查询缓慢的很大一部分是外部查询的 carpool_stop 上的连接,然后是 GROUP BY。 好的。还有哪些其他指标?我希望旅行和拼车表上的主键索引。表 carpool_stop(它没有主键)应该有两个索引,在 trip_id 和 carpool_id 上。 理想情况下,EXPLAIN 将显示内部查询首先运行,并在外部查询的任何部分运行之前获取行数。【参考方案2】:

仅基于标题:

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

【讨论】:

以上是关于查询MySQL中至少一个孩子满足约束1且所有孩子满足约束2的所有行的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 一对多关系建立(在navicat中)

谈一道LeetCode——分发糖果

文巾解题455. 分发饼干

455. 分发饼干

选择所有子项仅包含相同值且没有其他方案的行

查询列出所有分层的父母和兄弟姐妹及其孩子,但不列出自己的孩子