Data Modeling

A decomposition is lossless when the tables you create by splitting an original relation can be joined back together to produce exactly the original records: no missing records and no invented combinations. The practical proof usually does not come from trying a join on today’s data. It comes from the keys and functional dependencies that must hold for all valid data in the model.

What lossless decomposition means

Lossless decomposition of relational tables means you can split a relation into smaller relations and later join those smaller relations to recover the original relation exactly.

Suppose an original relation R has attributes A, B, and C. If you decompose it into R1(A, B) and R2(B, C), the decomposition is lossless only if joining R1 and R2 on B returns the same tuples that were in R. If the join creates extra combinations, the decomposition is lossy.

The word lossless can be slightly misleading. A lossy decomposition does not only lose information by dropping rows. It can also lose information by forgetting which attributes belonged together, which causes the join to create spurious rows.

Why practitioners should care before splitting tables

Practitioners split tables for good reasons: reducing repeated attributes, separating reference data from events, preparing tables for analytics, or making a model easier to maintain. The risk is that a visually tidy split can silently break the original meaning of the data.

Lossless decomposition matters when:

  • You normalize a wide operational table into entity and relationship tables.
  • You move repeated descriptive attributes into a lookup or dimension table.
  • You refactor analytics models and expect existing metrics to remain stable.
  • You separate facts from descriptors and need joins to preserve the original grain.

If the decomposition is not lossless, downstream joins may double-count, create impossible combinations, or make a dashboard appear internally consistent while answering the wrong question.

A small hypothetical table to reason from

Use this hypothetical order-line relation as the running example:

OrderLine(OrderID, LineNo, ProductID, ProductName, CustomerID)

The intended grain is one row per line on an order. Assume the following business rules are valid for this simplified example:

  • OrderID and LineNo together identify one order line.
  • ProductID identifies exactly one ProductName.
  • An order line records one ProductID and one CustomerID.

The important dependency for the decomposition is: ProductID determines ProductName. In relational notation, ProductID → ProductName.

OrderID LineNo ProductID ProductName CustomerID
1001 1 P10 Keyboard C7
1001 2 P20 Mouse C7
1002 1 P10 Keyboard C9

Example: a lossless decomposition

Now split the original relation into two relations:

  • OrderLineFact(OrderID, LineNo, ProductID, CustomerID)
  • Product(ProductID, ProductName)

The common attribute between the two decomposed tables is ProductID. Because ProductID determines ProductName, ProductID is sufficient to find the matching row in Product for each order line.

The join reconstructs the original attributes:

  • OrderLineFact contributes OrderID, LineNo, ProductID, and CustomerID.
  • Product contributes ProductName.
  • The shared ProductID connects each fact row to exactly the product name it had in the original relation.

This decomposition is lossless under the stated dependency. More formally, the intersection of the decomposed relations is ProductID, and ProductID determines all attributes in Product. That satisfies the binary lossless decomposition rule.

Counterexample: a decomposition that creates spurious rows

Now consider a different hypothetical relation:

ClinicAssignment(Doctor, Clinic, Specialty)

Suppose the original records say that Dr. Avery works at North Clinic in Cardiology and Dr. Blake works at North Clinic in Neurology. A tempting split is:

  • DoctorClinic(Doctor, Clinic)
  • ClinicSpecialty(Clinic, Specialty)

The shared attribute is Clinic. But Clinic does not determine Doctor, and Clinic does not determine Specialty. North Clinic can have multiple doctors and multiple specialties. When you join the decomposed tables on Clinic, the join pairs every doctor at North Clinic with every specialty at North Clinic.

The result includes invented records, such as Dr. Avery in Neurology and Dr. Blake in Cardiology, even though those combinations were not in the original relation. The decomposition forgot which doctor-specialty pairing was true.

Result row after joining decomposed tables Was it in the original?
Dr. Avery, North Clinic, Cardiology Yes
Dr. Avery, North Clinic, Neurology No; spurious row
Dr. Blake, North Clinic, Cardiology No; spurious row
Dr. Blake, North Clinic, Neurology Yes

The practical proof rule for a two-table decomposition

For a relation R decomposed into R1 and R2, the decomposition is lossless if the shared attributes determine all attributes of at least one decomposed table.

In compact form:

  • Let the shared attributes be R1 ∩ R2.
  • The decomposition is lossless if R1 ∩ R2 → R1, or if R1 ∩ R2 → R2.

This is the most useful practitioner rule for ordinary two-table splits. You do not need the shared attributes to be a key of both tables. They need to be a key for at least one side of the join, under the functional dependencies you are relying on.

In the order-line example, ProductID is the shared attribute. ProductID determines ProductID and ProductName, so it determines all attributes of Product. The split is lossless.

In the clinic example, Clinic is the shared attribute. Clinic does not determine all attributes of DoctorClinic because one clinic can have many doctors. Clinic also does not determine all attributes of ClinicSpecialty because one clinic can have many specialties. The split is lossy.

Operator rule

For a two-table split, the shared columns must identify one side of the join. If they merely describe a group, expect spurious rows.

