OLTP and OLAP
In short: OLTP is the many small queries of an application such as a checkout, and OLAP is the few large reporting queries that read all the rows.
Problem
The till and the accounts
The shop's database does two very different jobs at once. The customer at the till asks about one order and wants the answer right away. The owner on Monday morning asks "how much did we sell by month and by category", and that needs all the orders of the year.
The first are thousands a minute, small and by key. The second are a few a day, but each reads everything. The same database is rarely good at both. That is why there are different kinds of databases, and this module is about them.
The idea
OLTP: many small queries
OLTP (online transaction processing) is the job of the till: read or change a few rows that you know by key. The products in order 42:
The plan shows that the database does not read the whole table. It searches (SEARCH) with the index of the primary key and goes straight to the two rows:
OLTP queries also write. A purchase is one transaction: the order, its row and the stock are written together or not at all:
Try it
The customer asks
The customer calls about order 15. Show its status and date (columns status and ordered_at).
Solution
SELECT status, ordered_at FROM orders WHERE id = 15;WHERE id = 15: the order number is the primary key, and the database goes straight to the row. customer_id = 15 looks for the orders of customer 15, and that is a different question. The check also looks at the plan: a query that reads the whole table does not pass.
The idea
OLAP: few big queries
OLAP (online analytical processing) is the job of analysis: gather, group and compare many rows. The revenue by month for 2026:
A key does not help here. It needs all the order rows, so the plan starts with SCAN:
With 152 rows this is instant. With 50 million rows the report reads gigabytes and loads the disk, the memory and the processor for minutes. Meanwhile the till waits. And an index does not help, because the report wants everything anyway.
The idea
The data warehouse
The solution is for the reports not to touch the website's database. Every night a program copies the data into a separate database for analysis, called a data warehouse. The copying is called ETL: extract, transform, load.
During the transform the tables are usually merged into one wide table: everything about one sale in one row. This is denormalisation, which in the website's database would be a mistake: the name of the product repeats in every row. Here it is on purpose, because the data is only read and the report needs no JOIN. CREATE TABLE … AS SELECT makes a new table from the result of a query:
The cancelled orders are removed already during the copy, so the reports do not have to remember them.
More precisely, if you are curious
Real data warehouses (ClickHouse, BigQuery, Snowflake, Amazon Redshift) store the data by columns, not by rows. A revenue report reads only the columns day, category and amount and skips the rest. That is what the lesson about columnar databases is for. The wide table is usually a "fact table" with small lookup tables around it (dimensions: date, product, store), and the whole is called a star schema.
Try it
A report by category
From the table sales: the revenue by category for 2026 (columns category and revenue, rounded to a cent), from the largest down.
Solution
SELECT category, ROUND(SUM(amount), 2) AS revenue
FROM sales
WHERE day >= '2026-01-01'
GROUP BY category
ORDER BY revenue DESC;SUM(amount) with GROUP BY category and WHERE day >= '2026-01-01'. Without the condition on the year the sum is for all time, from September 2025. There is no JOIN in sales: the category is already in the row.
Exercise
How many orders by city
From the table sales: how many orders there are from each city (columns city and orders), from the most down, and for equal ones by the name of the city. A customer without a city is a row with NULL.
Solution
SELECT city, COUNT(DISTINCT order_id) AS orders
FROM sales
GROUP BY city
ORDER BY orders DESC, city;COUNT(DISTINCT order_id). In the wide table every row is one product of an order, and an order with three products is three rows. COUNT(*) counts the rows and gives 50 for Sofia, while the orders are 23. This is the most common mistake with wide tables.
The idea
The kind of data and the kind of question
OLTP and OLAP are the first split. The rest come from what the data is like and what you ask about it. Each lesson in the module is one such pair, and most of them have a simulator of the real database in the browser:
| The data and the question | Kind of database | Examples |
|---|---|---|
| I fetch by key, fast, often | key-value | Redis, Valkey, DynamoDB |
| A whole document with nested parts | document | MongoDB |
| What happened and in what order | event stream | Kafka, Redpanda |
| Who is connected to whom, in how many steps | graph | Neo4j |
| Sums over millions of rows, over time | columnar, time series | ClickHouse, TimescaleDB |
| Words in text | full-text search | Elasticsearch, OpenSearch |
| Similar in meaning | vector | pgvector, Qdrant |
| More data and queries than one server can take | cluster | replication, sharding |
At the end is the lesson on how to choose. The answer is almost always: a relational database for the truth and another kind next to it only for the question it is weak at.
Test
Check yourself
Related lessons
Section titled "Related lessons"- Builds on: ACID: all or nothing · BEGIN, COMMIT, ROLLBACK · Indexes · EXPLAIN: the query plan
- Continues in: Key-value and the cache · Graph databases · Columnar and time series databases · How to choose a database
- Tasks on this topic: Kinds of databases