A database stores data so it survives your program closing, and lets you ask questions of it without loading everything into memory.
SELECT name, email FROM users WHERE active = 1 ORDER BY name;That is SQL — the language nearly every database speaks. It reads almost like English, which is deliberate.
The model#
A relational database is a set of tables. Each table has columns (the fields) and rows (the records). One column is the primary key — a value unique to each row, usually called id.
users
id | name | email
----+------+------------------
1 | Ada | ada@example.com
2 | Sam | sam@example.com
orders
id | user_id | total
----+---------+-------
1 | 1 | 450
2 | 1 | 120orders.user_id is a foreign key pointing at users.id. That is the whole idea behind “relational” — you store each fact once and connect tables by id rather than repeating the name in every order.
The five statements#
-- Read
SELECT name, email FROM users WHERE active = 1 ORDER BY name LIMIT 10;
-- Create
INSERT INTO users (name, email) VALUES ('Ada', 'ada@example.com');
-- Update
UPDATE users SET active = 0 WHERE id = 2;
-- Delete
DELETE FROM users WHERE id = 2;
-- Combine tables
SELECT users.name, orders.total
FROM orders
JOIN users ON users.id = orders.user_id;Grouping and counting#
SELECT user_id, COUNT(*) AS order_count, SUM(total) AS spent
FROM orders
GROUP BY user_id
HAVING SUM(total) > 500
ORDER BY spent DESC;WHERE filters rows before grouping; HAVING filters the groups afterwards. Mixing those up is the most common SQL confusion after joins.
The rule that matters most#
Never build a query by joining strings with user input. This is SQL injection, and it is how databases get emptied.
# Never do this
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# Do this — the database keeps the value separate from the query
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))<?php
// PHP with PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute(["name" => $name]);
?>With a placeholder, the database treats the value as data and never as instructions — no matter what it contains. There is no situation where string-joining is the right answer.
A worked example in Python#
SQLite needs no server and stores everything in one file, which makes it ideal for learning:
import sqlite3
with sqlite3.connect("shop.db") as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
)
""")
conn.execute(
"INSERT OR IGNORE INTO users (name, email) VALUES (?, ?)",
("Ada", "ada@example.com"),
)
for row in conn.execute("SELECT id, name FROM users ORDER BY name"):
print(row)Which one to start with#
| Database | Good for |
|---|---|
| SQLite | Learning, small apps. One file, no server, built into Python. |
| PostgreSQL | Real projects. Strict, capable, widely used. |
| MySQL / MariaDB | Very common in web hosting and PHP projects. |
| MongoDB | Document storage rather than tables. Different model — learn SQL first. |
Learn on SQLite. The SQL transfers almost unchanged.
Indexes#
Searching a column without an index means checking every row. On a large table that is the difference between instant and slow:
CREATE INDEX idx_users_email ON users(email);Index the columns you filter or join on. Do not index everything — each one costs time on every write.
Questions people ask#
What is an ORM?
A library that maps database rows to objects so you write User.find(1) instead of SQL. Convenient, and worth learning SQL first so you can tell what it is doing when something is slow.
SQL or NoSQL?
Start with SQL. Most data is relational, and the concepts transfer. Document databases solve specific problems you have not met yet.
What is a transaction?
A group of changes that all succeed or all fail together — moving money between accounts being the classic example. In Python’s sqlite3, the with block commits on success and rolls back on an exception.
Where do I put the database password?
In an environment variable, never in code, and never committed. See Git and GitHub explained for .gitignore.