Help improve SovranCode?

Google Analytics can measure anonymous usage after you choose Allow. Essential account and progress features work either way.

SovranCode
SQL: Query, Model, and Analyze Data Tables, Rows, Columns, and Schemas
This device
Course contentsTables, Rows, Columns, and Schemas · 32 topics

Relational foundations

Databases, SQL, and the relational modelTables, rows, columns, and schemasData types and NULLPrimary keys, foreign keys, and relationships

Reading and filtering data

Your first SELECT queryConditions with WHERESorting, limiting, and distinct valuesText, dates, patterns, and conditional results

Reports, aggregation, and analytics

Aggregate functionsGROUP BY and HAVINGWindow functionsCommon table expressions

Combining related data

INNER, LEFT, RIGHT, and FULL joinsPlannedSelf joins and many-to-many dataPlannedSubqueries and correlated subqueriesPlannedUNION, INTERSECT, and EXCEPTPlanned

Designing reliable schemas

CREATE TABLE and schema designPlannedConstraints and data integrityPlannedNormalization and intentional denormalizationPlannedViews and materialized viewsPlanned

Writing and protecting data

INSERT, UPDATE, and DELETEPlannedUpserts and conflict handlingPlannedTransactions and savepointsPlannedIsolation, locks, and concurrencyPlanned

Performance and administration

Indexes and access pathsPlannedEXPLAIN and query plansPlannedQuery tuning patternsPlannedBackups, restores, and migrationsPlanned

Security and production workflow

Users, roles, and least privilegePlannedPreventing SQL injectionPlannedStored procedures, functions, and triggersPlannedProduction data projectPlanned
SQL: Query, Model, and Analyze Data12 complete · 20 planned

Relational foundations

Databases, SQL, and the relational modelTables, rows, columns, and schemasData types and NULLPrimary keys, foreign keys, and relationships

Reading and filtering data

Your first SELECT queryConditions with WHERESorting, limiting, and distinct valuesText, dates, patterns, and conditional results

Reports, aggregation, and analytics

Aggregate functionsGROUP BY and HAVINGWindow functionsCommon table expressions

Combining related data

INNER, LEFT, RIGHT, and FULL joinsPlannedSelf joins and many-to-many dataPlannedSubqueries and correlated subqueriesPlannedUNION, INTERSECT, and EXCEPTPlanned

Designing reliable schemas

CREATE TABLE and schema designPlannedConstraints and data integrityPlannedNormalization and intentional denormalizationPlannedViews and materialized viewsPlanned

Writing and protecting data

INSERT, UPDATE, and DELETEPlannedUpserts and conflict handlingPlannedTransactions and savepointsPlannedIsolation, locks, and concurrencyPlanned

Performance and administration

Indexes and access pathsPlannedEXPLAIN and query plansPlannedQuery tuning patternsPlannedBackups, restores, and migrationsPlanned

Security and production workflow

Users, roles, and least privilegePlannedPreventing SQL injectionPlannedStored procedures, functions, and triggersPlannedProduction data projectPlanned
PREVIOUS LESSONDatabases, SQL, and the Relational Model
NEXT LESSONData Types and NULL
Relational foundations · Lesson 02 105 min

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.

What you will leave with

You will be able to distinguish a table from a query result, define row grain, read a column contract, use explicit INSERT column lists, inspect SQLite schema metadata, explain why row order is never implicit, recognize repeating-group design mistakes, and describe a safe schema-migration workflow.

A table is a contract at four levels

Table purposeName one entity or event and write the sentence that every row must satisfy.
Row grainDecide exactly what one row represents before choosing columns or loading records.
Column contractsGive every attribute a name, value family, nullability rule, default, and relevant constraints.
Schema boundaryTreat the durable blueprint separately from the current records and from any one query result.

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.

TABLEproducts

One row represents one sellable product.

PRIMARY KEYproduct_idINTEGER · stable identity
BUSINESS KEYskuTEXT · required · unique
DESCRIPTIONnameTEXT · required
MEASUREprice_centsINTEGER · zero or more
STATEactiveINTEGER · defaults to 1
Rows change as products are created or edited. The schema keeps every row inside the same contract.
01

Name the subject

Use a concrete plural noun for a collection of records. Team conventions may prefer singular names; consistency matters more than folklore.

02

Write the row sentence

“One row represents one sellable product” exposes attributes that belong and unrelated facts that do not.

