Master SQL from scratch with this comprehensive beginner's tutorial. Learn database fundamentals, queries, joins, and practical SQL skills for data analysis and development.
SQL (Structured Query Language) is the language of data. Whether you're a data analyst, web developer, data scientist, or business professional—SQL is an essential skill.
Every major application uses databases, and SQL is how you communicate with them. This comprehensive tutorial takes you from zero to writing complex queries that extract meaningful insights from data.
SQL is a standard programming language designed for managing and manipulating relational databases. It allows you to:
| Reason | Explanation |
|---|---|
| Universal | Works across all relational databases |
| High Demand | Required for most data roles |
| Easy to Learn | English-like syntax |
| Powerful | Handles millions of records efficiently |
| Foundation | Basis for data science and analytics |
| Career Impact | Opens doors to many roles |
| Industry | Use Case |
|---|---|
| Tech | Backend development, data storage |
| Finance | Transaction processing, reporting |
| Healthcare | Patient records, analytics |
| E-commerce | Inventory, customer data |
| Marketing | Customer analysis, campaigns |
| Feature | SQL (Relational) | NoSQL |
|---|---|---|
| Structure | Tables (rows & columns) | Documents, key-value, graphs |
| Schema | Fixed, predefined | Flexible, dynamic |
| Examples | MySQL, PostgreSQL, Oracle | MongoDB, Redis, Cassandra |
| Best For | Structured data, relations | Unstructured, scale |
| Option | Best For | Setup |
|---|---|---|
| SQLite | Beginners, simple practice | No install needed |
| MySQL | Web development | Install separately |
| PostgreSQL | Advanced features | Install separately |
| Online SQLite | Immediate practice | sqliteonline.com |
Database
├── Table 1 (e.g., users)
│ ├── Columns (id, name, email, age)
│ └── Rows (each user's data)
├── Table 2 (e.g., orders)
│ └── ...
└── Table 3 (e.g., products)
└── ...
| Term | Definition |
|---|---|
| Database | Collection of organized data |
| Table | Data organized in rows and columns |
| Row/Record | Single entry in a table |
| Column/Field | Attribute of data (name, age, etc.) |
| Primary Key | Unique identifier for each row |
| Foreign Key | Links one table to another |
| Schema | Structure/design of database |
users table: | id | name | email | age | city | |----|------|-------|-----|------| | 1 | John | [email protected] | 28 | Delhi | | 2 | Jane | [email protected] | 24 | Mumbai | | 3 | Bob | [email protected] | 35 | Bangalore |
products table: | id | name | price | category | |----|------|-------|----------| | 1 | Laptop | 50000 | Electronics | | 2 | Phone | 20000 | Electronics | | 3 | Book | 500 | Books |
orders table: | id | user_id | product_id | quantity | order_date | |----|---------|------------|----------|------------| | 1 | 1 | 1 | 1 | 2024-01-15 | | 2 | 1 | 3 | 2 | 2024-01-20 | | 3 | 2 | 2 | 1 | 2024-02-01 |
Retrieve data from a table:
Select all columns:
SELECT * FROM users;
Select specific columns:
SELECT name, email FROM users;
Select with alias:
SELECT name AS customer_name, email AS customer_email
FROM users;
Filter results based on conditions:
SELECT * FROM users
WHERE age > 25;
Comparison Operators: | Operator | Meaning | |----------|---------| | = | Equal | | != or <> | Not equal | | > | Greater than | | < | Less than | | >= | Greater than or equal | | <= | Less than or equal |
Combine multiple conditions:
-- AND: Both conditions must be true
SELECT * FROM users
WHERE age > 25 AND city = 'Delhi';
-- OR: Either condition can be true
SELECT * FROM users
WHERE city = 'Delhi' OR city = 'Mumbai';
-- NOT: Negate condition
SELECT * FROM users
WHERE NOT city = 'Bangalore';
Range of values:
SELECT * FROM products
WHERE price BETWEEN 1000 AND 30000;
Match any value in a list:
SELECT * FROM users
WHERE city IN ('Delhi', 'Mumbai', 'Bangalore');
Pattern matching:
-- Starts with 'J'
SELECT * FROM users WHERE name LIKE 'J%';
-- Ends with 'n'
SELECT * FROM users WHERE name LIKE '%n';
-- Contains 'oh'
SELECT * FROM users WHERE name LIKE '%oh%';
-- Second character is 'o'
SELECT * FROM users WHERE name LIKE '_o%';
| Wildcard | Meaning |
|---|---|
| % | Any sequence of characters |
| _ | Any single character |
Check for null values:
SELECT * FROM users
WHERE phone IS NULL;
SELECT * FROM users
WHERE phone IS NOT NULL;
Sort results:
-- Ascending (default)
SELECT * FROM users ORDER BY age;
-- Descending
SELECT * FROM users ORDER BY age DESC;
-- Multiple columns
SELECT * FROM users ORDER BY city, age DESC;
Restrict number of results:
-- Get first 5 rows
SELECT * FROM products
ORDER BY price DESC
LIMIT 5;
OFFSET (skip rows):
-- Skip first 5, get next 5 (pagination)
SELECT * FROM products
LIMIT 5 OFFSET 5;
Get unique values:
SELECT DISTINCT city FROM users;
SELECT DISTINCT category FROM products;
Functions that calculate values across rows:
| Function | Purpose |
|---|---|
| COUNT() | Count rows |
| SUM() | Add values |
| AVG() | Average value |
| MAX() | Maximum value |
| MIN() | Minimum value |
-- Count all users
SELECT COUNT(*) FROM users;
-- Count users in Delhi
SELECT COUNT(*) FROM users WHERE city = 'Delhi';
-- Total product value
SELECT SUM(price) FROM products;
-- Average product price
SELECT AVG(price) FROM products;
-- Most expensive product
SELECT MAX(price) FROM products;
-- Cheapest product
SELECT MIN(price) FROM products;
Group rows and apply aggregates:
-- Count users per city
SELECT city, COUNT(*) as user_count
FROM users
GROUP BY city;
-- Average price per category
SELECT category, AVG(price) as avg_price
FROM products
GROUP BY category;
-- Total sales per product
SELECT product_id, SUM(quantity) as total_sold
FROM orders
GROUP BY product_id;
Filter grouped results (WHERE is for rows, HAVING is for groups):
-- Cities with more than 2 users
SELECT city, COUNT(*) as user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 2;
-- Categories with average price > 10000
SELECT category, AVG(price) as avg_price
FROM products
GROUP BY category
HAVING AVG(price) > 10000;
JOINs combine data from multiple tables.
| Type | Description |
|---|---|
| INNER JOIN | Only matching rows from both tables |
| LEFT JOIN | All rows from left table, matching from right |
| RIGHT JOIN | All rows from right table, matching from left |
| FULL JOIN | All rows from both tables |
-- Get orders with user names
SELECT orders.id, users.name, orders.order_date
FROM orders
INNER JOIN users ON orders.user_id = users.id;
-- All users and their orders (even users with no orders)
SELECT users.name, orders.id as order_id
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
-- Orders with user and product details
SELECT
users.name as customer,
products.name as product,
orders.quantity,
orders.order_date
FROM orders
INNER JOIN users ON orders.user_id = users.id
INNER JOIN products ON orders.product_id = products.id;
For cleaner queries:
SELECT u.name, p.name as product, o.quantity
FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN products p ON o.product_id = p.id;
Add new data:
-- Insert single row
INSERT INTO users (name, email, age, city)
VALUES ('Alice', '[email protected]', 26, 'Chennai');
-- Insert multiple rows
INSERT INTO users (name, email, age, city)
VALUES
('Mike', '[email protected]', 30, 'Delhi'),
('Sara', '[email protected]', 22, 'Pune');
Modify existing data:
-- Update single field
UPDATE users
SET age = 29
WHERE id = 1;
-- Update multiple fields
UPDATE users
SET age = 30, city = 'Hyderabad'
WHERE name = 'John';
-- Update with calculation
UPDATE products
SET price = price * 1.1
WHERE category = 'Electronics';
⚠️ Always use WHERE with UPDATE! Without it, all rows are updated.
Remove data:
-- Delete specific row
DELETE FROM users
WHERE id = 5;
-- Delete with condition
DELETE FROM orders
WHERE order_date < '2023-01-01';
⚠️ Always use WHERE with DELETE! Without it, all rows are deleted.
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
department VARCHAR(50),
salary DECIMAL(10, 2),
hire_date DATE,
is_active BOOLEAN DEFAULT TRUE
);
| Type | Description | Example |
|---|---|---|
| INT | Integer | 42 |
| VARCHAR(n) | Variable string up to n | 'Hello' |
| TEXT | Long text | Long description |
| DECIMAL(p,s) | Precise decimal | 99.99 |
| DATE | Date | 2024-01-15 |
| DATETIME | Date and time | 2024-01-15 14:30:00 |
| BOOLEAN | True/False | TRUE |
| Constraint | Purpose |
|---|---|
| PRIMARY KEY | Unique identifier |
| FOREIGN KEY | Link to another table |
| NOT NULL | Must have value |
| UNIQUE | No duplicates |
| DEFAULT | Default value if none provided |
| CHECK | Validate data |
Modify table structure:
-- Add column
ALTER TABLE users
ADD phone VARCHAR(15);
-- Remove column
ALTER TABLE users
DROP COLUMN phone;
-- Modify column
ALTER TABLE users
MODIFY COLUMN name VARCHAR(150);
-- Rename column
ALTER TABLE users
RENAME COLUMN name TO full_name;
Delete entire table:
DROP TABLE employees;
-- Only if exists
DROP TABLE IF EXISTS employees;
A query inside another query:
-- Products more expensive than average
SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products);
-- Users who have placed orders
SELECT * FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);
-- Average orders per user
SELECT AVG(order_count) as avg_orders_per_user
FROM (
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
) as user_orders;
-- Products with order count
SELECT
name,
price,
(SELECT COUNT(*) FROM orders WHERE product_id = products.id) as times_ordered
FROM products;
Conditional logic in queries:
SELECT
name,
price,
CASE
WHEN price < 1000 THEN 'Budget'
WHEN price < 10000 THEN 'Mid-range'
ELSE 'Premium'
END as price_category
FROM products;
Combine results from multiple queries:
-- All cities from both users and suppliers
SELECT city FROM users
UNION
SELECT city FROM suppliers;
-- UNION ALL keeps duplicates
SELECT city FROM users
UNION ALL
SELECT city FROM suppliers;
Saved queries that act like tables:
-- Create view
CREATE VIEW customer_orders AS
SELECT u.name, p.name as product, o.quantity
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
-- Use view
SELECT * FROM customer_orders WHERE name = 'John';
Speed up queries on large tables:
-- Create index
CREATE INDEX idx_users_email ON users(email);
-- Create unique index
CREATE UNIQUE INDEX idx_users_email ON users(email);
Understanding how SQL processes queries:
| Order | Clause | Purpose |
|---|---|---|
| 1 | FROM | Choose tables |
| 2 | JOIN | Combine tables |
| 3 | WHERE | Filter rows |
| 4 | GROUP BY | Group rows |
| 5 | HAVING | Filter groups |
| 6 | SELECT | Choose columns |
| 7 | DISTINCT | Remove duplicates |
| 8 | ORDER BY | Sort results |
| 9 | LIMIT | Restrict rows |
This is why you can't use column aliases in WHERE but can in ORDER BY.
SELECT email, COUNT(*) as count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
SELECT MAX(price) as second_highest
FROM products
WHERE price < (SELECT MAX(price) FROM products);
SELECT
order_date,
quantity,
SUM(quantity) OVER (ORDER BY order_date) as running_total
FROM orders;
SELECT
name,
price,
RANK() OVER (ORDER BY price DESC) as price_rank
FROM products;
| Practice | Example |
|---|---|
| Use uppercase for keywords | SELECT, FROM, WHERE |
| Use lowercase for tables/columns | users, email |
| One clause per line | Easy to read |
| Indent subqueries | Clear structure |
| Use meaningful aliases | u for users, not x |
| Tip | Why |
|---|---|
| Select only needed columns | SELECT name, email not SELECT * |
| Use indexes on WHERE columns | Speeds up filtering |
| Avoid functions in WHERE | WHERE date > '2024-01-01' not WHERE YEAR(date) = 2024 |
| Use LIMIT when testing | Don't fetch millions of rows |
| Analyze slow queries | Use EXPLAIN |
Basics: 1-2 weeks. Comfortable for jobs: 1-2 months. Advanced: ongoing with practice.
Start with any—concepts transfer. MySQL and PostgreSQL are most popular. SQLite is easiest for beginners.
SQL alone can get data analyst roles. Combined with Python/R, it opens data science roles. With programming, backend development roles.
SQL is the language. MySQL is a database system that uses SQL. Other systems (PostgreSQL, Oracle) also use SQL with slight variations.
Use online platforms (SQLBolt, LeetCode), practice with Kaggle datasets, or create a local database with sample data.
Learning database skills? Explore more resources on Sproutern for programming tutorials, career guidance, and skill development.
Our team of career experts, industry professionals, and former recruiters brings decades of combined experience in helping students and freshers launch successful careers.
Discover the best programming languages to learn for career growth and high-paying tech jobs....
Master Data Structures and Algorithms with this complete roadmap. From arrays to dynamic programming...
If you found this article helpful, please cite it as: