How to implement constraints that go beyond a single row in PostgreSQL?Data inconsistency prohibition if a...

Why do objects rebound after hitting the ground?

Do the speed limit reductions due to pollution also apply to electric cars in France?

Is layered encryption more secure than long passwords?

Why did Ylvis use "go" instead of "say" in phrases like "Dog goes 'woof'"?

Why write a book when there's a movie in my head?

Does rolling friction increase speed of a wheel?

Would water spill from a bowl in a Bag of Holding?

What happened to Hermione’s clothing and other possessions after she wiped her parents’ memories of her?

Buying a "Used" Router

Was there a pre-determined arrangement for the division of Germany in case it surrendered before any Soviet forces entered its territory?

Are all power cords made equal?

Is practicing on a digital piano harmful to an experienced piano player?

How do I avoid the "chosen hero" feeling?

What are some idioms that means something along the lines of "switching it up every day to not do the same thing over and over"?

What are some ways of extending a description of a scenery?

What does an unprocessed RAW file look like?

Identical projects by students at two different colleges: still plagiarism?

Why "rm -r" is unable to delete this folder?

smartctl reports overall health test as passed but the tests failed?

Is there a way to pause a running process on Linux systems and resume later?

How unreachable are Jupiter's moons from Mars with the technology developed for going to Mars?

Why don't you get burned by the wood benches in a sauna?

Was Opportunity's last message to Earth "My battery is low and it's getting dark"?

Minimum Viable Product for RTS game?



How to implement constraints that go beyond a single row in PostgreSQL?


Data inconsistency prohibition if a table refers to another via two many-to-many relationshipsHow to implement a 'default' flag that can only be set on a single rowHow to constrain dataHow to change schema so that account_id reference is unique among 3 tablesTables names in a supertype/subtype schemaHow can I implement a sequence for each foreign key value?Foreign Key Constraint over two tablesHow to store user activity that supports undo?How should I model a database structure to store word information on flashcards?How to check if there are no time gaps in PostgreSQL?













2















How to implement constraints that go beyond a single row in PostgreSQL?



Examples 1:
With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?



Examples 2:
With 5 tables: Session, Auth which has a sessionId and accountId fields, Account, Link which links Accounts to Users, and User. How can I enforce that a Session can only link to Accounts (via Auth) that are linked to the same User (via Link)? In other words, a Session cannot link to Accounts that are linked to different Users.



Thank you!










share|improve this question
















bumped to the homepage by Community 15 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 2





    A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

    – a_horse_with_no_name
    Jul 11 '17 at 5:45













  • Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

    – geeko
    Jul 11 '17 at 5:50











  • Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

    – joanolo
    Jul 11 '17 at 6:29








  • 2





    @geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

    – ypercubeᵀᴹ
    Jul 11 '17 at 8:09








  • 2





    Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

    – Daniel Vérité
    Jul 12 '17 at 18:26
















2















How to implement constraints that go beyond a single row in PostgreSQL?



Examples 1:
With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?



Examples 2:
With 5 tables: Session, Auth which has a sessionId and accountId fields, Account, Link which links Accounts to Users, and User. How can I enforce that a Session can only link to Accounts (via Auth) that are linked to the same User (via Link)? In other words, a Session cannot link to Accounts that are linked to different Users.



Thank you!










share|improve this question
















bumped to the homepage by Community 15 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 2





    A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

    – a_horse_with_no_name
    Jul 11 '17 at 5:45













  • Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

    – geeko
    Jul 11 '17 at 5:50











  • Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

    – joanolo
    Jul 11 '17 at 6:29








  • 2





    @geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

    – ypercubeᵀᴹ
    Jul 11 '17 at 8:09








  • 2





    Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

    – Daniel Vérité
    Jul 12 '17 at 18:26














2












2








2








How to implement constraints that go beyond a single row in PostgreSQL?



Examples 1:
With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?



Examples 2:
With 5 tables: Session, Auth which has a sessionId and accountId fields, Account, Link which links Accounts to Users, and User. How can I enforce that a Session can only link to Accounts (via Auth) that are linked to the same User (via Link)? In other words, a Session cannot link to Accounts that are linked to different Users.