03

Define each attribute

A column is not just a label. It carries meaning, an allowed value family, and rules shared by every writer.

04

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.

SQL BROWSER RUNNER

Build the products table

Create a table with meaningful column contracts, insert two records, and inspect the resulting rows.

CREATE TABLE products (
  product_id INTEGER PRIMARY KEY,
  sku TEXT NOT NULL UNIQUE,
  name TEXT NOT NULL,
  price_cents INTEGER NOT NULL CHECK (price_cents >= 0),
  active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
);

INSERT INTO products
  (product_id, sku, name, price_cents)
VALUES
  (101, 'KB-001', 'Mechanical Keyboard', 7999),
  (102, 'MS-001', 'Wireless Mouse', 3499);

SELECT product_id, sku, name, price_cents, active
FROM products
ORDER BY product_id;
QUERY OUTPUT
Edit the query, predict the rows it will return, then run it.

Each run starts a fresh SQLite database in an isolated browser worker. Your edited SQL is saved on this device, but the database and its rows are discarded after the run.

Try this next: Add a third product without supplying active, then confirm the default value appears. Next, try a negative price and read the constraint error.

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.

NAMEWhat does it mean?

Choose an unambiguous domain term and include units where needed.

TYPEWhat family of values?

Text, integer, decimal, date, binary, and engine-specific types have different operations.

REQUIRED?Can it be absent?

Use nullability deliberately instead of allowing absence by accident.

RULESWhat values are valid?

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.

SQL BROWSER RUNNER

Map values to named columns

Compare two explicit INSERT statements, observe defaults, and verify that a reordered column list is still correct.

CREATE TABLE course_sessions (
  session_id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  room TEXT NOT NULL DEFAULT 'Online',
  seats INTEGER NOT NULL CHECK (seats BETWEEN 1 AND 100),
  published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1))
);

INSERT INTO course_sessions
  (session_id, title, seats)
VALUES
  (1, 'SQL Foundations', 30);

INSERT INTO course_sessions
  (title, session_id, room, seats, published)
VALUES
  ('Schema Workshop', 2, 'Lab A', 18, 1);

SELECT session_id, title, room, seats, published
FROM course_sessions
ORDER BY session_id;
QUERY OUTPUT
Edit the query, predict the rows it will return, then run it.

Each run starts a fresh SQLite database in an isolated browser worker. Your edited SQL is saved on this device, but the database and its rows are discarded after the run.

Try this next: Insert a third session using only session_id, title, and seats. Then try omitting seats and explain the error.

A default is not a placeholder for missing requirements

Use a default only when the omitted value has one honest interpretation. An unknown shipping country should not silently become the business’s home country, and an absent payment status should not silently become paid.

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.

Database

Operational boundary

A managed collection of schema objects and durable records.

Schema or namespace

Named organization

A logical home for related objects in engines that support database schemas.

Table

One record contract

A named relation whose rows share the same columns and integrity rules.

Column

One attribute contract

A named part of every row with a declared type and constraints.

Qualify names when ambiguity matters

Production queries often qualify tables with a schema and columns with a table alias. This makes ownership clear in multi-schema databases and prevents ambiguous column names in joins. Exact namespace syntax and search-path behavior are DBMS-specific.

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.

SQL BROWSER RUNNER

Read SQLite column metadata

Inspect the products table column by column, then read the CREATE TABLE statement stored in SQLite's schema catalog.

CREATE TABLE products (
  product_id INTEGER PRIMARY KEY,
  sku TEXT NOT NULL UNIQUE,
  name TEXT NOT NULL,
  price_cents INTEGER NOT NULL CHECK (price_cents >= 0),
  active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
);

SELECT name, type, "notnull", dflt_value, pk
FROM pragma_table_info('products')
ORDER BY cid;

SELECT name, sql
FROM sqlite_schema
WHERE type = 'table' AND name = 'products';
QUERY OUTPUT
Edit the query, predict the rows it will return, then run it.

Each run starts a fresh SQLite database in an isolated browser worker. Your edited SQL is saved on this device, but the database and its rows are discarded after the run.

Try this next: Add a stock_count column to CREATE TABLE, rerun the notebook, and find its type, nullability, default, and primary-key flag in the metadata result.

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.

SQL BROWSER RUNNER

Add and backfill a column

