当前位置:   article > 正文

介绍 KingbaseES 的 Lateral 连接

left join lateral 是什么意思

什么是 Lateral 连接

根据文档,它的作用是:

LATERAL 关键字可以位于子 SELECT FROM 项之前。这允许子 SELECT 引用 FROM 列表中出现在它之前的 FROM 项的列。(没有 LATERAL,每个子 SELECT 都是独立评估的,因此不能交叉引用任何其他 FROM 项。)
FROM中出现的表函数,前面也可以加上关键字Lateral,但对于函数来说,Lateral是可选的;FROM在任何情况下,函数的参数都可以引用对前“表”的列。

基本上,它的作用是对于主“表”中的每一行,它使用主选择行作为参数来计算子查询。与for循环遍历查询返回的行,非常相似。

Lateral 的用途

语法糖

语法糖(Syntactic sugar),指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。 通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

横向连接允许您重用计算列,使您的查询整洁易读。让我们通过一起重写一个糟糕的查询来了解横向连接。

  1. select
  2. (pledged / fx_rate) as pledged_usd,
  3. (pledged / fx_rate) / backers_count as avg_pledge_usd,
  4. (goal / fx_rate) - (pledged / fx_rate) as amt_from_goal,
  5. (deadline - launched_at) / 86400.00 as duration,
  6. ((goal / fx_rate) - (pledged / fx_rate)) / ((deadline - launched_at) / 86400.00) as usd_needed_daily
  7. from kickstarter_data;

使用横向连接,可以只定义一次计算列,然后可以在查询的其他部分引用这些列值。

  1. select
  2. pledged_usd,
  3. avg_pledge_usd,
  4. amt_from_goal,
  5. duration,
  6. (usd_from_goal / duration) as usd_needed_daily
  7. from kickstarter_data,
  8. lateral (select pledged / fx_rate as pledged_usd) pu,
  9. lateral (select pledged_usd / backers_count as avg_pledge_usd) apu,
  10. lateral (select goal / fx_rate as goal_usd) gu,
  11. lateral (select goal_usd - pledged_usd as usd_from_goal) ufg,
  12. lateral (select (deadline - launched_at)/86400.00 as duration) dr;

子查询增强模式

LATERAL连接更像是相关子查询,而不是普通子查询,因为LATERAL连接右侧的表达式对其左侧的每一行进行比较 - 就像相关子查询一样 - 而普通子查询只根据关联条件比较一次。(查询计划器有方法,可以优化两者的性能。)
相关答案与代码示例并排解决相同的问题:

优化 GROUP BY 查询以检索每个用户的最新行
对于返回多个 column,LATERAL连接通常更简单、更干净和更快。
另外,请记住,相关子查询的等价物是LEFT JOIN LATERAL ... ON true:

  • LATERAL 和交叉应用是一回事。

参考以下查询。

  1. Select A.*
  2. , (Select min(B.val) Column1 from B where B.Fk1 = A.PK )
  3. , (Select max(B.val) Column2 from B where B.Fk1 = A.PK )
  4. FROM A ;

在这种情况下,可以使用 LATERAL 。

  1. Select A.*
  2. , x.Column1
  3. , x.Column2
  4. FROM A
  5. LEFT JOIN LATERAL (
  6. Select min(B.val) Column1, max(B.val) Column2, B.Fk1 from B where B.Fk1 = A.PK
  7. ) x ON true;

在此查询中,由于条件子句,不能使用普通连接,可以使用 LATERAL 或交叉应用。

LATERAL 或交叉应用有更多用法,但这是最常见的一种。

避免重复执行子查询或函数

用户行为A的每行记录的optdate值,与 用户行为B表 的最大 optdate值 并列表格。

数据准备

用户字典表,用户行为A表,用户行为B表

  1. create table users (user_id int PRIMARY KEY, username text);
  2. create table optA (opta_id int PRIMARY KEY, user_id int, optdate timestamp , note text) ;
  3. create index optA_i1 on optA(user_id,optdate desc );
  4. create table optB (optb_id int PRIMARY KEY, user_id int, optdate timestamp , note text) ;
  5. create index optB_i1 on optB(user_id,optdate desc );
普通语句

每行都执行用户行为B表的查询语句,消耗很多CPU时长

  1. select optA.*,(select max(optdate) from optB where optB.user_id=optA.user_id)
  2. from optA
  3. where opta.user_id=88;
基本优化

使用子查询,实现避免重复执行用户行为B表的查询语句

  1. select optA.*, optB.*
  2. from optA
  3. join (select user_id, max(optdate) from optB group by user_id) optB on optB.user_id = optA.user_id
  4. where opta.user_id = 88;
LATERAL 连接

依然有逐行执行子查询的现象

  1. select optA.*, optB.*
  2. from optA
  3. cross join lateral (select max(optdate)
  4. from optB
  5. where optB.user_id = optA.user_id) optB
  6. where opta.user_id = 88;
使用字典表和 LATERAL 连接

在两个事实表之间,使用字典表作为过度,可以避免重复执行子查询

  1. select optA.*, optB.*
  2. from optA
  3. join users on users.user_id=optA.user_id
  4. cross join lateral (select max(optdate)
  5. from optB
  6. where optB.user_id = users.user_id) optB
  7. where opta.user_id = 88;

分组查询,获得每个用户的最新时间,或者最新行

数据准备

用户日志表,包含user_id 和 log_date

  1. create table log
  2. (
  3. log_date timestamp,
  4. user_id int,
  5. note text
  6. );
  7. insert into log
  8. select now() - ((100000 * random())::numeric(20, 3)::text)::interval log_date,
  9. (random() * 1000000)::int % 100 id,
  10. md5(id)
  11. from generate_series(1, 100000) id
  12. order by random();
  13. -- 索引列与排序列的次序和模式,保持一致
  14. create index log_i1 on log (user_id, log_date DESC NULLS LAST);
