PostgreSQL之SEMI-JOIN半连接

news2024/11/17 20:26:25

什么是Semi-Join半连接

Semi-Join半连接,当外表在内表中找到匹配的记录之后,Semi-Join会返回外表中的记录。但即使在内表中找到多条匹配的记录,外表也只会返回已经存在于外表中的记录。而对于子查询,外表的每个符合条件的元组都要执行一轮子查询,效率比较低下。此时使用半连接操作优化子查询,会减少查询次数,提高查询性能。其主要思路是将子查询上拉到父查询中,这样内表和外表是并列关系,外表的每个符合条件的元组,只需要在内表中找符合条件的元组即可,所以效率会大大提高。

1

当参与等值JOIN表达式存在有重复值时, 如果不需要找出该表其他字段的值(也就是仅使用JOIN字段/表达式), 那么JOIN时只需要查每个值的第一条, 然后就可以跳到下一个值. 在数据库中常常被用来优化 in, exists, not exists, = any(), except 等操作(或者逻辑上成立的其他JOIN场景).

还有什么特别的join?PostgreSQL 与关系代数 (Equi-Join , Semi-Join , Anti-Join , Division)

并不是所有数据库都实现了所有场景的semi join, 例如 Oracle中的半连接,MySQL也有半连接

如果未实现, 有什么方法可以模拟semi-join?递归/group by/distinct on/distinct

Semi-Join 例子

准备测试数据

postgres=# create table a (id int, info text, ts timestamp);  
CREATE TABLE  
postgres=# create table b (like a);  
CREATE TABLE  
postgres=# insert into a select id, md5(random()::text), now() from generate_series(0,1000000) as t(id);  
INSERT 0 1000001  
  
-- b表的100万行记录中b.id只有11个唯一值  
postgres=# insert into b select random()*10, md5(random()::text), now() from generate_series(0,1000000) as t(id);  
INSERT 0 1000001  
  
postgres=# create index on a (id);  
CREATE INDEX  
postgres=# create index on b (id);  
CREATE INDEX

未优化SQL

select a.* from a where exists (select 1 from b where a.id=b.id);  
  
postgres=# explain analyze select a.* from a where exists (select 1 from b where a.id=b.id);  
                                                                     QUERY PLAN                                                                       
----------------------------------------------------------------------------------------------------------------------------------------------------  
 Merge Join  (cost=18436.17..18436.66 rows=11 width=45) (actual time=226.590..226.598 rows=11 loops=1)  
   Merge Cond: (a.id = b.id)  
   ->  Index Scan using a_id_idx on a  (cost=0.42..27366.04 rows=1000001 width=45) (actual time=0.010..0.013 rows=12 loops=1)  
   ->  Sort  (cost=18435.74..18435.77 rows=11 width=4) (actual time=226.576..226.577 rows=11 loops=1)  
         Sort Key: b.id  
         Sort Method: quicksort  Memory: 25kB  
         ->  HashAggregate  (cost=18435.44..18435.55 rows=11 width=4) (actual time=226.568..226.570 rows=11 loops=1)  
               Group Key: b.id  
               Batches: 1  Memory Usage: 24kB  
               ->  Index Only Scan using b_id_idx on b  (cost=0.42..15935.44 rows=1000001 width=4) (actual time=0.010..77.936 rows=1000001 loops=1)  
                     Heap Fetches: 0  
 Planning Time: 0.189 ms  
 Execution Time: 226.630 ms  
(13 rows)

以上查询没有使用semi-join, 性能很一般.

由于b表的100万行记录中b.id只有11个唯一值, 可以使用semi-join进行加速.

用法参考: 《用PostgreSQL找回618秒逝去的青春 - 递归收敛优化》

使用递归模拟SEMI-JOIN, 只需要 0.171 ms 既可得出b表 11个值的结果.

with recursive tmp as (  
  select min(id) as id from b   
  union all   
  select (select min(b.id) from b where b.id > tmp.id) from tmp where tmp.id is not null  
)   
select * from tmp where tmp.id is not null;  
  
 id   
