Skip to content

4 SQL Statements

SQL syntax, data types, functions, optimizer behavior, and worked examples.
GASource pages: PDF 34-69Original PDF on GitHub

PDF page 34

This section explains new features related to SQL statements.

3.2.1 ALTER DEFAULT PRIVILEGE

Default permissions can now be set for large objects. The defaclobjtype column in the pg_default_acl catalog will output the L indicating a large object. [0d6c477]

Example 31 Default privileges for LARGE OBJECT
postgres=> ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO PUBLIC ;
ALTER DEFAULT PRIVILEGES

3.2.2 ALTER SUBSCRIPTION

The two_phase option can now be altered in the ALTER SUBSCRIPTION statement; the SUBSCRIPTION object must be disabled once in order to alter the two_phase option. [1462aad]

Example 32 Modify the two_phase option
postgres=# SELECT subname, subtwophasestate FROM pg_subscription ;
 subname | subtwophasestate
---------+------------------
 sub1    | e
(1 row)
postgres=# ALTER SUBSCRIPTION sub1 DISABLE ;
ALTER SUBSCRIPTION
postgres=# ALTER SUBSCRIPTION sub1 SET (two_phase=off) ;
ALTER SUBSCRIPTION
postgres=# SELECT subname, subtwophasestate FROM pg_subscription ;
 subname | subtwophasestate
---------+------------------
 sub1    | d
(1 row)

PDF page 35

3.2.3 ALTER TABLE

The following syntax has been added to the ALTER TABLE statement.

ALTER TABLE ALTER CONSTRAINT

The SET INHERIT clause (or SET NO INHERIT clause) can be specified in the ALTER CONSTRAINT clause. [f4e53e1]

Example 33 Specifying the SET INHERIT clause
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 VARCHAR(10) NOT NULL) ;
CREATE TABLE
postgres=> ALTER TABLE data1 ALTER CONSTRAINT data1_col2_not_null NO INHERIT ;
ALTER TABLE
ALTER TABLE NOT NULL NOT VALID

The NOT VALID clause can be specified for NOT NULL constraints in an ALTER TABLE statement. [a379061]

Example 34 Specifying the NOT VALID clause
postgres=> ALTER TABLE data1 ADD CONSTRAINT nn_data1_col2 NOT NULL
col2 NOT VALID ;
ALTER TABLE
postgres=> \d+ data1
                                                 Table "public.data1"
 Column |         Type          | Collation | Nullable | Default | Storage  …
--------+-----------------------+-----------+----------+---------+--------- …
 col1   | integer               |           | not null |         | plain    …
col2   | character varying(10) |           | not null |         | extende  …
Indexes:
    "data1_pkey" PRIMARY KEY, btree (col1)
Not-null constraints:
    "data1_col1_not_null" NOT NULL "col1"
    "nn_data1_col2" NOT NULL "col2" NOT VALID
Access method: heap

PDF page 36

ALTER TABLE ONLY DROP CONSTRAINT

ALTER TABLE DROP CONSTRAINT statements with ONLY clause for partitioned tables can now be executed. In previous versions, this would have resulted in an error. [4dea33c]

Example 35 Specifying the ONLY clause
postgres=> CREATE TABLE part1(c1 INT PRIMARY KEY, c2 INT CHECK(c2 > 0))
PARTITION BY RANGE(c1) ;
CREATE TABLE
postgres=> CREATE TABLE part1v1 PARTITION OF part1 FOR VALUES
FROM (0) TO (1000000) ;
CREATE TABLE
postgres=> ALTER TABLE ONLY part1 DROP CONSTRAINT part1_c2_check ;
ALTER TABLE

3.2.4 ANALYZE

The following new features have been implemented for the ANALYZE statement.

ONLY clause

In previous versions, executing an ANALYZE statement on a partition table would recursively collect statistics for the partition. By specifying the ONLY clause, statistics can be collected only for partition tables. [62ddf7e]

Example 36 Specifying the ONLY clause
postgres=> ANALYZE VERBOSE ONLY part1 ;
INFO:  analyzing "public.part1" inheritance tree
INFO:  "part1v1": scanned 5406 of 5406 pages, containing 500000 live rows and
0 dead rows; 30000 rows in sample, 500000 estimated total rows
INFO:  finished analyzing table "postgres.public.part1"
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 5445 hits, 0 reads, 0 dirtied
WAL usage: 5 records, 0 full page images, 1195 bytes, 0 buffers full
system usage: CPU: user: 0.04 s, system: 0.00 s, elapsed: 0.04 s
ANALYZE

PDF page 37

Output of I/O statistics and WAL statistics

The output of the ANALYZE VERBOSE statement now includes I/O and WAL statistics. The I/O statistics output is similar to the logs for the parameter log_autovacuum_min_duration setting. [4c1b4cd, bb77752]

Example 37 Output of the ANALYZE VERBOSE statement
postgres=> ANALYZE VERBOSE data1 ;
INFO:  analyzing "public.data1"
INFO:  "data1": scanned 5406 of 5406 pages, containing 333333 live rows and
666667 dead rows; 30000 rows in sample, 333333 estimated total rows
INFO:  finished analyzing table "postgres.public.data1"
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 5421 hits, 0 reads, 0 dirtied
WAL usage: 4 records, 0 full page images, 928 bytes, 0 buffers full
system usage: CPU: user: 0.02 s, system: 0.00 s, elapsed: 0.02 s
ANALYZE
Lines beginning with “INFO:” are I/O statistics and lines beginning with “WAL usage:” are WAL
statistics.
Output of delay time

If the parameter track_cost_delay_timing is set to ‘on’, the ANALYZE VERBOSE statement will now output “delay time”. The same information is also output in the log of automatic VACUUM. [7720082]

