In parent-child relationships, it’s common you’ll want to ensure there’s at least one child row for every parent row. For example, every account must have a payment method, every airport must have a runway, and every team must have a player.
This is a one-to-at-least-one relationship. For every parent row there must be one or more matching child rows. Oracle AI Database offers two constraints you can use to enforce this:
Read the discussion to help you choose which of these to use or check the cheat sheet.

Guarantee one-to-at-least-one with assertions
Assertions enable you to define cross-row, cross-table constraints.
They have two key parts:
all: a subquery that returns the rows to check.satisfy: a Boolean expression; the database rejects rows where this isfalse.
So, to ensure there’s at least one player on every team:
select allthe teams.satisfythereexistsa player for this team.
Giving an assertion like:
/* Ensure there is a player listed for every team */
create assertion at_least_one_player_per_team
check (
all ( select team_id from teams ) team
satisfy (
exists (
select 'a player'
from players play
where play.team_id = team.team_id
)
)
)
deferrable initially deferred;
This is a simple, declarative way to define the rule. But assertions were only added in release 23.26.1. If you’re working with older releases, you’ll need to use the foreign key method below.
Before we get to that, note the deferrable initially deferred clause in the assertion. By default, the database validates constraints after each statement. Meaning it checks the assertion is true as soon as you insert the parent row. But child rows don’t yet exist, so the assertion raises an error!
/* Force assertion to be checked at the statement level */
set constraint all immediate;
/* There are no players assigned to the team, so the assertion raises an error */
insert into teams
set team_id = 1,
team_name = 'No players';
ORA-08601: SQL assertion (CHRIS.AT_LEAST_ONE_PLAYER_PER_TEAM) violated.
You also can’t insert any players because they have a foreign key back to the team. Deferring the constraint delays validation until commit. This avoids the circular dependency problem.
For example:
/* Defer the assertion until commit */
set constraint at_least_one_player_per_team deferred;
/* The insert now succeeds...*/
insert into teams
set team_id = 1,
team_name = 'No players';
/* ...but the commit doesn't if there are no players */
commit;
ORA-02091: transaction rolled back
ORA-08601: SQL assertion (CHRIS.AT_LEAST_ONE_PLAYER_PER_TEAM) violated.
/* Insert the team and player in the same transaction */
insert into teams
set team_id = 1,
team_name = 'One player';
insert into players
set player_id = 1,
player_name = 'First player',
team_id = 1;
/* Commit now succeeds because the team has a player */
commit;
The simplicity of assertions makes these an obvious choice if you’re running the latest release of Oracle AI Database. If you’re working with older releases, you need use the foreign key method. To do this you need to extend the schema. As we’ll see this, is something you may want to do whichever version you’re using.