----  
  0  
  1  
  2  
  3  
  4  
  5  
  6  
  7  
  8  
  9  
 10  
(11 rows)

执行计划如下

postgres=# explain analyze with recursive tmp as (  
  select min(id) as id from b   
  union all   
  select (select min(b.id) from b where b.id > tmp.id) from tmp where tmp.id is not null  
)   
select * from tmp where tmp.id is not null;  
                                                                          QUERY PLAN                                                                            
--------------------------------------------------------------------------------------------------------------------------------------------------------------  
 CTE Scan on tmp  (cost=50.07..52.09 rows=100 width=4) (actual time=0.028..0.134 rows=11 loops=1)  
   Filter: (id IS NOT NULL)  
   Rows Removed by Filter: 1  
   CTE tmp  
     ->  Recursive Union  (cost=0.44..50.07 rows=101 width=4) (actual time=0.025..0.126 rows=12 loops=1)  
           ->  Result  (cost=0.44..0.45 rows=1 width=4) (actual time=0.024..0.025 rows=1 loops=1)  
                 InitPlan 3 (returns $1)  
                   ->  Limit  (cost=0.42..0.44 rows=1 width=4) (actual time=0.021..0.022 rows=1 loops=1)  
                         ->  Index Only Scan using b_id_idx on b b_1  (cost=0.42..18435.44 rows=1000001 width=4) (actual time=0.020..0.020 rows=1 loops=1)  
                               Index Cond: (id IS NOT NULL)  
                               Heap Fetches: 0  
           ->  WorkTable Scan on tmp tmp_1  (cost=0.00..4.76 rows=10 width=4) (actual time=0.007..0.007 rows=1 loops=12)  
                 Filter: (id IS NOT NULL)  
                 Rows Removed by Filter: 0  
                 SubPlan 2  
                   ->  Result  (cost=0.45..0.46 rows=1 width=4) (actual time=0.007..0.007 rows=1 loops=11)  
                         InitPlan 1 (returns $3)  
                           ->  Limit  (cost=0.42..0.45 rows=1 width=4) (actual time=0.006..0.006 rows=1 loops=11)  
                                 ->  Index Only Scan using b_id_idx on b  (cost=0.42..6979.51 rows=333334 width=4) (actual time=0.006..0.006 rows=1 loops=11)  
                                       Index Cond: ((id IS NOT NULL) AND (id > tmp_1.id))  
                                       Heap Fetches: 0  
 Planning Time: 0.177 ms  
 Execution Time: 0.171 ms  
(23 rows)

使用递归模拟semi-join, SQL改写如下:

select a.* from a where exists (select 1 from b where a.id=b.id);  
  
改写成  
  
select a.* from a where exists (select 1 from   
(  
with recursive tmp as (  
  select min(id) as id from b   
  union all   
  select (select min(b.id) from b where b.id > tmp.id) from tmp where tmp.id is not null  
)   
select * from tmp where tmp.id is not null  
) b  
 where a.id=b.id);

改写后速度从226.630 ms 提升到 0.246 ms

