sqloraclehierarchical-data

Map table to exactly one of three


I have the following tables (taken out of a larger schema). It worked well for the intitial purpose (i.e., optionally map only the parent category to the wallets_balance_history table).

enter image description here

Now, there is a need to change this when we introduced sub-categories (i.e., categories level 1, and 2).

Essentially, I need to modify this schema such that I can query all the sub-categories (if any) for any single row in wallets_balance_history table. The categories all are optional unless they a parent of a sub-category. That is, they can be null, or only parent category, or both parent category and category_level_1, or all: category, category_level_1, and category_level_2. But you can't have category_level_n without the parent category.

Option 1: Add fk to wallets_balance_history for every category level (i.e., add three columns mapped to the to the three category levels). It's the easiest, but does it follow the best practice? what if the number of categories grow.

Option 2: Add bridge/lookup table between category and wallets_balance_history table, say bridge. In this table I store the each category in separate rows, but all share the same bridge id. Then use that bridge id in wallets_balance_history table. The bridge id won't be PK (since it will not be unique in bridge

Is there better, cleaner option? I want to be able to query the categories associated with the balance history without building complex if/else statements.


Solution

  • Don't use multiple tables.

    Option 3:

    Create one table and have a hierarchical data structure with a referential constraint that points to the primary key of the same table:

    CREATE TABLE categories (
      id          NUMBER(10,0)
                  CONSTRAINT categories__id__pk PRIMARY KEY,
      name        VARCHAR2(200),
      description VARCHAR2(4000),
      wallet_id   REFERENCES wallets (id),
      parent_id   REFERENCES categories (id),
      user_id     NUMBER(10,0),
      status_id   NUMBER(10,0),
      type_id     NUMBER(10,0),
      created_by  VARCHAR2(20),
      created_on  DATE,
      updated_by  VARCHAR2(20),
      updated_on  DATE,
      CONSTRAINT categories__wi_xor_pi__nn CHECK (
           (wallet_id IS NULL AND parent_id IS NOT NULL)
        OR (wallet_id IS NOT NULL AND parent_id IS NULL)
      )
    )
    

    The wallets_balance_history can then point to the categories.id column and, because there is only a single table, you do not need to worry about trying to add multiple columns or a bridging table.

    If you then want to query the categories then use a hierarchical query.