从跨越多对多的 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
我有以下三个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