从跨越多对多的 postgres 查询中删除 CROSS JOIN LATERAL

Remove CROSS JOIN LATERAL from postgres query that spans many to many

我有以下三个table(多对多):

地点

+====+==============+===+===+=============+
| id | coord_system | x | y | last_update |
+====+==============+===+===+=============+
|    |              |   |   |             |
+----+--------------+---+---+-------------+

映射

+=============+============+
| location_id | history_id |
+=============+============+
|             |            |
+-------------+------------+

历史

+====+=======+======+
| id | speed | date |
+====+=======+======+
|    |       |      |
+----+-------+------+

位置 table 表示特定坐标系内的物理 x、y 位置。对于每个 x,y 位置,历史记录中至少有一行 table 存在。历史记录 table 中的每一行都可以指向位置 table.

中的多行

需要注意的重要一点是 (coord_system, x, y) 已编入索引并且是唯一的。我认为这没什么区别,但所有 id 和 coord_system 都是 UUID。在下面的代码示例中,我将使用字母来使其更易于阅读。 location 和 history 有额外的列,但不改变问题的范围。位置 table 上的 last_update 列应该与历史记录 table 上的日期列匹配(我稍后会在 post 中回过头来)。

目标是获取范围为 (coor_system, x, y) 的最新历史记录行。目前这是通过 CROSS JOIN LATERAl 完成的,例如

SELECT *
FROM location loc
CROSS JOIN LATERAL
  (SELECT *
   FROM history hist
   LEFT JOIN mapping map ON hist.id = map.history_id
   WHERE map.location_id = loc.id
   ORDER BY date DESC limit(1)) AS records
WHERE loc.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
  AND (x >= 403047
       AND x <= 404047)
  AND (y >= 16451337
       AND y <= 16452337);

对于 x、y 和 coord_system 的特定范围,查询需要约 25 秒才能 运行 和 returns 182 351 行。

我在 SQL 方面不是很有经验,但认为使用常规连接也可以实现此查询的目标。如果我使用相同的 x、y 和 coord_system“过滤器”跨三个 table 进行连接,则需要大约 2 秒和 returns ~300 万行。我试图变得聪明并使用日期来计算结果:运行e:

SELECT *
FROM history hist
RIGHT JOIN mapping map ON hist.id = map.history_id
RIGHT JOIN location loc ON loc.id = map.location_id
WHERE loc.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
  AND (x >= 403047
       AND x <= 404047)
  AND (y >= 16451337
       AND y <= 16452337)
  AND location.last_update = hist.date

这与原始查询的结果非常接近。结果是在大约 3 秒内生成了 182 485 行。不幸的是,结果需要完全相同。我猜我在查询中犯了一个逻辑错误,所以来到这里希望有人能指出来。

我的问题是:是否有一种聪明的方法可以让联接只获取 history.date 列中具有“最新”日期的行?正如预期的那样,我正在尝试尽可能快地进行查询 运行,同时保持正确的结果集。

在下面的 table 中,我展示了一个连接示例和我期望的结果(标记在“return_row”列中)。


+=============+==============+===+===+=============+============+============+=======+============+============+
| location.id | coord_system | x | y | location_id | history_id | history.id | speed |    date    | return_row |
+=============+==============+===+===+=============+============+============+=======+============+============+
|           0 | a            | 1 | 1 |           0 |          0 |          0 |   3.0 | 2020/10/31 | *          |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           0 | a            | 1 | 1 |           0 |          1 |          1 |   3.1 | 2020/10/30 |            |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           0 | a            | 1 | 1 |           0 |          2 |          2 |   3.2 | 2020/10/29 |            |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           1 | a            | 1 | 2 |           1 |          3 |          3 |   3.1 | 2020/10/31 | *          |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           1 | a            | 1 | 2 |           1 |          4 |          4 |   3.0 | 2020/10/30 |            |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           2 | a            | 2 | 2 |           2 |          5 |          5 |     4 | 2020/10/31 | *          |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
|           3 | b            | 1 | 1 |           3 |          6 |          6 |     5 | 2020/10/1  | *          |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+


DISTINCT ON 配合使用效果更好吗?

SELECT DISTINCT ON (l.id) l.id, h.date, ... -- enumerate the columns here
FROM location l
LEFT JOIN mapping m ON m.location_id = l.id
LEFT JOIN history h ON h.id = m.history_id
WHERE 
    l.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
    AND l.x BETWEEN 403047 AND 404047
    AND l.y BETWEEN 16451337 AND 16452337
ORDER BY l.id, h.date DESC