Skip to main content

Entity-Attribute-Value (EAV) Abuse

EAV abuse stores structured data as generic name/value rows to dodge schema changes, destroying type safety, constraints, and query performance. Model structured data with real columns or a JSONB column, reserving EAV for genuinely dynamic attributes.

The entity-attribute-value (EAV) pattern stores data as triples: an entity ID, an attribute name, and a value, all in one generic table. Instead of products(id, color, weight), you get attributes(entity_id, attr_name, attr_value) with one row per attribute. It is sometimes legitimate for genuinely sparse, user-defined attributes, but it is abused when used as the default model for structured data to avoid writing migrations.

Why It Happens

EAV promises infinite flexibility: add a new attribute without a schema change. Teams reach for it when requirements feel unstable, when building multi-tenant products where each customer wants custom fields, or when a relational database is forced to behave like a document store. The appeal is avoiding the perceived friction of ALTER TABLE.

Why It Hurts

EAV throws away everything a relational database is good at. The value column must be a single type, usually text, so numbers, dates, and booleans lose their types and their validation. Foreign keys and check constraints cannot target individual attributes. Reading one logical record requires pivoting many rows, often with multiple self-joins, which is slow and hard to write. Indexes become coarse and ineffective. Reporting tools cannot understand the schema. What looked like flexibility becomes a write-only data swamp.

Warning Signs

  • A table with columns literally named attribute_name and attribute_value.
  • Reconstructing one business object requires several joins or a pivot.
  • Queries filter on attr_name = 'price' instead of a typed column.
  • Application code re-implements type checking the database should enforce.

Better Alternatives

For structured data, model it with real columns and normalization, accepting that schema changes are a healthy, routine part of development. For a bounded set of optional or sparse attributes, a JSONB (or equivalent semi-structured) column gives flexibility while preserving indexing and partial typing within one row. For per-tenant customization, consider schema-per-tenant or a sidecar custom-fields table with typed columns per data type rather than one universal text column.

How to Refactor Out of It

Identify the attributes that are actually used and their real types. Create typed columns or a JSONB column to hold them. Backfill by pivoting the EAV rows into the new structure, casting values to their proper types and quarantining any that fail. Switch reads and writes to the new model, then retire the EAV table. Going forward, reserve EAV strictly for truly dynamic, user-defined fields, and document that constraint so it does not creep back into core entities.