Part of Yahya.Dev Language Docs

SQL Documentation

Structured reference for learning SQL from basics to advanced. Written as a developer-first language guide, not a course.

โ— v2.0 โ€” 2026
โ— Relational DB Guide
โ— 8 Sections
โ— PostgreSQL 17 ยท MySQL 9
๐Ÿ“˜
01 / INTRO

Introduction to SQL

SQL (Structured Query Language) is the standard language for interacting with relational databases. It allows you to create, read, update, and delete data โ€” as well as define schemas and control access.

๐Ÿ“Œ Note

SQL is not a programming language in the traditional sense โ€” it's a declarative query language. You describe what you want, not how to get it.

Why SQL matters

Nearly every web application, SaaS product, or data dashboard relies on a relational database. SQL is the universal language to talk to them โ€” and in 2026, it's also the backbone of AI data pipelines.

๐ŸŒ Web Apps๐Ÿ“Š Dashboards ๐Ÿ›’ E-commerce๐Ÿ“ฑ Mobile Backends ๐Ÿ” Analyticsโš™๏ธ SaaS Platforms ๐Ÿค– AI / Data Science

Popular SQL databases

DatabaseTypeCommon Use2026 Status
PostgreSQLOpen source RDBMSSaaS, complex appsv17 โ€” Recommended
MySQLOpen source RDBMSWeb apps, WordPressv9.x โ€” Strong JSON
SQLiteEmbedded RDBMSMobile apps, local devStable
SQL ServerMicrosoft RDBMSEnterprise, .NET apps2025 โ€” AI built-in
MariaDBMySQL forkCloud databasesActive

Data Types Reference

Choosing the right data type is critical for performance, storage, and correctness.

TypeUse CaseExampleNotes
INT / BIGINTWhole numbers, IDs150Use BIGINT for large tables
DECIMAL(p,s)Money, precise decimalsDECIMAL(10,2)Never use FLOAT for currency
VARCHAR(n)Short variable strings'Yahya.Dev'Max n chars, efficient storage
TEXTLong text contentArticle body, logsNo length limit
BOOLEANTrue / False flagsTRUE, FALSEStored as 0/1 in MySQL
TIMESTAMPDate + time values2026-01-15 14:30:00Use UTC always
DATEDate only2026-01-15No time component
UUIDUnique identifiersuuid_generate_v4()Better than INT for distributed
JSON / JSONBSemi-structured data'{"role":"admin"}'JSONB is indexed in PostgreSQL
SERIALAuto-incrementing IDsPrimary key columnSERIAL = PG, AUTO_INCREMENT = MySQL
โœฆ 2026 Tip

In PostgreSQL 17 and MySQL 9.x, JSONB columns can be indexed and queried with the ->> operator โ€” a powerful alternative to adding new columns for semi-structured data.

๐Ÿ“—
02 / SYNTAX

Syntax Basics

SQL statements are human-readable. Keywords are conventionally uppercase, though SQL is case-insensitive for keywords.

The SELECT statement

The most fundamental SQL command โ€” retrieves data from one or more tables.

SQL
SELECT * FROM users;
SQL โ€” SELECT specific columns
SELECT id, name, email
FROM   users;

Statement structure

SELECTSpecifies which columns to retrieve. Use * for all columns.
FROMNames the table to query data from.
WHEREFilters rows based on a condition (optional).
ORDER BYSorts the result set (optional).
LIMITRestricts number of rows returned (optional).
SQL โ€” full SELECT anatomy
SELECT   id, name, email
FROM     users
WHERE    active = 1
ORDER BY created_at DESC
LIMIT    10;
โœฆ Best Practice

Always prefer selecting specific columns over SELECT * in production โ€” it reduces data transfer and makes queries explicit and maintainable.

๐Ÿ“™
03 / FILTERING

Filtering & Sorting

WHERE clause

Filters which rows are included in the result. Supports comparison and logical operators.

SQL โ€” WHERE operators
-- Equality
SELECT * FROM users WHERE role = 'admin';
-- Comparison
SELECT * FROM products WHERE price > 50;
-- Range
SELECT * FROM orders WHERE total BETWEEN 100 AND 500;
-- Pattern match
SELECT * FROM users WHERE email LIKE '%@gmail.com';
-- Multiple conditions
SELECT * FROM users WHERE active = 1 AND role = 'editor';
-- NULL check
SELECT * FROM users WHERE deleted_at IS NULL;

