All Products
Search
Document Center

PolarDB:Use any column of a partitioned table as the primary key

Last Updated:Mar 28, 2026

Standard PostgreSQL requires the primary key of a partitioned table to include all partition keys and does not allow foreign keys to reference a partitioned table's primary key. PolarDB for PostgreSQL removes both restrictions: you can define a primary key on any column, regardless of whether it is part of the partition key, and reference it with a foreign key.

This is particularly useful when migrating from Oracle, which natively supports global indexes. You can keep your existing schema design without restructuring partition keys to satisfy PostgreSQL's primary key requirements.

Prerequisites

This feature is supported on PolarDB for PostgreSQL clusters running PostgreSQL 14 with minor engine version 2.0.14.10.17.0 or later.

To check your minor engine version, run:

SHOW polardb_version;

Or view it in the console. If your version does not meet the requirement, upgrade the minor engine version.

How it works

PolarDB for PostgreSQL uses global indexes to enforce uniqueness across all partitions when a primary key or UNIQUE constraint does not include all partition keys. A global index spans the entire partitioned table rather than a single partition.

Index typeWhen it is usedScope
Local indexPrimary key or UNIQUE constraint includes all partition keys (default PostgreSQL behavior)Single partition
Global indexPrimary key or UNIQUE constraint does not include all partition keysAll partitions

The polar_pk_in_non_partition_column_mode parameter controls whether creating a primary key on a non-partition column is allowed, and which index type is used:

ValueBehavior
none / local_pkCreating a primary key on a non-partition column is not allowed. An error is returned if attempted.
global_indexA global index is used as the constraint when creating a primary key on a non-partition column.

Limitations

LimitationImpactRecommendation
Primary key includes all partition keysA local index is used by default, regardless of the polar_pk_in_non_partition_column_mode setting.Use global_index mode and explicitly create a global unique index if you need a global primary key on a column that is also the partition key.
UNIQUE constraint includes all partition keysA local index is used as the unique constraint by default.Same as above: create a global unique index explicitly if cross-partition uniqueness is required.
Adding a primary key or UNIQUE constraint to an existing partitioned tableA local index may not satisfy uniqueness across all existing partitions.Always use a global index when adding a primary key or UNIQUE constraint to an existing partitioned table.
Foreign key references a row that is updated across partitionsA cross-partition update is equivalent to a delete followed by an insert. The foreign key check runs on the delete and will fail, causing the update to be rejected.Avoid cross-partition updates on rows referenced by a foreign key. Restructure the operation as an explicit delete and insert if needed.

Examples

Create a primary key on a non-partition column

The following example shows the error you get when polar_pk_in_non_partition_column_mode is set to none and you try to create a primary key on column b, which is not the partition key:

SET polar_pk_in_non_partition_column_mode = none;
CREATE TABLE pt1 (a int, b int primary key, c varchar) PARTITION BY RANGE(a);
ERROR:  unique constraint on partitioned table must include all partitioning columns
DETAIL:  PRIMARY KEY constraint on table "pt1" lacks column "a" which is part of the partition key.

Set polar_pk_in_non_partition_column_mode to global_index to allow the non-partition primary key. PolarDB for PostgreSQL creates a global index on column b:

SET polar_pk_in_non_partition_column_mode = global_index;
CREATE TABLE pt1 (a int, b int primary key, c varchar) PARTITION BY RANGE(a);
CREATE TABLE pt1_p1 PARTITION OF pt1 FOR VALUES FROM (0) TO (1);
\d pt1
               Partitioned table "public.pt1"
 Column |       Type        | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
 a      | integer           |           |          |
 b      | integer           |           | not null |
 c      | character varying |           |          |
Partition key: RANGE (a)
Indexes:
    "pt1_pkey" PRIMARY KEY, btree (b) GLOBAL
Number of partitions: 1 (Use \d+ to list them.)

Primary key that includes all partition keys

When the primary key includes all partition keys, a local index is used by default, regardless of the polar_pk_in_non_partition_column_mode setting. This is the default PostgreSQL behavior:

CREATE TABLE pt2 (a int primary key, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE pt2_p1 PARTITION OF pt2 FOR VALUES FROM (0) TO (1);
\d pt2
               Partitioned table "public.pt2"
 Column |       Type        | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
 a      | integer           |           | not null |
 b      | integer           |           |          |
 c      | character varying |           |          |
Partition key: RANGE (a)
Indexes:
    "pt2_pkey" PRIMARY KEY, btree (a)
Number of partitions: 1 (Use \d+ to list them.)

Convert a local primary key to a global primary key

To convert an existing local primary key to a global one, drop the existing constraint, create a global unique index, then attach it as the primary key:

ALTER TABLE pt2 DROP CONSTRAINT pt2_pkey;
CREATE UNIQUE INDEX pt2_pkey ON pt2 (a) GLOBAL;
ALTER TABLE pt2 ADD PRIMARY KEY USING INDEX pt2_pkey;
\d pt2
               Partitioned table "public.pt2"
 Column |       Type        | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
 a      | integer           |           | not null |
 b      | integer           |           |          |
 c      | character varying |           |          |
Partition key: RANGE (a)
Indexes:
    "pt2_pkey" PRIMARY KEY, btree (a) GLOBAL
Number of partitions: 1 (Use \d+ to list them.)