postgres=# explain analyze select a.* from a where exists (select 1 from   
(  
with recursive tmp as (  
  select min(id) as id from b   
  union all   
  select (select min(b.id) from b where b.id > tmp.id) from tmp where tmp.id is not null  
)   
select * from tmp where tmp.id is not null  
) b  
 where a.id=b.id);  
                                                                                QUERY PLAN                                                                                  
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 Nested Loop  (cost=53.76..318.49 rows=100 width=45) (actual time=0.154..0.189 rows=11 loops=1)  
   ->  HashAggregate  (cost=53.34..54.34 rows=100 width=4) (actual time=0.144..0.149 rows=11 loops=1)  
         Group Key: tmp.id  
         Batches: 1  Memory Usage: 24kB  
         ->  CTE Scan on tmp  (cost=50.07..52.09 rows=100 width=4) (actual time=0.027..0.139 rows=11 loops=1)  
               Filter: (id IS NOT NULL)  
               Rows Removed by Filter: 1  
               CTE tmp  
                 ->  Recursive Union  (cost=0.44..50.07 rows=101 width=4) (actual time=0.024..0.130 rows=12 loops=1)  
                       ->  Result  (cost=0.44..0.45 rows=1 width=4) (actual time=0.023..0.024 rows=1 loops=1)  
                             InitPlan 3 (returns $1)  
                               ->  Limit  (cost=0.42..0.44 rows=1 width=4) (actual time=0.020..0.021 rows=1 loops=1)  
                                     ->  Index Only Scan using b_id_idx on b b_1  (cost=0.42..18435.44 rows=1000001 width=4) (actual time=0.019..0.019 rows=1 loops=1)  
                                           Index Cond: (id IS NOT NULL)  
                                           Heap Fetches: 0  
                       ->  WorkTable Scan on tmp tmp_1  (cost=0.00..4.76 rows=10 width=4) (actual time=0.008..0.008 rows=1 loops=12)  
                             Filter: (id IS NOT NULL)  
                             Rows Removed by Filter: 0  
                             SubPlan 2  
                               ->  Result  (cost=0.45..0.46 rows=1 width=4) (actual time=0.007..0.007 rows=1 loops=11)  
                                     InitPlan 1 (returns $3)  
                                       ->  Limit  (cost=0.42..0.45 rows=1 width=4) (actual time=0.006..0.006 rows=1 loops=11)  
                                             ->  Index Only Scan using b_id_idx on b  (cost=0.42..6979.51 rows=333334 width=4) (actual time=0.006..0.006 rows=1 loops=11)  
                                                   Index Cond: ((id IS NOT NULL) AND (id > tmp_1.id))  
                                                   Heap Fetches: 0  
   ->  Index Scan using a_id_idx on a  (cost=0.42..2.63 rows=1 width=45) (actual time=0.003..0.003 rows=1 loops=11)  
         Index Cond: (id = tmp.id)  
 Planning Time: 0.295 ms  
 Execution Time: 0.246 ms  
(29 rows)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1385177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫入门学习(二)——response对象

