It is now possible to specify the SET ALL TABLES clause and the SET ALL SEQUENCES clause in an ALTER PUBLICATION statement. This is only possible for publications that specify the ALL TABLES/SEQUENCES clause or for empty publications. [5984ea8, fd36606, 493f8c6, 5509055]
Example 40Executing the ALTER PUBLICATION statement
postgres=# CREATE PUBLICATION pub1 FOR ALL TABLES ;
CREATE PUBLICATION
postgres=# ALTER PUBLICATION pub1 SET ALL TABLES EXCEPT (TABLE data2) ;
ALTER PUBLICATION
postgres=# CREATE PUBLICATION pub2 ;
CREATE PUBLICATION
postgres=# ALTER PUBLICATION pub2 SET ALL SEQUENCES ;
ALTER PUBLICATION
3.2.4 ALTER SUBSCRIPTION statement
The ALTER SUBSCRIPTION REFRESH SEQUENCES statement, which updates the sequence information included in the subscription, has been added. [f0b3573]
Syntax 6 - ALTER SUBSCRIPTION REFRESH SEQUENCES statement
Example 43Setting the ENFORCED clause for CHECK constraints
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 INT) ;
CREATE TABLE
postgres=> ALTER TABLE data1 ADD CONSTRAINT data1_col2_check1
CHECK(col2 < 1000) ;
ALTER TABLE
postgres=> INSERT INTO data1 SELECT g1, g1 FROM generate_series(1, 999) g1 ;
INSERT 0 999
postgres=> ALTER TABLE data1 ALTER CONSTRAINT data1_col2_check1 NOT ENFORCED ;
ALTER TABLE
postgres=> INSERT INTO data1 VALUES (1000, 1000) ;
INSERT 0 1
postgres=> ALTER TABLE data1 ALTER CONSTRAINT data1_col2_check1 ENFORCED ;
ERROR: check constraint "data1_col2_check1" of relation "data1" is violated by
some row
postgres=> DELETE FROM data1 WHERE col2=1000 ;
DELETE 1
postgres=> ALTER TABLE data1 ALTER CONSTRAINT data1_col2_check1 ENFORCED ;
ALTER TABLE
PDF page 48
Option autovacuum_parallel_workers
The autovacuum_parallel_workers option has been added for tables. This option can also be specified in the CREATE TABLE statement. It specifies the number of parallel VACUUM worker processes. The default value for this option is -1, and it inherits the value from the autovacuum_max_parallel_workers parameter. If this option is set to 0, parallel VACUUM is disabled. [1ff3180]
Example 44Configuring the number of parallel workers for automatic VACUUM
postgres=> ALTER TABLE data1 SET (autovacuum_parallel_workers = 4) ;
ALTER TABLE
Optimizing ALTER TABLE Statements
When using the ALTER TABLE ADD COLUMN statement, the addition of a column with a CHECK constraint or a NOT NULL constraint on a domain can now be checked for the need to rewrite the table. If no rewrite is necessary, the table will not be rewritten. [a0b6ef2]
3.2.6 CHECKPOINT statement
The following options have been added to the CHECKPOINT statement.
MODE option
The MODE option determines how checkpoints are executed. The MODE FAST option is the default behavior and executes checkpoints as quickly as possible. If the MODE SPREAD option is specified, checkpoints are executed with reduced load based on relevant parameters. [2f698d7]
Depending on the options specified, the output log will change as follows.
Option
Log message
No specification
LOG: checkpoint starting: fast force wait
FLUSH_UNLOGGED
LOG: checkpoint starting: fast force wait flush-unlogged
Table 22Output logs
3.2.7 COPY statement
The following new features have been implemented to the COPY statement.
ON_ERROR option setting
It is now possible to specify the “set_null” for the on_error option in the COPY FROM statement. This option sets the column value to NULL if a data type conversion error occurs. [2a525cc]
PDF page 50
Example 47ON_ERROR option
postgres=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
col1 | integer | | |
col2 | integer | | |
postgres=# COPY data1 FROM stdin WITH (format 'csv', on_error 'set_null') ;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself, or an EOF signal.
>> 100,100
>> 200,bad_data
>> 300,300
>> \.
NOTICE: in 1 row, columns were set to null due to data type incompatibility
COPY 3
postgres=# SELECT * FROM data1 ;
col1 | col2
------+------
100 | 100
200 | null
300 | 300
(3 rows)
Partition Table Support
The COPY TO statement can now be executed on partition tables. In previous versions, it was necessary to execute the COPY (SELECT …) TO statement instead of using the partition table name. [4bea91f]
PDF page 51
Example 48Specifying the partition table
postgres=# \d part1
Partitioned table "public.part1"
Column | Type | Collation | Nullable | Default
--------+-----------------------+-----------+----------+---------
col | integer | | not null |
col2 | character varying(10) | | |
Partition key: RANGE (c1)
Indexes:
"part1_pkey" PRIMARY KEY, btree (col1)
Number of partitions: 2 (Use \d+ to list them.)
postgres=# COPY part1 TO '/tmp/part1.csv' CSV ;
COPY 2000000
Number of header rows
It is now possible to specify the number of header rows in the data. [bc2f348]
Example 49Specifying the number of header rows
postgres=# COPY data1 FROM stdin WITH (FORMAT CSV, HEADER 2) ;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself, or an EOF signal.
>> HEADER1
>> HEADER2
>> 100,data1
>> 200,data2
>> \.
COPY 2
JSON format output
It is now possible to specify “json” as the FORMAT option for the COPY TO statement. The additional option “force_array” can also be specified. [7dadd38, 4c0390a]
PDF page 52
Example 50Output to JSON format
postgres=# COPY data1 TO stdout WITH (FORMAT json) ;
{"col1":1,"col2":"data1"}
{"col1":2,"col2":"data2"}
{"col1":3,"col2":"data3"}
{"col1":4,"col2":"data4"}
{"col1":5,"col2":"data5"}
postgres=# COPY data1 TO stdout WITH (FORMAT json, FORCE_ARRAY) ;
[
{"col1":1,"col2":"data1"}
,{"col1":2,"col2":"data2"}
,{"col1":3,"col2":"data3"}
,{"col1":4,"col2":"data4"}
,{"col1":5,"col2":"data5"}
]
3.2.8 CREATE PUBLICATION statement
It is now possible to specify the FOR ALL SEQUENCES clause, which applies to all sequences. This clause requires a user with the SUPERUSER privilege to execute the CREATE PUBLICATION statement. To indicate whether this clause has been specified, the puballsequences column has been added to the pg_publication catalog. [96b3784]
Syntax 11 - FOR ALL SEQUENCES clause
CREATEPUBLICATIONpublication_nameFORALLSEQUENCES
PDF page 53
Example 51Specifying the FOR ALL SEQUENCES clause
postgres=# CREATE PUBLICATION pub1 FOR ALL SEQUENCES ;
CREATE PUBLICATION
postgres=# \dRp
List of publications
Name | Owner | All tables | All sequences | Inserts | Updates | Deletes…
------+----------+------------+---------------+---------+---------+--------…
pub1 | postgres | f | t | t | t | t …
(1 row)
No tables can be added to a publication that specifies the ALL SEQUENCES clause.
Example 52Add a table to the publication
postgres=# ALTER PUBLICATION pub1 ADD TABLE data1 ;
ERROR: publication "pub1" is defined as FOR ALL SEQUENCES
DETAIL: Tables or sequences cannot be added to or dropped from FOR ALL SEQUENCES
publications.
3.2.9 CREATE SCHEMA statement
It is now possible to specify objects other than tables, views, indexes, sequences, triggers, and GRANT in the CREATE SCHEMA statement. [a9c350d, 404db8f]
Example 53The CREATE SCHEMA Statement and Creating Objects
postgres=# CREATE SCHEMA schema1
CREATE TABLE data1(col1 INT PRIMARY KEY, col2 VARCHAR(10))
CREATE COLLATION col1 (LOCALE="C")
CREATE DOMAIN d1 AS INT ;
CREATE SCHEMA
ThefollowingCREATEclausescanbespecified:
PDF page 54
Object
PostgreSQL 18
PostgreSQL 19
Note
AGGREGATE
No
Yes
COLLATION
No
Yes
DOMAIN
No
Yes
FUNCTION
No
Yes
INDEX
Yes
Yes
OPERATOR
No
Yes
PROCEDURE
No
Yes
SEQUENCE
Yes
Yes
TABLE
Yes
Yes
TEXT SEARCH CONFIGURATION
No
Yes
TEXT SEARCH DICTIONARY
No
Yes
TEXT SEARCH PARSER
No
Yes
TEXT SEARCH TEMPLATE TRIGGER
No
Yes
Yes
Yes
TYPE
No
Yes
VIEW
Yes
Yes
GRANT
Yes
Yes
Table 23Types of objects that can be created at the same time
3.2.10 CREATE STATISTICS statement
It is now possible to create extended statistics by specifying a virtual column. [f7f4052]
Example 54Creating Extended Statistics for Virtual Generated Columns
postgres=> CREATE TABLE data1(col1 INT, col2 INT GENERATED ALWAYS AS
(col1 + 100) VIRTUAL) ;
CREATE TABLE
postgres=> CREATE STATISTICS data1_stat1 ON (col2) FROM data1 ;
CREATE STATISTICS
PDF page 55
3.2.11 DELETE / UPDATE statement
The FOR PORTION OF clause can now be specified in UPDATE and DELETE statements. When a range is specified in the FOR PORTION OF clause, a new tuple matching that range is created, and the updates specified in the SET clause are applied to it. The original tuple is split into tuples that fall outside the range specified in the FOR PORTION OF clause. [8e72d91]
Since the behavior can be confusing, a sample will be used to explain it. Initially, the range1 table contains two tuples: one in the col1 column (of type DATERANGE) representing the range “April 1, 2026 - May 1, 2026” and another representing the range “May 1, 2026 - June 1, 2026”.
Example 55Tuples before the update
postgres=> SELECT * FROM range1 ORDER BY col1 ;
col1 | col2
-------------------------+------
[2026-04-01,2026-05-01) | 100
[2026-05-01,2026-06-01) | 200
(2 rows)
If the FOR PORTION OF clause is set to "2026/04/10-2026/04/20", the tuples to be updated are
selected (tuples where col2=100). These tuples are split into the following three tuples.
• 2026/04/01-2026/04/10 (The first half of the original tuple range)
• 2026/04/10-2026/04/20 (Tuple to be updated; updated using a SET clause)
• 2026/04/20-2026/05/01 (The second half of the original tuple range)
PDF page 56
Example 56Tuples after update
postgres=> UPDATE range1 FOR PORTION OF col1 FROM '2026-04-10' TO '2026-04-20'
SET col2=300 ;
UPDATE 1
postgres=> SELECT * FROM range1 ORDER BY col1 ;
col1 | col2
-------------------------+------
[2026-04-01,2026-04-10) | 100
[2026-04-10,2026-04-20) | 300
[2026-04-20,2026-05-01) | 100
[2026-05-01,2026-06-01) | 200
(4 rows)
3.2.12 EXPLAIN statement
The following features have been added to the EXPLAIN statement.
Output of I/O Statistics
The IO option has been added to the EXPLAIN statement to output I/O statistics. The IO option must be specified together with the ANALYZE option. [681daed, 3b1117d, e157fe6]
postgres=> EXPLAIN (ANALYZE, WAL) UPDATE data1 SET col2='update'
WHERE col1 < 100 ;
QUERY PLAN
------------------------------------------------------------------------------
Update on data1 (cost=0.00..1.50 rows=0 width=0) (actual time=0.225..0.225…
Buffers: shared hit=121 read=1 dirtied=3
WAL: records=80 fpi=2 bytes=9790 fpi bytes=3656
-> Seq Scan on data1 (cost=0.00..1.50 rows=40 width=38) (actual time=0.…
Filter: (col1 < '100'::numeric)
Buffers: shared hit=1
Planning Time: 0.281 ms
Execution Time: 0.363 ms
(8 rows)
Subquery Name
The subquery blocks in the executed execution plan now display unique names. Previously, only numbers were displayed. [8c49a48]
PDF page 59
Example 60PostgreSQL 19 Output
postgres=> EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM data1 WHERE col1 =
(SELECT MAX(col1) FROM data1) ;
QUERY PLAN
------------------------------------------------------------------------------
Aggregate
InitPlan expr_1
-> Result
Replaces: MinMaxAggregate
InitPlan minmax_1
-> Limit
-> Index Only Scan Backward using data1_pkey on data1
data1_1
-> Index Only Scan using data1_pkey on data1
Index Cond: (col1 = (InitPlan expr_1).col1)
(9 rows)
Example 61PostgreSQL 18 Output
postgres=> EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM data1 WHERE col1 =
(SELECT MAX(col1) FROM data1) ;
QUERY PLAN
------------------------------------------------------------------------------
Aggregate
InitPlan 2
-> Result
InitPlan 1
-> Limit
-> Index Only Scan Backward using data1_pkey on data1
data1_1
-> Index Only Scan using data1_pkey on data1
Index Cond: (col1 = (InitPlan 2).col1)
(8 rows)
PDF page 60
3.2.13 GRANT statement
It is now possible to specify a grantee other than the current user in the GRANTED BY clause of GRANT and REVOKE statements. In previous versions, this resulted in the error “ERROR: grantor must be current user.”. [dd1398f]
Example 62Specifying the GRANTED BY clause
postgres=> CREATE TABLE data1(col1 INT PRIMARY KEY, col2 VARCHAR(10)) ;
CREATE TABLE
postgres=> GRANT SELECT ON data1 TO demo2 WITH GRANT OPTION ;
GRANT
postgres=> \connect postgres postgres
You are now connected to database "postgres" as user "postgres".
postgres=# GRANT SELECT ON data1 TO demo3 GRANTED BY demo2 ;
GRANT
postgres=# \dp+
Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+-------+-------+--------------------+-------------------+----------
public | data1 | table | demo=arwdDxtm/demo+| |
| | | demo2=r*/demo +| |
| | | demo3=r/demo2 | |
(1 row)
3.2.14 INSERT statement
It is now possible to specify the DO SELECT clause in an INSERT ON CONFLICT statement. [8832709]
postgres=> INSERT INTO data1 VALUES (100, 'coflict100') ON CONFLICT (col1)
DO SELECT FOR UPDATE RETURNING * ;
col1 | col2
------+------------
100 | coflict100
(1 row)
INSERT 0 1
3.2.15 REPACK statement
The REPACK statement has been added to PostgreSQL 19. The REPACK statement is the SQL statement that executes both the VACUUM FULL and CLUSTER statements in the single command. When the REPACK statement is executed on its own, it targets all tables and materialized views for which the current user has MAINTAIN privileges. It is also possible to specify specific tables, but UNLOGGED tables and partitioned tables cannot be specified. The USING INDEX clause sorts the data based on the information in the specified index. The progress of the REPACK statement can be monitored via the pg_stat_progress_repack view. [ac58465, 28d534e, e76d8c7, 5dbb63f]
Syntax 14 - REPACK statement
REPACK[(options)][object][USINGINDEX[index_name]]
The following options can be specified in a REPACK statement.
Option
Description
ANALYZE
Execute an ANALYZE statement after the REPACK operation is complete
CONCURRENTLY
Minimize locks and allow access to other SQL statements
VERBOSE
Output a detailed execution log
Table 24Options for the REPACK statement
PDF page 62
Example 64Executing the REPACK statement
postgres=> REPACK (ANALYZE, VERBOSE) data1 ;
INFO: repacking "public.data1" in physical order
INFO: "public.data1": found 1666667 removable, 3333333 nonremovable row
versions in 27028 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.58 s, system: 0.20 s, elapsed: 0.94 s.
INFO: analyzing "public.data1"
INFO: "data1": scanned 18019 of 18019 pages, containing 3333333 live rows and
0 dead rows; 30000 rows in sample, 3333333 estimated total rows
INFO: finished analyzing table "postgres.public.data1"
avg read rate: 1582.075 MB/s, avg write rate: 0.088 MB/s
buffer usage: 23 hits, 18023 reads, 1 dirtied
WAL usage: 3 records, 1 full page images, 2165 bytes, 1560 full page image
bytes, 0 buffers full
system usage: CPU: user: 0.06 s, system: 0.01 s, elapsed: 0.08 s
REPACK
It is now possible to specify a NULL value in SET statements and ALTER SYSTEM SET statements. [ff4597a]
Example 67Specifying NULL values in the SET statement
postgres=> SHOW search_path ;
search_path
-----------------
"$user", public
(1 row)
postgres=> SET search_path TO NULL ;
SET
postgres=> SHOW search_path ;
search_path
-------------
(1 row)
3.2.18 VACUUM statement
The following information has been added to the output of the VACUUM VERBOSE statement.
Memory Information
Memory information beginning with “memory usage:” is output. [736f754e]
PDF page 65
Example 68Memory Information
postgres=> VACUUM (VERBOSE) data1 ;
INFO: vacuuming "postgres.public.data1"
…
WAL usage: 185293 records, 92311 full page images, 613464473 bytes, 576198816
full page image bytes, 1416
memory usage: dead item storage 2.58 MB accumulated across 1 reset (limit 64.00
MB each)
system usage: CPU: user: 0.33 s, system: 0.95 s, elapsed: 32.05 s
VACUUM
Parallel Worker Information
Information about the parallel workers used by the VACUUM statement is output. [adcdbe9]
Example 69Parallel Worker Information
postgres=> VACUUM (PARALLEL 2, VERBOSE) data1 ;
INFO: vacuuming "postgres.public.data1"
INFO: launched 1 parallel vacuum worker for index vacuuming (planned: 2)
INFO: finished vacuuming "postgres.public.data1": index scans: 1
…
item identifiers removed
parallel workers: index vacuum: 1 planned, 1 launched in total
index "data1_pkey": pages: 27421 in total, 0 newly deleted, 0 currently deleted,
0 reusable
…
system usage: CPU: user: 0.11 s, system: 1.35 s, elapsed: 5.66 s
VACUUM
3.2.19 WAIT FOR LSN statement
The WAIT FOR LSN statement, a new SQL syntax, has been added to PostgreSQL 19. This SQL statement is mainly executed on standby instances in a streaming replication environment. When the WAIT FOR LSN statement is executed, it waits until the specified LSN is applied. [447aae1, 3b4e53a, 49a181b]
PDF page 66
Syntax 15 - WAIT FOR LSN statement
WAITFORLSN'lsn'[WITH(option[,…])]
The following options are available.
MODE ‘mode’
Specify the mode for how long to wait. The following values can be specified. “primary_flush” is specified on the primary server; all others are specified on the standby server.
Primary server flush Apply WAL to the standby server Write WAL to the standby server Flush WAL on the standby server
On the primary server
Table 25MODE Option Settings
TIMEOUT ’timeout'
Specify the maximum wait time. If no value is specified or if zero (0) is specified, this statement will wait indefinitely.
NO_THROW
Even if a timeout occurs, no error is thrown.
PDF page 67
Example 70Executing the WAIT FOR LSN statement
postgres=> WAIT FOR LSN '0/0301EF70' ;
status
---------
success
(1 row)
postgres=> WAIT FOR LSN '0/0301FF00' WITH (TIMEOUT '10s') ;
ERROR: timed out while waiting for target LSN 0/0301FF00 to be replayed;
current standby_replay LSN 0/0301EF70
postgres=> WAIT FOR LSN '0/0301FF00' WITH (TIMEOUT '10s', NO_THROW) ;
status
---------
timeout
(1 row)
The WAIT FOR LSN statement returns the following value.
Return value
Description
Note
success
The specified LSN has been reached
timeout
The wait time has exceeded the timeout
not in recovery
The executed instance is not in recovery state
Table 26Return value of the WAIT FOR LSN statement
3.2.20 Functions
The following functions have been added or enhanced.
Window Functions
It is now possible to specify the IGNORE NULLS clause (ignore NULL values) and the RESPECT NULLS clause (include NULL values) for the window functions LEAD, LAG, FIRST_VALUE, LAST_VALUE, and NTH_VALUE. The default is RESPECT NULLS. [25a30bb]
PDF page 68
Example 71Specifying the IGNORE NULLS clause and the RESPECT NULLS clause
postgres=> SELECT LEAD(col1) IGNORE NULLS OVER (), LAG(col1) RESPECT NULLS OVER
() FROM data1 ;
lead | lag
-------+-------
2 |
3 | 1
4 | 2
5 | 3
6 | 4
…
Function encode and decode
It is now possible to specify base64url and base32hex as encoding formats. [e1d9171, 497c117]
Example 72Base64 URL encoding and Base32 hex encoding
The error_on_null function has been added; it returns an error if the input value is NULL, and returns the input value if it is not NULL. [2b75c38]
PDF page 69
Syntax 16 - Function error_on_null
anyelementerror_on_null(anyelement)
Example 73Execution of the error_on_null function
postgres=> SELECT error_on_null(NULL::INT) ;
ERROR: null value not allowed
Function random
Variations of the random function that specify maximum and minimum values now support the date, timestamp, and timestamp with time zone types. [faf071b]
postgres=> SELECT random('2024-09-09'::date, '2025-09-09'::date),
random('2024-09-09'::timestamptz, '2025-09-09'::timestamptz) ;
random | random
------------+-------------------------------
2025-02-04 | 2025-02-16 07:24:48.518771+09
(1 row)
Function range_minus_multi and multirange_minus_multi
The range_minus_multi and multirange_minus_multi functions have been added to remove ranges from a range and return multiple ranges, respectively. [5eed8ce]
Syntax 18 - Function range_minus_multi and multirange_minus_multi
It is now possible to specify NaN as the first parameter, operand, of the width_bucket function. The following SQL statement would have caused an error in previous versions. [7374b3a]
Example 79Executing of the pg_clear_extended_stats
postgres=> CREATE STATISTICS data1_stat1 ON col1,col2 FROM data1 ;
CREATE STATISTICS
postgres=> SELECT pg_clear_extended_stats('public', 'data1', 'public',
'data1_stat1', true) ;
-[ RECORD 1 ]-----------+-
pg_clear_extended_stats |
Function pg_get_multixact_stats
The pg_get_multixact_stats function, which retrieves the status of multi-transactions, has been added. This function returns the following values. [97b1017]
PDF page 73
Column name
Data type
Description
num_mxids
bigint
Multi-transaction IDs in use
num_members
bigint
Size of multi-transaction ID members in use
members_size
bigint
Storage size used by members
oldest_multixact
xid
Oldest multi-transaction ID
Table 28Return value of the pg_get_multixact_stats function
Example 80Execution of the pg_get_multixact_stats function
The pid parameter has been added to specify the worker process ID using origin. The default value is 0, which indicates the first process ID. [5b14870] [5b14870]
Example 83Definition of the pg_replication_origin_session_setup function
postgres=> \dfS pg_replication_origin_session_setup
List of functions
-[ RECORD 1 ]-------+--------------------------------------
Schema | pg_catalog
Name | pg_replication_origin_session_setup
Result data type | void
Argument data types | node_name text, pid integer DEFAULT 0
Type | func
Function pg_restore_extended_stats
The pg_restore_extended_stats function, which restores a table’s extended statistics, has been added. [0e80f3f, 302879b, ba97bf9]
Example 84Executing of the pg_restore_extended_stats function
postgres=> CREATE STATISTICS stat1_data1 ON col1,col2 FROM data1 ;
CREATE STATISTICS
postgres=> SELECT pg_restore_extended_stats(
'schemaname', 'public',
'relname', 'data1',
'statistics_schemaname', 'public',
'statistics_name', 'stat1_data1',
'inherited', false,
'n_distinct', '[{"attributes" : [1,2], "ndistinct" : 4}]'::pg_ndistinct) ;
-[ RECORD 1 ]-------------+--
pg_restore_extended_stats | t
Function pg_stat_get_backend_wal
The pg_stat_get_backend_wal function now includes wal_fpi_bytes (of type numeric), which indicates the number of FPI bytes, in its return value. [f9a09aa]
Function pg_sync_replication_slots
The pg_sync_replication_slots function now includes a feature that retries until synchronization is complete. [0d2d4a0]
3.2.21 JSON
The following enhancements regarding JSON data have been implemented.
Added jsonpath methods
The following jsonpath methods have been added. [bd4f879]
Remove leading and trailing characters Convert to lowercase Convert to uppercase Capitalize the first letter and convert the rest to lowercase Convert the specified string Return elements split by the specified string
Table 29Added string methods
Example 85Executing the newly added jsonpath string method
It is now possible to specify the IS JSON predicate for the domain. In previous versions, the IS JSON predicate was only available for the text, json, jsonb, and bytea data types. [3b4c2b9]
Example 86IS JSON Predicate for the Domain
postgres=> CREATE DOMAIN jd1 AS jsonb CHECK ((VALUE ->'val')::text <> '3') ;
CREATE DOMAIN
postgres=> SELECT '{"val": 1}'::jd1 IS JSON ;
?column?
----------
t
(1 row)
postgres=> SELECT '{"val": 3}'::jd1 IS JSON ;
ERROR: value for domain jd1 violates check constraint "jd1_check"
3.2.22 Data Types
The following extensions have been implemented for data types in PostgreSQL 19.
Data type regdatabase
The regdatabase data type, which represents a database OID, has been added. To retrieve the OID of the current database, it can be written as follows. [bd09f02]
The operator classes for GiST indexes of type inet and cidr have been changed to inet_ops and cidr_ops, respectively. As a result of this change, the btree_gist extension is no longer required. The following CREATE INDEX statements will result in an error in PostgreSQL 18 and earlier. The pg_upgrade command will fail if the older database contains the gist_inet_ops or gist_cidr_ops operator classes. [b352d3d]
PDF page 79
Example 90GiST indexes for inet and cidr types
postgres=> CREATE TABLE data1(col1 INET, col2 CIDR) ;
CREATE TABLE
postgres=> CREATE INDEX idx1_data1 ON data1 USING gist (col1) ;
CREATE INDEX
postgres=> CREATE INDEX idx2_data1 ON data1 USING gist (col2) ;
CREATE INDEX
Tsearch-related data types
The analysis now uses the database’s default collation. Previous versions used the database’s CTYPE setting. [e113f9c]
3.2.23 PL/pgSQL
The following features have been added to the PL/pgSQL module.
Optimizing Simple SELECT INTO Statements
The execution of the SELECT INTO statement that uses a simple scalar expression to assign to a single variable is no longer treated as an SQL statement execution; instead, it is now converted into an assignment operation. Due to this update, the target SQL statement will no longer trigger hooks in the pg_stat_statements module. [ce8d5fe]
Reserved Word Removal
EXECUTE and STRICT have been removed from the reserved word list. These words can now be used as variable names and for other purposes. [999f172]
Example 91Using reserved keywords as variable names
postgres=> DO $$
DECLARE
EXECUTE INT;
BEGIN
EXECUTE := 100;
END;
$$;
DO
postgres=>
Eager aggregation optimization has been implemented to reduce the number of input tuples in join operations by performing partial aggregation before the join. This optimization is controlled by the enable_eager_aggregate parameter. The default value is “on”, meaning it is enabled. The group size required to consider eager aggregation is specified by the min_eager_agg_group_size parameter. The default value for this parameter is 8. [8e11859]
Example 92PostgreSQL 18 Execution Plan
postgres=> EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.col1, avg(t2.col3)
FROM eager1 AS t1 JOIN eager2 AS t2 ON t1.col2 = t2.col2
GROUP BY t1.col1 ORDER BY t1.col1 ;
QUERY PLAN
-------------------------------------------------------
GroupAggregate
Output: t1.col1, avg(t2.col3)
Group Key: t1.col1
-> Sort
Output: t1.col1, t2.col3
Sort Key: t1.col1
-> Hash Join
Output: t1.col1, t2.col3
Hash Cond: (t2.col2 = t1.col2)
-> Seq Scan on public.eager2 t2
Output: t2.col1, t2.col2, t2.col3
-> Hash
Output: t1.col1, t1.col2
-> Seq Scan on public.eager1 t1
Output: t1.col1, t1.col2
(15 rows)
PDF page 81
Example 93PostgreSQL 19 Execution Plan
postgres=> EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.col1, avg(t2.col3)
FROM eager1 AS t1 JOIN eager2 AS t2 ON t1.col2 = t2.col2
GROUP BY t1.col1 ORDER BY t1.col1 ;
QUERY PLAN
-------------------------------------------------------------------
Finalize GroupAggregate
Output: t1.col1, avg(t2.col3)
Group Key: t1.col1
-> Sort
Output: t1.col1, (PARTIAL avg(t2.col3))
Sort Key: t1.col1
-> Hash Join
Output: t1.col1, (PARTIAL avg(t2.col3))
Hash Cond: (t1.col2 = t2.col2)
-> Seq Scan on public.eager1 t1
Output: t1.col1, t1.col2, t1.col3
-> Hash
Output: t2.col2, (PARTIAL avg(t2.col3))
-> Partial HashAggregate
Output: t2.col2, PARTIAL avg(t2.col3)
Group Key: t2.col2
-> Seq Scan on public.eager2 t2
Output: t2.col1, t2.col2, t2.col3
(18 rows)
Using Memoize with ANTI JOIN
The memoization feature is now available for SEMI JOIN and ANTI JOIN. [0da29e4]
PDF page 82
Example 94PostgreSQL 18 Execution Plan
postgres=> EXPLAIN (COSTS OFF, BUFFERS OFF)
SELECT COUNT(*) FROM data1 d1 LEFT JOIN
LATERAL (SELECT DISTINCT ON (c1) c1, c2, d1.c1 AS x FROM data1 d2) d2
ON d1.c1 + 1 = d2.c1 WHERE d2.c1 IS NULL ;
QUERY PLAN
------------------------------------------------------
Aggregate
-> Nested Loop Anti Join
-> Seq Scan on data1 d1
-> Subquery Scan on d2
Filter: ((d1.c1 + 1) = d2.c1)
-> Unique
-> Sort
Sort Key: d2_1.c1
-> Seq Scan on data1 d2_1
(9 rows)
PDF page 83
Example 95PostgreSQL 19 Execution Plan
postgres=> EXPLAIN (COSTS OFF, BUFFERS OFF)
SELECT COUNT(*) FROM data1 d1 LEFT JOIN
LATERAL (SELECT DISTINCT ON (c1) c1, c2, d1.c1 AS x FROM data1 d2) d2
ON d1.c1 + 1 = d2.c1 WHERE d2.c1 IS NULL ;
QUERY PLAN
------------------------------------------------------------
Aggregate
-> Nested Loop Anti Join
-> Seq Scan on data1 d1
-> Memoize
Cache Key: (d1.c1 + 1), d1.c1
Cache Mode: binary
-> Subquery Scan on d2
Filter: ((d1.c1 + 1) = d2.c1)
-> Unique
-> Sort
Sort Key: d2_1.c1
-> Seq Scan on data1 d2_1
(12 rows)
Rewriting the COUNT function
If possible, the COUNT(any) clause is rewritten as a COUNT(*) clause. This rewrite is limited to cases where a non-NULL column is specified and there is no ORDER BY or DISTINCT clause. This change reduces overhead and improves performance. [42473b3]
PDF page 84
Example 96PostgreSQL 18 Execution Plan
postgres=> EXPLAIN (VERBOSE) SELECT COUNT(col1) FROM data1 ;
QUERY PLAN
----------------------------------------------------------------------------
Aggregate (cost=17906.00..17906.01 rows=1 width=8)
Output: count(col1)
-> Seq Scan on public.data1 (cost=0.00..15406.00 rows=1000000 width=4)
Output: col1, col2
(4 rows)
Example 97PostgreSQL 19 Execution Plan
postgres=> EXPLAIN (VERBOSE) SELECT COUNT(col1) FROM data1 ;
QUERY PLAN
----------------------------------------------------------------------------
Aggregate (cost=17906.00..17906.01 rows=1 width=8)
Output: count(*)
-> Seq Scan on public.data1 (cost=0.00..15406.00 rows=1000000 width=0)
Output: col1, col2
(4 rows)
Parallelization of TID Range Scans
It is now possible to execute TID Range Scans using parallel queries. [0ca3b16]
Example 98PostgreSQL 18 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE ctid > '(10,0)' AND ctid < '(30,1)';
QUERY PLAN
-----------------------------------------------------------------
Tid Range Scan on data1 (cost=0.01..61.00 rows=3700 width=10)
TID Cond: ((ctid > '(10,0)'::tid) AND (ctid < '(30,1)'::tid))
(2 rows)
PDF page 85
Example 99PostgreSQL 19 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE ctid > '(10,0)' AND ctid < '(30,1)';
QUERY PLAN
------------------------------------------------------------------------------
Gather (cost=0.01..33.25 rows=3700 width=10)
Workers Planned: 4
-> Parallel Tid Range Scan on data1 (cost=0.01..33.25 rows=925 width=10)
TID Cond: ((ctid > '(10,0)'::tid) AND (ctid < '(30,1)'::tid))
(4 rows)
Partial Hash Index
HASH indexes created as partial indexes can now be used in execution plans when a WHERE clause is specified that matches their conditions. This feature has been backported to PostgreSQL 14 and later. [5528e8d]
Example 100PostgreSQL 18 Execution Plan
postgres=> CREATE INDEX idx1_data1 ON data1 USING hash (col2) WHERE col2=1000 ;
CREATE INDEX
postgres=> EXPLAIN SELECT * FROM data1 WHERE col2=2000 ;
QUERY PLAN
----------------------------------------------------------
Seq Scan on data1 (cost=0.00..17906.00 rows=1 width=14)
Filter: (col2 = 2000)
(2 rows)
Example 101PostgreSQL 19 Execution Plan
postgres=> CREATE INDEX idx1_data1 ON data1 USING hash (col2) WHERE col2=1000 ;
CREATE INDEX
postgres=> EXPLAIN SELECT * FROM data1 WHERE col2=1000 ;
QUERY PLAN
-------------------------------------------------------------------------
Index Scan using idx1_data1 on data1 (cost=0.00..8.02 rows=1 width=14)
Index Cond: (col2 = 1000)
(2 rows)
PDF page 86
Optimization of the IS DISTINCT FROM clause
The IS DISTINCT FROM clause for columns with a NOT NULL constraint is now converted to the “<>” operator. [0a37961]
Example 102PostgreSQL 18 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1 IS DISTINCT FROM col1 ;
QUERY PLAN
---------------------------------------------------------------
Seq Scan on data1 (cost=0.00..17906.00 rows=995000 width=10)
Filter: (col1 IS DISTINCT FROM col1)
(2 rows)
Example 103PostgreSQL 19 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1 IS DISTINCT FROM col1 ;
QUERY PLAN
---------------------------------------------------------------
Seq Scan on data1 (cost=0.00..17906.00 rows=995000 width=10)
Filter: (col1 <> col1)
(2 rows)
Anti-Join Transformation of the NOT IN Clause
If it is determined to be safe, the NOT IN clause uses an Anti-Join in the execution plan. [383eb21]
PDF page 87
Example 104PostgreSQL 18 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1 NOT IN (SELECT col1 FROM
data1 WHERE col1 < 10000) ;
QUERY PLAN
--------------------------------------------------------------------------
Seq Scan on data1 (cost=194.99..364.97 rows=5000 width=8)
Filter: (NOT (ANY (col1 = (hashed SubPlan 1).col1)))
SubPlan 1
-> Seq Scan on data1 data1_1 (cost=0.00..169.99 rows=9999 width=4)
Filter: (col1 < 10000)
(5 rows)
Example 105PostgreSQL 19 Execution Plan
postgres=> EXPLAIN SELECT * FROM data1 WHERE col1 NOT IN (SELECT col1 FROM
data1 WHERE col1 < 10000) ;
QUERY PLAN
----------------------------------------------------------------------------
Hash Anti Join (cost=29.98..48.71 rows=1 width=8)
Hash Cond: (data1.col1 = data1_1.col1)
-> Seq Scan on data1 (cost=0.00..14.99 rows=999 width=8)
-> Hash (cost=17.49..17.49 rows=999 width=4)
-> Seq Scan on data1 data1_1 (cost=0.00..17.49 rows=999 width=4)
Filter: (col1 < 10000)
(6 rows)
Details of the Result Node
Information about the source of the result has been added to the Result node. As a result, information from the “Replace:” node will now be included in the execution results of the EXPLAIN statement. [f2bae51]
PDF page 88
Example 106PostgreSQL 19 Execution Plan
postgres=> EXPLAIN (COSTS OFF) SELECT * FROM data1 WHERE
col1=(SELECT MAX(col1) FROM data1) ;
QUERY PLAN
----------------------------------------------------------------------------…
Index Scan using data1_pkey on data1
Index Cond: (col1 = (InitPlan expr_1).col1)
InitPlan expr_1
-> Result
Replaces: MinMaxAggregate
InitPlan minmax_1
-> Limit
-> Index Only Scan Backward using data1_pkey on data1 …
(8 rows)