PostgreSQL查询引擎——上拉子链接SubLink

news2024/9/22 13:39:40

子查询是查询语句中经常出现的一种类型,是比较耗时的操作。优化子查询对查询效率的提升有直接的影响。从子查询出现在SQL语句的位置看,它可以出现在目标列、FROM子句、WHERE子句、JOIN/ON子句、GROUPBY子句、HAVING子句、ORDERBY子句等位置。子查询出现在不同位置对优化的影响如下:

  • 目标列位置:子查询如果位于目标列,则只能是标量子查询,否则数据库可能返回类型“错误:子查询必须只能返回一个字段”的提示
  • FROM子句位置:相关子查询【子查询的执行依赖于外层父查询的一些属性值。子查询因依赖于父查询的参数,当父查询的参数改变时,子查询需要根据新参数值重新执行】出现在FROM子句中,数据库可能返回类似“在FROM子句中的子查询无法参考相同查询级别中的关系”的提示,所以相关子查询不同出现在FROM子句中;非相关子查询【子查询的执行不依赖于外层父查询的任何属性值,这样的子查询具有独立性,可独立求解,形成一个子查询计划先于外层的查询求解】出现在FROM子句中,可上拉子查询到父层,在多表连接时统一考虑连接代价后择优。
  • WHERE子句位置:出现在WHERE子句中的子查询是一个条件表达式的一部分,而表达式可以分解为操作符和操作数;根据参与运算的数据类型的不同,操作符也不尽相同,这对子查询均有一定要求(如INT型的等值操作,要求子查询必须是标量子查询)。另外,子查询出现在WHERE子句中的格式也有用谓词指定的一些操作,如IN、BETWEEN、EXISTS等。
  • JOIN/ON子句位置:join/on子句可以拆分为两部分,一是JOIN块类似于FROM子句,二是ON子句类似于WHERE子句,这两部分都可以出现子查询。子查询的处理方式同FROM子句和WHERE子句。
  • GROUPBY子句位置:目标列必须和GROUPBY关联。可将子查询写在GROUPBY位置处,但子查询用在GROUPBY处没有实用意义。
  • ORDERBY子句位置:可将子查询写在ORDERBY位置处。但ORDERBY操作是作用在整条SQL语句上的,子查询用在ORDERBY处没有实用意义。
    请添加图片描述

PostgreSQL数据库将上述子查询概念进行了分类:子查询通常以范围表的方式存在(select * from student, (select * from score) as sc);子链接以表达式的方式存在(SELECT (select avg(degree) from score), sname FROM STUDENT)【在实际应用中,可以通过子句所在位置来区分子链接和子查询,出现在FROM关键字后的子句是子查询;出现在WHERE/ON等约束条件投影中的子句是子链接】。子链接是出现在表达式中的子查询(A SubLink represents a subselect appearing in an expression, and in some cases also the combining operator(s) just above it.)
子链接使用的特定谓词表达式如下所示:

