1

By "disjoint" I mean mutually exclusive sets of ID values. No overlap between both tables.
For example, the sequence generator for the id column on both tables should work in conjunction to make sure they are always disjoint. I am not sure if this is possible. So, I thought I would just ask here.

Table A

id name
0 abc
1 cad
2 pad
3 ial

Table B

id name
40 pal
50 sal
Vivek
  • 344
  • 1
  • 9
  • So you're using `SERIAL` in both tables? – Dai Sep 24 '21 at 23:32
  • that is correct. – Vivek Sep 24 '21 at 23:34
  • Then you can't do it like that, because that's how `SERIAL` works. To what extent can you redesign your database? If the values come from **two separate** `SERIAL` columns then you cannot prevent them from ever possibly having the same value. There is a solution to this problem which requires using a third table, however. – Dai Sep 24 '21 at 23:35
  • I think I can accommodate a third table. How would that solution look like? – Vivek Sep 24 '21 at 23:39
  • The only clean solution is: remodel your data. This could imply a third table. This ugly problem should not exist. – wildplasser Oct 02 '21 at 14:25

3 Answers3

4

A different hack-around:


CREATE TABLE odd(
        id INTEGER GENERATED ALWAYS AS IDENTITY (START 1 INCREMENT 2)
        , val integer
        );


CREATE TABLE even(
        id INTEGER GENERATED ALWAYS AS IDENTITY (START 2 INCREMENT 2)
        , val integer
        );

INSERT INTO odd (val)
SELECT GENERATE_SERIES(1,10);

INSERT INTO even (val)
SELECT GENERATE_SERIES(1,20);

SELECT * FROM odd;
SELECT * FROM even;

Result:


CREATE TABLE
CREATE TABLE
INSERT 0 10
INSERT 0 20
 id | val 
----+-----
  1 |   1
  3 |   2
  5 |   3
  7 |   4
  9 |   5
 11 |   6
 13 |   7
 15 |   8
 17 |   9
 19 |  10
(10 rows)

 id | val 
----+-----
  2 |   1
  4 |   2
  6 |   3
  8 |   4
 10 |   5
 12 |   6
 14 |   7
 16 |   8
 18 |   9
 20 |  10
 22 |  11
 24 |  12
 26 |  13
 28 |  14
 30 |  15
 32 |  16
 34 |  17
 36 |  18
 38 |  19
 40 |  20
(20 rows)
wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • 1
    Clever! You can make this resilient by adding a `CHECK` constraint to ensure numbers are `MOD 2` or `MOD 2 + 1` respectively. – Dai Sep 26 '21 at 23:14
3

A very simple way is to share the same SEQUENCE:

CREATE TABLE a (
  id serial PRIMARY KEY
, name text
);

CREATE TABLE b (
  id int PRIMARY KEY
, name text
);

SELECT pg_get_serial_sequence('a', 'id');  -- 'public.a_id_seq'

ALTER TABLE b ALTER COLUMN id SET DEFAULT nextval('public.a_id_seq');  -- !

db<>fiddle here

This way, table a "owns" the sequence, while table b draws from the same source. You can also create an independent SEQUENCE if you prefer.

Note: this only guarantees mutually exclusive new IDs (even under concurrent write load) while you don't override default values and also don't update them later.

Related:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
2

Welcome to the painful world of inter-table constraints or assertions - this is something that ISO SQL and pretty much every RDBMS out there does not handle ergonomically...

(While ISO SQL does describe both deferred-constraints and database-wide assertions, as far as I know only PostgreSQL implements deferred-constraints, and no production-quality RDBMS supports database-wide assertions).


One approach is to have a third-table which is the only table with SERIAL (aka IDENTITY aka AUTO_INCREMENT) with a discriminator column which combined forms the table's primary-key, then the other two tables have an FK constraint to that PK - but they'll also need the same discriminator column (enforced with a CHECK constraint), but you will never need to reference that column in most queries.

As your post doesn't tell us what the real table-names are, I'll use my own.

Something like this:

CREATE TABLE postIds (
    postId   int     NOT NULL SERIAL,
    postType char(1) NOT NULL, /* This is the discriminator column. It can only contain ONLY either 'S' or 'G' which indicates which table contains the rest of the data */

    CONSTRAINT PK_postIds PRIMARY KEY ( postId, postType ),
    CONSTRAINT CK_type CHECK ( postType IN ( 'S', 'G' ) )
);

CREATE TABLE shitposts (
    postId   int     NOT NULL,
    postType char(1) DEFAULT('S'),

    foobar   nvarchar(255)     NULL,
    etc      int           NOT NULL,

    CONSTRAINT PK_shitpostIds PRIMARY KEY ( postId, postType ),
    CONSTRAINT CK_type CHECK ( postType = 'S' ),
    CONSTRAINT FK_shitpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
);

CREATE TABLE goldposts (
    postId   int     NOT NULL,
    postType char(1) DEFAULT('G'),

    foobar   nvarchar(255)     NULL,
    etc      int           NOT NULL,

    CONSTRAINT PK_goldpostIds PRIMARY KEY ( postId, postType ),
    CONSTRAINT CK_type CHECK ( postType = 'G' ),
    CONSTRAINT FK_goldpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
)

With this design, it is impossible for any row in shitposts to share a postId value with a post in goldposts and vice-versa.

However it is possible for a row to exist in postIds without having any row in both goldposts and shitposts. Fortunately, as you are using PostgreSQL you could add a new FK constraint from postIds to both goldposts and shitposts but use it with deferred-constraints.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • There is no `nvarchar` in Postgres. And I'd consider `"char"` (with quotes) instead of `char(1)`. (Never use the type `char`) And set `postType` to `NOT NULL` or the multicolumn FK can be sidestepped with default `MATCH SIMPLE` behavior - see: https://dba.stackexchange.com/a/71508/3684. Other than that, it's a good (if tedious) solution! – Erwin Brandstetter Sep 25 '21 at 00:19
  • @ErwinBrandstetter Yes, thank you for pointing that out - I mostly work in T-SQL instead of Postgres so my T-SQLisms slip through. – Dai Sep 25 '21 at 00:23
  • Aside: `SERIAL` and `IDENTITY` are related but separate concepts in Postgres. See: https://stackoverflow.com/a/9875517/939860 – Erwin Brandstetter Sep 25 '21 at 00:24
  • @ErwinBrandstetter This thread says that they’re the same concept/feature, but IDENTITY is ISO-compliant and SERIAL is legacy: https://stackoverflow.com/questions/55300370/postgresql-serial-vs-identity – Dai Sep 25 '21 at 02:01