0

I have a table called timesheets. In that table there are 4 columns for which I want to create a constraint so that only the following combinations are possible:

  • If user_is is set then task_schedule_id must be null.
  • If location_id or customer_id is set, then require user_id to be not null and ensure that not both location_id and customer_id are set.
  • If task_schedule_id is set then require user_id, customer_id and location_id to be null.

This is the table:

CREATE TABLE IF NOT EXISTS timesheets
(
    id               uuid        NOT NULL DEFAULT gen_random_uuid(),
    created_at       timestamptz NOT NULL DEFAULT current_timestamp,
    updated_at       timestamptz NOT NULL DEFAULT current_timestamp,
    deleted_at       timestamptz NULL,

    -- Where and who (check if location_id or customer_id is set then require user)
    location_id      uuid        NULL,
    customer_id      uuid        NULL,
    user_id          uuid        NULL,

    -- Or what... BUT not both
    task_schedule_id uuid        NULL,

    -- Billing
    billable         bool        NOT NULL DEFAULT TRUE,
    billed_at        timestamptz NULL,
    
    -- fks and pk
    FOREIGN KEY (user_id) REFERENCES users (id),
    FOREIGN KEY (task_schedule_id) REFERENCES task_schedules (id),
    FOREIGN KEY (location_id) REFERENCES locations (id),
    FOREIGN KEY (customer_id) REFERENCES customers (id),
    PRIMARY KEY (id)
);

And this is what I have so far:

ALTER TABLE timesheets
    ADD constraint only_one_group
        check (
                ((user_id is null and customer_id is null and location_id is null) and
                 task_schedule_id is not null)
                or
                (user_id is not null and not (customer_id is null and location_id is null) and
                 (customer_id is null or location_id is null) and
                 task_schedule_id is null)
            );

The context is that a task_schedule links to a task which can contain a location_id and or a customer_id. The idea is that timesheets can be created globally or in combination with tasks.

Tom
  • 4,070
  • 4
  • 22
  • 50

1 Answers1

2

You can write the constraints as:

ALTER TABLE timesheets
  ADD constraint just_user__or__location_or_customer_with_user__or__just_task check (
      (
        user_id is not null
        and task_schedule_id is null
        and (
          (location_id is null and customer_id is null)
          and (location_id is not null or customer_id is not null)
        )
      ) or (
        (location_id is not null or customer_id is not null)
        and not (location_id is not null and customer_id is not null)
        and user_id is not null
      ) or (
        task_schedule_id is not null
        and user_id is null
        and location_id is null
        and customer_id is null
      )
    );
Tom
  • 4,070
  • 4
  • 22
  • 50
The Impaler
  • 45,731
  • 9
  • 39
  • 76
  • @Tom Please see revised solution, – The Impaler Jan 03 '21 at 15:47
  • When I write severals `OR` with parenthesis, `CONSTRAINT` is well created but parenthesis are not saved (just the first and the last) if I'm looking at the `SQL` definition with `pgAdmin` for example. How to explain this and doesn't it corrupt the definition ? – GeoGyro Sep 14 '21 at 15:42