类型表达式解释解析
EXISTS_SUBLINKEXISTS(SELECT …)EXISTS谓词EXISTS select_with_parens
ALL_SUBLINK(lefthand) op ALL (SELECT …)ALL谓词a_expr subquery_Op sub_type select_with_parens或a_expr subquery_Op sub_type ‘(’ a_expr ‘)’
ANY_SUBLINK(lefthand) op ANY (SELECT …)ANY/IN/SOME谓词a_expr IN_P in_expr或a_expr NOT_LA IN_P in_expr或a_expr subquery_Op sub_type ‘(’ a_expr ')'或a_expr subquery_Op sub_type select_with_parens或a_expr subquery_Op sub_type ‘(’ a_expr ‘)’
ROWCOMPARE_SUBLINK(lefthand) op (SELECT …)IsA(lexpr, RowExpr) IsA(rexpr, SubLink) ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK 转为ROWCOMPARE_SUBLINK
EXPR_SUBLINK(SELECT with single targetlist item …)select_with_parens或select_with_parens indirection或创建plan时生成create_plan_recurse–>create_minmaxagg_plan–>SS_make_initplan_from_plan
MULTIEXPR_SUBLINK(SELECT with multiple targetlist items …)transformExprRecurse --> transformMultiAssignRef ((SubLink *) maref->source)->subLinkType == EXPR_SUBLINK转为MULTIEXPR_SUBLINK
ARRAY_SUBLINKARRAY(SELECT with single targetlist item …)ARRAY select_with_parens
CTE_SUBLINKWITH query (never actually part of an expression)SS_process_ctes --> CTE_SUBLINK

对于ALL、ANY和ROWCOMPARE,左手边是一个表达式列表,其长度与子选择的targetlist的长度相同。ROWCOMPARE将始终拥有一个包含多个条目的列表;如果子选择只有一个目标,那么解析器将创建一个EXPR_SUBLINK(子选择之上的任何运算符都将单独表示)。ROWCOMPARE、EXPR和MULTIEXPR要求subselect最多传递一行(如果不返回行,则结果为NULL)。ALL、ANY和ROWCOMPARE需要组合运算符来传递布尔结果。ALL和ANY分别使用and和OR语义组合每行结果。For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the same length as the subselect’s targetlist. ROWCOMPARE will always have a list with more than one entry; if the subselect has just one target then the parser will create an EXPR_SUBLINK instead (and any operator above the subselect will be represented separately). ROWCOMPARE, EXPR, and MULTIEXPR require the subselect to deliver at most one row (if it returns no rows, the result is NULL). ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean results. ALL and ANY combine the per-row results using AND and OR semantics respectively. ARRAY只需要一个目标列,并使用子选择产生的任意行数创建目标列类型的数组。ARRAY requires just one target column, and creates an array of the target column’s type using any number of rows resulting from the subselect.
SubLink被归类为Expr节点,但它实际上不是可执行的;在计划期间,它必须在表达式树中被“子计划”节点替换。SubLink is classed as an Expr node, but it is not actually executable; it must be replaced in the expression tree by a SubPlan node during planning.
注意:在gram.y的原始输出中,testexpr只包含左手表达式的原始形式(如果有的话),而operName是组合运算符的字符串名称。此外,subselect是一个原始的解析树。在解析分析过程中,解析器将testexpr转换为一个完整的布尔表达式,该表达式将左侧值与表示子选择的输出列的PARAM_SUBLINK节点进行比较。子选择转换为查询。这是在保存的规则和重写器中看到的表示形式。NOTE: in the raw output of gram.y, testexpr contains just the raw form of the lefthand expression (if any), and operName is the String name of the combining operator. Also, subselect is a raw parsetree. During parse analysis, the parser transforms testexpr into a complete boolean expression that compares the lefthand value(s) to PARAM_SUBLINK nodes representing the output columns of the subselect. And subselect is transformed to a Query. This is the representation seen in saved rules and in the rewriter. 在EXISTS、EXPR、MULTIEXPR和ARRAY子链接中,testexpr和operName未使用并且始终为null。In EXISTS, EXPR, MULTIEXPR, and ARRAY SubLinks, testexpr and operName are unused and are always null.
subLinkId当前仅用于MULTIEXPR子链接,在其他子链接中为零。这个数字标识UPDATE语句的SET列表中不同的多个赋值子查询。它仅在特定的目标列表中是唯一的。MULTIEXPR的输出列由列表中其他位置出现的PARAM_MULTEXPR参数引用。subLinkId is currently used only for MULTIEXPR SubLinks, and is zero in other SubLinks. This number identifies different multiple-assignment subqueries within an UPDATE statement’s SET list. It is unique only within a particular targetlist. The output column(s) of the MULTIEXPR are referenced by PARAM_MULTIEXPR Params appearing elsewhere in the tlist.
CTE_SUBLINK的情况从未出现在实际的SUBLINK节点中,但它用于为WITH子查询生成的子计划中。The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used in SubPlans generated for WITH subqueries.

PostgreSQL主要对ANY_SUBLINK和EXISTS_SUBLINK两种类型的子链接尝试提升。

谓词形式描述
[NOT] INLH [NOT] IN EXPR如果提升,则变为[反]半连接([Anti-] Semi Join)
ANY/SOMELH OP ANY EXPR如果提升,则变为半连接,即Semi Join
[NOT] EXISTS[NOT] EXISTS EXPR如果提升,则变为[反]半连接([Anti-] Semi Join)

pull_up_sublinks

本文主要介绍函数pull_up_sublinks尝试将ANY和EXISTS子链接上拉为半连接或反半连接(Attempt to pull up ANY and EXISTS SubLinks to be treated as semijoins or anti-semijoins.)。子句“foo-op ANY(sub-SELECT)”可以通过向上拉子SELECT成为范围表条目rangetable entry并将隐含的比较视为半联接的quals来处理。然而,这种优化仅适用于WHERE或JOIN/ON子句的顶级,因为我们无法区分ANY在涉及NULL输入的情况下应该返回FALSE还是NULL。此外,在外部联接的ON子句中,只有当子链接退化时(即,仅引用联接的可为null的一侧),我们才能这样做。在这种情况下,将半联接向下推到联接的可为null的一侧是合法的。如果子链接引用了任何不可为null的边变量,那么它将不得不作为外部联接的一部分进行计算,这使得事情变得太复杂了。A clause “foo op ANY (sub-SELECT)” can be processed by pulling the sub-SELECT up to become a rangetable entry and treating the implied comparisons as quals of a semijoin. However, this optimization only works at the top level of WHERE or a JOIN/ON clause, because we cannot distinguish whether the ANY ought to return FALSE or NULL in cases involving NULL inputs. Also, in an outer join’s ON clause we can only do this if the sublink is degenerate (ie, references only the nullable side of the join). In that case it is legal to push the semijoin down into the nullable side of the join. If the sublink references any nonnullable-side variables then it would have to be evaluated as part of the outer join, which makes things way too complicated.在类似的条件下,EXISTS和NOT EXISTS子句可以通过拉起子SELECT并创建半联接或反半联接来处理。Under similar conditions, EXISTS and NOT EXISTS clauses can be handled by pulling up the sub-SELECT and creating a semijoin or anti-semijoin. 这个例程搜索这样的子句,并执行必要的解析树转换(如果有的话)。This routine searches for such clauses and does the necessary parsetree transformations if any are found. 这个例程必须在preprocess_expression之前运行,所以quals子句还没有简化为隐式AND格式,也不能保证是AND/OR平面的。这意味着我们需要递归地搜索显式AND子句。我们一碰到非AND项目就停止。This routine has to run before preprocess_expression(), so the quals clauses are not yet reduced to implicit-AND format, and are not guaranteed to be AND/OR-flat either. That means we need to recursively search through explicit AND clauses. We stop as soon as we hit a non-AND item.

void pull_up_sublinks(PlannerInfo *root) {
	Relids		relids;
	/* Begin recursion through the jointree */
	Node	   *jtnode = pull_up_sublinks_jointree_recurse(root,
											   (Node *) root->parse->jointree, /* 查询语句的FROM和WHREE子句对应部分 */
											   &relids);
	/* root->parse->jointree must always be a FromExpr, so insert a dummy one if we got a bare RangeTblRef or JoinExpr out of the recursion. */
	if (IsA(jtnode, FromExpr)) root->parse->jointree = (FromExpr *) jtnode;
	else root->parse->jointree = makeFromExpr(list_make1(jtnode), NULL);
}

pull_up_sublinks_jointree_recurse

static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, Relids *relids)函数用于递归上拉各种类型子句中存在的子链接(IN、[NOT] EXISTS类型)。对于子句中的FromExpr、JoinExpr,一是递归调用本身函数自身处理其中可能存在的子链接;二是调用pull_up_sublinks_qual_recurse函数处理其中的quals限制条件。

如果jtnode是RangeTblRef - reference to an entry in the query’s rangetable,范围表(RangeTblEntry)表示的是查询对象,或是一个普通的关系或是一个FROM子句中出现的子查询(a sub-select in FROM),或是连接子句的连接结果(result of a JOIN clause)。
在这里插入图片描述
如果子链接是范围表,直接合并到表示关系的relids中去。

	else if (IsA(jtnode, RangeTblRef)){
		int			varno = ((RangeTblRef *) jtnode)->rtindex;
		*relids = bms_make_singleton(varno); /* jtnode is returned unmodified */
	}

处理FromExpr,首先递归处理每一个FROM中的对象,上拉其中的子链接,并为其创建FromExpr封装。最后递归上拉子链接中的条件。

		/* First, recurse to process children and collect their relids */
		foreach(l, (FromExpr *) jtnode->fromlist){
			Relids		childrelids;
			Node	   *newchild = pull_up_sublinks_jointree_recurse(root, lfirst(l), &childrelids);
			List	   *newfromlist = lappend(newfromlist, newchild);
			frelids = bms_join(frelids, childrelids);
		}
		/* Build the replacement FromExpr; no quals yet */
		FromExpr   *newf = makeFromExpr(newfromlist, NULL);
		/* Set up a link representing the rebuilt jointree */
		Node	   *jtlink = (Node *) newf;
		/* Now process qual --- all children are available for use */
		newf->quals = pull_up_sublinks_qual_recurse(root, f->quals, &jtlink, frelids, NULL, NULL);		