Thank you!










share|improve this question
















How to implement constraints that go beyond a single row in PostgreSQL?



Examples 1:
With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?



Examples 2:
With 5 tables: Session, Auth which has a sessionId and accountId fields, Account, Link which links Accounts to Users, and User. How can I enforce that a Session can only link to Accounts (via Auth) that are linked to the same User (via Link)? In other words, a Session cannot link to Accounts that are linked to different Users.



Thank you!







postgresql constraint






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jul 11 '17 at 11:12







geeko

















asked Jul 11 '17 at 5:38









geekogeeko

18916




18916





bumped to the homepage by Community 15 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 15 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.










  • 2





    A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

    – a_horse_with_no_name
    Jul 11 '17 at 5:45













  • Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

    – geeko
    Jul 11 '17 at 5:50











  • Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

    – joanolo
    Jul 11 '17 at 6:29








  • 2





    @geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

    – ypercubeᵀᴹ
    Jul 11 '17 at 8:09








  • 2





    Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

    – Daniel Vérité
    Jul 12 '17 at 18:26














  • 2





    A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

    – a_horse_with_no_name
    Jul 11 '17 at 5:45













  • Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

    – geeko
    Jul 11 '17 at 5:50











  • Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

    – joanolo
    Jul 11 '17 at 6:29








  • 2





    @geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

    – ypercubeᵀᴹ
    Jul 11 '17 at 8:09








  • 2





    Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

    – Daniel Vérité
    Jul 12 '17 at 18:26








2




2





A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

– a_horse_with_no_name
Jul 11 '17 at 5:45







A unique constraint goes beyond a single row and so do exclusion constraint or foreign key constraints. What exactly is your question?

– a_horse_with_no_name
Jul 11 '17 at 5:45















Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

– geeko
Jul 11 '17 at 5:50





Here is an example with 3 tables Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type of previous purchases if any?

– geeko
Jul 11 '17 at 5:50













Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

– joanolo
Jul 11 '17 at 6:29







Surprising scenario... I'd go for a trigger, or a user-defined function for such a case. It is also possible that a sufficiently strange exclusion constraint (that would be more or less an inclusion) would do the trick as well.

– joanolo
Jul 11 '17 at 6:29






2




2





@geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

– ypercubeᵀᴹ
Jul 11 '17 at 8:09







@geeko edit the question and put all relevant information there, like the example in above comment. What you describe can probably be solved with a simple UNIQUE constraint, if I understand correctly.

– ypercubeᵀᴹ
Jul 11 '17 at 8:09






2




2





Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

– Daniel Vérité
Jul 12 '17 at 18:26





Constraints are part of the database design as much as tables. For instance the (person,food_type) is a relationship that might be expressed as a table, with a unique constraint, and it doesn't go beyond a single row. Or food_type can be a field in the Person table.

– Daniel Vérité
Jul 12 '17 at 18:26










2 Answers
2






active

oldest

votes


















0














Using an EXCLUSION CONSTRAINT




Examples 1: With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?




CREATE TABLE person ( pid serial PRIMARY KEY, pname text );
CREATE TABLE food ( fid serial PRIMARY KEY, fname text );
CREATE TABLE person_food (
pid int REFERENCES person NOT NULL,
fid int REFERENCES food NOT NULL,
EXCLUDE USING gist( pid WITH =, fid WITH != )
);
INSERT INTO person (pid,pname) VALUES (1,'John'),(2,'Mary');
INSERT INTO food (fid,fname) VALUES (1,'Pickles'),(2,'Glue');
INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
INSERT INTO person_food (pid,fid) VALUES (1,2); -- boom.
ERROR: conflicting key value violates exclusion constraint "person_food_pid_fid_excl"
DETAIL: Key (pid, fid)=(1, 2) conflicts with existing key (pid, fid)=(1, 1).





