Tables, Rows, Columns, and Schemas
A table is not simply a spreadsheet stored on a server. It is a named contract for one kind of fact. This lesson teaches you to state what one row means, give every column one clear job, inspect the schema the database actually accepted, write explicit records, and plan changes without treating production data as disposable.
A table is a contract at four levels
Read a table as a blueprint, not a rectangle
A table should model one coherent subject. If its name is products, one row should represent one product—not a product in some rows, a supplier in others, and a subtotal in a final row. This exact meaning is the table’s grain. When grain is unclear, duplicates, partial updates, and confusing queries follow.
The visual below separates the contracts hidden inside a familiar table. The table name identifies the subject. Each column names one attribute of that subject. Constraints narrow the values the database will accept. Rows are current observations that must obey the blueprint.
One row represents one sellable product.
product_idINTEGER · stable identityskuTEXT · required · uniquenameTEXT · requiredprice_centsINTEGER · zero or moreactiveINTEGER · defaults to 1Name the subject
Use a concrete plural noun for a collection of records. Team conventions may prefer singular names; consistency matters more than folklore.
Write the row sentence
“One row represents one sellable product” exposes attributes that belong and unrelated facts that do not.
Define each attribute
A column is not just a label. It carries meaning, an allowed value family, and rules shared by every writer.
Protect the contract
Keys and constraints make invalid states harder to store, even when data arrives outside the main application.
Turn the blueprint into a real table
This runnable example expresses the visual as SQLite SQL. Read each line before running it. The first column provides stable row identity. The SKU is a required unique business value. Money is stored as integer cents so values such as 79.99 do not depend on binary floating-point rounding. The state flag has an explicit default and a bounded set of accepted values.
Build the products table
Create a table with meaningful column contracts, insert two records, and inspect the resulting rows.
Edit the query, predict the rows it will return, then run it.
Rows are records, not positions
A row groups attributes that describe one occurrence at the chosen grain. Its identity comes from data—normally a primary key—not from being “row 7.” Deleting a row or changing an execution plan can change the order in which records happen to appear. If a consumer needs a stable sequence, the query must request it with ORDER BY.
Rows should not contain presentation-only separators, totals mixed into detail data, or several logical records packed into one text value. A spreadsheet might include a blank line or “TOTAL” row for reading; a relational table keeps records consistent and asks a query to calculate or format a report.
Identity is data
A key identifies the exact record independently of its current position in a result.
Grain is one sentence
Every record answers the same kind of question at the same level of detail.
Order is requested
A table does not promise first, last, newest, or alphabetical rows until a query specifies the rule.
A column needs one meaning and one contract
A useful column name tells readers what a value means without opening application code. Prefer price_cents to value1, published_at to date, and billing_email to a generic text. Include the unit when ambiguity could cause a real defect. Names become a long-lived API used by queries, migrations, reports, and integrations.
The declared type describes a broad family of values. NOT NULL says absence is invalid. DEFAULT supplies a value when the writer omits that column; it does not repair an explicitly invalid value. UNIQUE protects candidate identifiers, while CHECK expresses row-level rules. The next lesson explores types and NULL in depth.
Choose an unambiguous domain term and include units where needed.
Text, integer, decimal, date, binary, and engine-specific types have different operations.
Use nullability deliberately instead of allowing absence by accident.
Defaults and constraints protect shared truths at the storage boundary.
Write rows with explicit column lists
Name the target columns in every ordinary INSERT. The second insert below deliberately lists columns in a different order to prove that the names—not table-definition position—control the mapping. The first insert omits room and published, so their declared defaults apply.
Map values to named columns
Compare two explicit INSERT statements, observe defaults, and verify that a reordered column list is still correct.
Edit the query, predict the rows it will return, then run it.
Schema means blueprint—and sometimes namespace
Developers use schema in two related ways. First, it means the database blueprint: tables, columns, types, constraints, indexes, and relationships. In PostgreSQL and several other systems, a schema is also a named namespace inside a database, so sales.orders and support.orders can be distinct tables. SQLite has a simpler model and commonly uses main and temp database namespaces.
Operational boundary
A managed collection of schema objects and durable records.
Named organization
A logical home for related objects in engines that support database schemas.
One record contract
A named relation whose rows share the same columns and integrity rules.
One attribute contract
A named part of every row with a declared type and constraints.
Inspect what the database accepted
Do not rely only on the migration file you intended to run. The live catalog is the database’s account of its current structure. SQLite exposes a schema table and table-valued pragma functions. PostgreSQL exposes standard information_schema views plus richer pg_catalog metadata. MySQL also provides information_schema. Learn the catalog for the engine you operate.
Read SQLite column metadata
Inspect the products table column by column, then read the CREATE TABLE statement stored in SQLite's schema catalog.
Edit the query, predict the rows it will return, then run it.
Schema changes are data changes with a longer memory
An ALTER TABLE changes the contract for existing and future rows. Adding a required column raises an immediate question: what value should old rows receive? In the example, status has an honest default, so existing members can satisfy the new rule. When no honest default exists, a safer migration may add a nullable column, backfill valid values in batches, verify the data, and only then make the column required.
Add and backfill a column
Add a constrained status column, observe the default on existing records, then update one member deliberately.
Edit the query, predict the rows it will return, then run it.
- 01Describe the invariant
State why the new structure is needed and which old and new records must remain valid.
- 02Inspect real data
Measure nulls, duplicates, ranges, volume, and application versions before assuming the migration is safe.
- 03Test a versioned migration
Exercise upgrade, verification, and recovery against representative data in the production DBMS.
- 04Deploy and observe
Coordinate compatible application changes, monitor duration and errors, then confirm the intended constraint exists.
Recognize table-design smells early
Several values in one cell
Repeated values are hard to validate, join, rename, and index. Model a separate relationship when tags become real data.
Numbered repeating columns
A fixed set of slots turns the next value into a schema migration. A child table models a growing collection.
Meaning or unit is hidden
Name the currency or establish it at a clear parent boundary; choose a numeric representation suited to money.
Unbounded magic values
Misspellings create new accidental states. Use a reference table, constrained domain, or explicit check where appropriate.
Derived facts drift
Stored calculations need a clear consistency owner. Otherwise source rows change while duplicated totals become stale.
Generic names erase intent
A query can be syntactically correct and still impossible to review because its vocabulary does not describe the domain.
Independent lab: design a workshops table
Begin with the working table, then strengthen it. Add a required starts_at column using a text timestamp suitable for this SQLite exercise. Insert two more workshops. Return only published workshops with at least 20 seats, ordered by title. Finally, inspect the schema and write one sentence explaining the meaning of a row.
Build a clear workshop record contract
The starter runs, but your work is to extend its schema, records, filtered report, and explanation without weakening an existing constraint.
Edit the query, predict the rows it will return, then run it.
Lesson review
A table is a named contract for one kind of record. Row grain defines what a single record means. Columns name attributes and combine a type with nullability, defaults, and constraints. The schema is the durable blueprint, while rows are changing state and query results are temporary shapes. Explicit inserts document value mappings; metadata catalogs reveal the structure the database actually has; versioned migrations evolve that structure deliberately.
- I can write a one-sentence grain statement before creating a table.
- I can explain the name, type, requiredness, default, and constraints of a column.
- I never rely on row position or an implicit result order.
- I can inspect SQLite table metadata and compare it with the intended DDL.
- I can describe a staged, verifiable schema migration instead of editing production ad hoc.
- I recognize repeating values, numbered columns, mixed grain, vague names, and unsafe derived data as design smells.