Example 38 Output of the ANALYZE VERBOSE statement
postgres=> ANALYZE VERBOSE data1 ;
INFO:  analyzing "public.data1"
INFO:  "data1": scanned 5406 of 5406 pages, containing 500000 live rows and 0
dead rows; 30000 rows in sample, 500000 estimated total rows
INFO:  finished analyzing table "postgres.public.data1"
delay time: 0.000 ms
I/O timings: read: 25.770 ms, write: 0.000 ms
…
WAL usage: 4 records, 1 full page images, 8240 bytes, 0 buffers full
system usage: CPU: user: 0.01 s, system: 0.03 s, elapsed: 0.05 s
ANALYZE

PDF page 38

Output of WAL buffer full

The WAL buffer full count is now output when the ANALYZE VERBOSE statement is executed. [6a8a7ce]

Example 39 Output of the ANALYZE VERBOSE statement
postgres=> ANALYZE VERBOSE data1 ;
INFO:  analyzing "public.data1"
INFO:  "data1": scanned 5406 of 5406 pages, containing 500000 live rows and 0
dead rows; 30000 rows in sample, 500000 estimated total rows
INFO:  finished analyzing table "postgres.public.data1"
avg read rate: 0.000 MB/s, avg write rate: 0.300 MB/s
buffer usage: 5425 hits, 0 reads, 1 dirtied
WAL usage: 3 records, 1 full page images, 3544 bytes, 0 buffers full
system usage: CPU: user: 0.02 s, system: 0.00 s, elapsed: 0.02 s

3.2.5 COPY

The following features have been added to the COPY statement

COPY TO statement from a materialized view

The COPY TO statement can now specify the materialized view. [534874f]

Example 40 COPY TO statement for materialized view
postgres=> CREATE MATERIALIZED VIEW mview1 AS SELECT col1, col2 FROM data1 ;
SELECT 1000000
postgres=> COPY mview1 TO '/tmp/data1.csv' CSV ;
COPY 1000000
Addition of the reject_limit option

The COPY FROM statement now has a reject_limit option. This option allows the maximum number of errors to be specified when errors are ignored (on_error=ignore). If the number of errors exceeds the specified value, the COPY statement will fail. [4ac2a9b, a39297e]

PDF page 39

Example 41 Specifying the REJECT_LIMIT option
postgres=> \! cat data1.csv
1,data1
2,data2
ABC,data3
4,data4
DEF,data5
postgres=> COPY data1 FROM '/home/postgres/data1.csv' WITH (FORMAT csv,
ON_ERROR ignore, REJECT_LIMIT 1, LOG_VERBOSITY verbose) ;
NOTICE:  skipping row due to data type incompatibility at line 3 for column
"col1": "ABC"
NOTICE:  skipping row due to data type incompatibility at line 5 for column
"col1": "DEF"
ERROR:  skipped more than REJECT_LIMIT (1) rows due to data type incompatibility
CONTEXT:  COPY data1, line 5, column col1: "DEF"
Added value for log_verbosity option

The log_verbosity option can now be ‘silent’ in the COPY statement. Specifying this value suppresses log output. [e7834a1

3.2.6 CREATE FOREIGN TABLE

The LIKE clause now allows copying column definitions from other tables (or foreign tables). [302cf157]

Example 42 Specifying the LIKE Clause for External Tables
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 VARCHAR(10)) ;
CREATE TABLE
postgres=> CREATE FOREIGN TABLE data2(LIKE data1 EXCLUDING ALL) SERVER remsvr1 ;
CREATE FOREIGN TABLE
The values that can be specified in the INCLUDING / EXCLUDING clause are partially different
from those in the CREATE TABLE statement.

PDF page 40

ClauseCREATE TABLE statementCREATE FOREIGN TABLE statement
COMMENTSYesYes
COMPRESSIONYesNo
CONSTRAINTSYesYes
DEFAULTSYesYes
GENERATEDYesYes
IDENTITYYesNo
INDEXESYesNo
STATISTICSYesYes
STORAGEYesNo
ALLYesYes
Table 17 Values that can be specified in the INCLUDING / EXCLUDING clause

3.2.7 CREATE INDEX

Creation of GIN indexes is now processed in parallel with B-Tree and BRIN indexes. [8492feb] 8492feb

3.2.8 CREATE TABLE

The following enhancements have been implemented for the CREATE TABLE statement

WITHOUT OVERLAPS clause

It is now possible to specify a WITHOUT OVERLAPS clause that does not allow duplicate ranges for primary key constraints (PRIMARY KEY), unique constraints (UNIQUE) and foreign key constraints (FOREIGN KEY). At least two columns are required when specifying this clause for a primary key constraint. It can also be specified when adding a primary key in an ALTER TABLE statement. The column CONPERIOD has been added to the pg_constraint catalog to represent the

WITHOUT OVERLAPS clause.  The ON {UPDATE, DELETE} clause is not yet supported.

[fc0438b, 89f908a]

Syntax 5 - PRIMARY KEY clause

PRIMARY KEY (column_name1, …, column_nameN WITHOUT OVERLAPS)

PDF page 41

Example 43 WITHOUT OVERLAPS clause in the primary key
postgres=> CREATE TABLE temporal_range1 (
    id int4range,
    valid_at daterange,
    CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
) ;
CREATE TABLE
postgres=> \d temporal_range1
            Table "public.temporal_range1"
  Column  |   Type    | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
 id       | int4range |           | not null |
 valid_at | daterange |           | not null |
Indexes:
    "temporal_rng_pk" PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
ENFORCED clause

Allows the ENFORCED or NOT ENFORCED clause to be specified for CHECK constraints and foreign key constraints. If nothing is specified or the ENFORCED clause is specified, PostgreSQL will behave as before, with CHECK constraints operating at the appropriate time. If the NOT ENFORCED clause is specified, the CHECK constraint will not work. The column CONFORCED has been added to the pg_constraint catalog to indicate this specification. [ca87c41, eec0040]

PDF page 42