ORDER BY & LIMIT

SQL โ€” ORDER BY + LIMIT + OFFSET
-- Sort newest first
SELECT id, name, created_at
FROM   users
ORDER BY created_at DESC;
-- Top 5 most expensive products
SELECT name, price FROM products ORDER BY price DESC LIMIT 5;
-- Paginate: page 2, 10 items per page
SELECT * FROM posts ORDER BY id LIMIT 10 OFFSET 10;
๐Ÿ”ง
04 / CRUD

CRUD Operations

CRUD โ€” Create, Read, Update, Delete โ€” maps to INSERT, SELECT, UPDATE, DELETE.

INSERT

SQL โ€” INSERT
-- Single row
INSERT INTO users (name, email, role)
VALUES ('Yahya', 'yehiahwary515@gmail.com', 'admin');
-- Multiple rows
INSERT INTO products (name, price, stock)
VALUES ('Widget A', 29.99, 100), ('Widget B', 49.99, 50), ('Widget C', 9.99, 200);

UPDATE

โš ๏ธ Warning

Always include a WHERE clause with UPDATE. Without it, every row in the table will be modified.

SQL โ€” UPDATE
UPDATE users
SET    role = 'moderator'
WHERE  id = 42;

UPDATE users
SET    name = 'Yahya A.', updated_at = NOW()
WHERE  id = 42;

DELETE

SQL โ€” DELETE
-- Hard delete
DELETE FROM users WHERE id = 42;
-- Soft delete (preferred in production)
UPDATE users SET deleted_at = NOW() WHERE id = 42;
-- Bulk delete expired sessions
DELETE FROM sessions WHERE expires_at < NOW();
๐Ÿ”—
05 / JOINS

Joins

JOINs combine rows from two or more tables based on a related column.

INNER JOIN

A
B

Returns only rows matching in both tables.

LEFT JOIN

A
B

All rows from left table + matched rows from right (NULL where no match).

RIGHT JOIN

A
B

All rows from right table + matched rows from left.

INNER JOIN

SQL โ€” INNER JOIN
SELECT
  o.id        AS order_id,
  u.name      AS customer,
  u.email,
  o.total,
  o.created_at
FROM   orders AS o
INNER JOIN users AS u ON o.user_id = u.id;

LEFT JOIN

SQL โ€” LEFT JOIN (find users without orders)
SELECT
  u.id,
  u.name,
  COUNT(o.id) AS order_count
FROM      users AS u
LEFT JOIN orders AS o ON o.user_id = u.id
GROUP BY  u.id, u.name
ORDER BY  order_count DESC;
โœฆ Tip

Use table aliases (u, o) to keep JOIN queries readable. 90% of performance issues come from JOINs without indexes on the join columns.

โšก
06 / ADVANCED

Advanced SQL

GROUP BY & Aggregate Functions

Aggregate functions compute a single value from a set of rows. GROUP BY divides rows into groups before applying them.

SQL โ€” GROUP BY + aggregates
SELECT
  user_id,
  COUNT(*)   AS total_orders,
  SUM(total) AS revenue,
  AVG(total) AS avg_order,
  MAX(total) AS biggest_order
FROM   orders
GROUP BY user_id
ORDER BY revenue DESC;

HAVING

HAVING filters after grouping โ€” unlike WHERE which filters before aggregation.

SQL โ€” HAVING
SELECT user_id, COUNT(*) AS order_count
FROM   orders
GROUP BY user_id
HAVING COUNT(*) > 5
ORDER BY order_count DESC;

Subqueries

SQL โ€” Subquery
-- Users who placed at least one completed order
SELECT name, email FROM users
WHERE id IN (
  SELECT DISTINCT user_id FROM orders WHERE status = 'completed'
);
-- Products above average price
SELECT name, price FROM products
WHERE price > (SELECT AVG(price) FROM products);

Indexing

SQL โ€” Indexes
CREATE INDEX idx_users_email ON users (email);
CREATE UNIQUE INDEX idx_users_email_unique ON users (email);
CREATE INDEX idx_orders_user_status ON orders (user_id, status);
DROP INDEX idx_users_email;
๐Ÿ“Œ Rule of Thumb

Index columns used in WHERE, JOIN ON, and ORDER BY. Don't over-index โ€” each index slows INSERT/UPDATE.