Why joining today’s tables is a test, not a proof

A common practical move is to decompose the table, join the decomposed tables back together, and compare the result to the original. This is useful, but it proves less than many teams think.

If the joined result differs from the original, you have found a real problem with the decomposition, the join condition, or the data. But if the joined result matches today, that only proves the current sample did not expose the problem. Tomorrow’s data may still break the assumption unless the model enforces or documents the dependency.

A data test can answer: did this decomposition preserve this current dataset? A dependency proof answers: will this decomposition preserve every valid dataset that follows the declared rules?

Practical warning

A successful reconstruction test on current data is evidence, not proof. The proof comes from functional dependencies that hold for all valid data.

Checklist for evaluating a proposed decomposition

Before approving a table split, work through these checks:

  1. Name the original relation and grain. Be precise about what one row means before decomposition.
  2. List the decomposed relations. Write their attributes explicitly.
  3. Identify the shared attributes. These are the attributes that will be used to join the decomposed tables.
  4. State the functional dependencies. Do not rely on intuition. Write rules such as ProductID → ProductName.
  5. Check whether the shared attributes determine one side. For a binary decomposition, the intersection must determine all attributes of R1 or all attributes of R2.
  6. Look for many-to-many pairing risk. If both sides can have multiple rows for the same shared value, the join can create spurious combinations.
  7. Decide how the dependency is protected. Use keys, uniqueness constraints, reference tables, model contracts, or tests depending on the system.
  8. Run a reconstruction test anyway. It is not a full proof, but it catches implementation mistakes and bad assumptions in current data.
Question Good sign Warning sign
What are the shared attributes? They are a key for one decomposed table. They are only a label, category, or grouping field.
Can each shared value match many rows on both sides? No; one side is unique for the shared value. Yes; the join may create combinations.
Is the dependency enforced or documented? There is a declared key, uniqueness rule, or reliable model contract. The dependency is assumed because the current data happens to look clean.
Does history change the meaning? The determinant includes the needed version or effective period. Current and historical meanings are mixed under one key.

Common failure modes that make a split lossy

Most lossy decompositions come from a small set of modeling mistakes.

  • Joining on a category instead of an identifier. A region, status, clinic, or product category often groups many records. It usually does not identify one record.
  • Separating attributes that only make sense together. If Doctor and Specialty are paired facts, splitting them through Clinic may destroy that pairing.
  • Assuming current uniqueness is permanent. A column may look unique today only because the dataset is small or incomplete.
  • Ignoring time or version. If ProductID maps to different names over time, then ProductID alone may not determine ProductName for historical records. The determining attributes may need an effective date, version, or surrogate key.
  • Using nullable join attributes as if they were keys. Nulls complicate reconstruction because unknown values do not behave like ordinary matching values in SQL joins.
  • Confusing SQL row counts with relational equality. SQL systems often preserve duplicate rows unless you explicitly remove them. If duplicate multiplicity matters, the model needs an identifier for the row-level fact.
Modeling checkpoint

When time, version, or history matters, re-check the dependency. A key that identifies the current description may not identify the historical description.

Relational proof versus SQL implementation

The lossless decomposition rule is a relational modeling rule. SQL is the implementation language many practitioners use to check or apply the model. The two are related, but not identical.

In relational theory, a relation is a set of tuples. In SQL practice, query results can contain duplicates, joins may be written with explicit conditions, and null handling can change the result of comparisons. That means a logically lossless design can still be implemented incorrectly with the wrong join condition or incomplete key.

For implementation checks, compare both directions: the original rows should be present after reconstruction, and the reconstruction should not contain rows that were absent from the original. When duplicates are meaningful, include a stable row identifier or a complete key in the comparison.

A simple decision framework

Use this sequence when deciding whether a decomposition is safe:

  1. If the shared attributes are a declared key of one decomposed table, the binary decomposition is a strong candidate for being lossless. Confirm that the declared key reflects the business rule, not only current data.
  2. If both sides can repeat the shared attributes, assume the decomposition is lossy until proven otherwise. Repeating values on both sides create the conditions for many-to-many recombination.
  3. If time changes the dependency, include time in the determinant. For example, ProductID may not determine ProductName historically if names are versioned. ProductID plus effective period or product-version key may be required.
  4. If no dependency can justify the split, keep the attributes together or introduce the missing relationship table. A tidy schema is not worth losing the original facts.

The goal is not to avoid decomposition. The goal is to decompose only where the join path preserves the facts you started with.

Key takeaways

  • Lossless decomposition means the decomposed tables join back to exactly the original relation.
  • For a two-table decomposition, the shared attributes must determine all attributes of at least one decomposed table.
  • A join that passes on today’s data is a useful test, but it is not a proof for future valid data.
  • Spurious rows appear when the decomposition forgets which attribute values belonged together.
  • Keys, functional dependencies, grain, time, and null behavior all affect whether reconstruction is valid in practice.

Next step

Take one wide table in your environment and write down its grain, proposed decomposed tables, shared attributes, and functional dependencies. Then apply the binary lossless decomposition rule before running a reconstruction test on the current data.

Recommended next reads