PostgreSQL之SEMI-JOIN半连接

什么是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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

概率论与数理统计-第7章 假设检验

假设检验的基本概念 二、假设检验的基本思想 假设检验的基本思想实质上是带有某种概率性质的反证法,为了检验一个假设H0,是否正确,首先假定该假设H0正确,然后根据抽取到的样本对假设H0作出接受或拒绝的决策,如果样本观察值导致了…

如何从 Keras 中的深度学习目录加载大型数据集

一、说明 数据集读取,使用、在磁盘上存储和构建图像数据集有一些约定,以便在训练和评估深度学习模型时能够快速高效地加载。本文介绍Keras 深度学习库中的ImageDataGenerator类等工具自动加载训练、测试和验证数据集。 二、ImageDataGenerator加载数据集…

CMake入门教程【高级篇】配置文件(configure_file)

😈「CSDN主页」:传送门 😈「Bilibil首页」:传送门 😈「动动你的小手」:点赞👍收藏⭐️评论📝 文章目录 1.configure_file作用2.详细使用说明3.完整代码示例4.实战使用技巧与注意事项5.总结分析1.configure_file作用

添加气泡与菜单

目录 1、添加气泡 1.1、文本提示气泡 1.2、带按钮的提示气泡 1.3、自定义气泡 2、菜单 2.1、创建默认样式的菜单 2.2、创建自定义样式的菜单 1、添加气泡 Popup属性可绑定在组件上显示气泡弹窗提示,设置弹窗内容、交互逻辑和显示状态。主要用于…

【野火i.MX6ULL开发板】利用microUSB线烧入Debian镜像

0、前言 烧入Debian镜像有两种方式:SD卡、USB SD卡:需要SD卡(不是所有型号都可以,建议去了解了解)、SD卡读卡器 USB:需要microUSB线 由于SD卡的网上资料很多了,又因为所需硬件(SD卡…

基于人工蜂群算法多无人机轨迹规划

#生物背景 蜜蜂是一种群居生物,生物学家研究发现蜜蜂以跳舞的方式来交换蜜源信息。根据分工的不同,蜜蜂被分为三个工种:引领峰、跟随蜂、侦察蜂。 侦察蜂的职责是侦察蜜源(即蜜蜂的食物),一旦某一个侦察蜂…

XCTF:MISCall[WriteUP]

使用file命令,查看该文件类型 file d02f31b893164d56b7a8e5edb47d9be5 文件类型:bzip2 使用bzip2命令可对该文件进行解压 bzip2 -d d02f31b893164d56b7a8e5edb47d9be5 生成了一个后缀为.out的文件 再次使用file命令,查看该文件类型 file…

2024最新适用于 Windows 、Mac 的最佳屏幕录制软件

屏幕录制软件可以帮助我们录制 PC 和MacBook的实时屏幕视频。如果您想为 优酷录制视频,或者您正在为您的公司制作基于视频的项目,并且需要捕获屏幕的实时视频录制,那么我们在此列出了 一 款适合您的 Windows 、Mac的 2024 年最佳屏幕录制软件…

Qt框架学习 --- CTK

系列文章目录 文章目录 系列文章目录前言一、准备阶段二、使用介绍1.核心思想2.源码2.1.框架部分资源目录树2.2.框架部分源码2.3.插件部分资源目录树2.4.插件部分源码 3.文件结构4.运行效果 总结 前言 随着开发的深入,CTK框架还是要关注一下。了解CTK还是有必要的。…

Apache Doris 聚合函数源码阅读与解析|源码解读系列

笔者最近由于工作需要开始调研 Apache Doris,通过阅读聚合函数代码切入 Apache Doris 内核,同时也秉承着开源的精神,开发了 array_agg 函数并贡献给社区。笔者通过这篇文章记录下对源码的一些理解,同时也方便后面的新人更快速地上…

【Oracle】数据库对象

一、视图 1、视图概述 视图是一种数据库对象 视图 > 封装sql语句 > 虚拟表 2、视图的优点 简化操作:视图可以简化用户处理数据的方式。着重于特定数据:不必要的数据或敏感数据可以不出现在视图中。视图提供了一个简单而有效的安全机制&#x…

Odrive 学习系列一:vscode 编译Odrive

搭建环境可参考Markerbase教程,很详细了。 简单说一两点: 解压ODrive-fw-v0.5.1.zip: 打开ODrive-fw-v0.5.1文件夹,找到Firmware文件夹,用vscode打开该文件夹: 按照以下内容操作: 编译工程: 打开 中断(terminal),输入 make -j4 回车 进行编译。编译…

基于JavaWeb+BS架构+SpringBoot+Vue基于hive旅游数据的分析与应用系统的设计和实现

基于JavaWebBS架构SpringBootVue基于hive旅游数据的分析与应用系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 1 概 述 5 1.1 研究背景 5 1.2 研究意义 5 1.3 研究内容…

C# FreeSql使用,基于Sqlite的DB Frist和Code First测试

文章目录 前言FreeSql 简单连接数据库服务不存在没装FreeSql.All装了FreeSql.All安装包选择 DBFirst安装命令行生成器生成Bat创建脚本 基于Sqlite的Code Frist文件夹自动导出到Debug目录Sqlite 数据库安装和创建Sqlite连接数据库自动增列增表测试增列删列改列名同名列改属性 Co…

python进行简单的app自动化测试(pywinauto)+ 截屏微信二维码

一、开始需要了解准备 1、安装 pip install pywinauto2、选择(后面会通过工具进行判断用哪个) 3、自动化控制进程的范围 示例 Application单进程 Desktop多进程 4、程序辅助检测工具 3中的下载连接 链接 点击放大镜拖到对应位置即可 二、简单的开始…

前端页面优化做的工作

1.分析模块占用空间 new (require(webpack-bundle-analyzer).BundleAnalyzerPlugin)() 2.使用谷歌浏览器中的layers,看下有没有影响性能的模块,或者应该销毁没销毁的 3.由于我们页面中含有很大的序列帧动画,所以会导致页面性能低&#xff0…

合并 K 个升序链表[困难]

一、题目 给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。 示例 1: 输入:lists [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如…

Maven 依赖管理项目构建工具 教程

Maven依赖管理项目构建工具 此文档为 尚硅谷 B站maven视频学习文档,由官方文档搬运而来,仅用来当作学习笔记用途,侵删。 另:原maven教程短而精,值得推荐,下附教程链接。 atguigu 23年Maven教程 目录 文章目…

2024-01-11 部署Stable Diffusion遇挫记

点击 <C 语言编程核心突破> 快速C语言入门 部署Stable Diffusion遇挫记 前言一、一如既往的GitHub部署二、使用的感受总结 create by Stable Diffusion; prompt: fire water llama 前言 要解决问题: 由于近期的努力, 已经实现语音转文字模型, 通用chat迷你大模型的本地…

适配 IOS 安全区域

安全区域指的是一个可视窗口范围&#xff0c;处于安全区域的内容不受圆角&#xff08;corners&#xff09;、齐刘海&#xff08;sensor housing&#xff09;、小黑条&#xff08;Home Indicator&#xff09;影响。 造成这个问题的主要原因就是 iphoneX 之后在屏幕上出现了所谓…