Add a constrained status column, observe the default on existing records, then update one member deliberately.

CREATE TABLE members (
  member_id INTEGER PRIMARY KEY,
  email TEXT NOT NULL UNIQUE
);

INSERT INTO members (member_id, email) VALUES
  (1, 'ada@example.test'),
  (2, 'grace@example.test');

ALTER TABLE members
ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'paused'));

UPDATE members
SET status = 'paused'
WHERE member_id = 2;

SELECT member_id, email, status
FROM members
ORDER BY member_id;
QUERY OUTPUT
Edit the query, predict the rows it will return, then run it.

Each run starts a fresh SQLite database in an isolated browser worker. Your edited SQL is saved on this device, but the database and its rows are discarded after the run.

Try this next: Add a third member after ALTER TABLE without naming status. Then attempt an unsupported status and inspect which constraint protects the table.

  1. 01
    Describe the invariant

    State why the new structure is needed and which old and new records must remain valid.

  2. 02
    Inspect real data

    Measure nulls, duplicates, ranges, volume, and application versions before assuming the migration is safe.

  3. 03
    Test a versioned migration

    Exercise upgrade, verification, and recovery against representative data in the production DBMS.

  4. 04
    Deploy and observe

    Coordinate compatible application changes, monitor duration and errors, then confirm the intended constraint exists.

DDL behavior differs across engines

Locking, transactional behavior, online index creation, supported ALTER TABLE operations, and rollback options vary. A lesson runner can teach the reasoning, but production migrations must be tested in the exact database engine and version you deploy.

Recognize table-design smells early

tags = “sql,beginner,free”

Several values in one cell

Repeated values are hard to validate, join, rename, and index. Model a separate relationship when tags become real data.

phone_1, phone_2, phone_3

Numbered repeating columns

A fixed set of slots turns the next value into a schema migration. A child table models a growing collection.

price = 79.99

Meaning or unit is hidden

Name the currency or establish it at a clear parent boundary; choose a numeric representation suited to money.

status = “anything”

Unbounded magic values

Misspellings create new accidental states. Use a reference table, constrained domain, or explicit check where appropriate.

order_total copied everywhere

Derived facts drift

Stored calculations need a clear consistency owner. Otherwise source rows change while duplicated totals become stale.

data, value, field1

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.

SQL BROWSER RUNNER

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.

CREATE TABLE workshops (
  workshop_id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  format TEXT NOT NULL CHECK (format IN ('online', 'onsite')),
  seats INTEGER NOT NULL CHECK (seats > 0),
  published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1))
);

INSERT INTO workshops
  (workshop_id, title, format, seats, published)
VALUES
  (1, 'Query Reading', 'online', 40, 1),
  (2, 'Schema Design', 'onsite', 18, 1),
  (3, 'Index Preview', 'online', 25, 0);

SELECT workshop_id, title, format, seats
FROM workshops
WHERE published = 1
ORDER BY title;
QUERY OUTPUT
Edit the query, predict the rows it will return, then run it.

Each run starts a fresh SQLite database in an isolated browser worker. Your edited SQL is saved on this device, but the database and its rows are discarded after the run.

Try this next: Add starts_at, insert two valid workshops, filter published rows with at least 20 seats, and append PRAGMA table_info('workshops') so the result proves your schema.

Lab review criteria

One row still means one workshop; every column describes that workshop; the timestamp is required for every inserted row; format and published values remain bounded; the report has an explicit filter and order; and the final metadata query shows the structure you intended.

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.
KNOWLEDGE CHECK

Check your table and schema reasoning

Answer all eight questions, then use the explanations to repair any weak part of your mental model.

01What should one row in a products table represent?
02Which part of a column contract requires every row to supply a value?
03Why should INSERT statements usually name their target columns?
04Which query should you use when presentation order matters?
05What is the difference between a schema and the current rows?
06Why is a comma-separated list of tags inside one text column often a poor relational design?
07What is the safest general workflow for a production schema change?
08What does PRAGMA table_info('products') show in this lesson runner?
PREVIOUS LESSONDatabases, SQL, and the Relational Model
NEXT LESSONData Types and NULL
ON THIS PAGETables, Rows, Columns, and SchemasLesson mapTable blueprintCreate a tableRow contractsColumn contractsExplicit insertsSchemasInspect metadataSchema evolutionDesign smellsIndependent labLesson reviewKnowledge check
Course contents