SQL (PostgreSQL, MySQL, SQLite) Module 1 – Start here: databases and your first query
What SQL is and how relational databases work
Tables, rows and columns, what SQL is and how standard it is, how PostgreSQL, MySQL and SQLite differ, and a first query you run in the browser.
What you will learn
- Explain how a relational database stores data as tables, rows and columns
- Describe what SQL is, and which parts are standard and which belong to one engine
- Compare client-server engines such as PostgreSQL and MySQL with SQLite, a library
- Run a first SELECT and read the result grid it returns
On this page
Programs that remember things, such as orders, train bookings, exam marks or chat messages, keep them in a database, and the most widely used databases are worked with in SQL. This lesson explains what a relational database stores, what SQL is and how standard it is, which engines this track uses and how they differ, and then runs your first query.
Tables, rows and columns
A relational database keeps its data in tables. A table has a name and a fixed list of columns. Each column has a name and a data type, the kind of value it holds, such as whole numbers or text. Each row is one thing the table describes, and every row of a table has the same columns. (“Relation” is the mathematical word for a table, which is where the name comes from.) SQLite is more relaxed about types than most engines: it treats a column’s type as a preference rather than a rule, which the module on data types explains.
Here is a table of five products, made and read with SQL:
-- A small table of products. CREATE TABLE names the table and its columns,
-- INSERT adds five rows, and SELECT * asks for every column of every row.
CREATE TABLE products (
product_id INTEGER PRIMARY KEY, -- the key: one number per product
name TEXT NOT NULL,
category TEXT NOT NULL,
price_paise INTEGER NOT NULL -- 100 paise = 1 rupee
);
INSERT INTO products (product_id, name, category, price_paise) VALUES
(1, 'Basmati rice 1 kg', 'Grains', 14999),
(2, 'Toor dal 500 g', 'Pulses', 8950),
(3, 'Masala chai 250 g', 'Beverages', 12000),
(4, 'Coconut oil 1 L', 'Oils', 21500),
(5, 'Ragi flour 1 kg', 'Grains', 6800);
SELECT * FROM products; Output
┌────────────┬───────────────────┬───────────┬─────────────┐ │ product_id │ name │ category │ price_paise │ ├────────────┼───────────────────┼───────────┼─────────────┤ │ 1 │ Basmati rice 1 kg │ Grains │ 14999 │ │ 2 │ Toor dal 500 g │ Pulses │ 8950 │ │ 3 │ Masala chai 250 g │ Beverages │ 12000 │ │ 4 │ Coconut oil 1 L │ Oils │ 21500 │ │ 5 │ Ragi flour 1 kg │ Grains │ 6800 │ └────────────┴───────────────────┴───────────┴─────────────┘
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < products.sql
The script has three statements, and each ends with a semicolon:
CREATE TABLE products (…)makes an empty table with four columns and says what each one holds.INSERT INTO products … VALUES …adds five rows.SELECT * FROM productsasks for every column (*) of every row. The engine answers with a result, which is a table of its own, drawn here as a grid.
Read the grid like a spreadsheet: the top line has the column names, and each line under it is one row. Two details of this table come back again and again in this track:
- Prices are whole numbers of paise, and 100 paise make a rupee. Whole numbers add up exactly, which numbers with a fraction do not always do inside a computer, so this track keeps amounts of money that way.
product_idis the table’s key. Every product has a number of its own, so product 3 is always the masala chai, even if two products one day have the same name. Keys are also how one table points to rows of another; the module on joins covers them properly.
Rows have no order of their own
The products came back in the order they were added, but SQL does not promise that. Unless a query asks for an order
(with ORDER BY, in the next module), the engine may return the rows in whatever order is quickest for it. SQLite
even has a switch for testing this: PRAGMA reverse_unordered_selects makes most queries without ORDER BY return
their rows the other way round.
CREATE TABLE products (product_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
INSERT INTO products (product_id, name) VALUES
(1, 'Basmati rice 1 kg'),
(2, 'Toor dal 500 g'),
(3, 'Masala chai 250 g');
SELECT name FROM products;
-- A switch SQLite has for testing: most queries without ORDER BY now
-- return their rows the other way round.
PRAGMA reverse_unordered_selects = ON;
SELECT name FROM products; Output
┌───────────────────┐ │ name │ ├───────────────────┤ │ Basmati rice 1 kg │ │ Toor dal 500 g │ │ Masala chai 250 g │ └───────────────────┘ ┌───────────────────┐ │ name │ ├───────────────────┤ │ Masala chai 250 g │ │ Toor dal 500 g │ │ Basmati rice 1 kg │ └───────────────────┘
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < row_order.sql
The table and the query are the same both times; only the order changed. SQLite’s documentation explains why the switch exists: an order that stays the same from one run to the next tempts programs to rely on it, and they break when it changes. When the order matters, ask for it.
What SQL is
SQL, which stands for Structured Query Language, is the language of relational databases. With it you create tables, add, change and delete rows, and, most of all, ask questions about the data.
SQL is declarative: a query describes the result you want, not the steps to compute it. “The names of the products that cost less than 100 rupees” is a complete question in SQL; you do not say whether to read the whole table or look the prices up in an index, or which row to check first. A part of the engine called the query planner decides that. This is why adding an index can make a query much faster without a word of the query changing, as the module on indexes shows.
SQL statements come in a few families, and later modules of this track cover each one:
- Queries read data:
SELECT. - Data changes add, change and remove rows:
INSERT,UPDATEandDELETE. - Definitions create and change tables and other objects:
CREATE TABLE,ALTER TABLEandDROP TABLE. - Control groups changes into transactions (
BEGIN,COMMIT,ROLLBACK) and grants permissions (GRANT,REVOKE).
One standard, many dialects
SQL is an international standard, ISO/IEC 9075, “Database Language SQL”. It is revised from time to time, and the edition that the PostgreSQL 18 manual compares itself with is SQL:2023. Engines implement much of it but not all: that manual counts at least 170 of the 177 mandatory “Core” features as supported, and notes that no engine claims full Core conformance. Every engine also adds features the standard does not have, such as its own functions and data types.
The SQL that one engine understands is its dialect. The basics in this module work the same in PostgreSQL, MySQL and SQLite, but details differ, even in arithmetic:
-- Two whole numbers, then a whole number and a decimal one.
SELECT 7 / 2 AS whole_numbers, 7 / 2.0 AS with_a_decimal; Output
┌───────────────┬────────────────┐ │ whole_numbers │ with_a_decimal │ ├───────────────┼────────────────┤ │ 3 │ 3.5 │ └───────────────┴────────────────┘
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < integer_division.sql
Runs on this device, in your browser. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs.
Your run, in this browser
SQLite divides one whole number by another to a whole number, cut toward zero, so 7 / 2 is 3. With a decimal point
on either side, the answer keeps its fraction. PostgreSQL does the same with whole numbers, but in MySQL / keeps
the fraction, so the same query there gives 3.5000. Whenever behaviour like this differs, this track says which
engine does what, and every recorded output on a page names the engine that printed it.
Three engines, two designs
This track teaches the SQL of the three databases named most often in the 2025 Stack Overflow Developer Survey, by people who had done extensive development work with them in the year before: PostgreSQL (55.6% of the 26,083 who answered the database question), MySQL (40.5%) and SQLite (37.5%). The three are built in two different ways.
Where the database engine runs
Text description of the diagram
The diagram has two parts, one above the other.
Client-server, as in PostgreSQL and MySQL: your application and other programs send SQL to the database server, a program of its own. Only the server opens the data files; it reads and writes them for every program that connects to it.
Embedded, as in SQLite: the SQLite library is part of your application. Your code hands it SQL, and the library reads and writes the database itself, which is one ordinary file, here shop.db. There is no server in between.
- PostgreSQL and MySQL are client-server engines. The database server is a program of its own, often on another computer, and it alone opens the data files. Applications connect to it and send SQL; the server checks who may do what, lets many users read and write at the same time, and sends the results back.
- SQLite is a library. It runs inside your application, with no server to install or configure, and a whole database is one ordinary file that you can copy, send or back up like any other. The SQLite project describes it as storage for one application or device, not a shared store of data for a whole organisation.
| Criterion | PostgreSQL, MySQL | SQLite |
|---|---|---|
| How it runs | A server that apps connect to | A library inside your app |
| Where the data is | Files that only the server opens | One ordinary file, or memory |
| Writers at once | Many | One per database file |
| Who may use the data | Users and rights kept by the server | Anyone allowed to open the file; no GRANT or REVOKE |
| When to choose | Data that many people and programs share over a network | Data that belongs to one app, device or file |
The next lesson shows how to run all three: SQLite in your browser and on your own computer, and PostgreSQL and MySQL on your computer.
Your first query
Every query starts with SELECT, and the simplest ones do not even need a table:
SELECT 'namaste' AS greeting, 2 + 3 AS total; Output
┌──────────┬───────┐ │ greeting │ total │ ├──────────┼───────┤ │ namaste │ 5 │ └──────────┴───────┘
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < first_query.sql
Runs on this device, in your browser. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs.
Your run, in this browser
'namaste'is text. Text in SQL goes between single quotes.2 + 3is an expression, and the engine works out its value.AS greetingandAS totalgive the result’s columns their names.- The semicolon ends the statement.
A SELECT without FROM has no table to read, so it returns exactly one row; PostgreSQL, MySQL and SQLite all
allow it. Press Run, then Edit: change the text, add a third column such as 10 * 4 AS forty, and run it
again.
A common first mistake: text without quotes
Leave out the quotes and the word is no longer text:
-- The same greeting without its single quotes.
SELECT namaste AS greeting; Output (exit status 1)
Printed as an error (standard error)
Parse error near line 2: no such column: namaste
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < missing_quotes.sql
Without quotes, SQLite reads namaste as a name, the name of a column, and there is no column called that, so it
stops with an error. When you see “no such column” for a word you meant as text, look for missing single quotes.
Putting text in double quotes is a different trap, which the lesson on SELECT and FROM explains.
Key takeaways
- A relational database stores data in tables. Each column has a name and a type, each row is one thing, and rows have no order unless a query asks for one.
- SQL describes the result you want; the engine’s query planner decides how to get it.
- The standard is ISO/IEC 9075, currently SQL:2023. Each engine speaks its own dialect of it, so this track names the engine whenever behaviour differs.
- PostgreSQL and MySQL are servers that applications connect to; SQLite is a library that keeps a whole database in one file.
- Text goes between single quotes; a word without quotes is read as a name.
Exercise
Exercise · Easy · SQL
List each city with the length of its name
The table cities has one column, name, with one city in each row. Write a query that returns every city with the number of characters in its name, in two columns:
city: the name, as it is in the table;letters: how many characters the name has, counted by SQL'slength()function (length('Pune')is 4).
If the table held only Kochi and Pune, the result would be:
┌───────┬─────────┐
│ city │ letters │
├───────┼─────────┤
│ Kochi │ 5 │
│ Pune │ 4 │
└───────┴─────────┘
The order of the rows does not matter. The sample tests run your query on two small cities tables of their own.
Starter code · query.sql
-- Return two columns, city and letters, for every row of cities.
SELECT name FROM cities; The sample tests · sample_tests.yaml
# Sample tests (public): each test builds its own cities table in a fresh database, runs the learner's query and
# compares the result with the expected rows (the order of rows does not matter; column names must match).
tests:
- name: counts the characters of each city's name
setup: |
CREATE TABLE cities (name TEXT NOT NULL);
INSERT INTO cities (name) VALUES ('Kochi'), ('Pune'), ('Thiruvananthapuram');
expected:
columns: [city, letters]
rows:
- [Kochi, 5]
- [Pune, 4]
- [Thiruvananthapuram, 18]
- name: counts a space as a character
setup: |
CREATE TABLE cities (name TEXT NOT NULL);
INSERT INTO cities (name) VALUES ('New Delhi'), ('Navi Mumbai'), ('Agra');
expected:
columns: [city, letters]
rows:
- [New Delhi, 9]
- [Navi Mumbai, 11]
- [Agra, 4] A hint
You need one column straight from the table and one computed from it, each renamed with AS: SELECT name AS city, … AS letters FROM cities. A space is a character too, so length('New Delhi') is 9.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (sql.js): nothing is sent to mysmartcopilot.com. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- PostgreSQL 18 Documentation: Concepts (The PostgreSQL Global Development Group)
- PostgreSQL 18 Documentation: SQL Conformance (The PostgreSQL Global Development Group)
- PostgreSQL 18 Documentation: Mathematical Functions and Operators (The PostgreSQL Global Development Group)
- PostgreSQL 18 Documentation: Planner/Optimizer (The PostgreSQL Global Development Group)
- MySQL 8.4 Reference Manual: Arithmetic Operators (Oracle Corporation)
- About SQLite (SQLite)
- Appropriate Uses For SQLite (SQLite)
- SQL Features That SQLite Does Not Implement (SQLite)
- SQLite: The SELECT statement (SQLite)
- SQLite: SQL Language Expressions (SQLite)
- SQLite: PRAGMA reverse_unordered_selects (SQLite)
- SQLite: Datatypes In SQLite (SQLite)
- SQLite: Floating Point Numbers (SQLite)
- PostgreSQL 18 Documentation: SELECT (omitted FROM clauses) (The PostgreSQL Global Development Group)
- MySQL 8.4 Reference Manual: SELECT Statement (Oracle Corporation)
- MySQL 8.4 Reference Manual: What is MySQL? (Oracle Corporation)
- Stack Overflow Developer Survey: Technology (Stack Overflow)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress