Create an Initial Duality View

Create the initial duality view that exposes the baseline canonical storage model used throughout the schema evolution patterns in this chapter. Every remaining topic evolves this model while preserving compatibility for applications that continue using earlier document shapes.

Dave starts with a small lemonade stand. Although the business has only one cart today, he expects to expand to multiple locations. Stand information is therefore modeled separately from sales so that each stand has its own identity, location, and pricing information.

A sale records who bought lemonade, how many cups were sold, how much was charged, and which stand fulfilled the order.

This initial model intentionally makes several simplifying business assumptions. A sale represents one product. Customer information belongs directly to the sale. Stand location is represented by one field. These assumptions are appropriate for a small business, but they will gradually change as the business grows.

This topic introduces the first JSON-relational duality view. Unlike the compatibility APIs introduced later in the chapter, this view is not intended to remain a long-term contract. Its purpose is to expose the initial canonical storage model from which every remaining evolution pattern begins.

Relational Schema

The initial relational model intentionally contains only two business entities. Keeping the starting model simple makes the later evolution patterns easier to understand because each subsequent topic evolves one of these original design decisions.

Table Role
sales Stores sale-level information such as date, quantity, amount, customer name, email address, and stand reference.
stands Stores stand-level information such as location and price per cup.

Initial Relational DDL

CREATE TABLE stands (
  stand_id       INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY,
  location       VARCHAR2(100) NOT NULL,
  price_per_cup  NUMBER(10, 2) DEFAULT 2.5 NOT NULL,
  CONSTRAINT stands_pk PRIMARY KEY (stand_id)
);

CREATE TABLE sales (
  sale_id        INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY,
  sale_date      DATE DEFAULT SYSDATE,
  quantity       NUMBER NOT NULL,
  amount         NUMBER(10, 2) NOT NULL,
  customer_name  VARCHAR2(50) DEFAULT 'User' NOT NULL,
  email_address  VARCHAR2(100) DEFAULT NULL,
  stand_id       INTEGER DEFAULT 1 NOT NULL,
  CONSTRAINT sales_pk PRIMARY KEY (sale_id),
  CONSTRAINT sales_stand_fk FOREIGN KEY (stand_id) REFERENCES stands (stand_id)
);

The relationship is:

sales.stand_id -> stands.stand_id

Each sale references exactly one stand. Although the information is stored in two relational tables, applications work with a single JSON document.

At this point, the canonical storage model is simple. A sale owns customer information directly, references one stand, and implicitly represents one product. Later topics evolve each of those assumptions as the business grows.

First Duality View

sales_view exposes the canonical storage model as a single JSON document. Applications work with one business object, while the underlying storage model remains relational.

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW sales_view AS
sales @insert @update @delete
{
    _id: sale_id,
    date: sale_date,
    quantity,
    amount,
    customer_name,
    email_address,
    stand_info: stands @insert @update @nodelete
    {
        _id: stand_id,
        location,
        price_per_cup
    }
};

The application sees a document shaped like this:

{
    "_id": 1,
    "date": "2026-01-12",
    "quantity": 8,
    "amount": 22.00,
    "customer_name": "nora",
    "email_address": "nora@example.com",
    "stand_info": {
        "_id": 1,
        "location": "MARKET CART, NORTH PLAZA",
        "price_per_cup": 2.75
    }
}

What the View Introduces

Although this example is intentionally simple, it establishes the architectural separation that every remaining topic builds upon.

Applications work with a JSON sale document. The database stores the same business information in relational tables. The duality view connects those two representations without requiring applications to understand the underlying storage model.

Design decision Why it matters
Sale is the document root. A sale is the business transaction applications naturally read and write.
Stand is represented as a nested object. Stand information belongs with each sale document while remaining an independent business entity in the canonical storage model.
@nodelete The lifetime of a stand is independent of any individual sale.

This separation between the document API and the canonical storage model is the architectural foundation for every evolution pattern that follows. Later topics evolve the JSON shape, the relational schema, or both, while preserving compatibility for existing applications.

Evolution Check

At this point, the canonical storage model reflects a deliberately simple business model:

The remaining topics evolve one assumption at a time. As the business grows, the canonical storage model changes to reflect new business requirements, while compatibility APIs preserve earlier document shapes for existing applications.

sales_view is only the introductory view. The first durable compatibility API begins in the next scenario with sales_v1.