Programming

Databases and SQL, the basics

A database is a program that keeps your data in tables of rows and columns and lets several programs read from it and write to it at the same time without ruining each other's work. SQL is the language you use to say what you want: <code>SELECT</code> to read, <code>INSERT</code> to add and <code>UPDATE</code> to change.

  • Lesson 10 of 14
  • Beginner
  • Free, no signup

Three tables, and the column that links them

  1. users table

    2
    • id = 42, Maryam

      primary key: unique and unchanging

    • id = 43, Saeed

      names and emails change, the id does not

  2. orders table

    2
    • order 1001, user_id = 42

      foreign key: the customer name is not repeated

    • order 1002, user_id = 42

      one user can have several orders

  3. products table

    1
    • id = 7, price and name

      the price is written here, not inside every order

Each card is a row and each column a table. This picture shows the relationships, not the indexes and constraints, which are where speed and correctness actually come from.

Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.

Why is a text file not enough?

You can keep user records in a text file, and for a hundred rows it works fine. Three things break it, and all three show up in the first month.

First, concurrency: when two people sign up in the same instant, two programs try to write to one file and one of the two writes is lost. Second, search: finding the user with a given email in a file means reading the whole file, and that gets slower as the file grows. Third, correctness: nothing in a text file stops two users being recorded with the same email.

A database is a program that solves those three: locking for simultaneous writes, indexes for fast lookup, and constraints so invalid data never gets in. You do not talk to it in order to have a file saved; you talk to it to get those three guarantees.

And a point that gets said less often: a database does not replace files. Images, video and downloads usually stay on disk and only their address is recorded in a table. WordPress works that way too, which is why a database backup on its own is not a backup of the site.

Tables, rows, columns and that id column

A table looks like a spreadsheet sheet: the columns say what is stored and each row is one record. A users table has id, name and email columns and each row is one user. Nothing complicated so far.

The id column has a special role, though, and its name is the primary key: a value unique across the whole table that never changes. It exists because nothing else can be relied on; people share names, change their email and lose their phone number. That is why orders keep the customer's id rather than their name, and that reference is called a foreign key.

Now a real example that breaks a common assumption. The main table of this very site, which WordPress calls posts, had 784 rows on the day this lesson was checked, but those 784 rows were 24 different kinds of thing: 263 blog posts, 240 media files, 37 pages, and the rest projects, messages, listings and a few others.

The lesson in that number is useful to anyone: a table is a shape, not a subject. Anything that needs the same columns can sit in the same table and be separated from the rest by one more column. The practical consequence is that a SELECT on such a table with no condition hands you a mixture of four unrelated things.

The three commands that do most of the work

SQL is a language for requesting, not for giving step by step instructions. You do not say how to search; you say what you want, and the database chooses the route itself.

SELECT name, email FROM users WHERE city = 'Tehran';

INSERT INTO users (name, email, city) VALUES ('Maryam', 'm@example.com', 'Shiraz');

UPDATE users SET city = 'Isfahan' WHERE id = 42;

Three things in those three lines are worth seeing. SELECT says which columns, FROM says from which table and WHERE says which rows. If you do not know the columns, SELECT * brings all of them, which is fine for looking around and a bad habit inside program code, because the day a column is added your program receives something it did not expect.

The most dangerous word on this page is that WHERE, by being absent. UPDATE users SET city = 'Isfahan'; is not a syntax error; it is perfectly valid and changes the city of every user. The documentation of both MySQL and MariaDB says so plainly, and both even have a mode that refuses such a statement.

The habit that solves this is typing one more line: write every UPDATE or DELETE first as a SELECT with the same condition and see how many rows come back. If that number is not what you expected, your condition is wrong and you have just found out, rather than finding out after the data is gone.

Before running an UPDATEfor real data

Do this

  • Run the same condition as a SELECT first and look at the row count
  • If the number does not match your expectation, fix the condition, not the statement
  • Practise on a copy of the data, not on live data
  • Pass user input through a placeholder, not by gluing it in