๐Ÿš€
07 / PRO LEVEL

Pro Level SQL

These are the features that separate a junior from a senior developer. Master these and you can handle any real-world database problem.

CTEs โ€” Common Table Expressions

A CTE (WITH clause) lets you name a temporary result set and reference it like a table. It makes complex queries readable, reusable, and maintainable โ€” logic flows top-to-bottom like reading a story.

โœฆ CTE vs Subquery

Use a CTE when you need to reference the same subquery more than once, or when the subquery is complex enough to deserve a descriptive name.

SQL โ€” Basic CTE
WITH active_users AS (
  SELECT id, name, email
  FROM   users
  WHERE  active = TRUE
    AND  deleted_at IS NULL
)
SELECT *
FROM   active_users
WHERE  email LIKE '%@company.com'
ORDER BY name;
SQL โ€” Chained CTEs
WITH
  order_totals AS (
    SELECT user_id, COUNT(*) AS order_count, SUM(total) AS lifetime_value
    FROM   orders
    WHERE  status = 'completed'
    GROUP BY user_id
  ),
  high_value AS (
    SELECT user_id FROM order_totals
    WHERE lifetime_value > 1000 AND order_count >= 3
  )
SELECT u.name, u.email, ot.order_count, ot.lifetime_value
FROM       users        AS u
INNER JOIN order_totals AS ot ON u.id = ot.user_id
INNER JOIN high_value   AS hv ON u.id = hv.user_id
ORDER BY ot.lifetime_value DESC;

Window Functions

Window functions perform calculations across rows related to the current row โ€” without collapsing them like GROUP BY does. They're the most powerful analytical feature in SQL.

ROW_NUMBER()Unique sequential integer per row within the partition. No ties.
RANK()Like ROW_NUMBER but ties get same rank, with gaps after (1, 1, 3).
DENSE_RANK()Like RANK without gaps (1, 1, 2).
LAG() / LEAD()Access values from previous or next rows within the partition.
SUM() OVER()Running totals or partial aggregates without collapsing rows.
PARTITION BYDivides rows into groups (like GROUP BY, but rows are kept). Used inside OVER().
SQL โ€” ROW_NUMBER, RANK, DENSE_RANK
SELECT
  employee_name,
  department,
  salary,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num,
  RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank,
  DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rank
FROM employees
ORDER BY department, salary DESC;
SQL โ€” Running Total + LAG (month-over-month growth)
SELECT
  month,
  revenue,
  SUM(revenue) OVER (ORDER BY month)  AS running_total,
  LAG(revenue) OVER (ORDER BY month)  AS prev_month,
  ROUND(
    (revenue - LAG(revenue) OVER (ORDER BY month))
    / NULLIF(LAG(revenue) OVER (ORDER BY month), 0) * 100
  , 1) AS growth_pct
FROM monthly_revenue
ORDER BY month;
SQL โ€” Top-N per group (classic pattern)
-- Top 3 best-selling products per category
WITH ranked AS (
  SELECT
    category, product_name, total_sold,
    ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sold DESC) AS rn
  FROM products
)
SELECT category, product_name, total_sold
FROM   ranked
WHERE  rn <= 3
ORDER BY category, total_sold DESC;

Transactions

A transaction groups multiple SQL statements into a single atomic unit. Either all succeed, or none take effect โ€” the foundation of data integrity.

๐Ÿ”’ ACID Properties

