Skip to Main Content
  • Questions
  • Constraint to allow either NULL or distinct values

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Samuel.

Asked: February 05, 2018 - 3:43 pm UTC

Last updated: February 26, 2018 - 11:30 am UTC

Version: 10g+

Viewed 1000+ times

You Asked

Hi guys,

I wonder if it is possible to create a constraint on a table which does either allow NULL in a specified column for a group of items *or* a unique value.

I set up an example. Imagine you can either set a jedi-unit to manage themselves and not to give explicit orders where they should do their guarding duty. In that case you would leave fk_outpost blank - the unit self-managing.
Or you can give explicit orders, for example half of a jedi-unit guarding outpost 1, half guarding outpost 2.

A unit can either be
- Self-managing, in which case it will have a single guard duty with an outpost of null
- Managed, in which case there can be many outposts for the unit. All the outposts for that unit must be different

You cannot mix those two states, therefore rows 1 and 2 of the final select would be illegal and I would like to prevent that state from happening with constraints.

I thought about using triggers, but I think it won't be reliable with multiple session then, would it?

Best regards,
Samuel


with LiveSQL Test Case:

and Chris said...

Here's one way to approach this:

- Add an "order management" property to Jedi units with values SELF and MANAGED (or similar)
- Propagate this jedi_guard_contingent and when this is :
- MANAGED ensure fk_output is not null
- SELF ensure fk_output is null
- Create a unique constraint over ( fk_jedi_unit, fk_outpost )

And you're done!

create table outpost ( 
    id integer not null primary key 
);

insert into outpost values ( 1 );
insert into outpost values ( 2 );

create table jedi_unit ( 
    id integer not null primary key, 
    members integer default 10 not null ,
    order_management varchar2(10) not null,
    unique (id, order_management)
);

insert into jedi_unit values ( 1, 10, 'SELF' );
insert into jedi_unit values ( 2, 10, 'MANAGED' );
insert into jedi_unit values ( 3, 10, 'SELF' );

create table jedi_guard_contingent ( 
    id integer not null primary key,
    fk_jedi_unit integer not null, 
    order_management varchar2(10) not null, 
    fk_outpost integer, 
    used_members integer not null, 
    constraint jgc_fk_unit foreign key ( fk_jedi_unit, order_management ) 
      references jedi_unit( id, order_management ), 
    constraint jgc_fk_outpost foreign key ( fk_outpost ) references outpost( id ) ,
    constraint jgc_output_managed_c check ( 
      case when order_management = 'MANAGED' then fk_outpost else 1 end is not null 
    ),
    constraint jgc_output_self_c check ( 
      case when order_management = 'SELF' then fk_outpost end is null 
    ),
    unique (fk_jedi_unit, fk_outpost)
);

insert into jedi_guard_contingent values ( 1, 1, 'SELF', null, 5 );
insert into jedi_guard_contingent values ( 2, 1, 'SELF', 1, 5 );
insert into jedi_guard_contingent values ( 3, 2, 'MANAGED', 1, 5 );
insert into jedi_guard_contingent values ( 4, 2, 'MANAGED', 2, 5 );
insert into jedi_guard_contingent values ( 5, 3, 'SELF', null, 10 );
insert into jedi_guard_contingent values ( 6, 1, 'SELF', 3, 5 );
insert into jedi_guard_contingent values ( 7, 2, 'MANAGED', null, 5 );
insert into jedi_guard_contingent values ( 8, 1, 'SELF', null, 5 );
select * from jedi_guard_contingent;

ID   FK_JEDI_UNIT   ORDER_MANAGEMENT   FK_OUTPOST   USED_MEMBERS   
   1              1 SELF                     <null>              5 
   3              2 MANAGED                       1              5 
   4              2 MANAGED                       2              5 
   5              3 SELF                     <null>             10 


Depending on how you want to manage changes to jedi_guard_contingent, you may want to create the FK to jedi_unit deferrable. This allows you to update the order management property of a unit. Then changes its assignments.

I thought about using triggers, but I think it won't be reliable with multiple session then, would it?

It's possible to do this with triggers. But it's tricky, making mistakes likely.

And thanks for the cool test case ;)

Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Possible alternative for some cases

Kim Berg Hansen, February 05, 2018 - 8:29 pm UTC

If the items array does not contain other arrays of name/value pairs than the two arrays of interest, an alternative can be to just wildcard the arrayname instead of someAttributes and someOtherAttributes:

SELECT j.*
  FROM t, json_table(doc_json, '$.items[*]' COLUMNS (
    item_id VARCHAR2 PATH '$.id',
    NESTED PATH '$.*[*]' COLUMNS (
      attr_name VARCHAR2 PATH '$.name',
      attr_value VARCHAR2 PATH '$.value'
    )
   )
  ) j
;


The disadvantage of course is that it will pick up all name/value arrays within the items array. So you need to be sure of your JSON to use the wildcard approach.
Chris Saxon
February 06, 2018 - 9:54 am UTC

I think you meant this for the other question, right? ;)

Samuel Nitsche, February 06, 2018 - 7:40 am UTC


And how to enforce Jedi_guard_contingent is either SELF or MANAGED but not both?

Samuel Nitsche, February 26, 2018 - 9:16 am UTC

A follow-up to that question: How could I now enforce jedi_guard_contingent to contain either SELF or MANAGED entries - but not both?
I could easily insert the following:

insert into jedi_guard_contingent values ( 9, 2, 'SELF', null, 5 );


which would lead to illegal data. Any ideas how to prevent that besides AFTER trigger in an autonomous transaction?
Chris Saxon
February 26, 2018 - 11:30 am UTC

This is an example of an ARC relationship. I discuss solutions at:

https://blogs.oracle.com/sql/implementing-a-mutually-exclusive-relationship-in-the-database

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.