普通语句

顺序扫描log表或复合条件的所有记录,通过聚合函数max和窗口函数row_number

  1. explain analyse
  2. select user_id, max(log_date)
  3. from log
  4. group by user_id;
  5. explain analyse
  6. select *
  7. from (select *, row_number() over (partition by user_id order by log_date desc ) sn from log) l
  8. where sn = 1;
递归 CTE 语句

方便检索单列或整行,使用表格的整行类型。仅读取每个用户的最新记录,使用的总数据块数和执行时长,远少于普通语句。

  1. WITH RECURSIVE cte AS (
  2. ( -- 需要括号
  3. SELECT l AS my_row -- 整行记录
  4. FROM log l
  5. ORDER BY user_id, log_date DESC NULLS LAST
  6. LIMIT 1
  7. )
  8. UNION ALL
  9. SELECT (SELECT l -- 整行记录
  10. FROM log l
  11. WHERE l.user_id > (c.my_row).user_id
  12. ORDER BY l.user_id, l.log_date DESC NULLS LAST
  13. LIMIT 1)
  14. FROM cte c
  15. WHERE (c.my_row).user_id IS NOT NULL
  16. )
  17. SELECT (my_row).* -- 分解行
  18. FROM cte
  19. WHERE (my_row).user_id IS NOT NULL
  20. ORDER BY (my_row).user_id;
使用 LATERAL 连接的递归 CTE 语句

递归 CTE 语句,逻辑复杂不易理解,而且每行记录的列,有聚合分解计算。
使用LATERAL 连接,不仅语句易读,而且可以节省10%的CPU计算时长。

  1. WITH RECURSIVE cte AS (
  2. ( -- 需要括号
  3. SELECT *
  4. FROM log
  5. WHERE 1 = 1
  6. ORDER BY user_id, log_date DESC NULLS LAST
  7. LIMIT 1
  8. )
  9. UNION ALL
  10. SELECT l.*
  11. FROM cte c
  12. CROSS JOIN LATERAL (
  13. SELECT l.*
  14. FROM log l
  15. WHERE l.user_id > c.user_id -- lateral 参照条件
  16. ORDER BY l.user_id, l.log_date DESC NULLS LAST
  17. LIMIT 1
  18. ) l
  19. )
  20. TABLE cte
  21. ORDER BY user_id;
users字典表和 LATERAL 连接

只要user_id保证每个相关项恰好有一行,表格布局就几乎无关紧要,理想情况下,表格的物理排序与log表格同步。
查询语句,包含字典表和 LATERAL 连接 。由于使用更简洁的查询树,执行时长较递归CTE节省10%。

  1. CREATE TABLE users (
  2. user_id INT PRIMARY KEY
  3. , username text NOT NULL
  4. );
  5. insert into users select generate_series(1,100) id, md5(id) ;
  6. SELECT u.user_id, l.*
  7. FROM users u
  8. cross join LATERAL (
  9. SELECT l.*
  10. FROM log l
  11. WHERE l.user_id = u.user_id -- lateral参照
  12. ORDER BY l.log_date DESC NULLS LAST
  13. LIMIT 1
  14. ) l ;
不使用 LATERAL 连接的 select 子查询

拥有users字典表时,也可以不依靠 LATERAL 连接,达到不读取多余记录的查询语句。
由于记录行,分解成若干列,需要CPU计算用时,比 LATERAL 连接多用时长10%,且与列的数量正相关。

  1. SELECT (combo1).*
  2. FROM (
  3. SELECT u.user_id
  4. , (SELECT (l.*)::log
  5. FROM log l
  6. WHERE l.user_id = u.user_id
  7. ORDER BY l.log_date DESC NULLS LAST
  8. LIMIT 1) AS combo1
  9. FROM users u
  10. ) sub;

Lateral 的限制

  • 数据类型转换,cast vs :: 。

这两种语法格式,都是“显式类型转换”,完全相同。在SQL代码中的某些特殊位置的表达式,只允许使用函数式表示法。

  1. -- 合法一
  2. SELECT elem[1], elem[2]
  3. FROM ( VALUES ('1,2'::TEXT) ) AS q(arr),
  4. LATERAL CAST(String_To_Array(q.arr, ',') AS INT[]) AS elem
  5. ;
  6. -- 合法二
  7. SELECT elem[1], elem[2]
  8. FROM ( VALUES ('1,2'::TEXT) ) AS q(arr),
  9. LATERAL (select String_To_Array(q.arr, ',')::INT[] AS elem) as t
  10. ;
  11. -- 非法
  12. SELECT elem[1], elem[2]
  13. FROM ( VALUES ('1,2'::TEXT) ) AS q(arr),
  14. LATERAL String_To_Array(q.arr, ',')::int[] AS elem ;
  15. 错误: 语法错误 在 "::" 或附近的
  16. 3LATERAL String_To_Array(q.arr, ',')::int[] AS elem;

另一种SQL语句,CREATE INDEX 语句,也会触发相同的错误信息。如果使用cast函数和数据类型名函数,则是合法的 create index 语句。

  1. create index t02_i1 on t02 (id::int);
  2. 错误: 语法错误 在 "::" 或附近的
  3. 1create index t02_i2 on t02 (id::int);
  4. -- 合法改写 CREATE INDEX 语句
  5. create index t02_i1 on t02 ((id::int));
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/761808
推荐阅读
相关标签
  

闽ICP备14008679号