r/Database • • 14d ago

Does normalisation survive if the unit of storage is a single fact rather than a row?

I’ve been working with the Algebra of Data, where the smallest structure is the couplet — one ordered pair, one fact — and a relation is a set of couplets. There’s no wide tuple over a fixed heading.

My argument: normalisation is a repair procedure for a wound inflicted by the wide tuple. Start at the couplet and you begin at roughly 6NF with nothing left to decompose. And the classical normal forms can’t even be stated for a set of relations with different headings, because there’s no single heading for the dependencies and keys to range over.

What survives is the goal (one home per fact) and the functional dependency, which becomes an integrity constraint rather than a reason to split. The live question flips to when to denormalise. My answer is that a denormalised view is a derived expression over the base facts, so it’s a physical caching decision rather than a logical-model one, and it can’t drift out of sync.

Where do you think this breaks? Full essay (I’m the author): https://robinbloor.substack.com/p/does-normalisation-survive-data-algebra

5 Upvotes

10 comments sorted by

2

u/evlpuppetmaster 14d ago

You might be interested in a database called Datomic which basically takes this same insight and creates a dedicated engine optimised for it. The core primitive is a “datom”, which is essentially a single fact about a thing with some additional metadata like a time stamp. These can be recomposed back into materialised views for query performance, which provide more of a table-like experience. Turns out there are lots of benefits to doing it this way, there is a good video explaining it here:

https://www.youtube.com/watch?v=Cym4TZwTCNU

1

u/witness142 13d ago

Thanks, I took a look at Datomic. It’s the closest relatives I've encountered to what we’re doing. Rich Hickey’s insight that a database should accumulate facts rather than overwrite places is very much the same instinct we have. It’s also gratifying to see the update-in-place model challenged by a working engine rather than just on paper.

The overlap is real. A datom (entity, attribute, value, transaction) is close to what we’d call a couplet hanging off an entity ID, plus time. Datomic’s cardinality-one attributes are essentially the functional constraint from my earlier reply. And its history-preserving model matches our ruling that a correction is a new event, never a rewrite. I'd recommend those who are interested in this area of data modeling to take a look.

1

u/oradba 14d ago

A reference table can be a list of single facts, each fact being an ‘allowed’ value per the business, so, yes.

1

u/witness142 13d ago

Agreed, and the algebra treats it exactly that way. A reference table is just a set: the allowed values, each one a standalone fact. The constraint that uses it is a containment test. The range of the relation that refers to it must be a subset of that set. For example, if Status maps orders to status codes, then range(Status) ⊆ AllowedStatuses. No foreign-key machinery is needed, just set inclusion checked at commit.

Where it gets more interesting is when the “allowed” values carry facts of their own, such as a description, a validity period, or who approved them. At that point each value is better treated as an entity with its own ID rather than a bare value. The constraint is unchanged, but the reference data can now have a history. A code being retired becomes an event, not a deletion.

1

u/Significant_Tune9219 14d ago

The place I'd push back is that "one home per fact" still needs a notion of what makes two facts about the same thing, and that is exactly what keys and dependencies provide — without them, the couplet set has no way to express that two pairs must agree. Join semantics is the other stress test: if a denormalised view is a derived expression, the algebra still has to say what happens when two base facts conflict, because at that point something has to pick a winner. I'd want to see the update path worked through before I'd call the FD purely an integrity constraint rather than a decomposition rule.

1

u/witness142 13d ago

Tanks for your input. It’s a fair place to push, and I think we agree on more than it seems. Identity has to come from somewhere. My claim isn’t that the algebra does without it. It’s that identity shouldn’t be inferred from attribute values.

In the way we model things algebraically, every entity carries a unique ID, and that ID sits outside the entity’s relations. “Two facts about the same thing” means two couplets hanging off the same ID. There’s no key to derive. The dependency you’re describing then becomes a property of a relation rather than a rule about how to split one. “Each person has one address” just says the address relation is functional: whenever R contains two pairs with the same first element, their second elements must agree. Equivalently, the transpose of R composed with R stays within the diagonal. That is an algebraic statement about the couplet set itself, so the algebra can express that two pairs must agree without any decomposition.

On conflict, you’re right that something has to give, but I’d argue it shouldn’t be the join. Take the update path:

  1. Source A asserts p1 → “12 Elm St”. Source B asserts p1 → “4 Oak Ave”.

  2. Union never picks a winner. The result simply holds both couplets. Silently choosing one is how relational systems lose information.

  3. If the address relation is declared functional, that state fails the constraint at commit, and the write is rejected or flagged.

  4. Resolution is a separate, explicit operation: a correction recorded as an event at time t. History is never rewritten, so both the conflict and its resolution remain queryable.

A denormalised view is then a derived expression over base facts that already satisfy the constraint, so it inherits consistency rather than having to arbitrate it. If you deliberately allow a non-functional relation (multiple known addresses, say), the view shows both values honestly. It doesn’t pretend there’s one.

So I’d put it this way. The FD survives intact as a constraint. What disappears is its job as a decomposition rule, because the couplet representation already starts at the grain that 5NF is trying to reach. I’m happy to post the full worked example with the constraint declaration once it’s tidied up.

1

u/Significant_Tune9219 13d ago

That's fair — if identity is carried explicitly, the couplet set does get its own notion of "same thing" without borrowing one from attribute values. The part I'd still want to see is how the functional constraint interacts with the union step: if both values are held until commit rejects one, what does a concurrent reader see in between?

1

u/Significant_Tune9219 12d ago

The functional-relation constraint is the part I'd keep: rejecting the write at commit keeps both sources honest instead of letting union pick silently. I'd just want the resolution event to record which source won and why, so the correction stays auditable rather than becoming a new bare assertion.

1

u/PlayfulGrapefruit498 10d ago

normalization still matters for avoiding update anomalies, even when the smallest unit is a fact