92 Supplier Relation Examples

This chapter uses a small supplier example to show why both s-relations and m-relations are useful views of Predica data. Its purpose is to make the extension view concrete.

Mixed Cardinalities

Consider a supplier type with attributes having different cardinality constraints:

entity Supplier
{
    Integer id;
    String city*;
    Integer status?;
}

entity ShipmentsSystem
{
    Supplier supplier*;
}

Suppose a particular shipments system contains two suppliers. The first supplier has id 17, is located in London and Athens, and has no status. The second supplier has id 23, is located in Paris, and has status 20.

As unary predicate extensions on each supplier, this can be displayed compactly as one m-tuple per supplier:

{
    < id: {17}, city: {"London", "Athens"}, status: {} >,
    < id: {23}, city: {"Paris"}, status: {20} >
}

This is an object-centric view. Each supplier remains one represented object, and each attribute records the extension of the corresponding unary predicate for that supplier.

Observing Id and City

The path expression s.supplier.(id, city) may be displayed as a logic frame whose result attributes are id and city:

id=I:Integer, city=C:String
where ∃S:Supplier (s.supplier(S) ∧ S.id(I) ∧ S.city(C))

Under the heading {id, city}, the object-centric m-tuples above project to the m-relation:

M =
{
    < id: {17}, city: {"London", "Athens"} >,
    < id: {23}, city: {"Paris"} >
}

Multiplying out gives the s-relation extension:

M!_{id,city} =
{
    { id: 17, city: "London" },
    { id: 17, city: "Athens" },
    { id: 23, city: "Paris" }
}

This is where an m-relation can be a useful implementation representation. It records two supplier rectangles rather than three id-city rows, and the saving can be much larger when several attributes have many values.

Adding Optional Status

Now expose id, city, and status as single-valued relation attributes:

id=I:Integer, city=C:String, status=T:Integer
where ∃S:Supplier (s.supplier(S) ∧ S.id(I) ∧ S.city(C) ∧ S.status(T))

The corresponding m-relation is:

N =
{
    < id: {17}, city: {"London", "Athens"}, status: {} >,
    < id: {23}, city: {"Paris"}, status: {20} >
}

Under the heading {id, city, status}, the first m-tuple expands to no s-tuples because status has no values. Therefore:

N!_{id,city,status} =
{
    { id: 23, city: "Paris", status: 20 }
}

This is the correct s-relation extension of the frame above. The frame asks for assignments to I, C, and T such that S.status(T) holds. For supplier 17 no such T exists.

Preserving the Empty Extension

Sometimes the desired result is not "one row for each status value", but "one row per supplier, where the status attribute records the whole status extension". That is a different result shape. It must return a set-valued term rather than multiply out status as a single-valued relation attribute.

id=I:Integer, cities=CS:Set<String>, statuses=TS:Set<Integer>
where ∃S:Supplier (
    s.supplier(S)
    ∧ S.id(I)
    ∧ CS = { C:String | S.city(C) }
    ∧ TS = { T:Integer | S.status(T) }
)

For the sample data, this gives a supplier-preserving s-relation:

{
    { id: 17, cities: {"London", "Athens"}, statuses: {} },
    { id: 23, cities: {"Paris"}, statuses: {20} }
}

The rule of thumb is therefore:

  • Use multiplied-out s-relation attributes when the result should contain one row per value combination.
  • Use set-valued result terms when the result should preserve an object while recording an attribute's whole predicate extension, including the empty extension.

Why This Matters

The relational-model view is excellent for the s-relation extension. The m-relation view is useful because Predica objects naturally carry many unary predicate extensions with different cardinality constraints. A single m-tuple can preserve that structure without immediately forming every Cartesian combination.

The m-relation is still trusted only through expansion. It is a correct representation of the s-relation extension when its heading-relative expansion is the intended s-relation.