Do not

  • An UPDATE or DELETE with no WHERE
  • A query built by gluing in the user's text
  • Running a statement somebody else wrote that you do not understand

This list does not replace a backup. An UPDATE with no condition on real data, with no backup, is not reversible.

Do not confuse the database with the cache

Two things run side by side on this very server and their jobs are entirely different. One is MariaDB, from the MySQL family, which holds all of the site's durable data. The other is Redis, an object cache, which keeps the results of questions just asked of the database in memory so the next request does not have to ask again.

The difference fits in one sentence: if we wipe the cache completely, nothing is lost and only the next few requests are slower, because everything is rebuilt from the database. If we lose the database, everything is gone.

From that one sentence comes a practical rule that serves you everywhere: never keep something only in the cache. A cache is entitled to be empty at any moment, whether from a restart or from memory filling up, and a program that relies on a value being in the cache fails on the day it is not. A cache is for speed, not for keeping.

And that same separation clarifies hosting decisions: a database needs fast disk and regular backups, a cache needs memory. On the WordPress hosting we set up ourselves both sit side by side, but only one of the two is backed up, and that is exactly how it should be.

Durability against speed: two jobs, not two options

The database

  • The final reference: if it is not here, it does not exist
  • Lives on disk and gets backed up
  • Constraints and keys keep invalid data out

The cache

  • A temporary copy of something just built
  • Sits in memory and is entitled to be empty at any moment
  • Clearing it destroys nothing, it only slows things down

These two are not rivals and one does not replace the other. The common mistake is putting something only on the right pan.

What makes a query safe?

The most dangerous mistake in working with a database is building a query by gluing in text the user typed. When an SQL statement is made of two pieces, the user's text and your command, the database has no way to tell which part was data and which was instruction. That class of vulnerability is called SQL injection.

The fix is old and simple and the same everywhere: put a placeholder where the value goes in the query text and hand the value over separately. Then whatever the user wrote, even if it looks like an SQL statement itself, is read as nothing but a string. In WordPress the prepare method on the wpdb object does exactly this, and WordPress's own documentation treats it as required before any query carrying user input.

One practical note that buys you time right here: in most projects you do not write raw SQL at all. WordPress and frameworks have a layer that builds the query for you, and builds it safely. Knowing SQL is so that you can understand what that layer produced and why it is slow, not so that you write every query by hand.

The fast path, with AI

Turning a sentence into a query is something language models do strangely well, which is exactly why it is dangerous: a wrong query that runs and returns plausible rows is worse than one that errors. The routine we use differs from "write me a query" in two ways: we give the table structure rather than the data, and we always ask for a SELECT first, even when the goal is changing data.

  1. Take the table structure and send that, not the rows. A <code>SHOW CREATE TABLE</code> or a list of columns with their types is enough, and no customer data leaves your machine.
  2. With the recipe below, first ask for a SELECT that shows exactly the rows about to change, together with a count.
  3. Run that SELECT and weigh the number against your own expectation. If it does not match, the job stops here and the condition has to change; do not move on to the second statement.
  4. Only then ask for the UPDATE, with that same condition unchanged, and run it on real data only when you have a fresh backup.

Copy-ready recipe

This is my table structure:

{SHOW CREATE TABLE output, or the column list with types}

What I want done:
{write it in plain language, for example: clear the city of every user with no order in the last six months}

Answer in this order:
1. List the assumptions you need that are not in the structure above. If an assumption is needed, ask first and write no query.
2. Give a SELECT that returns exactly the rows about to change, plus a count of those rows.
3. After that, give the UPDATE, with the same condition as the SELECT, unchanged.
4. Say what the worst case is if the condition turns out to be wrong.

I have given you no real data and you do not need any. If you need data to answer, say why.

