暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

[译文] 了解 PostgreSQL 中的 LATERAL 连接

原创 Hans-Jürgen Schönig 2021-08-12
1058

LATERAL 连接是 PostgreSQL 和其他关系数据库(如 Oracle、DB2 和 MS SQL)鲜为人知的特性之一。然而,横向连接是一个非常有用的功能,看看你可以用它们完成什么是有意义的。

更仔细地检查 FROM

在我们深入研究 LATERAL 之前,有必要坐下来从更哲学的层面考虑 SQL 中的 SELECT 和 FROM 子句。下面是一个例子:

SELECT whatever FROM tab;
复制

基本上,我们可以将此语句视为一个循环。用伪代码编写此 SQL 语句类似于以下代码段:

for x in tab loop “do whatever” end loop
复制

对于表中的每个条目,我们执行 SELECT 子句所说的操作。通常数据只是按原样返回。SELECT 语句可以看作是一个循环。但是如果我们需要一个“嵌套”循环呢?这正是 LATERAL 的优势所在。

横向连接:创建示例数据

让我们想象一个简单的例子。想象一下,我们有一系列产品,而且我们还有客户的愿望清单。现在的目标是为每个愿望清单找到最好的 3 个产品。以下 SQL 片段创建了一些示例数据:

CREATE TABLE t_product AS SELECT id AS product_id, id * 10 * random() AS price, 'product ' || id AS product FROM generate_series(1, 1000) AS id; CREATE TABLE t_wishlist ( wishlist_id int, username text, desired_price numeric ); INSERT INTO t_wishlist VALUES (1, 'hans', '450'), (2, 'joe', '60'), (3, 'jane', '1500') ;
复制

产品表填充了 1000 种产品。价格是随机的,我们用了一个很有创意的名字来命名产品:

test=# SELECT * FROM t_product LIMIT 10; product_id | price | product ------------+--------------------+------------ 1 | 6.756567642432323 | product 1 2 | 5.284467408540081 | product 2 3 | 28.284196164210904 | product 3 4 | 13.543868035690423 | product 4 5 | 30.576923884383156 | product 5 6 | 26.572431211361902 | product 6 7 | 64.84599396020204 | product 7 8 | 21.550701384168747 | product 8 9 | 28.995584553969174 | product 9 10 | 17.31335004787411 | product 10 (10 rows)
复制

接下来,我们有一个愿望清单。

test=# SELECT * FROM t_wishlist; wishlist_id | username | desired_price -------------+----------+--------------- 1 | hans | 450 2 | joe | 60 3 | jane | 1500 (3 rows)
复制

如您所见,愿望清单属于一个用户,我们想要推荐的这三种产品都有一个期望的价格。

运行横向连接

在提供一些示例数据并将其加载到我们的 PostgreSQL 数据库后,我们可以解决问题并尝试提出解决方案。

假设我们想用伪代码找到每个愿望的前三个产品:

for x in wishlist loop for y in products order by price desc loop found++ if found <= 3 then return row else jump to next wish end end loop end loop
复制

重要的是我们需要两个循环。首先,我们需要遍历愿望清单,然后查看已排序的产品列表,选择 3 并移至下一个愿望清单。

让我们看看如何使用 LATERAL-join 来做到这一点:

SELECT * FROM t_wishlist AS w, LATERAL (SELECT * FROM t_product AS p WHERE p.price < w.desired_price ORDER BY p.price DESC LIMIT 3 ) AS x ORDER BY wishlist_id, price DESC;
复制

我们将一步一步地完成它。您在 FROM 子句中看到的第一件事是 t_wishlist 表。LATERAL 现在可以做的是使用愿望清单中的条目来发挥它的魔力。因此,对于心愿单中的每个条目,我们选择三个产品。要弄清楚我们需要哪些产品,我们可以使用 w.desired_price。换句话说:它就像一个“带参数的连接”。FROM 子句是我们伪代码中的“外循环”,而 LATERAL 可以看作是“内循环”。

结果集如下所示:

wishlist_id | username | desired_price | product_id | price | product -------------+----------+---------------+------------+--------------------+------------- 1 | hans | 450 | 708 | 447.0511375753179 | product 708 1 | hans | 450 | 126 | 443.6560873146138 | product 126 1 | hans | 450 | 655 | 438.0566432022443 | product 655 2 | joe | 60 | 40 | 59.32252841190291 | product 40 2 | joe | 60 | 19 | 59.2142714048882 | product 19 2 | joe | 60 | 87 | 58.78014573804254 | product 87 3 | jane | 1500 | 687 | 1495.8794483743645 | product 687 3 | jane | 1500 | 297 | 1494.4586352980593 | product 297 3 | jane | 1500 | 520 | 1490.7849437550085 | product 520 (9 rows)
复制

PostgreSQL 为每个愿望清单返回了三个条目,这正是我们想要的。这里的重要部分是 LIMIT 子句位于馈送到 LATERAL 的 SELECT 内部。因此,它限制了每个愿望清单的行数,而不是总行数。

PostgreSQL 在优化 LATERAL 连接方面做得非常好。在我们的例子中,执行计划看起来非常简单:

test=# explain SELECT * FROM t_wishlist AS w, LATERAL (SELECT * FROM t_product AS p WHERE p.price < w.desired_price ORDER BY p.price DESC LIMIT 3 ) AS x ORDER BY wishlist_id, price DESC; QUERY PLAN --------------------------------------------------------------------------------------- Sort (cost=23428.53..23434.90 rows=2550 width=91) Sort Key: w.wishlist_id, p.price DESC -> Nested Loop (cost=27.30..23284.24 rows=2550 width=91) -> Seq Scan on t_wishlist w (cost=0.00..18.50 rows=850 width=68) -> Limit (cost=27.30..27.31 rows=3 width=23) -> Sort (cost=27.30..28.14 rows=333 width=23) Sort Key: p.price DESC -> Seq Scan on t_product p (cost=0.00..23.00 rows=333 width=23) Filter: (price < (w.desired_price)::double precision) (9 rows)
复制

LATERAL joins 非常有用,在很多情况下都可以用来加速操作,或者只是让代码更容易理解。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

目录
  • 更仔细地检查 FROM
  • 横向连接:创建示例数据
  • 运行横向连接