Example 44 CHECK constraints that do not operate
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY,
col2 INT CHECK (col2 < 10) NOT ENFORCED) ;
CREATE TABLE
postgres=> INSERT INTO data1 VALUES (100, 100) ;
INSERT 0 1
postgres=> SELECT * FROM data1 ;
 col1  | col2
-------+-------
 100 |   100
(1 row)
postgres=> SELECT conenforced FROM pg_constraint
WHERE conname='data1_col2_check' ;
 conenforced
-------------
 f
(1 row)
NOT VALID clause

Allows the NOT VALID clause to be specified as a foreign key for partition tables. [b663b94]

Example 45 NOT VALID clause for foreign keys in partition tables
postgres=> CREATE TABLE fkdata1(col1 INT PRIMARY KEY, col2 VARCHAR(10)) ;
CREATE TABLE
postgres=> CREATE TABLE part1(col1 INT PRIMARY KEY, col2 VARCHAR(10))
PARTITION BY RANGE(col1) ;
CREATE TABLE
postgres=> ALTER TABLE part1 ADD FOREIGN KEY (col1) REFERENCES fkdata1
NOT VALID ;
ALTER TABLE

PDF page 43

GENERATED ALWAYS VIRTUAL clause

Allows the creation of auto-generated columns that do not hold the data. Specify a GENERATED ALWAYS VIRTUAL clause in the column definition. Previously, only GENERATED ALWAYS STORED clauses that retained actual data were supported. The VIRTUAL clause can be confirmed by the output of ‘v’ in the attgenerated column of the pg_attribute column. Indexes cannot be created on generated columns with VIRTUAL clause specified. [83ea6c5]

Example 46 GENERATED ALWAYS VIRTUAL clause
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 INT
GENERATED ALWAYS AS (col1 * 2) VIRTUAL, col3 VARCHAR(10)) ;
CREATE TABLE
postgres=> \d data1
                                 Table "public.data1"
 Column |         Type          | Collation | Nullable |           Default
--------+-----------------------+-----------+----------+-------------------…
 col1   | integer               |           | not null |
 col2   | integer               |           |          | generated always as
(col1 * 2)
 col3   | character varying(10) |           |          |
Indexes:
    "data1_pkey" PRIMARY KEY, btree (col1)
postgres=> INSERT INTO data1(col1, col3) VALUES (100, 'data1') ;
INSERT 0 1
postgres=> SELECT * FROM data1 ;
 col1  | col2  |  col3
-------+-------+-------
 100 |   200 | data1
(1 row)
If the generated value overflows the column definition, it is not checked in the INSERT or UPDATE
statement. The error occurs when the corresponding record is retrieved. CHECK constraints are
checked when executing INSERT and UPDATE statements.

PDF page 44