Before you trust the output: The model sees the table structure and does not see what the data means. It does not know whether an empty column in your project means "not filled yet" or "deliberately cleared", and it does not know about a trigger or a piece of code that does something else on the same change. Any number it states about affected rows is a guess; the real number is whatever your own SELECT returns. And the last boundary is repetitive but it matters: on live data the only thing that undoes a wrong UPDATE is a backup, not a model.

AI in this kind of work

On this topic a language model genuinely gives you time back: writing a query from a description, explaining a query somebody else wrote, and working out why a query is slow. Our position is to draw the line at the kind of statement rather than the difficulty of the task: for a <code>SELECT</code>, take the model's help and run the result with a clear conscience; for anything that changes data, look at the equivalent SELECT first.

Tools that actually help

  • Claude It answers the recipe on this page well, because when you explicitly ask it to list its assumptions first, it really does list them instead of guessing. Iran is on neither of Anthropic's two supported-countries lists; we read that on Anthropic's own page.
  • ChatGPT A common option for explaining an unfamiliar query and translating it into human language. We write nothing about access from Iran because we have not checked it: OpenAI's supported-countries page, like the rest of that domain, returns 403 to our server, and we do not write a claim with nothing behind it.
  • Gemini Write the simple, repetitive queries with a fast cheap model; this is where it belongs. For a query against a table with a complicated structure, a stronger model answers better. Google's own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.

Where it backfires

The main risk on this topic is a wrong query that raises no error. A missing condition or an OR where AND was meant produces a statement that runs and returns plausible rows; they are simply not the answer to your question. When that happens on a SELECT you get a wrong report, and when it happens on an UPDATE you have changed the data. The MySQL and MariaDB documentation spells out how serious the unconditional form is, and that is what makes the "SELECT first" order necessary.
The second risk is about data rather than queries: the rows of a real table are usually real people's information, and pasting them into a chat means taking personal data out of your control. You never need the rows to work with a model; the structure is enough, and that single decision removes this risk entirely. For how each tool can be paid for from Iran, see the buying guide.

Sources: MySQL: the UPDATE statement MariaDB: UPDATE WordPress: wpdb prepare Anthropic: supported countries Google: where the Gemini web app is available

Where this advice stops

This lesson covers relational databases at an introductory level and deliberately leaves several things out, because each wants its own lesson: JOIN and working across several tables, indexes and why a query is slow, normalisation, transactions and the guarantees a database gives when something crashes, and non relational databases that have no tables at all. There is a blunter boundary too: do not practise the statements on this page against real data. Make a copy of the database on your own computer and break whatever you like there; it is what we do too.

From our own work

The numbers in this lesson were read off this very server, on the date printed at the top of the page as the last check. This site's database has 86 tables, and the posts table where WordPress keeps content held 784 rows. The interesting part is the composition of those 784 rows: 24 different values in the type column, including 263 blog posts, 240 media files and 37 pages, plus freelance projects, contact messages, marketplace listings and several other kinds, all in that one table. Next to that table a Redis cache runs on the same server and, as the text said, wiping it completely destroys no data. Together the two are the best practical definition of the difference between a durable store and a fast copy, which is why they became this lesson's example.

Real follow-up questions

What is the difference between SQL and MySQL?

SQL is a language, while MySQL, MariaDB and PostgreSQL are programs that understand it. So what you learn works almost everywhere and only each program's details differ. MariaDB is a fork of MySQL, and the statements in this lesson are identical in both.

Do I need SQL to work with WordPress?

For day to day work, no; WordPress builds every query itself. It becomes necessary when you want to understand why the site got slow, or to pull a report no plugin gives you. Even then, start with reading and leave writing to live data until last.

Can a wrong UPDATE be undone?

Not on its own. If you ran the statement inside a transaction and have not committed it, it can be rolled back; otherwise the only route is restoring from a backup. Which is why, on real data, "do I have a fresh backup" is a question asked before the statement, not after it.