处理JoinExpr,递归处理连接对象中的左子树和右子树,上拉它们中的子连接;递归上拉子链接中的条件:主要体现在join类型,如果是inner join,则使用bms_union(leftrelids, rightrelids)参数,如果是left join则仅使用rightrelids,如果是right jon,则leftrelids。

		JoinExpr   *j = (JoinExpr *) palloc(sizeof(JoinExpr)); memcpy(j, jtnode, sizeof(JoinExpr)); Node	   *jtlink = (Node *) j; /* Make a modifiable copy of join node, but don't bother copying its subnodes (yet). */
		
        Relids		leftrelids; Relids		rightrelids;
		/* Recurse to process children and collect their relids */
		j->larg = pull_up_sublinks_jointree_recurse(root, j->larg, &leftrelids);
		j->rarg = pull_up_sublinks_jointree_recurse(root, j->rarg, &rightrelids);

		/* Now process qual, showing appropriate child relids as available, and attach any pulled-up jointree items at the right place. In the inner-join case we put new JoinExprs above the existing one (much as for a FromExpr-style join).  In outer-join cases the new JoinExprs must go into the nullable side of the outer join. The point of the available_rels machinations is to ensure that we only pull up quals for which that's okay. We don't expect to see any pre-existing JOIN_SEMI or JOIN_ANTI nodes here. */
		switch (j->jointype) {
			case JOIN_INNER: j->quals = pull_up_sublinks_qual_recurse(root, j->quals, &jtlink, bms_union(leftrelids, rightrelids), NULL, NULL); break;
			case JOIN_LEFT:  j->quals = pull_up_sublinks_qual_recurse(root, j->quals, &j->rarg, rightrelids, NULL, NULL); break;
			case JOIN_FULL: /* can't do anything with full-join quals */ break;
			case JOIN_RIGHT:
				j->quals = pull_up_sublinks_qual_recurse(root, j->quals, &j->larg, leftrelids, NULL, NULL); break;
			default: elog(ERROR, "unrecognized join type: %d", (int) j->jointype);
				break;
		}

		/* Although we could include the pulled-up subqueries in the returned relids, there's no need since upper quals couldn't refer to their outputs anyway.  But we *do* need to include the join's own rtindex because we haven't yet collapsed join alias variables, so upper levels would mistakenly think they couldn't use references to this join. */
		*relids = bms_join(leftrelids, rightrelids);
		if (j->rtindex) *relids = bms_add_member(*relids, j->rtindex);
		jtnode = jtlink;

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

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

相关文章

c语言指针进阶(一)

大家好,我是c语言boom成家宝。今天为大家分享的是c语言中很重要的一个知识点------指针的深入讲解。 目录 指针 指针数组 数组指针 函数指针 什么是指针? 首先,指针的本质是一个地址,指针在32位机器上的大小是4个字节&a…

python_day3_str

字符串str 按索引下标查找 str Hi, world, follow, admin print(str[0]) print(str[-1])index() str Hi, world, follow, admin print(str.index(world)) #首字母下标 print(str.index(w))字符串.replace(字符串1,字符串2):…

【Linux】高级IO(二)

文章目录 高级IO(二)I/O多路转接之pollpoll服务器 I/O多路转接之epollepoll相关函数epoll工作原理epoll回调机制epoll服务器epoll的优点 高级IO(二) I/O多路转接之poll poll也是系统提供的一个多路转接接口 poll系统调用也可以…

ruoyi若依 组织架构设计--[ 部门管理 ]

ruoyi若依 组织架构设计--[ 部门管理 ] 部门管理部门查询部门新增部门修改部门删除 部门管理 部门查询 需要注意的是,部门管理也有数据权限,比如A用户分配的数据权限(通过角色分配)是深圳总公司,那么A用户登录后看到的部门也是深圳总公司&am…

2023年前端面试题汇总-数据结构(二叉树)

对于树这个结构,最常见的就是二叉树。我们除了需要了解二叉树的基本操作之外,还需要了解一些特殊的二叉树,比如二叉搜索树、平衡二叉树等,另外还要熟悉二叉树的遍历方式,比如前序遍历、中序遍历、后序遍历、层序遍历。另外还要知道二叉树的常用遍历的方式:深度优先遍历和…

非线性优化知识

这里列下最小二乘的四种解法的优缺点. #mermaid-svg-CLbQz6o8j7JMq9MM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CLbQz6o8j7JMq9MM .error-icon{fill:#552222;}#mermaid-svg-CLbQz6o8j7JMq9MM .err…

前端开发中的单例模式

