Attributes as Columns

Assume we stored the shop API’s products in a relational database. Here is a products table we could use. It is the shop API’s table with one change for Shopend: each product records the shop it belongs to.

products
  product_id         integer   primary key
  shop_id            text      not null
  name               text      not null
  description        text      not null
  price_cents        integer   not null, check price_cents >= 0
  quantity           integer   not null, check quantity >= 0
  deleted_at         timestamp null

We decide every column of this table when we design it, and we give each column a type. A mug’s glaze and a shirt’s size are not among those columns, so there is nowhere to store them.

We could add a column for each attribute. Here are three products from three shops, with the price, stock, and description columns left out:

product_id shop_id name glaze size color scent
42 mugshop Blue mug deep blue
7001 threadline Blue linen shirt, medium medium blue
9001 wickandwax Cedar candle cedar

Each product has a value only in the few columns that apply to it. The rest of its columns are null.

A bigger problem is that the merchants choose the attributes. Suppose a new merchant sells bicycles and needs a frame size. Then we have to add a frame_size column to the products table. That table holds the products of every shop. Changing the schema of a table is database work that we plan and run ourselves, so a merchant cannot add an attribute on their own. With many shops, the table would soon have hundreds of columns. Most of them would be null in almost every row.

Two merchants can also use the same attribute name for different kinds of values. For threadline, size is text: small, medium, or large. A candle shop might use size for a numeric height in centimeters. A single size column has one type, so we have to choose how to represent both. We could store everything as text, but then the database cannot check that the candle’s height is a number.

A relational table has one set of columns, and every row has all of them. That works for the details every product has, such as its name, price, and stock. It does not work for attributes, because each merchant decides the attributes of their own products, and those attributes differ from shop to shop.