SQL stands for Structured Query Language.It is used to create, store, retrieve, manipulate, and manage data in relational databases. It allows users to communicate with a database and perform various operations on the data.
A database stores information in the form of tables, which consist of rows and columns. SQL provides commands to work with these tables efficiently.
Almost every real application — social media, banking, e-commerce, even EDUhub if it had a backend — stores its data in a database somewhere. SQL is how you actually interact with that data. It's a completely different kind of thinking from writing a program step by step; instead, you describe what data you want, and the database figures out how to get it.
This page covers the core fundamentals of SQL, step by step.
A database is just an organized collection of data. Inside a database, data is stored in tables — think of a table like a spreadsheet, with rows and columns.
A table in SQL is used to store data in the form of rows and columns.
Students table:
| id | name | age | grade |
|----|----------|-----|-------|
| 1 | Adithyan | 21 | A |
| 2 | Priya | 20 | B |
| 3 | Rahul | 22 | A |
id) that uniquely identifies each rowINT — whole numbersVARCHAR — text, with a maximum lengthDATE — calendar datesBOOLEAN — true or false
Before you can store any data, you need a table to put it in.
CREATE TABLE is how you build one from scratch,
defining each column's name and data type upfront.
CREATE TABLE is an SQL command used to create a new table in a database by defining its columns, data types, and constraints.
CREATE TABLE students (
id INT,
name VARCHAR(50),
age INT,
grade VARCHAR(2)
);
CREATE TABLE students — names the new table "students"VARCHAR(50) — specifies the maximum length of the text that can be stored in that column.
Table and column names are usually written in lowercase with
underscores (like student_name, not
StudentName) — this isn't a strict rule, just a widely
followed convention that keeps things consistent and readable.
CREATE TABLE defines a new table and its columns upfrontConstraints are rules you applied to a column when creating a table, making sure the data stored there stays valid and consistent. They're what prevent things like duplicate IDs or a required field being left empty.
Uniquely identifies each row in a table. no two rows can share the same value in this column, and it can't be left empty.
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50)
);
Forces a column to always have a value ,trying to insert a row without one will fail.
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
Similar to a primary key, but a table can have multiple
UNIQUE columns, while it can only have one primary key.
Useful for things like email addresses, where duplicates shouldn't
be allowed.
CREATE TABLE students (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE
);
Automatically fills in a value if none is provided when a row is inserted.
CREATE TABLE students (
id INT PRIMARY KEY,
grade VARCHAR(2) DEFAULT 'N/A'
);
A foreign key connects one table to another — it stores a value that matches a primary key in a different table, linking related data together without repeating it.
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES departments(id)
);
Here, dept_id in the students table must match an
id that already exists in the departments table — this is
what keeps related data connected and consistent.
PRIMARY KEY — uniquely identifies each row, can't be empty or duplicatedNOT NULL — the column must always have a valueUNIQUE — no duplicate values allowed, but multiple columns can have thisDEFAULT — automatically fills in a value when none is givenFOREIGN KEY — A foreign key is a field that links two tables by referring to the primary key of another table.
SELECT is the command you'll use constantly in SQL —
It is used to retrieve or display data from one or more tables in a database.
SELECT name, age FROM students;
This returns only the "name" and "age" columns from the students table, for every row.
The * symbol means "all columns" — a quick way to see
an entire table without listing every column by name.
SELECT * FROM students;
| id | name | age | grade |
|----|---------|-----|-------|
| 1 | Aravind | 21 | A |
| 2 | Priya | 20 | B |
| 3 | Rahul | 22 | A |
SELECT name FROM students;
-- Output:
-- Aravind
-- Priya
-- Rahul
AS gives a column a different name in the result,
without changing anything in the actual table.
SELECT name AS student_name FROM students;
SELECT retrieves data — it doesn't change anything in the table* selects every columnAS renames a column just in the result, not in the actual table
The WHERE clause is used with SQL statements to filter records based on a specific condition. It displays or affects only the rows that satisfy the condition.
| id | name | age | grade |
|----|---------|-----|-------|
| 1 | Aravind | 21 | A |
| 2 | Priya | 20 | B |
| 3 | Rahul | 22 | A |
SELECT name FROM students;
SELECT * FROM students WHERE age > 20;
This returns only Aravind and Rahul, since Priya is 20, not older than 20.
= — equal to!= or <> — not equal to> / < — greater than / less than>= / <= — greater than or equal to / less than or equal toSELECT * FROM students WHERE grade = 'A';
SELECT * FROM students WHERE age > 20 AND grade = 'A';
AND requires both conditions to be true. OR only requires one condition to be true.
The BETWEEN operator is used to select records where a value falls within a specified range.
It is usually used with the WHERE clause.
SELECT * FROM students WHERE age BETWEEN 20 AND 21;
Used for partial text matching. % is a wildcard,
standing in for any number of characters.
SELECT * FROM students WHERE name LIKE 'A%';
This finds any name starting with "A" — in our table, that's Aravind.
WHERE filters rows based on a conditionAND/OR logical operators used to combine multiple conditionsBETWEEN used to select values within a specific range.LIKE matches text patterns
INSERT is how you add new rows of data into a table.
While SELECT reads data, INSERT writes it.
INSERT INTO students (id, name, age, grade)
VALUES (4, 'Alex', 19, 'A');
The column names come first, then their matching values — notice the order has to line up exactly between the two lists.
If you're providing a value for every single column, in the exact order the table was created, you can skip listing the column names.
INSERT INTO students
VALUES (4, 'Alex', 19, 'A');
This works, but it's riskier — if the table's structure ever changes (a column gets added or reordered), this query could silently insert data into the wrong columns. Listing column names explicitly is generally the safer habit.
INSERT INTO students (id, name, age, grade)
VALUES
(5, 'Arjun', 20, 'B'),
(6, 'Divya', 21, 'A');
| id | name | age | grade |
|----|---------|-----|-------|
| 1 | Akhil | 21 | A |
| 2 | Arjun | 20 | B |
| 3 | Rahul | 22 | A |
| 4 | Meera | 19 | A |
| 5 | Pranav | 20 | B |
| 6 | Divya | 21 | A |
INSERT INTO adds new rows to a tableINSERT is used to add new data to a table.INSERT INTO specifies the table where data is added.VALUES specifies the actual data to be inserted.INSERT statement.
UPDATE changes existing data in a table, and
DELETE removes rows entirely. Both are powerful — and
both can cause real damage if used carelessly, since there's no
"undo" once a query runs.
UPDATE students
SET grade = 'A'
WHERE name = 'Priya';
This changes Priya's grade to 'A', using our same students table.
UPDATE students
SET age = 21, grade = 'A'
WHERE id = 2;
This is one of the most common — and costly — mistakes in SQL.
Without a WHERE clause, UPDATE applies to
every single row in the table.
UPDATE students
SET grade = 'A';
This sets everyone's grade to 'A' — probably not what was
intended. Always double-check a WHERE clause is in
place before running an UPDATE.
DELETE FROM students
WHERE name = 'Rahul';
This removes Rahul's row entirely from the table.
DELETE FROM students;
Without a WHERE clause, this deletes every
row in the table — the table itself still exists, but it's
now completely empty.
UPDATE changes existing data, DELETE removes rowsWHERE to target specific rowsWHERE applies the change to the entire table — always double-check before running
By default, a query returns rows in whatever order the database
happens to store them — not necessarily sorted in any meaningful
way. ORDER BY and LIMIT give you control
over that.
SELECT * FROM students ORDER BY age;
The ORDER BY clause is used to sort the records in ascending (smallest to largest) or descending (largest to smallest) order.
SELECT * FROM students ORDER BY age DESC;
DESC flips it to largest-to-smallest. Without it, SQL
defaults to ascending order (ASC), smallest to largest.
SELECT * FROM students ORDER BY grade, age DESC;
This sorts primarily by grade, and for students with the same grade, sorts by age (descending) as a tiebreaker.
The LIMIT clause is used to restrict the number of records displayed.
SELECT * FROM students ORDER BY age DESC LIMIT 2;
This returns only the 2 oldest students, instead of the entire table ,it useful for things like "show the top 10 results."
ORDER BY sorts results — ascending by default, use DESC for descending, ASC for ascendingLIMIT restricts how many rows are returnedAggregate functions are functions used to perform calculations on multiple rows and return a single result.
Counts the number of rows or values.
SELECT COUNT(*) FROM students;
Using our students table (6 rows after the inserts), this returns 6.
Adds up all the values in a numeric column.
SELECT SUM(age) FROM students;
Calculates the average of a numeric column.
SELECT AVG(age) FROM students;
Find the highest or lowest value in a column.
SELECT MAX(age) FROM students;
SELECT MIN(age) FROM students;
Aggregate functions work fine alongside a WHERE clause,
letting you summarize a filtered subset of data instead of the whole
table.
SELECT COUNT(*) FROM students WHERE grade = 'A';
COUNT() — counts rowsSUM() — adds up valuesAVG() — calculates an averageMAX()/MIN() — finds the highest/lowest valueWHERE to summarize filtered data
Aggregate functions are powerful on their own, but they get really
useful once combined with GROUP BY — which splits your
data into groups first, then runs the aggregate calculation
separately for each group, instead of over the whole table at once.
The GROUP BY clause is used to group rows with the same values in one or more columns. It is commonly used with aggregate functions like COUNT(), SUM(), and AVG().
SELECT grade, COUNT(*)
FROM students
GROUP BY grade;
Instead of one total count for the whole table, this returns a separate count for each grade — how many students got an A, how many got a B, and so on.
| id | name | age | grade |
|----|---------|-----|-------|
| 1 | Cecil | 21 | A |
| 2 | Priya | 20 | B |
| 3 | Rahul | 22 | A |
| 4 | Meera | 19 | A |
| 5 | Arjun | 20 | B |
| 6 | Divya | 21 | A |
SELECT grade, COUNT(*)
FROM students
GROUP BY grade;
-- Output:
-- grade | COUNT(*)
-- A | 4
-- B | 2
SELECT grade, AVG(age)
FROM students
GROUP BY grade;
This gives the average age of students within each grade group, rather than one average for everyone.
WHERE filters rows before grouping happens. If you want
to filter the groups themselves after they're calculated —
like "only show grades with more than 2 students" — you need
HAVING instead.
SELECT grade, COUNT(*)
FROM students
GROUP BY grade
HAVING COUNT(*) > 2;
GROUP BY splits data into groups before applying an aggregate functionHAVING filters groups after aggregation — WHERE can't do this
Real databases rarely keep everything in one giant table , data is
usually split across multiple related tables to avoid repetition.Therefore,
A JOIN is used to combine data from two or more tables based on a related column between them
Imagine every student's enrolled courses were stored directly in the students table — you'd end up repeating each student's name and age for every course they're taking. Instead, we keep a separate table just for enrollments, and connect the two using a shared ID.
| student_id | course |
|------------|------------|
| 1 | Math |
| 1 | Physics |
| 2 | Chemistry |
| 3 | Math |
Notice student_id matches up with the id
column in our students table — that's the shared link between them.
SELECT students.name, enrollments.course
FROM students
JOIN enrollments ON students.id = enrollments.student_id;
-- Output:
-- name | course
-- Akhil | Math
-- Akhil | Physics
-- Saran | Chemistry
-- Rahul | Math
This combines matching rows from both tables based on the condition
after ON — wherever a student's id matches
an enrollment's student_id.
By default, JOIN means INNER JOIN — it
only returns rows that have a match in both tables. A student with
no enrollments at all simply wouldn't appear in these results.
JOIN combines rows from two tables based on a shared columnON clause defines exactly how the tables are connectedJOIN only returns rows that have matches in both tablesThink you've got SQL down? Test yourself!
Start Quiz →