36

Is it possible to assign a foreign key to a json property in PostgreSQL? Here is an example what I would like to achieve, but it doesn't work:

CREATE TABLE Users (Id int NOT NULL PRIMARY KEY);

CREATE TABLE Data (
    Id int NOT NULL PRIMARY KEY,
    JsonData json NOT NULL, -- [{Id: 1, somedata: null},{Id: 2, somedata: null}, ...]
    CONSTRAINT FK_Users_Data FOREIGN KEY (JsonData->Id) REFERENCES Users(Id) -- this constraint will fail
);
5
  • No, that's not possible. Commented Jun 30, 2014 at 12:04
  • Why don't you take id from the json and use as FK in your table.. Commented Jun 30, 2014 at 12:19
  • @cracker: this is just an example; in my project I have an array of items in json field - without such constraint it will require to create a new table... Commented Jun 30, 2014 at 12:28
  • if you don't have the table then how can you give the foreign key? Commented Jun 30, 2014 at 12:36
  • You could create a trigger that extracts the id value from the JSON when the column is changed and then put it into a real column that can be used to define a foreign key. Commented Jun 30, 2014 at 13:09

4 Answers 4

18

It is not possible, and may not ever be possible, to assign a foreign key to a json property. It'd be a major and quite complicated change to PostgreSQL's foreign key enforcement. I don't think it's impossible to do, but would face similar issues to those experienced by the foreign-keys-to-arrays patch.

With 9.4 it'll be possible to make a whole json object a foreign key as jsonb supports equality tests. In 9.3 you can't even do that.

Sign up to request clarification or add additional context in comments.

2 Comments

Today (2015's), with stable pg9.4+ version, and its community, there are some "good practice" or usual pg-community convention to express foreign key (with formal or informal pg-assign)? See this old linking_in_json as ref to "JSON link conventions" ... Example, here @ArtemGr show alternative, and to express ID in a row, I like JSON Reference, but it is used in pg-community?)
@PeterKrauss No, not really. The situation hasn't changed significantly, much like foreign keys to array fields. There are some difficult performance issues to deal with even if someone wanted to implement it, and so far nobody does.
14

Yes it is possible but you will have to store another value. If you change your schema to:

CREATE TABLE Users (Id int NOT NULL PRIMARY KEY);

CREATE TABLE Data (
    Id int NOT NULL PRIMARY KEY,
    JsonData json NOT NULL,
    UserId int generated always as ((JsonData->>'Id')::int) stored references Users(Id)
);

INSERT INTO Users VALUES (1);

Foreign key that doesn't exist:

INSERT INTO Data VALUES (1, '{"Id": 3}');

Returns the error:

ERROR: insert or update on table "data" violates foreign key constraint "data_userid_fkey" DETAIL: Key (userid)=(3) is not present in table "users".

Foreign key that does work:

INSERT INTO Data VALUES (1, '{"Id": 1}');

Comments

7

Here's a little SPI function have_ids which I use for an integrity constraint on a one-to-many relationship with a jsonb column

CREATE TABLE foo (
  id INTEGER NOT NULL
)

CREATE TABLE bar (
  foo_ids pg_catalog.jsonb DEFAULT '[]'::jsonb NOT NULL,
  CONSTRAINT bar_fooids_chk CHECK (have_ids ('foo', foo_ids))
)

With a couple of triggers on foo it's almost as good as a foreign key.

8 Comments

Do you have a benchmark showing the performance (ideally comparing with its analog in SQL) of your have_ids() function? Even with "bad performance", if it is generic and stable, will be a good proposal to PostgreSQL community.
No, I haven't benchmarked it, but the performance characteristics seems very transparent to me. The function loops over the the JSONB array and performs a SELECT COUNT(*) FROM query for every ID. It's as fast as the sum of these queries. The performance cost of the function itself should be neglible. I don't have an SQL version, neither I know how to write one. I think PosgreSQL will eventually have first-class support for array-based foreign keys (wiki.postgresql.org/wiki/Todo#Referential_Integrity), so this workaround shouldn't be needed in the long term.
@ArtemGr i know it's old, but you could probably increase performance of that loop with COUNT(1) instead of COUNT(*)
@SampsonCrowley I haven't tested it in a while but IIRC the COUNT(*) is optimized away by PostgreSQL (e.g. the star is not expanded into any fields) and I tend to keep COUNT(*) for readability (POLA).
@ArtemGr ah, well I'm out of date then; I always COUNT(1) because it has the same readability (to me) and I've always heard it's more performant than counting column(s)
|
3

The foreign key parameter must be a column name:

http://www.postgresql.org/docs/current/static/sql-createtable.html

You will have to normalize

create table user_data (
    id int not null primary key,
    user_id int not null,
    somedata text,
    constraint fk_users_data foreign key (user_id) references Users(Id)
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.