Example 47 Behavior in case of overflow
postgres=> CREATE TABLE overflow1(col1 VARCHAR(3), col2 VARCHAR(3)
GENERATED ALWAYS AS (col1 || 'X')) ;
CREATE TABLE
postgres=> INSERT INTO overflow1(col1) VALUES ('ABC') ;
INSERT 0 1
postgres=> SELECT * FROM overflow1 ;
ERROR:  value too long for type character varying(3)
User-defined types and user-defined functions cannot be used for the virtual columns. [0cd69b3]
Example 48 Using user-defined functions
postgres=> CREATE FUNCTION udf1(p1 INT) RETURNS INT AS
$$ SELECT p1*5 $$ IMMUTABLE LANGUAGE SQL ;
CREATE FUNCTION
postgres=> CREATE TABLE data1(c1 INT PRIMARY KEY,
c2 INT GENERATED ALWAYS AS (udf1(c1)) VIRTUAL) ;
ERROR:  generation expression uses user-defined function
LINE 1: ...1(c1 INT PRIMARY KEY, c2 INT GENERATED ALWAYS AS (udf1(c1)) ...
                                                             ^
DETAIL:  Virtual generated columns that make use of user-defined functions are
not yet supported.

3.2.9 EXPLAIN

The following features have been added to the EXPLAIN statement.

Disabled Nodes Output

The output of the EXPLAIN statement now includes information on the number of disabled nodes (Disabled Nodes:). For example, disabling some execution plans by setting the parameter enable_seqscan to ‘false’, which was previously handled by increasing the cost, is now clearly recognized as a disabled node in PostgreSQL 18. [e222534, c01743a, 161320b]

PDF page 45

Example 49 Disabling Nodes (PostgreSQL 18)
postgres=> SET enable_seqscan = off ;
SET
postgres=> EXPLAIN SELECT * FROM data1 ;
                           QUERY PLAN
----------------------------------------------------------------
 Seq Scan on data1  (cost=0.00..15406.00 rows=1000000 width=10)
   Disabled: true
(2 rows)
Example 50 Disabling Nodes (PostgreSQL 17)
postgres=> SET enable_seqscan = off ;
SET
postgres=> EXPLAIN SELECT * FROM data1 ;
                                   QUERY PLAN
------------------------------------------------------------------------------
 Seq Scan on data1  (cost=10000000000.00..10000015406.00 rows=1000000 width=10)
(1 row)
Example 51 src/backend/optimizer/path/costsize.c(PostgreSQL 17)
cost_seqscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info)
{
…
        if (!enable_seqscan)
                startup_cost += disable_cost;
…
Storage Clause Output

Temporary storage information used during Window Aggregation and CTE Scan runs is now output as a Storage clause. [95d6e9a, 40708ac]

PDF page 46

Example 52 Windows Aggregation temporary storage information
postgres=> EXPLAIN (ANALYZE, COSTS OFF, BUFFERS OFF)
SELECT COUNT(*) OVER() FROM data1 ;
                               QUERY PLAN
-----------------------------------------------------------------------------
 WindowAgg (actual time=152.260..208.545 rows=1000000.00 loops=1)
   Window: w1 AS ()
   Storage: Disk  Maximum Storage: 9766kB
   ->  Index Only Scan using data1_pkey on data1 (actual time=0.024..60.216 …
         Heap Fetches: 0
         Index Searches: 1
 Planning Time: 0.059 ms
 Execution Time: 224.929 ms
(8 rows)
Example 53 CTE Scan Temporary Storage Information
postgres=>  EXPLAIN (ANALYZE, COSTS OFF, BUFFERS OFF) WITH cte1 AS
MATERIALIZED (SELECT col1, col2 FROM data1) SELECT * FROM cte1 WHERE col1 <
 10000 ;
                                  QUERY PLAN
------------------------------------------------------------------------------
 CTE Scan on cte1 (actual time=0.008..112.987 rows=9999.00 loops=1)
   Filter: (col1 < 10000)
   Rows Removed by Filter: 990001
   Storage: Disk  Maximum Storage: 19528kB
   CTE cte1
     ->  Seq Scan on data1 (actual time=0.005..30.641 rows=1000000.00 loop …
           Disabled: true
…
Parallel Bitmap Heap Scan Statistics

Statistics for Parallel Bitmap Heap Scan execution are now output for each worker node. [5a1e6df]

PDF page 47

Example 54 Parallel Bitmap Heap Scan Detailed Information
postgres=> EXPLAIN (COSTS OFF, ANALYZE, VERBOSE) SELECT AVG(col1) FROM data1
WHERE col1 BETWEEN 1000000 AND 2000000 ;
                                  QUERY PLAN
------------------------------------------------------------------------------
 Finalize Aggregate (actual time=98.946..99.991 rows=1 loops=1)
   Output: avg(col1)
   Buffers: shared hit=8125 read=16
   ->  Gather (actual time=98.801..99.981 rows=3 loops=1)
…
               ->  Parallel Bitmap Heap Scan on public.data1 (actual time=46…
                     Output: col1, col2
                     Recheck Cond: ((data1.col1 >= 1000000) AND (data1.col1 …
                     Heap Blocks: exact=1598
                     Buffers: shared hit=8125 read=16
…
Record Count Output Format

Record counts (actual rows) are now output to two decimal places. [ddb17e3, 95dbd827]

Example 55 Loop processing line count output
postgres=> EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM data1 WHERE col1 < 1000 ;
                                QUERY PLAN
------------------------------------------------------------------------------
Index Scan using data1_pkey on public.data1  (cost=0.42..39.77 rows=1048
width=10) (actual time=0.008..0.084 rows=999.00 loops=1)
   Output: col1, col2
   Index Cond: (data1.col1 < 1000)
   Buffers: shared hit=11
 Planning:
   Buffers: shared hit=4
 Planning Time: 0.096 ms
 Execution Time: 0.115 ms
(8 rows)

PDF page 48

Index Searches

The “Index Searches” item indicating the number of index searches is now output. [0fbceae]

Example 56 Index Search Count
postgres=> EXPLAIN (ANALYZE, COSTS OFF) SELECT COUNT(*) FROM data1 WHERE
col1 IN (100, 1000, 10000) ;
                                          QUERY PLAN
------------------------------------------------------------------------------
 Aggregate (actual time=0.021..0.021 rows=1.00 loops=1)
   Buffers: shared hit=10
   ->  Index Only Scan using data1_pkey on data1 (actual time=0.015..0.019 …
         Index Cond: (col1 = ANY ('{100,1000,10000}'::integer[]))
         Heap Fetches: 0
         Index Searches: 3
…
Window Function Output

The “Window:” clause is now output in the execution plan when using Window functions. [8b1b342]

Example 57 Window function output
postgres=> EXPLAIN SELECT AVG(col1) OVER (PARTITION BY col2) FROM data1 ;
                                QUERY PLAN
---------------------------------------------------------------------------
 WindowAgg  (cost=130488.33..146238.34 rows=1000000 width=36)
   Window: w1 AS (PARTITION BY col2)
   ->  Sort  (cost=128738.34..131238.34 rows=1000000 width=8)
         Sort Key: col2
         ->  Seq Scan on data1  (cost=0.00..15406.00 rows=1000000 width=8)
(5 rows)

PDF page 49

3.2.10 INSERT / UPDATE / DELETE / MERGE

The RETURNING clause of the update DML statement can now specify an OLD clause for preupdate information and a NEW clause for post-update information. The pre-update data (OLD) in the INSERT statement and the post-update data (NEW) in the DELETE statement will be NULL. [80feb72]

Syntax 6 - RETURNING Clause

RETURNING OLD.column1, NEW.column2
RETURNING OLD.*, NEW.*
RETURNING WITH (OLD AS alias1, NEW AS alias2) alias1.column1, …
Example 58 Output of old and new data in RETURNING clause
postgres=> UPDATE data1 SET col2='after' WHERE col1=100
RETURNING WITH (OLD AS o, NEW AS n) o.col1, o.col2, n.col2 ;
 col1  |   col2   |  col2
-------+----------+---------
 100 | before   | after
(1 row)
postgres=> \pset null null
Null display is "null".
postgres=> DELETE FROM data1 WHERE col1=200 RETURNING OLD.*, NEW.* ;
 col1  |   col2   |  col1  |  col2
-------+----------+--------+--------
 200   | before   | null   | null
(1 row)

3.2.11 VACUUM

The following new features have been added to the VACUUM statement.

ONLY clause

ONLY clause can now be specified for partitioned tables. No VACUUM processing is performed. [62ddf7e]

PDF page 50

Example 59 Execute the VACUUM ONLY statement
postgres=> VACUUM VERBOSE ONLY part1 ;
WARNING:  VACUUM ONLY of partitioned table "part1" has no effect
VACUUM
VERBOSE / Log output

When ‘on’ is specified for the parameter track_cost_delay_timing, ‘delay time’ is now output in the ANALYZE VERBOSE statement. The same information is also output to the log of automatic VACUUM. [7720082]

Example 60 Execute the VACUUM VERBOSE statement
postgres=> VACUUM VERBOSE data1 ;
INFO:  vacuuming "postgres.public.data1"
INFO:  finished vacuuming "postgres.public.data1": index scans: 0
pages: 0 removed, 5406 remain, 1 scanned (0.02% of total), 0 eagerly scanned
tuples: 0 removed, 500000 remain, 0 are dead but not yet removable
removable cutoff: 786, which was 0 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
visibility map: 0 pages set all-visible, 0 pages set all-frozen (0 were all-
visible)
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item
identifiers removed
delay time: 0.000 ms
I/O timings: read: 0.000 ms, write: 0.000 ms
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 13 hits, 0 reads, 0 dirtied
WAL usage: 0 records, 0 full page images, 0 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
Output WAL buffer full

The WAL buffer full count is now output when the VACUUM VERBOSE statement is executed. [6a8a7ce]

PDF page 51

Example 61 Execute the VACUUM VERBOSE statement
postgres=> VACUUM VERBOSE data1 ;
INFO:  vacuuming "postgres.public.data1"
INFO:  finished vacuuming "postgres.public.data1": index scans: 0
pages: 0 removed, 5406 remain, 1 scanned (0.02% of total), 0 eagerly scanned
tuples: 0 removed, 500000 remain, 0 are dead but not yet removable
removable cutoff: 763, which was 0 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
visibility map: 0 pages set all-visible, 0 pages set all-frozen (0 were all-
visible)
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item
identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 13 hits, 0 reads, 0 dirtied
WAL usage: 0 records, 0 full page images, 0 bytes, 0 buffers full
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
Release of end-of-table free block

The parameter vacuum_truncate has been added. This parameter determines the behavior when the

table attribute vacuum_truncate or the TRUNCATE option of the VACUUM statement is not set.

[0164a0f]

Example 62 Setting the vacuum_truncate parameter
postgres=> SET vacuum_truncate = off ;
SET
postgres=> VACUUM data1 ;
VACUUM

3.2.12 Data Types

The following enhancements have been implemented for PostgreSQL 18 data types.

PDF page 52

Bytea and integer type casts

Bytea and integer type (int2, int4, int8) data can now be cast to each other. [6da469b]

Example 63 Bytea and integer type casts
postgres=> SELECT '\x1122334455667788'::bytea::int8 ;
        int8
---------------------
 1234605616436508552
(1 row)
postgres=> SELECT 1234567890::int8::bytea ;
       bytea
--------------------
 \x00000000499602d2
(1 row)

3.2.13 Functions

The following functions have been added/enhanced.

Array_reverse function

The function array_reverse has been added, which returns an array with the first dimension of the array inverted. [49d6c7d]

Syntax 7 - Array_reverse function

anyarray array_reverse(anyarray)

PDF page 53

Example 64 Execution of the array_reverse function
postgres=> SELECT array_reverse(ARRAY['AB', 'CD', 'EF']) ;
-[ RECORD 1 ]-+-----------
array_reverse | {EF,CD,AB}
postgres=> SELECT array_reverse(ARRAY[[1,2],[3,4],[5,6]]) ;
-[ RECORD 1 ]-+--------------------
array_reverse | {{5,6},{3,4},{1,2}}
Casefold function

The casefold function is almost identical to the lower function, but it performs lower case conversion according to collation. Its main purpose is to facilitate case-insensitive string comparisons. It can only be used when the database encoding is UTF-8. Some characters may change the character length. If the locale PG_UNICODE_FAST is used, ß (U+00DF) will be converted to ‘ss’. [bfc5992]

Example 65 If the database collation is PG_C_UTF8
postgres=> SELECT casefold('Σ') ;
 casefold
----------
 σ
(1 row)
Example 66 If the database collation is PG_UNICODE_FAST
postgres=> SELECT casefold('ß') ;
 casefold
----------
 ss
(1 row)
Crc32 function

The functions crc32 to calculate CRC-32 and crc32c to calculate CRC-32C have been added. [760162f]

PDF page 54

Syntax 8 - Crc32 / Crc32c functions

bigint crc32(bytea)
bigint crc32c(bytes)
Example 67 Execution of the crc32 and crc32c functions
postgres=> SELECT crc32('ABC'::bytea) ;
   crc32
------------
 2743272264
(1 row)
postgres=> SELECT crc32c('ABC'::bytea) ;
   crc32c
------------
 2285480319
(1 row)
Extract function

The WEEK clause can now be specified for INTERVAL types. [6be39d7]

Example 68 Execution of the extract function
postgres=> SELECT EXTRACT(WEEK FROM INTERVAL '13 days 24 hours') ;
 extract
---------
       1
Gamma / lgamma function

The functions gamma and lgamma have been added to calculate gamma and log gamma values. [a3b6dfd]

Syntax 9 - Gamma / Lgamma functions

double precision gamma(double precision)
double precision lgamma(double precision)

PDF page 55

Example 69 Execution of the gamma / lgamma function
postgres=> SELECT gamma(2.5), lgamma(3.5) ;
       gamma       |       lgamma
-------------------+--------------------
 1.329340388179137 | 1.2009736023470743
(1 row)
Has_largeobject_privilege function

The has_largeobject_privilege function has been added to check for the existence of privileges on large objects. [4eada20]

Syntax 10 - Has_largeobject_privilege function

boolean has_largeobject_privilege(name, oid, text)
boolean has_largeobject_privilege(oid, oid, text)
boolean has_largeobject_privilege(oid, text)
Example 70 Execution of the has_largeobject_privilege function
postgres=> SELECT lo_create(1001) ;
-[ RECORD 1 ]---
lo_create | 1001
postgres=> SELECT has_largeobject_privilege(1001, 'SELECT') ;
-[ RECORD 1 ]-------------+--
has_largeobject_privilege | t
Json[b]_strip_nulls function

The json[b]_strip_nulls function now has a parameter strip_in_arrays to remove NULL values from an array. The default value is FALSE. [4603903]

Syntax 11 - Json_strip_nulls / Jsonb_strip_nulls functions

json json_strip_nulls(target json, strip_in_arrays boolean DEFAULT false)
jsonb jsonb_strip_nulls(target jsonb, strip_in_arrays boolean DEFAULT false)

PDF page 56

Example 71 Execution of the json_strip_nulls function
postgres=> SELECT json_strip_nulls('[1,2,null,4]', true) ;
-[ RECORD 1 ]----+--------
json_strip_nulls | [1,2,4]
Max / min function

Max and min functions can now be used for values of type bytea. [2d24fd9]

Syntax 12 - Max / min functions

bytea max(bytea)
bytea min(bytea)
Example 72 Execution of the max / min function for bytea type
postgres=> CREATE TABLE bytea1(col1 bytea) ;
CREATE TABLE
postgres=> INSERT INTO bytea1 VALUES
(decode('abcdef', 'hex')), (decode('123456', 'hex')) ;
INSERT 0 2
postgres=> SELECT max(col1), min(col1) FROM bytea1 ;
   max    |   min
----------+----------
 \xabcdef | \x123456
(1 row)
Reverse function

The version corresponding to bytea type has been added. [0697b23]

Syntax 13 - Reverse function

bytea reverse(bytea)

PDF page 57

Example 73 Execution of the reverse function
postgres=> SELECT reverse('\xabcdef12'::bytea) ;
  reverse
------------
 \x12efcdab
(1 row)
Pg_get_acl function

The function pg_get_acl has been added to retrieve the ACL settings of a database object. [4564f1c, d898665]

Syntax 14 - Pg_get_acl function

aclitem[] pg_get_acl(classid oid, objid oid, objsubid integer)
Example 74 Execution of the pg_get_acl function
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 VARCHAR(10)) ;
CREATE TABLE
postgres=> GRANT SELECT, MAINTAIN ON data1 TO user1 ;
GRANT
postgres=> SELECT UNNEST(
pg_get_acl('pg_class'::regclass, 'data1'::regclass::oid, 0)) ;
       unnest
--------------------
 demo=arwdDxtm/demo
 user1=rm/demo
(2 rows)
Pg_get_sequence_data function

The function pg_get_sequence_data, which returns a tuple of sequences, has been added. This function returns a NULL value if a sequence with no SELECT privilege is specified. This function is intended primarily for use in optimizing pg_dump commands. [c8b06bb, bd15b7d, a83a944]

PDF page 58

Syntax 15 - Pg_get_sequence_data function

record pg_get_sequence_data(sequence_oid regclass)
Example 75 Execution of the pg_get_sequence_data function
postgres=> CREATE SEQUENCE seq1 START WITH 100 ;
CREATE SEQUENCE
postgres=> SELECT NEXTVAL('seq1') ;
 nextval
---------
     100
(1 row)
postgres=> SELECT * FROM pg_get_sequence_data('seq1') ;
last_value | is_called
------------+-----------
        100 | t
(1 row)
Pg_ls_summariesdir function

The function pg_ls_summariesdir has been added to PostgreSQL 18. This function provides a list of WAL summary files. The function can be executed by a user with the SUPERUSER attribute or a user authorized by the pg_maintain role. If the WAL summarize feature is disabled, no records are returned. [4e1fad3]

Syntax 16 - Pg_ls_summariesdir function

SETOF record pg_ls_summariesdir()
Column nameData typeDescription
nametextWAL Summary File Name
sizebigintBytes in file
modificationtimestamp with time zoneLast Modified
Table 18 Column name and description of the return value of the pg_ls_summariesdir function

PDF page 59

Example 76 Execution of the pg_ls_summariesdir function
postgres=# SELECT * FROM pg_ls_summariesdir() ;
                    name                   | size  |      modification
---------…--------------------------------+-------+------------------------
 00000001…100002800000000010B2FC8.summary |  4808 | 2025-09-26 15:42:38+09
 00000001…10B2FC800000000015416F8.summary |  4994 | 2025-09-26 15:42:38+09
 00000001…15416F800000000015417F8.summary |    56 | 2025-09-26 15:42:38+09
 00000001…15417F800000000015481A0.summary |   230 | 2025-09-26 15:42:38+09
 00000001…15481A000000000015482A0.summary |    56 | 2025-09-26 15:42:38+09
(5 rows)
Pg_stat_get_backend_wal function

The function pg_stat_get_backend_wal has been added to retrieve WAL output information for a specified backend process. The result of executing this function is similar to the pg_stat_wal view, but on a per-backend-process basis. [76def4c]

Syntax 17 - Pg_stats_get_backend_wal function

record pg_stat_get_backend_wal(backend_pid integer)
Column nameData typeDescription
wal_recordsbigintNumber of WAL records
wal_fpibigintNumber of WAL full page images
wal_bytesnumericNumber of WAL bytes
wal_buffers_fullbigintNumber of storage outputs due to WAL buffer full
stats_resettimestamp with time zoneData reset time
Table 19 Column name and description of the return value of the pg_stat_get_backend_wal function
Example 77 Execution of the pg_stat_get_backend_wal function
postgres=> SELECT * FROM pg_stat_get_backend_wal(pg_backend_pid()) ;
 wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-----------+------------------+-------------
     4005580 |      28 | 262315709 |            28954 |
(1 row)

PDF page 60

Regular Expression Function

Argument names have been added to the regular expression function (regexp*). [580f872]

Example 78 Argument name of the regexp_count function
postgres=> \df regexp_count
                                             List of functions
   Schema   |     Name     | Result data type |                 Argument data
------------+--------------+------------------+-------------------------------
 pg_catalog | regexp_count | integer          | string text, pattern text   …
 pg_catalog | regexp_count | integer          | string text, pattern text,  …
 pg_catalog | regexp_count | integer          | string text, pattern text,  …
(3 rows)
To_number function and to_char function

The to_number function and to_char function now accept an RN indicating a Roman numeral as the format specification character. The case of the input string and the format specification character is ignored. [172e6b3]

Example 79 Specify Roman numerals in the to_number function
postgres=> SELECT to_number('MMXXV', 'rn'), to_char(1560, 'RN') ;
 to_number |     to_char
-----------+-----------------
      2025 |            MDLX
(1 row)
Uuidv7/uuidv4 functions

Uuidv7 function supporting UUID v7 and uuidv4 function supporting UUID v4 are added. uuidv7 is expected to suppress randomization of index storage pages by specifying the timestamp of function execution in the first part of uuidv7. The uuidv7 function is expected to suppress randomization of index storage pages by specifying a timestamp at the beginning of the function. [78c5e14]

Syntax 18 - UUIDv4 / UUIDv7 function

uuid uuidv7([shift interval])
uuid uuidv4()

PDF page 61

Example 80 Execution of the uuidv7 function and the uuidv4 function
postgres=> SELECT uuidv7() FROM generate_series(1, 5) ;
                uuidv7
--------------------------------------
 0193b90c-0818-772e-8fe2-ea74b2446cb2
 0193b90c-0818-775d-b20b-4551f631cd81
 0193b90c-0818-7761-a496-972341cb066c
 0193b90c-0818-7763-b66a-511858d491d7
 0193b90c-0818-7766-b1b9-7a5c2d05ee74
(5 rows)
postgres=> SELECT uuidv4() ;
                uuidv4
--------------------------------------
 9aaf5cc9-893e-46da-9690-dda84405ffe8
(1 row)

3.2.14 PL/pgSQL

The operator “=>” can now be used to specify parameters for named cursors. Previously, the operator “:=” was used. [246dedc]

Example 81 Named cursor parameter specification
CREATE FUNCTION func1(INTEGER) RETURNS TEXT AS $$
DECLARE
  cur1 CURSOR (v INTEGER) FOR SELECT col2 FROM data1 WHERE col1=v;
  val1 TEXT;
BEGIN
  OPEN cur1 (v => $1);
  FETCH cur1 INTO val1;
  RETURN val1;
END;
$$ LANGUAGE plpgsql;

PDF page 62

3.2.15 Optimizer

Faster execution plans are now selected.

ANY conversion of OR conditions

OR conditions for indexed columns are now automatically converted to ANY clauses. [d4378c0, ae45691]

Example 82 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1=1000 OR col1=2000 ;
                                  QUERY PLAN
------------------------------------------------------------------------------
 Bitmap Heap Scan on data1  (cost=8.87..16.78 rows=2 width=10)
   Recheck Cond: ((col1 = 1000) OR (col1 = 2000))
   ->  BitmapOr  (cost=8.87..8.87 rows=2 width=0)
         ->  Bitmap Index Scan on data1_pkey  (cost=0.00..4.43 rows=1 width=0)
               Index Cond: (col1 = 1000)
         ->  Bitmap Index Scan on data1_pkey  (cost=0.00..4.43 rows=1 width=0)
               Index Cond: (col1 = 2000)
(7 rows)
Example 83 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1=1000 OR col1=2000 ;
                                QUERY PLAN
--------------------------------------------------------------------------
 Index Scan using data1_pkey on data1  (cost=0.42..12.88 rows=2 width=10)
   Index Cond: (col1 = ANY ('{1000,2000}'::integer[]))
(2 rows)
ANY Conversion of Join OR Conditions

When an OR is used in a join condition, it can now be converted to an ANY clause. [627d634]

PDF page 63

Example 84 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM data1 AS d1, data2 AS d2
 WHERE d1.col1 = d2.col1 OR d1.col1 = d2.col2 ;
                               QUERY PLAN
------------------------------------------------------------------------
 Finalize Aggregate
   ->  Gather
         Workers Planned: 2
         ->  Partial Aggregate
               ->  Nested Loop
                     ->  Parallel Seq Scan on data1 d1
                     ->  Bitmap Heap Scan on data2 d2
                           Recheck Cond: ((d1.col1 = col1) OR (d1.col1 = col2))
                           ->  BitmapOr
                                 ->  Bitmap Index Scan on data2_pkey
                                       Index Cond: (col1 = d1.col1)
                                 ->  Bitmap Index Scan on idx1_data2
                                       Index Cond: (co;2 = d1.col1)
(13 rows)
Example 85 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM data1 AS d1, data2 AS d2
 WHERE d1.col1 = d2.col1 OR d1.col2 = d2.col2 ;
                               QUERY PLAN
------------------------------------------------------------------------
 Finalize Aggregate
   ->  Gather
         Workers Planned: 2
         ->  Partial Aggregate
               ->  Nested Loop
                     ->  Parallel Seq Scan on data2 d2
                     ->  Index Only Scan using data1_pkey on data1 d1
                           Index Cond: (col1 = ANY (ARRAY[d2.col1, d2.col2]))
(8 rows)

PDF page 64

IN (VALUES) Clause Converted to ANY Clause

The WHERE clause with IN (VALUES) is converted to an ANY clause. [c0962a1]

Example 86 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT * FROM data1
WHERE col1 IN (VALUES(100), (200)) ;
                  QUERY PLAN
-----------------------------------------------
 Nested Loop
   ->  Unique
         ->  Sort
               Sort Key: "*VALUES*".column1
               ->  Values Scan on "*VALUES*"
   ->  Index Scan using data1_pkey on data1
         Index Cond: (col1 = "*VALUES*".column1)
(7 rows)
Example 87 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT * FROM data1
WHERE col1 IN (VALUES(100), (200)) ;
                    QUERY PLAN
---------------------------------------------------
 Index Scan using data1_pkey on data1
   Index Cond: (col1 = ANY ('{100,200}'::integer[]))
(2 rows)
Right Semi Join

Right Semi Join is now available as the execution plan. It may be adopted for hash joins or merge joins. [aa86129e]

PDF page 65

Example 88 Adoption of Right Semi Join
postgres=> EXPLAIN (COSTS OFF, VERBOSE)
  SELECT COUNT(*) FROM tenk1 a WHERE (unique1, two) IN
    (SELECT unique1, ROW_NUMBER() OVER() FROM tenk1 b) ;
                                     QUERY PLAN
------------------------------------------------------------------------------
 Aggregate
   Output: count(*)
   ->  Hash Right Semi Join
         Hash Cond: ((b.unique1 = a.unique1) AND ((row_number() OVER (?))…
         ->  WindowAgg
               Output: b.unique1, row_number() OVER (?)
               ->  Seq Scan on public.tenk1 b
                     Output: b.unique1
         ->  Hash
               Output: a.unique1, a.two
               ->  Seq Scan on public.tenk1 a
                     Output: a.unique1, a.two
(12 rows)
UNIQUE Constraints and GROUP BY

When a column specified by a NOT NULL constraint and a UNIQUE index is included in a GROUP BY clause, non-target columns are now excluded from GROUP BY. The following example shows a col1 column with UNIQUE and NOT NULL constraints specified. [bd10ec5]

Example 89 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT col1, col2 FROM data1
GROUP BY col1, col2 ;
       QUERY PLAN
-------------------------
 HashAggregate
   Group Key: col1, col2
   ->  Seq Scan on data1
(3 rows)

PDF page 66

Example 90 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT col1, col2 FROM data1
GROUP BY col1, col2 ;
       QUERY PLAN
-------------------------
 HashAggregate
   Group Key: col1
   ->  Seq Scan on data1
(3 rows)
Optimization of the DISTINCT clause

Since the order of columns specified in the DISTINCT clause is not meaningful, optimization has been implemented to reorder them as needed. A new parameter, enable_distinct_reordering, has been added to change this behavior. The default value of this parameter is ‘on’, which causes the optimization to occur; to revert to PostgreSQL 17 behavior, set this parameter to ‘off’. In the example below, column col1 is modified in the execution plan because it is a primary key, and the processing of column col2 is disabled. [a8ccf4e]

Example 91 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT DISTINCT col2, col1 FROM data1 ;
       QUERY PLAN
-------------------------
 HashAggregate
   Group Key: col2, col1
   ->  Seq Scan on data1
(3 rows)

PDF page 67

Example 92 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT DISTINCT col2, col1 FROM data1 ;
                    QUERY PLAN
--------------------------------------------------
 Unique
   ->  Incremental Sort
         Sort Key: col1, col2
         Presorted Key: col1
         ->  Index Scan using data1_pkey on data1
(5 rows)
Self-join optimization

The optimization of replacing self-join operations with unjoin scans when the execution result is unaffected has been implemented. This behavior is controlled by the parameter enable_self_join_elimination. The default value is ‘on’ and the optimization is executed. In the following example, column col1 of table data1 is the primary key. [fc069a3]

Example 93 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT * FROM data1 d1, data1 d2 WHERE
d1.col1 = d2.col1 ;
            QUERY PLAN
----------------------------------
 Hash Join
   Hash Cond: (d1.col1 = d2.col1)
   ->  Seq Scan on data1 d1
   ->  Hash
         ->  Seq Scan on data1 d2
(5 rows)

PDF page 68

Example 94 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT * FROM data1 d1, data1 d2 WHERE
d1.col1 = d2.col1 ;
      QUERY PLAN
----------------------
 Seq Scan on data1 d2
(1 row)
Index Skip Scan

For an index consisting of multiple columns, if a match condition other than the first column is specified, the execution plan that uses the corresponding index is now created. In the following example, a composite index is created for columns col3 and col2, in that order.

Example 95 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN SELECT * FROM data2 WHERE col2=100 ;
                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on data2  (cost=0.00..17906.00 rows=1 width=14)
   Filter: (col2 = 100)
(2 rows)
Example 96 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN SELECT * FROM data2 WHERE col2=100 ;
                                QUERY PLAN
--------------------------------------------------------------------------
 Index Scan using idx2_data2 on data2  (cost=0.42..12.88 rows=1 width=14)
   Index Cond: (col2 = 100)
(2 rows)
Transformation of HAVING clauses into WHERE clauses

The optimization of transforming some HAVING clauses used in GROUPING SETS clauses into WHERE clauses has been implemented. [67a54b9]

PDF page 69

Example 97 Execution Plan of PostgreSQL 17
postgres=> EXPLAIN (COSTS OFF) SELECT col1, col2, COUNT(col1) FROM data1
GROUP BY GROUPING SETS ((col1, col2), (col1)) HAVING col1 > 1000 AND col2 >
1000 ;
                 QUERY PLAN
---------------------------------------------
 GroupAggregate
   Group Key: col1, col2
   Group Key: col1
   Filter: ((col1 > 1000) AND (col2 > 1000))
   ->  Sort
         Sort Key: col1, col2
         ->  Seq Scan on data1
(7 rows)
Example 98 Execution Plan of PostgreSQL 18
postgres=> EXPLAIN (COSTS OFF) SELECT col1, col2, COUNT(col1) FROM data1
GROUP BY GROUPING SETS ((col1, col2), (col1)) HAVING col1 > 1000 AND col2 >
1000 ;
             QUERY PLAN
-------------------------------------
 GroupAggregate
   Group Key: col1, col2
   Group Key: col1
   Filter: (col2 > 1000)
   ->  Sort
         Sort Key: col1, col2
         ->  Seq Scan on data1
               Filter: (col1 > 1000)
(8 rows)