14 Pushing relationships down into the entities
Schema which aligns closely with the RM
Predica can also express the supplier-and-parts database in a form that closely mirrors the classical RM structure:
entity Supplier
{
Integer id;
String name;
Integer status;
String city;
key {id};
→ shipment* = inverse shipment.supplier;
}
entity Part
{
Integer id;
String name;
Color color;
Real weight;
String city;
key {id};
→ shipment* = inverse shipment.part;
}
entity Shipment
{
Supplier supplier;
Part part;
Integer qty;
key {supplier,part};
}
entity ShipmentsSystem
{
Supplier supplier*;
Part part*;
Shipment shipment*;
constraints
{
shipment.supplier.Σ ⊆ supplier.Σ;
shipment.part.Σ ⊆ part.Σ;
}
}
This mirrors the classical RM structure.
The base attribute ShipmentsSystem.shipment resembles a global shipments relvar with
two foreign keys supplier and part.
The foreign keys are expressed as inclusion dependencies:
constraints
{
shipment.supplier.Σ ⊆ supplier.Σ;
shipment.part.Σ ⊆ part.Σ;
}
There are two derived attributes Supplier.shipment and Part.shipment
declared using inverse:
entity Supplier
{
...
→ shipment* = inverse shipment.supplier;
}
entity Part
{
...
→ shipment* = inverse shipment.part;
}
These enable path-based navigation from suppliers to shipments and from parts to shipments. For example, the following expression returns the parts shipped by London suppliers:
system.supplier.[city = "London"].shipment.part;
This is a faithful RM-style design, and it works perfectly in Predica. But it carries the same structural implications as the RM:
ShipmentsSystem.shipmentbehaves like a global relationship base relvar.- Navigation from suppliers/parts to shipments depends on
inverse. - A physical implementation would likely materialise a join table.
Predica supports this, but it does not require it.
Pushing relationships down into the entities
The schema used in the introduction pushes the relationship down into the entities that
participate in it.
This gives a logically equivalent schema that maps directly to a more efficient physical
implementation, avoiding the need for inverse entirely:
entity Supplier
{
Integer id;
String name;
Integer status;
String city;
Shipment shipment* where shipment.supplier = this;
key {id};
}
entity Part
{
Integer id;
String name;
Color color;
Real weight;
String city;
Shipment shipment* where shipment.part = this;
key {id};
}
entity Shipment
{
Supplier supplier;
Part part;
Integer qty;
key {supplier,part};
}
entity ShipmentsSystem
{
Supplier supplier*;
Part part*;
→ Shipment shipment* = either supplier.shipment, part.shipment;
}
This version is:
- logically equivalent to the RM-style schema
- physically more efficient
- free of inverse declarations
- free of global relationship relvars
The key idea is simple: A relationship belongs with the entities that participate in it. In Predica, this is not just a stylistic preference, it is a semantic advantage.
Why this works so well
A shipment is naturally “owned” by its supplier and its part. Predica lets you express this directly:
entity Supplier
{
...
Shipment shipment* where shipment.supplier = this;
}
entity Part
{
...
Shipment shipment* where shipment.part = this;
}
These are declarative constraints, not physical pointers.
For the supplier it means:
The shipments belonging to this supplier are exactly those whose supplier attribute equals this supplier.
This expresses the same information as a foreign key, but in a way that:
- supports downward navigation
- avoids global join tables
- avoids inverse declarations
- avoids surrogate identifiers
Shared ownership emerges naturally
Because both Supplier and Part define their shipments, the system exhibits logical shared ownership:
- each supplier records the shipments for which it is the supplier
- each part records the shipments that ship that part
This is shared ownership without:
- object identity
- reference semantics
- object identifiers
- aliasing problems
It is a purely logical notion of sharing, not an OO one.
The global relationship becomes derived, not stored
→ Shipment shipment* = either supplier.shipment, part.shipment;
This is a derived attribute, not a stored one. It declares two equivalent definitions, and the compiler may use whichever one is cheaper in a given query. This is the Predica analogue of:
- a view
- a derived relvar
- a logical relationship
but without the RM’s suggestion for a physical join table.
Emphasising local relationships in queries
Consider the following query in Tutorial D which finds the parts shipped by London suppliers:
(SP JOIN S WHERE CITY='London'){PNO} JOIN P
The most direct counterpart in Predica uses the global shipment predicate:
s.shipment.[supplier.city = "London"].part
However, we can take advantage of the local navigation from supplier to shipment and instead write:
s.supplier.[city = "London"].shipment.part
The second is preferred because it follows the structure of the schema, avoids filtering a global relationship, and is suggestive of an efficient implementation using local navigation.