Skip to main content
ANVISoftware Solutions
Lesson 2 of 22Beginner14 min

Tables, Rows and Columns

By the end of this lesson

Describe how data is organised and choose sensible column types.

A table holds one kind of thing. A customers table holds customers. A products table holds products. Mixing both into one table is the first design mistake people make, and every later lesson gets harder if you do.

Inside a table, a row is one of those things — one customer. A column is one fact about every thing in the table, such as the city. Where a row and a column meet you get a single value.

Four words that get used loosely and mean specific things:

Table
A named collection of rows that all describe the same kind of thing, with the same columns.
Row
One record. One customer, one order, one product. Sometimes called a record or a tuple.
Column
One named, typed fact that applies to every row. A column decides what values are allowed, not just what they are called.
Schema
The full description of your tables, their columns, their types and their rules. The schema is the design; the rows are the data.

A column is a promise, not a label

The important difference between a database table and a spreadsheet column is enforcement. In a spreadsheet, a column called Price is a heading. Someone can type "call for quote" into it and the file accepts it.

In a database, declaring that column as a decimal number means the database refuses anything that is not a number. The refusal happens at the moment of the bad write, so you find out immediately rather than discovering it three months later when a report fails to add up.

The customers table used throughout this course
SQL
CREATE TABLE customers (
    customer_id   INT           NOT NULL,
    company_name  NVARCHAR(120) NOT NULL,
    contact_name  NVARCHAR(120) NULL,
    email         NVARCHAR(200) NULL,
    city          NVARCHAR(80)  NULL,
    country       NVARCHAR(80)  NOT NULL,
    credit_limit  DECIMAL(12,2) NOT NULL,
    created_at    DATETIME2     NOT NULL
);
  • Each line is a column: a name, a type, and whether it may be missing.
  • INT stores a whole number. NVARCHAR(120) stores up to 120 characters of text in any alphabet. DECIMAL(12,2) stores up to 12 digits with exactly 2 after the decimal point. DATETIME2 stores a date and a time.
  • NOT NULL means the value is required. NULL means it may be absent — see the next section for why that distinction matters more than it looks like it should.
  • company_name is NOT NULL because a customer without a name is not a usable record. contact_name is optional because you may genuinely not know it yet.

NULL means "we do not know"

NULL is not zero and it is not an empty string. It means no value is recorded. A credit limit of 0 says the customer may not buy on credit. A credit limit of NULL says nobody has decided yet. Those are different facts and storing them the same way loses information.

Treat every nullable column as a question you will have to answer in every query that touches it. That is a real cost, so make a column NOT NULL whenever the value is genuinely always known. Filtering on nulls gets a full lesson later in this course.

Choosing types without regretting it later

Type choices that cause the most trouble when rushed:

  • Money: use DECIMAL, never FLOAT. Floating-point numbers cannot represent 0.10 exactly, so totals drift by fractions of a currency unit. DECIMAL(12,2) is exact.
  • Text length: pick a limit that reflects reality, not a guess. An email column of 20 characters will reject valid addresses; unlimited text on every column makes indexing harder later.
  • Identifiers that are not numbers: a phone number, a postcode or a product code is text, even when it looks numeric. Store 0141 as a number and you lose the leading zero.
  • Dates: use a date or datetime type, not text. Text dates sort as text, so 01/12/2026 sorts before 02/01/2025, and you cannot ask the database for "orders in the last 30 days".
  • True or false: use BIT (SQL Server) or BOOLEAN (PostgreSQL) rather than the text "Y" and "N", which nothing can validate.
The other four tables, so later examples have somewhere to run
SQL
CREATE TABLE products (
    product_id     INT           NOT NULL,
    product_name   NVARCHAR(150) NOT NULL,
    category       NVARCHAR(60)  NOT NULL,
    unit_price     DECIMAL(10,2) NOT NULL,
    units_in_stock INT           NOT NULL,
    discontinued   BIT           NOT NULL
);

CREATE TABLE employees (
    employee_id INT           NOT NULL,
    first_name  NVARCHAR(80)  NOT NULL,
    last_name   NVARCHAR(80)  NOT NULL,
    job_title   NVARCHAR(100) NOT NULL,
    hire_date   DATE          NOT NULL,
    manager_id  INT           NULL,
    salary      DECIMAL(12,2) NOT NULL
);

CREATE TABLE orders (
    order_id     INT          NOT NULL,
    customer_id  INT          NOT NULL,
    employee_id  INT          NULL,
    order_date   DATE         NOT NULL,
    status       NVARCHAR(20) NOT NULL,
    shipping_fee DECIMAL(10,2) NOT NULL
);

CREATE TABLE order_items (
    order_item_id INT           NOT NULL,
    order_id      INT           NOT NULL,
    product_id    INT           NOT NULL,
    quantity      INT           NOT NULL,
    unit_price    DECIMAL(10,2) NOT NULL
);
  • order_items stores unit_price again rather than reading it from products. That is deliberate: it records the price actually charged, so a later price change does not rewrite history.
  • employees.manager_id is nullable because somebody at the top has no manager.
  • These tables have no keys or links yet. The next two lessons add both.

Summary

  • A table holds one kind of thing; a row is one of them and a column is one typed fact about all of them
  • Column types are enforced, so a good type choice is validation you never have to write
  • NULL means no value recorded — it is not zero and not an empty string
  • Use DECIMAL for money, real date types for dates, and a separate table wherever a value repeats

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Try it yourself

Write a CREATE TABLE statement for a suppliers table. It needs an identifier, a company name, a contact email, a country, and the date the supplier was approved. One supplier is still awaiting approval, so consider which columns can be missing.

Show solution

approved_on is the interesting column. Making it nullable lets one column carry two facts: whether the supplier is approved, and when. An unapproved supplier has no approval date, and NULL records that honestly.

The alternative is a separate BIT column plus a date, which then has to be kept consistent — two columns that can disagree. Neither answer is wrong, but the nullable date has fewer ways to go bad.

SQL
CREATE TABLE suppliers (
    supplier_id  INT           NOT NULL,
    company_name NVARCHAR(120) NOT NULL,
    email        NVARCHAR(200) NULL,
    country      NVARCHAR(80)  NOT NULL,
    approved_on  DATE          NULL
);

Think about it

Think about it

A colleague suggests storing every value as text, because text accepts anything and nothing ever fails to save. What does that decision cost you later?

Show solution

You lose validation, ordering and arithmetic. The database can no longer reject a price of "about twelve pounds", so the bad value survives until a report tries to add it up.

Sorting breaks in ways that look almost right: as text, "100" sorts before "20". Date ranges stop working, because the database has no idea which part of 03/04/2026 is the month.

The deeper cost is that the checks do not disappear — they move into every piece of application code that reads the column, and they have to be written again in each one.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

Why is DECIMAL preferred over FLOAT for a price column?
A credit_limit column contains NULL for one customer. What does that mean?

Saved in this browser only.