Ensure one-to-at-least-one with foreign keys
The traditional way to enforce one-to-at-least-one relationships is with a mandatory column on the parent. This parent column has a foreign key to the child table. Thus, a child row must exist for every parent row, enforcing the at-least-one part of the rule.
To do this you need a way to pick which child row to reference from the parent. Often there’s a clear candidate for this. This happens when one of the child rows is the primary, owning, or default row for its parent. For example:
- Every account must have a default payment method.
- Every airport must have a primary runway.
- Every team must have a captain.
So, you can ensure every team has at least one player by stating its captain on the teams table. This captures a real rule, so consider this modelling technique over others, such as a flag on the players table to say if someone is the captain.
The code below adds this column to the teams table with a foreign key to players:
/* Drop assertion to use FK method instead */
drop assertion at_least_one_player_per_team;
/* Add a captain column to the teams table
Have a DEFERRABLE foreign key from the captain to players;
This ensures there is at least one player for every team */
alter table teams
add (
captain_player_id,
constraint team_captain_fk
foreign key ( captain_player_id )
references players
deferrable initially deferred
);
/* Set a captain for every team */
update teams team
set captain_player_id = (
select max ( player_id )
from players play
where play.team_id = team.team_id
);
/* Make the new column mandatory to ensure every team has a captain */
alter table teams
modify captain_player_id not null;
This creates a circular dependency between the parent and child. So, as with assertions, either the foreign key or not null constraint must be deferrable.
With this in place, every team must have a captain:
insert into teams
set team_id = 2,
team_name = 'Team with captain',
captain_player_id = 2;
insert into players
set player_id = 2,
player_name = 'Captain',
team_id = 2;
commit;
But this implementation is incomplete.
While the foreign key ensures the captain is a player, it could be a player assigned to a different team!
For example, you can make one player the captain for many teams:
/* Every team must have a captain... */
insert into teams
set team_id = 3,
team_name = 'Captain from wrong team',
captain_player_id = 1;
/* ...but it could be any player on any team! */
commit;
To avoid this, the foreign key from the parent back to the child must include the parent’s primary key. If, as in this example, the child table’s primary key doesn’t include the parent’s, you need to create a fake unique constraint on the child which does. Then have the parent foreign key point to this unique constraint.
For example:
/* Add unique constraint including the parent key
The captain FK will reference this */
alter table players
add constraint team_player_u
unique ( team_id, player_id );
/* Remove the old FK */
alter table teams
drop constraint team_captain_fk;
/* Add an FK that references the unique constraint on teams */
alter table teams
add constraint team_captain_fk
foreign key ( team_id, captain_player_id )
references players ( team_id, player_id )
deferrable initially deferred
novalidate;
/* Errors because captain is a player on a different team */
insert into teams
set team_id = 4,
team_name = 'Captain from wrong team',
captain_player_id = 1;
commit;
ORA-02091: transaction rolled back
ORA-02291: integrity constraint (CHRIS.TEAM_CAPTAIN_FK) violated - parent key not found
/* Insert team and captain playing for that team */
insert into teams
set team_id = 4,
team_name = 'Correct captain',
captain_player_id = 4;
insert into players
set player_id = 4,
player_name = 'Captain team 4',
team_id = 4;
commit;
This method also adds an extra step if you want to remove the captain from a team. You need to assign another player to remove them:
/* To remove the captain, you must have another player... */
insert into players
set player_id = 5,
player_name = 'New captain team 4',
team_id = 4;
/* ...that you can assign as captain of the team */
update teams
set captain_player_id = 5
where team_id = 4;
/* allowing you to remove the old captain */
delete players
where player_id = 4;
Using foreign keys to implement one-to-at-least-one works best when there’s a clear rule to pick one of the children. But it still works when none of the child rows could be considered the primary row.
In these cases, you’ll have to come up with your own method to choose which child row the parent references. The downside of doing this is people may infer meaning you didn’t intend from this relationship. You’ll have to balance how important the at-least-one rule is against any confusion picking any child row may bring.
Choosing how to enforce one-to-at-least-one relationships
If you’re running a release of Oracle AI Database before 23.26.1, the decision is made: you can only use foreign keys. When there’s an obvious way to pick which child row each parent should reference this can be the best approach. It captures the rule in a way you can see by glancing at the schema.
That said, assertions give several advantages. They save an extra update when you want to delete the primary child row for a parent and give you the flexibility to conditionally apply the at-least-one rule.
Because the all clause defines which rows the rule applies to, you can check a subset of the data. For example, you may only need to ensure active teams have at least one player; inactive teams can have no assigned players.
To do this, filter out inactive teams in the all clause:
/* Ensure active teams have at least one player
Inactive teams can have no players */
create assertion at_least_one_player_per_active_team
check (
all ( select team_id from teams where is_active ) team
satisfy (
exists (
select 'a player'
from players play
where play.team_id = team.team_id
)
)
)
deferrable initially deferred;
Assertions also provide a clear way to enforce the at-least-one rule when there is no obvious way to pick which child row a parent should reference. They enable you to express the logic with simple SQL without stretching the data model beyond its capacity.
If assertions are unavailable to you and there’s no clear candidate child row for each parent, you face a tough choice. Either define a synthetic rule for choosing a child, possibly introducing unintended meaning, or leave the rule unenforced in the database.
Decision cheat sheet
One-to-at-least-one relationships are common. Here’s a cheat sheet summarising how you choose which constraints to use to guarantee the at-least-one part of this rule.

Create these tables to run the examples in this post:
create table teams (
team_id integer primary key,
team_name varchar2(255) not null,
is_active boolean default false
);
create table players (
player_id integer primary key,
player_name varchar2(255) not null,
team_id references teams not null
);
