From 5391f1b8a8f8a7bb2fc0e3c6fc355fc0a147849d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 23 Feb 2026 08:43:39 +0100 Subject: [PATCH] Optimize SELECT * in EXISTS Transform a SELECT * inside an EXISTS into an empty select list. This allows writing EXISTS (SELECT * FROM ...) which is a common convention, without the overhead of expanding all the columns. The PostgreSQL documentation suggests writing EXISTS (SELECT 1 FROM ...), but not even all our tests and example code use that, so it's probably not universally known. The SQL standard also specifies this optimization. (It says that * expands to an arbitrary literal, but we can use an empty select list.) --- doc/src/sgml/func/func-subquery.sgml | 21 +++++++++++++++------ src/backend/parser/parse_expr.c | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/func/func-subquery.sgml b/doc/src/sgml/func/func-subquery.sgml index a9f2b12e48c..d51d32baa9c 100644 --- a/doc/src/sgml/func/func-subquery.sgml +++ b/doc/src/sgml/func/func-subquery.sgml @@ -66,12 +66,21 @@ <literal>EXISTS</literal> - Since the result depends only on whether any rows are returned, - and not on the contents of those rows, the output list of the - subquery is normally unimportant. A common coding convention is - to write all EXISTS tests in the form - EXISTS(SELECT 1 WHERE ...). There are exceptions to - this rule however, such as subqueries that use INTERSECT. + Since the result depends only on whether any rows are returned, and not on + the contents of those rows, the output list of the subquery is normally + unimportant. A possible coding convention is to write all + EXISTS tests in the form EXISTS(SELECT * FROM + ... WHERE ...). The * is optimized away in + this case, and no columns are actually fetched. (This optimization only + applies to output lists consisting solely of a single asterisk.) Another + common convention is to write EXISTS(SELECT 1 FROM ... WHERE + ...) or some other dummy constant. (Another alternative is to + omit the select list altogether (EXISTS(SELECT FROM ... WHERE + ...), but that is a PostgreSQL extension and would make your code + less portable and arguably harder to read.) These conventions are not + always applicable, such as in subqueries that use INTERSECT, + where the output list of the subquery affects the result of the subquery + itself. diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index dcfe1acc4c3..cb2239cb250 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1888,6 +1888,33 @@ transformSubLink(ParseState *pstate, SubLink *sublink) pstate->p_hasSubLinks = true; + /* + * If EXISTS(SELECT * ...), remove the output list. (See SQL:2023 SR 7.) + */ + if (sublink->subLinkType == EXISTS_SUBLINK) + { + if (IsA(sublink->subselect, SelectStmt)) + { + SelectStmt *s = castNode(SelectStmt, sublink->subselect); + + if (list_length(s->targetList) == 1) + { + ResTarget *rt = linitial_node(ResTarget, s->targetList); + + if (IsA(rt->val, ColumnRef) && !rt->name && !rt->indirection) + { + ColumnRef *cr = castNode(ColumnRef, rt->val); + + if (list_length(cr->fields) == 1 && IsA(linitial(cr->fields), A_Star)) + { + s->targetList = NIL; + } + } + } + } + } + /* * OK, let's transform the sub-SELECT. */ -- 2.53.0