大家好!我是码银,代码的码,银子的银🥰 欢迎关注🥰: CSDN:码银 公众号:码银学编程 前言 在本篇文章,我们继续讨论request模块。从上一节(爬虫学习(1)--reque…

【C++】异常机制

异常 一、传统的处理错误的方式二、C异常概念三、异常的使用1. 异常的抛出和捕获(1)异常的抛出和匹配原则(2)在函数调用链中异常栈展开匹配原则 2. 异常的重新抛出3. 异常安全4. 异常规范 四、自定义异常体系五、C 标准库的异常体…

编曲混音FL Studio21.2对电脑有什么配置要求

FL Studio 21是一款非常流行的音乐制作软件,它可以帮助音乐人和制作人创作出高质量的音乐作品。然而,为了保证软件的稳定性和流畅性,用户需要知道FL Studio 21对电脑的配置要求。本文将介绍FL Studio 21的配置要求,以帮助用户选择…

详解Java之Spring框架中事务管理的艺术

第1章:引言 大家好,我是小黑,咱们今天聊聊Spring框架中的事务管理。不管是开发小型应用还是大型企业级应用,事务管理都是个不可避免的话题。那么,为什么事务管理这么重要呢?假设在银行系统中转账时&#x…

Java实现海南旅游景点推荐系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

远程开发之vscode端口转发

远程开发之vscode端口转发 涉及的软件forwarded port 通过端口转发,实现在本地电脑上访问远程服务器上的内网的服务。 涉及的软件 vscode、ssh forwarded port 在ports界面中的port字段,填需要转发的IP:PORT,即可转发远程服务器中的内网端…

SSH镜像、systemctl镜像、nginx镜像、tomcat镜像

目录 一、SSH镜像 二、systemctl镜像 三、nginx镜像 四、tomcat镜像 五、mysql镜像 一、SSH镜像 1、开启ip转发功能 vim /etc/sysctl.conf net.ipv4.ip_forward 1sysctl -psystemctl restart docker 2、 cd /opt/sshd/vim Dockerfile 3、生成镜像 4、启动容器并修改ro…

AirServer2024官方最新版免费下载

AirServer官方版是一款使用方便的投屏软件,在教室,会议室以及游戏中极为方便。AirServer官方版支持IOS、Android、Windows、mac、Chromebook等多种设备,使用AirServer不需要其他的设备即可完成投屏操作,相比其他投屏软件&#xff…

星图地球——Landsat5_C2_TOA_T1数据集

简介 Landsat 5是美国陆地卫星系列(Landsat)的第五颗卫星,于1984年3月1日发射,2011年11月停止工作。16天可覆盖全球范围一次。Landsat5_C2_TOA数据集是由Collection2 level1数据通过MTL文件计算得到的TOA反射率产品。数据集的空间…

Spring Boot 中实现文件上传、下载、删除功能

🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…

VUE好看的个人简历模板

文章目录 1.设计来源1.1 首页界面1.2 关于我界面1.3 我的资历界面1.4 项目经验界面1.5 我的技能界面1.6 联系我界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/…

【python】12.字符串和正则表达式

使用正则表达式 正则表达式相关知识 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要,正则表达式就是用于描述这些规则的工具,换句话说正则表达式是一种工具,它定义了字符串的匹配模式(…

网站万词霸屏推广系统源码:实现关键词推广,轻松提高关键词排名,带完整的安装部署教程

现如今,互联网的快速发展,网站推广成为企业网络营销的重要手段。而关键词排名作为网站推广的关键因素,一直备受关注。罗峰给大家分享一款网站万词霸屏推广系统源码,该系统可实现关键词推广,有效提高关键词排名&#xf…

Github 2024-01-15开源项目周报Top14

根据Github Trendings的统计,本周(2024-01-15统计)共有14个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目6TypeScript项目3Jupyter Notebook项目3Java项目2Kotlin项目1C#项目1C项目1 Microsoft PowerToys:…

实现STM32烧写程序-(2)Flash Loader 发送指令解析

简介 实现STM32烧写程序-(1)获取Bootloader版本信息, 看了数据手册之后可以了解到指令的发送, 但实现之前可以使用现成的工具进行测试和查看他的收发情况; Usart Bootloader 指令列表 Usart Bootloader 指令列表 应答ACK | NACK ACK(0x79) 表示 正常答复, NACK(0x…

mybatis中的驼峰转换

一、有啥用 开发时常用对象来存储从数据库中的记录,开启驼峰转化即可实现数据库字段(通常使用_下划线连接)与对象属性的对应,如数据库中的first_name字段会转化为firstName与对象中的firstName属性对应。 二、配置 三、相关报错 数据库字段与对象属性…

我为什么要写RocketMQ消息中间件实战派上下册这本书?

我与RocketMQ结识于2018年,那个时候RocketMQ还不是Apache的顶级项目,并且我还在自己的公司做过RocketMQ的技术分享,并且它的布道和推广,还是在之前的首席架构师的带领下去做的,并且之前有一个技术神经质的人&#xff0…

广告投放场景中ABtest分析的评价、优化和决策建议

写在开头 在当今数字化的商业环境中,广告投放是企业获取客户和推动销售的重要手段。然而,随着市场竞争的加剧,制定有效的广告策略变得愈发复杂。在这个背景下,AB测试成为了广告主们不可或缺的工具之一。本文将深入探讨广告投放中…

swing快速入门(四十四)拖动、编辑JTree结点

注释很详细,直接上代码 新增内容(源码细节知识点巨多,建议细看) 1.设置JTree可编辑 2.使用JTree关联的数据模型实现节点的增删改 3.鼠标拖动节点事件设计及处理方法 4.手动刷新视图与自动刷新的方法区别 5.自定位节点视图方法 源码…

zepplin记录1

zepplin记录1 文章目录 zepplin记录1前言一、配置python环境二、测试可用性1.配置interpreter2.测试代码 总结 前言 Apache Zeppelin是一个开源的数据分析和可视化的交互式笔记本,类似于Jupyter Notebook。它支持多种编程语言(如Scala、Python、R、SQL等…