Transactions guarantee Atomicity (all or nothing), Consistency (valid state before & after), Isolation (concurrent transactions don't interfere), and Durability (committed data survives crashes).

1

BEGIN

Start the transaction โ€” changes won't be saved yet.

2

Run your statements

INSERT, UPDATE, DELETE โ€” execute but can still be reversed.

3

COMMIT or ROLLBACK

COMMIT saves permanently. ROLLBACK undoes everything back to BEGIN.

SQL โ€” Bank Transfer Transaction
BEGIN;

  UPDATE accounts
  SET    balance = balance - 500
  WHERE  id = 1;

  UPDATE accounts
  SET    balance = balance + 500
  WHERE  id = 2;

COMMIT;
-- Both updates happen together, or neither does
SQL โ€” Transaction with ROLLBACK + SAVEPOINT
BEGIN;

  INSERT INTO orders (user_id, total, status)
  VALUES (42, 199.99, 'pending');

  SAVEPOINT after_order;

  UPDATE inventory SET stock = stock - 1
  WHERE product_id = 7 AND stock > 0;

  -- If something fails, rollback to savepoint (keeps the order insert)
  -- ROLLBACK TO SAVEPOINT after_order;

  INSERT INTO audit_log (action, user_id) VALUES ('order_created', 42);

COMMIT;
โš ๏ธ Warning

Always wrap financial operations (payments, balance transfers, inventory deductions) in a transaction. A crash mid-operation without one leaves your data in a corrupted, half-updated state.

๐Ÿ’ก
08 / EXAMPLES

Real-World Examples

Production-style queries you'd write in real applications.

๐Ÿ‘ฅ

Users Table

Schema + common queries

A typical users table with JSONB metadata for flexibility.

SQL โ€” Schema + Queries
CREATE TABLE users (
  id         SERIAL        PRIMARY KEY,
  name       VARCHAR(100)  NOT NULL,
  email      VARCHAR(255)  UNIQUE NOT NULL,
  role       VARCHAR(20)   DEFAULT 'user',
  active     BOOLEAN       DEFAULT TRUE,
  metadata   JSONB         DEFAULT '{}',
  created_at TIMESTAMP     DEFAULT NOW(),
  deleted_at TIMESTAMP     NULL
);

SELECT * FROM users WHERE active = TRUE ORDER BY created_at DESC;
SELECT COUNT(*) FROM users WHERE role = 'admin';
-- Query JSONB column (PostgreSQL)
SELECT * FROM users WHERE metadata->>'plan' = 'pro';
๐Ÿ›’

E-Commerce Order Report

Multi-table query

Pull a full order report across orders, order_items, products, and users.

SQL โ€” Order Report
SELECT
  o.id                         AS order_id,
  u.name                       AS customer,
  u.email,
  p.name                       AS product,
  oi.quantity,
  oi.unit_price,
  (oi.quantity * oi.unit_price) AS line_total,
  o.status,
  o.created_at
FROM       orders      AS o
INNER JOIN users       AS u  ON o.user_id    = u.id
INNER JOIN order_items AS oi ON oi.order_id  = o.id
INNER JOIN products    AS p  ON oi.product_id = p.id
WHERE  o.status = 'completed'
  AND  o.created_at >= NOW() - INTERVAL '30 days'
ORDER BY o.created_at DESC;
โ˜๏ธ

SaaS MRR Analytics

Subscription metrics

MRR and churn rate for a SaaS platform.

SQL โ€” SaaS MRR Report
-- MRR by plan tier
SELECT
  p.name                         AS plan,
  COUNT(s.id)                    AS subscribers,
  SUM(p.monthly_price)           AS mrr,
  ROUND(AVG(p.monthly_price), 2) AS avg_revenue
FROM   subscriptions AS s
INNER JOIN plans AS p ON s.plan_id = p.id
WHERE  s.status = 'active' AND s.canceled_at IS NULL
GROUP BY p.name, p.monthly_price
ORDER BY mrr DESC;

-- Churn rate last 30 days
SELECT
  COUNT(*) AS churned,
  ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM subscriptions), 2) AS churn_pct
FROM subscriptions
WHERE canceled_at >= NOW() - INTERVAL '30 days';
๐Ÿ“Š

Dashboard Analytics

CTE + Window Functions combined

Monthly revenue trend with running total and growth % โ€” the kind of query you'd write for a dashboard in a system like YS-Matrix.

SQL โ€” Revenue Dashboard Query
WITH monthly AS (
  SELECT
    DATE_TRUNC('month', created_at) AS month,
    COUNT(*)                        AS orders,
    SUM(total)                      AS revenue
  FROM   orders
  WHERE  status = 'completed'
    AND  created_at >= NOW() - INTERVAL '12 months'
  GROUP BY DATE_TRUNC('month', created_at)
)
SELECT
  TO_CHAR(month, 'Mon YYYY')                       AS period,
  orders,
  revenue,
  SUM(revenue) OVER (ORDER BY month)               AS cumulative_revenue,
  LAG(revenue) OVER (ORDER BY month)               AS prev_month,
  ROUND(
    (revenue - LAG(revenue) OVER (ORDER BY month))
    / NULLIF(LAG(revenue) OVER (ORDER BY month), 0) * 100
  , 1)                                             AS growth_pct
FROM monthly
ORDER BY month;