share|improve this answer































    0














    For your first example, let's assume your tables are defined in the following way:



    CREATE TABLE person
    (
    person_id integer PRIMARY KEY,
    person_name text NOT NULL
    ) ;

    CREATE TABLE food
    (
    food_id integer PRIMARY KEY,
    food_name text NOT NULL,
    food_type text NOT NULL /* Unnormalized, for simplicity */
    ) ;

    CREATE TABLE purchase
    (
    purchased_at timestamp not null default now(),
    person_id integer NOT NULL REFERENCES person(person_id),
    food_id integer NOT NULL REFERENCES food(food_id),
    PRIMARY KEY (person_id, food_id, purchased_at)
    ) ;
    CREATE INDEX idx_purchaser_food_id
    ON purchase(food_id, person_id) ;


    I would create a function that checks for your constraint. It's a simple SQL statement, with one corner case (the first purchase), and a "literal" translation of your requirement: the current food type is in (the set of already existing ones):



    CREATE FUNCTION 
    allowed_food_type_for_person(in _person_id integer, in _food_id integer)
    RETURNS
    boolean AS
    $body$
    SELECT
    -- Corner case for first purchase
    (NOT EXISTS
    (SELECT
    *
    FROM
    purchase
    WHERE
    person_id = _person_id
    )
    )
    OR
    (
    -- Current food types
    (SELECT
    food_type
    FROM
    food
    WHERE
    food_id = _food_id)
    IN
    -- Existing food types
    (SELECT
    food_type
    FROM
    purchase
    JOIN food USING(food_id)
    WHERE
    person_id = _person_id)
    )
    $body$
    LANGUAGE SQL STABLE ;


    Now you can add the constraint you wish:



    ALTER TABLE purchase 
    ADD CONSTRAINT check_purchase_food_type_is_limited
    CHECK(allowed_food_type_for_person(person_id, food_id));


    Now, let's imagine this is your data:



    INSERT INTO person
    (person_id, person_name)
    VALUES
    (1, 'Alice Cooper'),
    (2, 'Bob Geldorf') ;

    INSERT INTO food
    (food_id, food_name, food_type)
    VALUES
    (1, 'Banana', 'Fruit'),
    (2, 'Orange', 'Fruit'),
    (3, 'Pear', 'Fruit'),
    (4, 'Lettuce', 'Vegetable'),
    (5, 'Coliflower', 'Vegetable'),
    (6, 'Cellery', 'Vegetable'),
    (7, 'Asparagus', 'Vegetable') ;


    You can have 'Alice' do a first purchase:



    INSERT INTO 
    purchase
    (person_id, food_id)
    VALUES
    (1, 1) ; -- Alice purchased a Banana (fruit)


    Now, she buys an orange, OK:



    INSERT INTO 
    purchase
    (person_id, food_id)
    VALUES
    (1, 2) ; -- Alice purchased an Orange (fruit), Ok


    But if she tries to buy 'Lettuce':



    INSERT INTO 
    purchase
    (person_id, food_id)
    VALUES
    (1, 4) ; -- Alice tries to purchase a Lettuce => fail



    ERROR: new row for relation "purchase" violates check constraint "check_purchase_food_type_is_limited"
    DETAIL: Failing row contains (2017-07-11 19:46:08.830481, 1, 4).


    You can play with it at dbfiddle here





    For your 2nd example, you'd do something equivalent, but more sophisticated. In any case, there are other ways to solve the "diamond problem" you seem to have, by defining the tables in a different way. Check Data inconsistency prohibition if a table refers to another via two many-to-many relationships, for a very good explanation.





    Caveat: As per comment from @DanielVerité: this can have concurrency problems that actual constraints don't have. They can (most probably) be solved by using TRANSACTION ISOLATION LEVEL SERIALIZABLE; incurring the heavy penalty it implies.





    Side question: Why are you against people having a diverse diet? ;-)






    share|improve this answer





















    • 1





      What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

      – Daniel Vérité
      Jul 12 '17 at 18:44













    • @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

      – joanolo
      Jul 14 '17 at 19:24













    Your Answer








    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "182"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f179542%2fhow-to-implement-constraints-that-go-beyond-a-single-row-in-postgresql%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    Using an EXCLUSION CONSTRAINT




    Examples 1: With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?




    CREATE TABLE person ( pid serial PRIMARY KEY, pname text );
    CREATE TABLE food ( fid serial PRIMARY KEY, fname text );
    CREATE TABLE person_food (
    pid int REFERENCES person NOT NULL,
    fid int REFERENCES food NOT NULL,
    EXCLUDE USING gist( pid WITH =, fid WITH != )
    );
    INSERT INTO person (pid,pname) VALUES (1,'John'),(2,'Mary');
    INSERT INTO food (fid,fname) VALUES (1,'Pickles'),(2,'Glue');
    INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
    INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
    INSERT INTO person_food (pid,fid) VALUES (1,2); -- boom.
    ERROR: conflicting key value violates exclusion constraint "person_food_pid_fid_excl"
    DETAIL: Key (pid, fid)=(1, 2) conflicts with existing key (pid, fid)=(1, 1).





    share|improve this answer




























      0














      Using an EXCLUSION CONSTRAINT




      Examples 1: With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?




      CREATE TABLE person ( pid serial PRIMARY KEY, pname text );
      CREATE TABLE food ( fid serial PRIMARY KEY, fname text );
      CREATE TABLE person_food (
      pid int REFERENCES person NOT NULL,
      fid int REFERENCES food NOT NULL,
      EXCLUDE USING gist( pid WITH =, fid WITH != )
      );
      INSERT INTO person (pid,pname) VALUES (1,'John'),(2,'Mary');
      INSERT INTO food (fid,fname) VALUES (1,'Pickles'),(2,'Glue');
      INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
      INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
      INSERT INTO person_food (pid,fid) VALUES (1,2); -- boom.
      ERROR: conflicting key value violates exclusion constraint "person_food_pid_fid_excl"
      DETAIL: Key (pid, fid)=(1, 2) conflicts with existing key (pid, fid)=(1, 1).





      share|improve this answer


























        0












        0








        0







        Using an EXCLUSION CONSTRAINT




        Examples 1: With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?




        CREATE TABLE person ( pid serial PRIMARY KEY, pname text );
        CREATE TABLE food ( fid serial PRIMARY KEY, fname text );
        CREATE TABLE person_food (
        pid int REFERENCES person NOT NULL,
        fid int REFERENCES food NOT NULL,
        EXCLUDE USING gist( pid WITH =, fid WITH != )
        );
        INSERT INTO person (pid,pname) VALUES (1,'John'),(2,'Mary');
        INSERT INTO food (fid,fname) VALUES (1,'Pickles'),(2,'Glue');
        INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
        INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
        INSERT INTO person_food (pid,fid) VALUES (1,2); -- boom.
        ERROR: conflicting key value violates exclusion constraint "person_food_pid_fid_excl"
        DETAIL: Key (pid, fid)=(1, 2) conflicts with existing key (pid, fid)=(1, 1).





        share|improve this answer













        Using an EXCLUSION CONSTRAINT




        Examples 1: With 3 tables: Person, Food which has a field called type, and Purchase which links Persons to Food. How can I enforce that a person can only purchase food of the same type from previous purchases if any?




        CREATE TABLE person ( pid serial PRIMARY KEY, pname text );
        CREATE TABLE food ( fid serial PRIMARY KEY, fname text );
        CREATE TABLE person_food (
        pid int REFERENCES person NOT NULL,
        fid int REFERENCES food NOT NULL,
        EXCLUDE USING gist( pid WITH =, fid WITH != )
        );
        INSERT INTO person (pid,pname) VALUES (1,'John'),(2,'Mary');
        INSERT INTO food (fid,fname) VALUES (1,'Pickles'),(2,'Glue');
        INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
        INSERT INTO person_food (pid,fid) VALUES (1,1); -- works
        INSERT INTO person_food (pid,fid) VALUES (1,2); -- boom.
        ERROR: conflicting key value violates exclusion constraint "person_food_pid_fid_excl"
        DETAIL: Key (pid, fid)=(1, 2) conflicts with existing key (pid, fid)=(1, 1).






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Jul 11 '17 at 20:19









        Evan CarrollEvan Carroll

        32.5k970221




        32.5k970221

























            0














            For your first example, let's assume your tables are defined in the following way:



            CREATE TABLE person
            (
            person_id integer PRIMARY KEY,
            person_name text NOT NULL
            ) ;

            CREATE TABLE food
            (
            food_id integer PRIMARY KEY,
            food_name text NOT NULL,
            food_type text NOT NULL /* Unnormalized, for simplicity */
            ) ;

            CREATE TABLE purchase
            (
            purchased_at timestamp not null default now(),
            person_id integer NOT NULL REFERENCES person(person_id),
            food_id integer NOT NULL REFERENCES food(food_id),
            PRIMARY KEY (person_id, food_id, purchased_at)
            ) ;
            CREATE INDEX idx_purchaser_food_id
            ON purchase(food_id, person_id) ;


            I would create a function that checks for your constraint. It's a simple SQL statement, with one corner case (the first purchase), and a "literal" translation of your requirement: the current food type is in (the set of already existing ones):



            CREATE FUNCTION 
            allowed_food_type_for_person(in _person_id integer, in _food_id integer)
            RETURNS
            boolean AS
            $body$
            SELECT
            -- Corner case for first purchase
            (NOT EXISTS
            (SELECT
            *
            FROM
            purchase
            WHERE
            person_id = _person_id
            )
            )
            OR
            (
            -- Current food types
            (SELECT
            food_type
            FROM
            food
            WHERE
            food_id = _food_id)
            IN
            -- Existing food types
            (SELECT
            food_type
            FROM
            purchase
            JOIN food USING(food_id)
            WHERE
            person_id = _person_id)
            )
            $body$
            LANGUAGE SQL STABLE ;


            Now you can add the constraint you wish:



            ALTER TABLE purchase 
            ADD CONSTRAINT check_purchase_food_type_is_limited
            CHECK(allowed_food_type_for_person(person_id, food_id));


            Now, let's imagine this is your data:



            INSERT INTO person
            (person_id, person_name)
            VALUES
            (1, 'Alice Cooper'),
            (2, 'Bob Geldorf') ;

            INSERT INTO food
            (food_id, food_name, food_type)
            VALUES
            (1, 'Banana', 'Fruit'),
            (2, 'Orange', 'Fruit'),
            (3, 'Pear', 'Fruit'),
            (4, 'Lettuce', 'Vegetable'),
            (5, 'Coliflower', 'Vegetable'),
            (6, 'Cellery', 'Vegetable'),
            (7, 'Asparagus', 'Vegetable') ;


            You can have 'Alice' do a first purchase:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 1) ; -- Alice purchased a Banana (fruit)


            Now, she buys an orange, OK:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 2) ; -- Alice purchased an Orange (fruit), Ok


            But if she tries to buy 'Lettuce':



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 4) ; -- Alice tries to purchase a Lettuce => fail



            ERROR: new row for relation "purchase" violates check constraint "check_purchase_food_type_is_limited"
            DETAIL: Failing row contains (2017-07-11 19:46:08.830481, 1, 4).


            You can play with it at dbfiddle here





            For your 2nd example, you'd do something equivalent, but more sophisticated. In any case, there are other ways to solve the "diamond problem" you seem to have, by defining the tables in a different way. Check Data inconsistency prohibition if a table refers to another via two many-to-many relationships, for a very good explanation.





            Caveat: As per comment from @DanielVerité: this can have concurrency problems that actual constraints don't have. They can (most probably) be solved by using TRANSACTION ISOLATION LEVEL SERIALIZABLE; incurring the heavy penalty it implies.





            Side question: Why are you against people having a diverse diet? ;-)






            share|improve this answer





















            • 1





              What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

              – Daniel Vérité
              Jul 12 '17 at 18:44













            • @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

              – joanolo
              Jul 14 '17 at 19:24


















            0














            For your first example, let's assume your tables are defined in the following way:



            CREATE TABLE person
            (
            person_id integer PRIMARY KEY,
            person_name text NOT NULL
            ) ;

            CREATE TABLE food
            (
            food_id integer PRIMARY KEY,
            food_name text NOT NULL,
            food_type text NOT NULL /* Unnormalized, for simplicity */
            ) ;

            CREATE TABLE purchase
            (
            purchased_at timestamp not null default now(),
            person_id integer NOT NULL REFERENCES person(person_id),
            food_id integer NOT NULL REFERENCES food(food_id),
            PRIMARY KEY (person_id, food_id, purchased_at)
            ) ;
            CREATE INDEX idx_purchaser_food_id
            ON purchase(food_id, person_id) ;


            I would create a function that checks for your constraint. It's a simple SQL statement, with one corner case (the first purchase), and a "literal" translation of your requirement: the current food type is in (the set of already existing ones):



            CREATE FUNCTION 
            allowed_food_type_for_person(in _person_id integer, in _food_id integer)
            RETURNS
            boolean AS
            $body$
            SELECT
            -- Corner case for first purchase
            (NOT EXISTS
            (SELECT
            *
            FROM
            purchase
            WHERE
            person_id = _person_id
            )
            )
            OR
            (
            -- Current food types
            (SELECT
            food_type
            FROM
            food
            WHERE
            food_id = _food_id)
            IN
            -- Existing food types
            (SELECT
            food_type
            FROM
            purchase
            JOIN food USING(food_id)
            WHERE
            person_id = _person_id)
            )
            $body$
            LANGUAGE SQL STABLE ;


            Now you can add the constraint you wish:



            ALTER TABLE purchase 
            ADD CONSTRAINT check_purchase_food_type_is_limited
            CHECK(allowed_food_type_for_person(person_id, food_id));


            Now, let's imagine this is your data:



            INSERT INTO person
            (person_id, person_name)
            VALUES
            (1, 'Alice Cooper'),
            (2, 'Bob Geldorf') ;

            INSERT INTO food
            (food_id, food_name, food_type)
            VALUES
            (1, 'Banana', 'Fruit'),
            (2, 'Orange', 'Fruit'),
            (3, 'Pear', 'Fruit'),
            (4, 'Lettuce', 'Vegetable'),
            (5, 'Coliflower', 'Vegetable'),
            (6, 'Cellery', 'Vegetable'),
            (7, 'Asparagus', 'Vegetable') ;


            You can have 'Alice' do a first purchase:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 1) ; -- Alice purchased a Banana (fruit)


            Now, she buys an orange, OK:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 2) ; -- Alice purchased an Orange (fruit), Ok


            But if she tries to buy 'Lettuce':



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 4) ; -- Alice tries to purchase a Lettuce => fail



            ERROR: new row for relation "purchase" violates check constraint "check_purchase_food_type_is_limited"
            DETAIL: Failing row contains (2017-07-11 19:46:08.830481, 1, 4).


            You can play with it at dbfiddle here





            For your 2nd example, you'd do something equivalent, but more sophisticated. In any case, there are other ways to solve the "diamond problem" you seem to have, by defining the tables in a different way. Check Data inconsistency prohibition if a table refers to another via two many-to-many relationships, for a very good explanation.





            Caveat: As per comment from @DanielVerité: this can have concurrency problems that actual constraints don't have. They can (most probably) be solved by using TRANSACTION ISOLATION LEVEL SERIALIZABLE; incurring the heavy penalty it implies.





            Side question: Why are you against people having a diverse diet? ;-)






            share|improve this answer





















            • 1





              What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

              – Daniel Vérité
              Jul 12 '17 at 18:44













            • @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

              – joanolo
              Jul 14 '17 at 19:24
















            0












            0








            0







            For your first example, let's assume your tables are defined in the following way:



            CREATE TABLE person
            (
            person_id integer PRIMARY KEY,
            person_name text NOT NULL
            ) ;

            CREATE TABLE food
            (
            food_id integer PRIMARY KEY,
            food_name text NOT NULL,
            food_type text NOT NULL /* Unnormalized, for simplicity */
            ) ;

            CREATE TABLE purchase
            (
            purchased_at timestamp not null default now(),
            person_id integer NOT NULL REFERENCES person(person_id),
            food_id integer NOT NULL REFERENCES food(food_id),
            PRIMARY KEY (person_id, food_id, purchased_at)
            ) ;
            CREATE INDEX idx_purchaser_food_id
            ON purchase(food_id, person_id) ;


            I would create a function that checks for your constraint. It's a simple SQL statement, with one corner case (the first purchase), and a "literal" translation of your requirement: the current food type is in (the set of already existing ones):



            CREATE FUNCTION 
            allowed_food_type_for_person(in _person_id integer, in _food_id integer)
            RETURNS
            boolean AS
            $body$
            SELECT
            -- Corner case for first purchase
            (NOT EXISTS
            (SELECT
            *
            FROM
            purchase
            WHERE
            person_id = _person_id
            )
            )
            OR
            (
            -- Current food types
            (SELECT
            food_type
            FROM
            food
            WHERE
            food_id = _food_id)
            IN
            -- Existing food types
            (SELECT
            food_type
            FROM
            purchase
            JOIN food USING(food_id)
            WHERE
            person_id = _person_id)
            )
            $body$
            LANGUAGE SQL STABLE ;


            Now you can add the constraint you wish:



            ALTER TABLE purchase 
            ADD CONSTRAINT check_purchase_food_type_is_limited
            CHECK(allowed_food_type_for_person(person_id, food_id));


            Now, let's imagine this is your data:



            INSERT INTO person
            (person_id, person_name)
            VALUES
            (1, 'Alice Cooper'),
            (2, 'Bob Geldorf') ;

            INSERT INTO food
            (food_id, food_name, food_type)
            VALUES
            (1, 'Banana', 'Fruit'),
            (2, 'Orange', 'Fruit'),
            (3, 'Pear', 'Fruit'),
            (4, 'Lettuce', 'Vegetable'),
            (5, 'Coliflower', 'Vegetable'),
            (6, 'Cellery', 'Vegetable'),
            (7, 'Asparagus', 'Vegetable') ;


            You can have 'Alice' do a first purchase:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 1) ; -- Alice purchased a Banana (fruit)


            Now, she buys an orange, OK:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 2) ; -- Alice purchased an Orange (fruit), Ok


            But if she tries to buy 'Lettuce':



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 4) ; -- Alice tries to purchase a Lettuce => fail



            ERROR: new row for relation "purchase" violates check constraint "check_purchase_food_type_is_limited"
            DETAIL: Failing row contains (2017-07-11 19:46:08.830481, 1, 4).


            You can play with it at dbfiddle here





            For your 2nd example, you'd do something equivalent, but more sophisticated. In any case, there are other ways to solve the "diamond problem" you seem to have, by defining the tables in a different way. Check Data inconsistency prohibition if a table refers to another via two many-to-many relationships, for a very good explanation.





            Caveat: As per comment from @DanielVerité: this can have concurrency problems that actual constraints don't have. They can (most probably) be solved by using TRANSACTION ISOLATION LEVEL SERIALIZABLE; incurring the heavy penalty it implies.





            Side question: Why are you against people having a diverse diet? ;-)






            share|improve this answer















            For your first example, let's assume your tables are defined in the following way:



            CREATE TABLE person
            (
            person_id integer PRIMARY KEY,
            person_name text NOT NULL
            ) ;

            CREATE TABLE food
            (
            food_id integer PRIMARY KEY,
            food_name text NOT NULL,
            food_type text NOT NULL /* Unnormalized, for simplicity */
            ) ;

            CREATE TABLE purchase
            (
            purchased_at timestamp not null default now(),
            person_id integer NOT NULL REFERENCES person(person_id),
            food_id integer NOT NULL REFERENCES food(food_id),
            PRIMARY KEY (person_id, food_id, purchased_at)
            ) ;
            CREATE INDEX idx_purchaser_food_id
            ON purchase(food_id, person_id) ;


            I would create a function that checks for your constraint. It's a simple SQL statement, with one corner case (the first purchase), and a "literal" translation of your requirement: the current food type is in (the set of already existing ones):



            CREATE FUNCTION 
            allowed_food_type_for_person(in _person_id integer, in _food_id integer)
            RETURNS
            boolean AS
            $body$
            SELECT
            -- Corner case for first purchase
            (NOT EXISTS
            (SELECT
            *
            FROM
            purchase
            WHERE
            person_id = _person_id
            )
            )
            OR
            (
            -- Current food types
            (SELECT
            food_type
            FROM
            food
            WHERE
            food_id = _food_id)
            IN
            -- Existing food types
            (SELECT
            food_type
            FROM
            purchase
            JOIN food USING(food_id)
            WHERE
            person_id = _person_id)
            )
            $body$
            LANGUAGE SQL STABLE ;


            Now you can add the constraint you wish:



            ALTER TABLE purchase 
            ADD CONSTRAINT check_purchase_food_type_is_limited
            CHECK(allowed_food_type_for_person(person_id, food_id));


            Now, let's imagine this is your data:



            INSERT INTO person
            (person_id, person_name)
            VALUES
            (1, 'Alice Cooper'),
            (2, 'Bob Geldorf') ;

            INSERT INTO food
            (food_id, food_name, food_type)
            VALUES
            (1, 'Banana', 'Fruit'),
            (2, 'Orange', 'Fruit'),
            (3, 'Pear', 'Fruit'),
            (4, 'Lettuce', 'Vegetable'),
            (5, 'Coliflower', 'Vegetable'),
            (6, 'Cellery', 'Vegetable'),
            (7, 'Asparagus', 'Vegetable') ;


            You can have 'Alice' do a first purchase:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 1) ; -- Alice purchased a Banana (fruit)


            Now, she buys an orange, OK:



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 2) ; -- Alice purchased an Orange (fruit), Ok


            But if she tries to buy 'Lettuce':



            INSERT INTO 
            purchase
            (person_id, food_id)
            VALUES
            (1, 4) ; -- Alice tries to purchase a Lettuce => fail



            ERROR: new row for relation "purchase" violates check constraint "check_purchase_food_type_is_limited"
            DETAIL: Failing row contains (2017-07-11 19:46:08.830481, 1, 4).


            You can play with it at dbfiddle here





            For your 2nd example, you'd do something equivalent, but more sophisticated. In any case, there are other ways to solve the "diamond problem" you seem to have, by defining the tables in a different way. Check Data inconsistency prohibition if a table refers to another via two many-to-many relationships, for a very good explanation.





            Caveat: As per comment from @DanielVerité: this can have concurrency problems that actual constraints don't have. They can (most probably) be solved by using TRANSACTION ISOLATION LEVEL SERIALIZABLE; incurring the heavy penalty it implies.





            Side question: Why are you against people having a diverse diet? ;-)







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jul 14 '17 at 19:27

























            answered Jul 11 '17 at 18:55









            joanolojoanolo

            9,73342153




            9,73342153








            • 1





              What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

              – Daniel Vérité
              Jul 12 '17 at 18:44













            • @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

              – joanolo
              Jul 14 '17 at 19:24
















            • 1





              What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

              – Daniel Vérité
              Jul 12 '17 at 18:44













            • @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

              – joanolo
              Jul 14 '17 at 19:24










            1




            1





            What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

            – Daniel Vérité
            Jul 12 '17 at 18:44







            What happens if the 3rd insert happens when the 2nd one is not yet committed? I think it passes without error and you end up with what's stored in the db violating the constraint. This is not the case for "real" constraints where concurrent inserts are automatically blocked until commit of other TXs involved in the constraint check.

            – Daniel Vérité
            Jul 12 '17 at 18:44















            @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

            – joanolo
            Jul 14 '17 at 19:24







            @DanielVérité: Very good point. I am not 100% sure. I actually do not (explicitly) lock anything, so, I guess some scenarios with high concurrency could cause some race conditions where this can happen. It also would depend on whether this CHECK is performed at the moment the rows are inserted, or when the transaction is commited. In PostgreSQL, as of 9.6, CHECK constraints cannot be deferred. I guess then, this would only work without risk under ISOLATION LEVEL SERIALIZABLE.

            – joanolo
            Jul 14 '17 at 19:24




















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Database Administrators Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f179542%2fhow-to-implement-constraints-that-go-beyond-a-single-row-in-postgresql%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Szabolcs (Ungheria) Altri progetti | Menu di navigazione48°10′14.56″N 21°29′33.14″E /...

            Discografia di Klaus Schulze Indice Album in studio | Album dal vivo | Singoli | Antologie | Colonne...

            How to make inet_server_addr() return localhost in spite of ::1/128RETURN NEXT in Postgres FunctionConnect to...