<img height="1" width="1" style="display:none;" alt="" src="https://px.ads.linkedin.com/collect/?pid=2826169&amp;fmt=gif">
Start  trial

    Start trial

      PostgreSQL's CREATE PUBLICATION command provides precise control over logical replication, enabling secure, efficient, and tailored data sharing.

      Typically, when using PostgreSQL Logical Replication (Publish/Subscribe), all data changes from published tables will be replicated to subscribers. But what if you need to replicate only specific columns for security? Or filter rows to reduce bandwidth? Or handle partitioned tables differently?

      The CREATE PUBLICATION command offers powerful options for tailoring exactly what data gets replicated. This comprehensive guide explores every syntax variation, helping you optimize replication for performance, security, and your specific use case.

      CREATE PUBLICATION name
        [ FOR ALL TABLES | FOR publication_object [, ... ] ]
      [ WITH ( publication_parameter# [= value] [, ... ] ) ]

      where publication_object is one of:
          TABLE table_and_columns [, ... ]
          TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ]

      and table_and_columns is:
        [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ]

      Command syntax from PostgreSQL documentation

      # publication_parameter: publish, publish_generated_columns, publish_via_partition_root

      One look at the syntax in the PostgreSQL documentation shows there are many ways to specify what data will be published.

      In this blog post, we will explore in detail all those different parts of the PostgreSQL CREATE PUBLICATION syntax that allow users to tailor exactly what table data they want to be logically replicated.

      This blog post builds upon and expands the material I presented at PGDU 2025, with the accompanying slides available here.

      System catalogs, views, and psql describe commands

      Internally, there are 3 main System Catalogs that tie everything together:

      • pg_publication
      • pg_publication_namespace
      • pg_publication_rel

      These are linked together as shown in the following diagram. Every kind of Publication can be represented by these catalogs, but note that not all catalogs are required for all CREATE PUBLICATION commands – e.g., the pg_publication_namespace is only used to describe a FOR TABLES IN SCHEMA publication.

      Please take time to familiarize yourself with this system catalog diagram because we’ll be returning to this later in the blog each time when describing how the various Publication clauses are internally represented.

      System Catalogs are linked together using the oid members as highlighted by the colour-coding below.

      System catalogs

      For a more human-readable summary of Publication information there is a System View:

      • pg_publication_tables

      There are also two psql describe commands:

      # \dRp+ my_pub

      Displays the publication information of my_pub including what tables are members.

      # \d my_tab

      Displays the table information of my_tab including what publications (if any) are publishing it

      The evolving functionality – History and future

      The CREATE PUBLICATION command has evolved with more functionalities being added with each new PostgreSQL version.

      Here are some of the major milestones, and some future features currently under development.

      Version Feature Example
      PostgreSQL 13 Empty Publication
      CREATE PUBLICATION mypub13;
      Specified Tables
      CREATE PUBLICATION mypub13 FOR TABLE t1,t2,t3;
      All Tables
      CREATE PUBLICATION mypub13 FOR ALL TABLES;
      Descendant Tables
      CREATE PUBLICATION mypub13 FOR ONLY TABLE parent;
      Partition Tables
      CREATE PUBLICATION mypub13 … WITH (publish_via_partition_root);
      DML Filtering
      CREATE PUBLICATION mypub13 … WITH (publish=’insert,update,delete,truncate’);
      PostgreSQL 15 Row Filters
      CREATE PUBLICATION mypub15WHERE (X=99);
      Column Lists
      CREATE PUBLICATION mypub15 FOR TABLE t1(c1,c2,c3);
      Specified Schemas
      CREATE PUBLICATION mypub15 FOR TABLES IN SCHEMA s1;
      PostgreSQL 18 Generated Columns
      CREATE PUBLICATION mypub18 … WITH (publish_generated_columns=stored);
      PostgreSQL 19
      Still In development
      All Sequences
      CREATE PUBLICATION mypub19 FOR ALL SEQUENCES;
      Exclude Tables
      CREATE PUBLICATION mypub19 FOR ALL TABLES EXCEPT (t98,t99);
      Exclude Columns
      CREATE PUBLICATION mypub19 FOR TABLE t1 EXCEPT (c98,c99);

      Table showing the Evolution of CREATE PUBLICATION command functionality

      A note about table shapes

      Although it is typical for the Subscriber-side table to have the same schema as the Published table, in fact the only requirement is that the published column names must exist in the subscriber table.

      This means different shape tables are possible at each side of the replication. For example, valid subscriber table variations include:

      • Different column order
      • Additional columns not in the publication
      • Subset of publisher columns (if using column lists)

      Visual examples of Subscriber tables that differ from the Publisher tables

      OK, now let’s work our way through all of the CREATE PUBLICATION syntax parts…

      All tables

      Syntax

      CREATE PUBLICATION pub_for_all_tables
      FOR ALL TABLES

      The publication includes:

      • All tables (present and future) from all schemas
      • All data from those tables (excluding generated column data)

      Catalogs:

      • \dRp+ shows "All tables" is true
      •  pg_publication. puballtables flag member is set true

      Use for: Development environments, complete database replication.

      Watch out: Includes all tables, even some you may not have intended to replicate.

      Benefit: New tables auto-replicate.

      System catalogs

      Examples:

      test_pub=# \dRp+
                                       Publication pub_for_all_tables
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | t          | t       | t       | t       | t         | none              | f
      (1 row)
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+-------------------
      oid          | 16401
      pubname      | pub_for_all_tables
      pubowner     | 10
      puballtables | t
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_tables;
            pubname       | schemaname | tablename |   attnames    | rowfilter 
      --------------------+------------+-----------+---------------+-----------
       pub_for_all_tables | public     | t1        | {c1,c2,c3,c4} | 
       pub_for_all_tables | public     | t2        | {c1,c2,c3,c4} | 
       pub_for_all_tables | public     | t3        | {c1,c2,c3,c4} | 
       pub_for_all_tables | s2         | t4        | {c1,c2,c3,c4} | 
       pub_for_all_tables | s2         | t5        | {c1,c2,c3,c4} | 
      (5 rows)

      Specific schemas

      Syntax

      CREATE PUBLICATION pub_for_schema
      FOR TABLES IN SCHEMA s2;

      The publication includes:

      • All tables (present and future) from the nominated schema
      • All data from those tables (excluding generated column data)

      Catalogs:

      • \dRp+ shows "Tables from schemas"
      • pg_publication_namespace catalog identifies the published schema

      Use for: Organizational boundaries.

      Benefit: New tables auto-replicate.

      img-dgm-blog-tailoring-create-publication-06

      System catalogs

      Examples:

      test_pub=# \dRp+
                                         Publication pub_for_schema
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables from schemas:
          "s2"
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+---------------
      oid          | 16402
      pubname      | pub_for_schema
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_tables;
          pubname     | schemaname | tablename |   attnames    | rowfilter 
      ----------------+------------+-----------+---------------+-----------
       pub_for_schema | s2         | t4        | {c1,c2,c3,c4} | 
       pub_for_schema | s2         | t5        | {c1,c2,c3,c4} | 
      (2 rows)

      Specific tables

      Syntax

      CREATE PUBLICATION pub_for_table
      FOR TABLE t3, s2.t4;

      The publication includes:

      • Only the specified tables
      • All data from those tables (excluding generated column data)

      Catalogs:

      • \dRp+ shows "Tables" of the publication
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel,prrelid member gives the table oid in pg_class
      •  pg_class. relnamespace member gives the schema oid in pg_namespace

      Use for: Bandwidth optimization, reporting databases, selective replication of high-value tables.

      img-dgm-blog-tailoring-create-publication-08

      System catalogs

      Examples:

      test_pub=# \dRp+
                                         Publication pub_for_table
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables:
          "public.t3"
          "s2.t4"
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+---------------
      oid          | 16404
      pubname      | pub_for_table
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      
      test_pub=# SELECT * FROM pg_publication_rel;
        oid  | prpubid | prrelid | prqual | prattrs 
      -------+---------+---------+--------+---------
       16405 |   16404 |   16391 |        | 
       16406 |   16404 |   16395 |        | 
      (2 rows)
      
      
      test_pub=# SELECT oid, relname, relnamespace FROM pg_class WHERE oid IN (16391, 16395);
        oid  | relname | relnamespace 
      -------+---------+--------------
       16391 | t3      |         2200
       16395 | t4      |        16394
      (2 rows)
      
      test_pub=# SELECT oid, nspname FROM pg_namespace WHERE oid IN (2200, 16394);
        oid  | nspname 
      -------+---------
        2200 | public
       16394 | s2
      (2 rows)
      
      test_pub=# SELECT * FROM pg_publication_tables;
          pubname    | schemaname | tablename |   attnames    | rowfilter 
      ---------------+------------+-----------+---------------+-----------
       pub_for_table | public     | t3        | {c1,c2,c3,c4} | 
       pub_for_table | s2         | t4        | {c1,c2,c3,c4} | 
      (2 rows)

      Column lists

      Syntax

      CREATE PUBLICATION pub_collist
      FOR TABLE t1(c2, c4);

      The publication includes:

      • Only the specified tables
      • Only data from the specified columns

      Catalogs:

      • \dRp+ shows "Tables" (and columns) of the publication
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel.prrelid member gives the table oid in pg_class
      • pg_publication_rel.prattrs member gives attribute numbers in pg_attribute
      • pg_class.relnamespace member gives the schema oid in pg_namespace

      Use for: Exclude sensitive data, large BLOBs, or columns missing on subscriber.

      Watch out: Must include replica identity columns.

      Benefit: Can reduce bandwidth

      System catalogs

      Examples:

      test_pub=# \dRp+
                                          Publication pub_collist
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables:
          "public.t1" (c2, c4)
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+------------
      oid          | 16407
      pubname      | pub_collist
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_rel;
        oid  | prpubid | prrelid | prqual | prattrs 
      -------+---------+---------+--------+---------
       16408 |   16407 |   16385 |        | 2 4
      (1 row)
      
      test_pub=# SELECT oid, relname, relnamespace FROM pg_class WHERE oid IN (16385);
        oid  | relname | relnamespace 
      -------+---------+--------------
       16385 | t1      |         2200
      (1 row)
      
      test_pub=# SELECT oid, nspname FROM pg_namespace WHERE oid IN (2200);
       oid  | nspname 
      ------+---------
       2200 | public
      (1 row)
      
      test_pub=# SELECT attrelid, attname, attnum FROM pg_attribute WHERE attrelid IN (16385) AND attnum IN (2,4);
       attrelid | attname | attnum 
      ----------+---------+--------
          16385 | c2      |      2
          16385 | c4      |      4
      (2 rows)
      
      test_pub=# SELECT * FROM pg_publication_tables;
         pubname   | schemaname | tablename | attnames | rowfilter 
      -------------+------------+-----------+----------+-----------
       pub_collist | public     | t1        | {c2,c4}  | 
      (1 row)

      Row filtering

      Syntax

      CREATE PUBLICATION pub_rowfilter
      FOR TABLE t1 WHERE (c2 = 1);

      The publication includes:

      • Only the specified tables
      • Only data from rows matching the specified condition

      Catalogs:

      • \dRp+ shows "Tables" (and row filter expressions) of the publication
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel.prrelid member gives the table oid in pg_class
      • pg_publication_rel.prqual member gives the (internal form) row filter expression
      • pgclass.relnamespace member gives the schema oid in pg_namespace

      Use for: Geographic filtering, time-based (hot data only), multi-tenant isolation, status filtering.

      Watch out: Filters must be immutable; can reduce bandwidth but costs some publisher CPU.

      System catalogs

      Examples:

      test_pub=# \dRp+
                                         Publication pub_rowfilter
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables:
          "public.t1" WHERE (c2 = 1)
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+--------------
      oid          | 16409
      pubname      | pub_rowfilter
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_rel;
      -[ RECORD 1 ]-------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      oid     | 16410
      prpubid | 16409
      prrelid | 16385
      prqual#| {OPEXPR :opno 96 :opfuncid 65 :opresulttype 16 :opretset false :opcollid 0 :inputcollid 0 :args ({VAR :varno 1 :varattno 2 :vartype 23 :vartypmod -1 :varcollid 0 :varnullingrels (b) :varlevelsup 0 :varnosyn 1 :varattnosyn 2 :location -1} {CONST :consttype 23 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location -1 :constvalue 4 [ 1 0 0 0 0 0 0 0 ]}) :location -1}
      prattrs | 
      
      test_pub=# SELECT oid, relname, relnamespace FROM pg_class WHERE oid IN (16385);
        oid  | relname | relnamespace 
      -------+---------+--------------
       16385 | t1      |         2200
      (1 row)
      
      test_pub=# SELECT oid, nspname FROM pg_namespace WHERE oid IN (2200);
       oid  | nspname 
      ------+---------
       2200 | public
      (1 row)
      
      test_pub=# SELECT * FROM pg_publication_tables;
          pubname    | schemaname | tablename |   attnames    | rowfilter 
      ---------------+------------+-----------+---------------+-----------
       pub_rowfilter | public     | t1        | {c1,c2,c3,c4} | (c2 = 1)
      (1 row)

      # The prqual member stores the WHERE clause in internal PostgreSQL node tree format. You'll typically use \dRp+ or pg_publication_tables for a human-readable version.

      Combinations – e.g., Column lists with Row filtering

      Syntax

      CREATE PUBLICATION pub_combo
      t1(c2, c4) WHERE (c2 = 1);

      The publication includes:

      • Only the specified tables
      • Only data from the specified columns, from rows matching the specified condition

      Catalogs:

      • \dRp+ shows "Tables" (and columns and row filter expressions) of the publication
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel.prrelid member gives the table oid in pg_class
      • pg_publication_rel.prattrs member gives attribute numbers in pg_attribute
      • pg_publication_rel.prqual member gives the (internal form) row filter expression
      • pgclass.relnamespace member gives the schema oid in pg_namespace

      System catalogs

      Generated columns

      In case you are unfamiliar with generated columns…

      # CREATE TABLE tg1(c1 int, c2 int, 
                       gc1 int GENERATED ALWAYS AS (c1 + 50) STORED,
                       gc2 int GENERATED ALWAYS AS (c2 * 100) STORED);#
      
      # INSERT INTO tg1 VALUES (1,1),(2,2),(3,3);
      
      # SELECT * FROM tg1;
       c1 | c2 | gc1 | gc2  
      ----+----+-----+-----
        1 |  1 | 51  | 100
        2 |  2 | 52  | 200
        3 |  3 | 53  | 300
      (3 rows)

      Example of a table with generated columns

      # Defining a table with generated columns

      • Default replication? By default, generated columns are NOT replicated. In the typical case where the subscriber and publisher tables are the same, there is no need for replication because subscriber-side generated column values will generate themselves.
      • So why publish them at all? Because it can be used when the publisher table has generated columns, but the subscriber table does not. Recall from the earlier (table "shape") section that the publisher/subscriber tables are often identical, but they don’t have to be.
      • What is allowed?
        • Publisher (generated col) Subscriber (generated col) not allowed
        • Publisher (generated col) Subscriber (regular col) allowed since PG18

      Syntax

      CREATE PUBLICATION pub_gencols 
      FOR TABLE tg1(c2, gen2), tg2 WITH (publish_generated_columns = none);

      This example has specified tables, but the publish_generated_columns option also works for "FOR ALL TABLES" and FOR TABLES IN SCHEMA"

      The publication includes:

      • If there is a column list (e.g. TG1) then publish all the specified columns regardless of the publish_generated_columns In this example, "TG1.gen2" will be published even though the option is false.
      • If there is no column list (e.g. TG2), publication of the generated column data depends on the publish_generated_columns In this example TG2 does not publish generated columns because the option is none. If the option was stored, then all columns (c1, c2, gen1, gen2) of TG2 would have been published.

      Catalogs:

      • \dRp+ shows "Tables" (and columns) of the publication, along with the option value
      • pg_publication.pubgencols member gives the option value
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel.prrelid member gives the table oids in pg_class
      • pg_publication_rel.prattrs member gives attribute numbers in pg_attribute
      • pg_class.relnamespace member gives the schema oid in pg_namespace

      Use when: Subscriber has regular columns but publisher has generated columns.

      Don't use when: Both sides have generated columns (subscriber will regenerate own values).

      img-dgm-blog-tailoring-create-publication-16

      System catalogs

      Examples:

      test_pub=# \dRp+
                                          Publication pub_gencols
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables:
          "public.tg1" (c2, gen2)
          "public.tg2"
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+------------
      oid          | 16431
      pubname      | pub_gencols
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_rel;
        oid  | prpubid | prrelid | prqual | prattrs 
      -------+---------+---------+--------+---------
       16432 |   16431 |   16415 |        | 2 4
       16433 |   16431 |   16423 |        | 
      (2 rows)
      
      test_pub=# SELECT oid, relname, relnamespace FROM pg_class WHERE oid IN (16415, 16423);
        oid  | relname | relnamespace 
      -------+---------+--------------
       16415 | tg1     |         2200
       16423 | tg2     |         2200
      (2 rows)
      
      test_pub=# SELECT oid, nspname FROM pg_namespace WHERE oid IN (2200);
       oid  | nspname 
      ------+---------
       2200 | public
      (1 row)
      
      test_pub=# SELECT attrelid, attname, attnum FROM pg_attribute WHERE attrelid IN (16415) AND attnum IN (2,4);
       attrelid | attname | attnum 
      ----------+---------+--------
          16415 | c2      |      2
          16415 | gen2    |      4
      (2 rows)
      
      
      test_pub=# SELECT * FROM pg_publication_tables;
         pubname   | schemaname | tablename | attnames  | rowfilter 
      -------------+------------+-----------+-----------+-----------
       pub_gencols | public     | tg1       | {c2,gen2} | 
       pub_gencols | public     | tg2       | {c1,c2}   | 
      (2 rows)

      Nothing (An "empty" Publication)

      Syntax

      CREATE PUBLICATION pub_nothing;

      The publication includes:

      • Nothing

      Catalogs:

      • \dRp+ shows default option values of the publication

      Use for: Incremental setup, dynamic table management, testing. Add tables later via ALTER PUBLICATION.

      According to the CREATE PUBLICATION documentation, this "is useful if tables or schemas are to be added later".

      System catalogs

      Examples:

      test_pub=# \dRp+
                                          Publication pub_nothing
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f #
      (1 row)
      
      test_pub=# SELECT * FROM pg_publication;
      -[ RECORD 1 ]+------------
      oid          | 16434
      pubname      | pub_nothing
      pubowner     | 10
      puballtables | f
      pubinsert    | t
      pubupdate    | t
      pubdelete    | t
      pubtruncate  | t
      pubviaroot   | f
      pubgencols   | n
      
      test_pub=# SELECT * FROM pg_publication_rel;
       oid | prpubid | prrelid | prqual | prattrs 
      -----+---------+---------+--------+---------
      (0 rows)
      
      test_pub=# SELECT * FROM pg_publication_tables;
       pubname | schemaname | tablename | attnames | rowfilter 
      ---------+------------+-----------+----------+-----------
      (0 rows)

      # All default values

      Tailoring by DML operations

      Publish or do not publish according to the DML operation.

      Syntax (some examples):

      CREATE PUBLICATION pub_dml_insertonly
      FOR TABLE t1 WITH (publish='insert');
      CREATE PUBLICATION pub_dml_insertupdate
      FOR TABLE t1 WITH (publish='insert,update');
      CREATE PUBLICATION pub_dml_noinsert
      FOR TABLE t1 WITH (publish='update,delete,truncate');
      CREATE PUBLICATION pub_dml_notruncate
      FOR TABLE t1 WITH (publish='insert,update,delete');
      CREATE PUBLICATION pub_dml_default
      FOR TABLE t1;
      CREATE PUBLICATION pub_dml_none
      FOR TABLE t1 WITH (publish='');

      The publication includes:

      • All schema, table, column data according to the CREATE PUBLICATION syntax
      • Data for DML operations (INSERT, UPDATE, DELETE, TRUNCATE) is replicated only if the respective publish option is set
      • Default: Everything

      Catalogs and views:

      • \dRp+ shows flags for the publish option values in effect
      • pg_publication members give the option values
      • pg_publication_rel catalog itemizes the tables of the publication
      • pg_publication_rel.prrelid member gives the table oids in pg_class
      • pg_class.relnamespace member gives the schema oid in pg_namespace

      Use for: Audit logs (INSERT only), data warehouses (no TRUNCATE), compliance retention (no DELETE).

      Gotcha: Subscription initial copy_data ignores this setting.

      System catalogs

      Examples:

      test_pub=# \dRp+
                                        Publication pub_dml_default
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | t         | none              | f
      Tables:
          "public.t1"
      
                                       Publication pub_dml_insertonly
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | f       | f       | f         | none              | f
      Tables:
          "public.t1"
      
                                      Publication pub_dml_insertupdate
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | f       | f         | none              | f
      Tables:
          "public.t1"
      
                                        Publication pub_dml_noinsert
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | f       | t       | t       | t         | none              | f
      Tables:
          "public.t1"
      
                                          Publication pub_dml_none
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | f       | f       | f       | f         | none              | f
      Tables:
          "public.t1"
      
                                       Publication pub_dml_notruncate
        Owner   | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
      ----------+------------+---------+---------+---------+-----------+-------------------+----------
       postgres | f          | t       | t       | t       | f         | none              | f
      Tables:
          "public.t1"
      
      
      test_pub=# SELECT * FROM pg_publication_rel;
        oid  | prpubid | prrelid | prqual | prattrs 
      -------+---------+---------+--------+---------
       16436 |   16435 |   16385 |        | 
       16438 |   16437 |   16385 |        | 
       16440 |   16439 |   16385 |        | 
       16442 |   16441 |   16385 |        | 
       16444 |   16443 |   16385 |        | 
       16446 |   16445 |   16385 |        | 
      (6 rows)
      
      
      test_pub=# SELECT oid, relname, relnamespace FROM pg_class WHERE oid IN (16385);
        oid  | relname | relnamespace 
      -------+---------+--------------
       16385 | t1      |         2200
      (1 row)
      
      test_pub=# SELECT oid, nspname FROM pg_namespace WHERE oid IN (2200);
       oid  | nspname 
      ------+---------
       2200 | public
      (1 row)
      
      test_pub=# SELECT * FROM pg_publication_tables;
             pubname        | schemaname | tablename |   attnames    | rowfilter 
      ----------------------+------------+-----------+---------------+-----------
       pub_dml_insertonly   | public     | t1        | {c1,c2,c3,c4} | 
       pub_dml_insertupdate | public     | t1        | {c1,c2,c3,c4} | 
       pub_dml_noinsert     | public     | t1        | {c1,c2,c3,c4} | 
       pub_dml_notruncate   | public     | t1        | {c1,c2,c3,c4} | 
       pub_dml_default      | public     | t1        | {c1,c2,c3,c4} | 
       pub_dml_none         | public     | t1        | {c1,c2,c3,c4} | 
      (6 rows)

      A simple DML filtering example

      An example allowing only INSERT operations to be replicated:

      Publisher Subscriber
      CREATE PUBLICATION pub FOR TABLE t1
      WITH (publish = 'insert');
       
       
      CREATE SUBSCRIPTION sub
      CONNECTION 'dbname=test_pub' PUBLICATION pub;
      INSERT INTO t1 VALUES (1,2,3,4);
      Note – the INSERT is replicated because publish = 'insert'
      DELETE FROM t1 WHERE (c1 = 1);
      Note – the DELETE is NOT replicated
      SELECT * FROM t1;
      c1 | c2 | c3 | c4
      ----+----+----+----
      (0 rows)
      SELECT * FROM t1;
      c1 | c2 | c3 | c4
      ----+----+----+----
      1 | 2 | 3 | 4
      (1 row)

      Publication parameters for partition tables

      ill-office-worker-31-variation-02Partition table replication is governed by the publish_via_partition_root boolean parameter. This parameter is a bit different because it does not determine WHAT data is published; it describes HOW the data is published.

      The following examples demonstrate how using this parameter changes the replication behaviour.

      When the parameter is false

      CREATE PUBLICATION mypub … WITH (publish_via_partition_root=false);

      When the parameter is false, partitions are replicated to subscriber tables of the same name. In this example the subscriber-side has same partition tables as the publisher side. We could also have just had 3 regular tables at the subscriber-side and not have a T_root table at all.

      Example: publish_via_partition_root=false

      When the parameter is true

      CREATE PUBLICATION mypub … WITH (publish_via_partition_root=true);

      When parameter is true, all the partition data is replicated to a single table with the same name as the partition root of the publisher. It means that the subscriber-side root table must be present, but that is the only requirement – e.g. in this first example, the subscriber root table is just a regular table with no partitions at all.

      Example 1: publish_via_partition_root=true

      CREATE PUBLICATION mypub … WITH (publish_via_partition_root=true)

      In the second example below the parameter is true still, but now the subscriber root table is not a regular table – it too is a partitioned table. Notice that because replication is through the T_root, the subscriber-side partitions here are different to those on the publisher.

      Example 2: publish_via_partition_root=true

      Publication command keywords for descendant tables

      CREATE PUBLICATION mypub FOR TABLE ONLY employee;

      The ONLY keyword indicates that only the Parent table is published, but not any descendant/Child tables.

      Example: replicating ONLY a parent table

      In the following example, the optional '*' indicates that the parent is published as well as all descendant tables.

      CREATE PUBLICATION mypub FOR TABLE employee *;

      Example: replicating the parent and descendant tables

      Combinations

      Where they are compatible, the various CREATE PUBLICATION clauses can be combined in any order.

      The following matrix gives an idea of what is possible:

        FOR ALL TABLES TABLES IN SCHEMA s TABLE t TABLE t (column list) WHERE (row filter) WITH (publish_gen cols) WITH (publish DML ops)
      FOR ALL TABLES
      TABLES IN SCHEMA s
      TABLE t
      TABLE t (column list)
      WHERE (row filter)
      WITH (publish generated cols)
      WITH (publish DML ops)

      Note: Row filters and column lists can only be applied to specific tables, not to FOR ALL TABLES or TABLES IN SCHEMA.

      Combining multiple clauses can result in powerful (albeit complicated) publications.

      Syntax flexibility

      • TABLES IN SCHEMA and TABLES can be combined in any order:
        CREATE PUBLICATION pub FOR TABLES IN SCHEMA s2, TABLE t1;
        CREATE PUBLICATION pub FOR TABLE t1, TABLES IN SCHEMA s2;
      • Specifying the same SCHEMA or same TABLE multiple times is permitted.
        CREATE PUBLICATION pub FOR TABLES IN SCHEMA s2, s2, s2;
        CREATE PUBLICATION pub FOR TABLE t1, t1, t1;

        But, specifying different column lists for the same table is not permitted:

        CREATE PUBLICATION pub FOR TABLE t1, t1(c1), t1(c2);
        ERROR: conflicting or redundant column lists for table "t1"
      • If the TABLE is a member of a SCHEMA, then it is "absorbed" by the schema clause:
        CREATE PUBLICATION pub FOR TABLES IN SCHEMA s2, TABLE s2.t4;
      • There are "short" and "long" ways to express exactly the same combinations:
        CREATE PUBLICATION pub FOR TABLE t1, t2;
        CREATE PUBLICATION pub FOR TABLE t1, TABLE t2;
        CREATE PUBLICATION pub FOR TABLES IN SCHEMA public, s2;
        CREATE PUBLICATION pub FOR TABLES IN SCHEMA public, TABLES IN SCHEMA s2;
      • To specify row filters for multiple tables, do so per table:
        CREATE PUBLICATION pub FOR TABLE t1 WHERE (c1 > 2), t2 WHERE (c3 = 6);
      • The WITH clause allows multiple options to be specified at the same time:
        CREATE PUBLICATION pub FOR TABLE t1 WITH (publish = 'insert', publish_generated_columns=stored);

      Quick reference

      Choose your publication type based on your needs:

      Requirement Command
      Need everything?
      CREATE PUBLICATION … FOR ALL TABLES
      Organize by schema?
      CREATE PUBLICATION … FOR TABLES IN SCHEMA
      Know exact tables?
      CREATE PUBLICATION … FOR TABLE t1, t2, t3
      Exclude sensitive columns? ⇒
      Add column list
      CREATE PUBLICATION … FOR TABLE t1(col1, col2)
      Filter by geography/tenant/time? ⇒
      Add WHERE clause
      CREATE PUBLICATION … FOR TABLE t1 WHERE (condition)
      Control which operations replicate?
      CREATE PUBLICATION … WITH (publish='...')
      Handle partitions differently?
      CREATE PUBLICATION … WITH (publish_via_partition_root=true)
      Setting up incrementally? ⇒
      Start empty, then ALTER
      CREATE PUBLICATION …; ALTER PUBLICATION …

      Quick reference table

      Conclusion

      ill-office-worker-27-variation-01This concludes our in-depth exploration of the CREATE PUBLICATION command syntax. By now, you should recognize that this command can vary from straightforward to highly complex.

      Additionally, as briefly noted, there are nuanced interactions between publications and subscriptions, where decisions made during CREATE PUBLICATION can significantly impact the outcome.

      For more complex publications, it is advisable to experiment with test data to ensure the behaviour aligns with your expectations.

      For the future

      img-businessman-in-suit-02-isolated-03

      The CREATE PUBLICATION command continues to evolve.

      The following are currently under development and may become part of PG19.

      • SEQUENCES
        • CREATE PUBLICATION … FOR ALL SEQUENCES
        • CREATE PUBLICATION … FOR SEQUENCES IN SCHEMA s1
        • CREATE PUBLICATION … FOR SEQUENCE seq1,seq2,seq3
      • EXCEPT (table list)
        • CREATE PUBLICATION … FOR ALL TABLES EXCEPT (t1,t2,t3)
        • CREATE PUBLICATION … FOR TABLES IN SCHEMA s1 EXCEPT (t1,t2,t3)
      • EXCEPT (column list)
        • CREATE PUBLICATION … FOR TABLE t1 EXCEPT (c1,c2,c3)

      Further reading

      This blog was primarily all about the CREATE PUBLICATION command and all its variations. Naturally, there are lots of topics that have overlap with this area of functionality

      Here are some related topics for further reading:

      • CREATE SUBSCRIPTION
        Of course, there is the other side of the coin to CREATE PUBLICATION.
        There are also some special considerations for how Subscriptions work together with Publications:
        • e.g. Subscription with initial copy_data=true may ignore Publication Row Filtering
        • e.g. The Publication ‘publish’ option is for DML only; it has no effect for Subscription initial data synchronization
        • e.g. When a Subscription subscribes multiple Publications the row filters all get merged
        • e.g. When a Subscription subscribes the same table from multiple Publications columns list must be identical
      • ALTER PUBLICATION
        We saw already that there is a use-case for creating an empty Publication up-front and then using ALTER PUBLICATION to add members to it. After altering the Publication, the Subscriber may need to do REFRESH PUBLICATION to get the changes.
      • ALTER SUBSCRIPTION … REFRESH PUBLICATION
        Might be needed if the publication changes, or when using FOR ALL TABLES future tables and tables have been added after the Subscription is already created.
      • DDL Replication
        Currently there is no implementation for PostgreSQL, but there have been various proposals for this functionality.
      • REPLICA IDENTITY
        A table’s REPLICA IDENTITY defines what columns are necessary to uniquely identify a row of table data. If a table without a replica identity is added to a publication that replicates UPDATE or DELETE operations, then subsequent UPDATE or DELETE operations will cause an error on the publisher. You might encounter Replica Identity errors when publishing column lists if the necessary columns are not published.

      Where to find more information

      Topics: PostgreSQL, Logical replication, Database replication, PostgreSQL guide, SQL tutorial, CREATE PUBLICATION

      Receive our blog

      Search by topic

      Posts by Tag

      See all
      Learn more about the extended and unique features that
      Fujitsu Enterprise Postgres
      provides to harness your data.
      Click below to view the list of features.
      Peter Smith
      Principal Software Development Engineer
      As Senior Software Development Engineer, PostgreSQL based solutions at Fujitsu, Peter has more than 30 years of experience software engineering, playing a pivotal role as C/Java developer in a diverse range of Fujitsu projects.
      Recently recognized as a contributor to PostgreSQL community, Peter has also presented at conferences and posted blogs about PostgreSQL internals.

      Receive our blog

      Fill the form to receive notifications of future posts

      Search by topic

      see all >