Skip to main content
eLearner.app
Module 9 · Lesson 2 of 434/57 in the course~12 min
Module lessons (2/4)

Multiple and table-level constraints

Ensuring that the data in the database is correct at the most basic layer ("data integrity") is fundamental. If you only write safety into the Node.js application, bugs could cause inconsistent data in the DB. Constraints at the DDL level are the last, impenetrable line of defense.

The main constraints

In addition to PRIMARY KEY (which makes a column unique and NOT NULL) and the classic NOT NULL and UNIQUE, there are two fundamental categories for the logical consistency of the data:

1. FOREIGN KEY (Referential Integrity)

A REFERENCES constraint ensures that a linking ID really exists in the "parent" table it points to. For example, an order must be linked to an existing customer.

SQL
CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  customer_id INTEGER REFERENCES customers(id)
);

What does the Foreign Key do?

  • It prevents you from inserting a customer_id that does not exist in customers.
  • It prevents you from DELETE (deleting) a customer from customers if there are orders pointing to them (it protects against "orphan" records).

We can also decide what should happen when the "parent" is deleted:

SQL
customer_id INTEGER REFERENCES customers(id) ON DELETE CASCADE

With ON DELETE CASCADE, if we delete the customer, Postgres will automatically remove all linked orders! (Use with great caution; normally the default setting that blocks the operation is preferable.)

2. CHECK Constraints

A CHECK constraint validates the row by testing a boolean expression before proceeding to save.

SQL
CREATE TABLE contratti (
  id SERIAL PRIMARY KEY,
  stipendio NUMERIC(10,2) CHECK (stipendio > 0),
  data_inizio DATE,
  data_fine DATE,
  CHECK (data_fine > data_inizio)
);

The first CHECK is a column constraint (it only refers to stipendio). The second CHECK at the bottom is a table constraint (it can cross values from multiple columns, and must be declared at the bottom).

If an INSERT or UPDATE tries to save the end before the start, Postgres will reject it with an error!

Naming Constraints

It is often good practice to give constraints an explicit name (using the CONSTRAINT keyword). This way, when Postgres blocks a record, the error will tell you for example "constraint 'stipendio_positivo' violated", which is much clearer for debugging!

SQL
CREATE TABLE prodotti (
  id SERIAL PRIMARY KEY,
  nome VARCHAR(50),
  CONSTRAINT nome_univoco UNIQUE(nome)
);

Try it

Exercise#sql.m9.l2.e1
Attempts: 0Loading…

Create a 'prenotazioni' table with id (SERIAL PRIMARY KEY) and a 'customer_id' (INTEGER). Use CONSTRAINT fk_cliente FOREIGN KEY (customer_id) REFERENCES customers(id) to give an explicit name to the Foreign Key constraint. Do it at the bottom, as a table constraint.

Loading editor…
Show hint

Write CONSTRAINT fk_cliente FOREIGN KEY (local_column) REFERENCES external_table(external_col)

Solution available after 3 attempts

Constraints with custom logic

Exercise#sql.m9.l2.e2
Attempts: 0Loading…

Create an 'eventi' table with id (SERIAL PRIMARY KEY), posti_totali (INTEGER) and biglietti_venduti (INTEGER). Add a table constraint named 'check_capienza' where biglietti_venduti must never be greater than posti_totali.

Loading editor…
Show hint

Add the constraint at the bottom using CONSTRAINT check_capienza CHECK ( expression )

Solution available after 3 attempts