在前端开发中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。 在JavaScript中,可以使用以下几种方式来实现单例模式: 字面量方式: const singleton {// …

JVM 调优测试Jmeter 压测

Jmeter 内存不足了,修个5个线程吧 测试结果: Jmeter配置参数 5个线程,每个线程1秒跑1000次 测试串行吞吐量 -XX:PrintGCDetails -Xmx128M -Xms128M -XX:HeapDumpOnOutOfMemoryError -XX:UseSerialGC -XX:PermSize32M GC回收4次 吞吐量138…

SQL Server 2008每天自动备份数据库

在SQL Server 2008数据库中。为了防止数据的丢失我们就需要按时的来备份数据库了。要是每天都要备份的话,人工备份会很麻烦的,自动备份的话就不需要那么麻烦了,只要设置好了,数据库就会自动在你设置的时间段里备份。那么自动备份要…

ihrm项目结构详解

大体介绍 云服务的三种模式 Iaas:基础设施即服务 Pass:平台即服务 Saas:软件即服务 系统设计 主键id生成策略 lombok data setter getter noArgs(无参构造) 模块搭建 1 企业得增删改查 2 全局异常处理器 3 跨域…

选择排序算法介绍

算法介绍 选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是每次从待排序的元素中选取最小(或最大)的元素,放到已排序部分的末尾,直到全部元素排序完毕。 以下是选择排序的详细步骤&…

【实战】 六、用户体验优化 - 加载中和错误状态处理(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十)

文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理1~234.用useAs…

java实现一个简单的webSocket聊天demo

java实现一个简单的webSocket聊天demo 一、依赖二、配置准备三、demo代码编写四、启动测试五、编写业务 一、依赖 添加pom文件依赖 <!-- websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

IDEA环境配置汇总

1、配置项目编码 2、配置运行看板Services IDEA开启并配置Services窗口 这里已经配置好了&#xff0c;如果没有&#xff0c;就点击&#xff0c;点击Run Configuration Type&#xff0c;选择所需要的&#xff0c;点击即可。 配置spring与docker看板(按照上面的方法来配置&am…

【Python】查询SQL并用柱状图展示

需求&#xff1a; 查询2022年各月订单量&#xff0c;并用柱状图展示 SQL&#xff1a; select date_format(create_time,%Y-%m) as mon ,count(distinct order_id) as ord_cnt from prod.order_info where date_format(create_time,%Y-%m) between 2022-01 and 2022-12 group…

Mac OS装Windows系统开启虚拟化

目录 引言前提macOS开启虚拟化mac下的Windows开启虚拟化双系统开启虚拟化修改启动管理程序开启虚拟化 注意事项 引言 在开发工作中&#xff0c;很多软件需要用到virtual box&#xff0c;但是使用virtual box需要开启虚拟化&#xff0c;而有些苹果笔记本虚拟化是关闭的&#xf…

【GitHub】一条命令快速部署 Kubernetes(k8s) 集群的工具-sealos

Sealos 是一个GitHub上优秀的开源项目&#xff0c;目前项目点赞数已达&#xff1a;10.2k&#xff0c;核心特性&#xff1a; 管理集群生命周期下载和使用完全兼容 OCI 标准的分布式应用定制化分布式应用Sealos Cloud 项目开源协议&#xff1a;Apache-2.0 项目主开发语言&…

NSS [SWPUCTF 2022 新生赛]funny_web

NSS [SWPUCTF 2022 新生赛]funny_web 账号NSS 密码2122693401 私货不去细细研究了&#xff0c;直接看题。 num不等于12345&#xff0c;但是intval&#xff08;num&#xff09;等于12345 ①intval():可以获取变量的整数值intval()中有一个特性&#xff0c;其中若传入1e4&…

tensorboard与torchinfo的使用

目录 1. tensorboard1.1 本地使用1.2 远程服务器使用 2. torchinfoRef 1. tensorboard 1.1 本地使用 只需要掌握一个 torch.utils.tensorboard.writer.SummaryWriter 接口即可。 在初始化 SummaryWriter 的时候&#xff0c;通常需要指定log的存放路径。这个路径默认是 runs/…

Python脚本小工具之文件与内容搜索

目录 一、前言 二、代码 三、结果 一、前言 ​日常工作中&#xff0c;经常需要在指定路径下查找指定类型的文件&#xff0c;或者是指定内容的查找&#xff0c;在window环境中&#xff0c;即可以使用一些工具如notepad或everything&#xff0c;也可以使用python脚本。但在l…