Learn SQL in One Blog — The Only Guide You'll Ever Need
A complete SQL guide covering SELECT, INSERT, UPDATE, DELETE, JOINs, Views, Stored Procedures, Triggers, aggregate functions, GROUP BY, ORDER BY, date functions, transactions, EXPLAIN, and more — with real examples and 23 sections of copy-paste-ready queries.
February 18, 2026
This is the only SQL blog you need. No fluff, no theory overload — just clean SQL with real examples using data from my college Deogiri Institute of Engineering (CSE) and my team at Coding Jr. Every concept is explained, every query is copy-paste ready. Let's go.
What's Inside — click any topic to jump there
- Database Operations
- CREATE TABLE
- INSERT INTO
- SELECT & WHERE
- UPDATE
- DELETE
- ALTER TABLE
- ORDER BY
- LIMIT & OFFSET
- DISTINCT
- Aggregate Functions — COUNT, SUM, AVG, MAX, MIN
- GROUP BY & HAVING
- String Functions
- Date Functions
- Transactions — COMMIT, ROLLBACK, SAVEPOINT
- JOINs — INNER, LEFT, RIGHT, FULL, Self JOIN
- Views — CREATE VIEW, ALTER VIEW, DROP VIEW
- TRUNCATE vs DELETE vs DROP
- EXPLAIN
- Stored Procedures
- Triggers
- Cheat Sheet
- Practice Questions
1. Database Operations
Everything starts with a database. A database is just a container that holds your tables.
CREATE DATABASE deogiri_cse;
USE deogiri_cse;
SHOW DATABASES;
-- Delete a database permanently
DROP DATABASE IF EXISTS test_db;
USE database_name; before working with tables, otherwise MySQL won't know where to look.
2. CREATE TABLE
Tables store your actual data in rows and columns. Let's create three tables for our examples.
Students Table
CREATE TABLE students (
roll_no INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150),
age INT,
city VARCHAR(80),
department VARCHAR(10) DEFAULT 'CS',
enrollment_date DATE
);
Quick breakdown:
INT— whole numbersVARCHAR(100)— text up to 100 charactersDATE— date in YYYY-MM-DD formatPRIMARY KEY— unique identifier, no duplicates allowedNOT NULL— this field cannot be emptyDEFAULT 'CS'— auto-fills with 'CS' if not provided
Company Team Table
CREATE TABLE company_team (
emp_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
role VARCHAR(100),
company VARCHAR(100),
experience INT,
salary INT
);
Marks Table
CREATE TABLE marks (
id INT PRIMARY KEY AUTO_INCREMENT,
roll_no INT,
subject VARCHAR(50),
score INT,
exam_date DATE,
FOREIGN KEY (roll_no) REFERENCES students(roll_no)
);
AUTO_INCREMENT means MySQL auto-generates the id value. FOREIGN KEY links roll_no to the students table — so you can't add marks for a student that doesn't exist.
To check your table structure:
DESC students;
SHOW TABLES;
3. INSERT INTO
Let's fill our tables with real data — my friends from college and my team at Coding Jr.
Insert Students
INSERT INTO students VALUES
(1, 'Sagar Waghmare', 'sagar@deogiri.ac.in', 21, 'Chhatrapati Sambhajinagar', 'CS', '2023-08-01'),
(2, 'Atharva Wandhare', 'atharva@deogiri.ac.in', 21, 'Chhatrapati Sambhajinagar', 'CS', '2023-08-01'),
(3, 'Rajat Ranvir', 'rajat@deogiri.ac.in', 21, 'Pune', 'CS', '2023-08-01'),
(4, 'Sourabh Ugawekar', 'sourabh@deogiri.ac.in', 21, 'Chhatrapati Sambhajinagar', 'CS', '2023-08-02'),
(5, 'Omkar Pandhekar', 'omkar@deogiri.ac.in', 22, 'Chhatrapati Sambhajinagar', 'IT', '2023-08-02'),
(6, 'Ankita Patil', 'ankita@deogiri.ac.in', 21, 'Jalna', 'CS', '2023-08-01'),
(7, 'Krishna Shelke', 'krishna@deogiri.ac.in', 21, 'Nashik', 'IT', '2023-08-03'),
(8, 'Nikhil Pawar', 'nikhil@deogiri.ac.in', 22, 'Chhatrapati Sambhajinagar', 'CS', '2023-08-01'),
(9, 'Shrikant Borse', 'shrikant@deogiri.ac.in', 21, 'Dhule', 'IT', '2023-08-02'),
(10, 'Tanvi Kale', 'tanvi@deogiri.ac.in', 21, 'Chhatrapati Sambhajinagar', 'CS', '2023-08-01'),
(11, 'Siddhali Kathar', 'siddhali@deogiri.ac.in', 21, 'Beed', 'CS', '2023-08-03'),
(12, 'Isha Deshpandhe', 'isha@deogiri.ac.in', 21, 'Chhatrapati Sambhajinagar', 'IT', '2023-08-02'),
(13, 'Tanmay Jadhav', 'tanmay@deogiri.ac.in', 22, 'Latur', 'CS', '2023-08-01');
Insert Company Team
INSERT INTO company_team VALUES
(1, 'Raj', 'Senior Developer', 'Coding Jr', 5, 95000),
(2, 'Akshay', 'Full Stack Developer', 'Coding Jr', 3, 75000),
(3, 'Sneh Soni', 'Mentor & Developer', 'Coding Jr', 4, 85000),
(4, 'Aditya', 'Backend Developer', 'Coding Jr', 2, 65000),
(5, 'Sagar Waghmare', 'Developer Intern', 'Coding Jr', 1, 500000);
Insert Marks
INSERT INTO marks (roll_no, subject, score, exam_date) VALUES
(1, 'DBMS', 92, '2026-01-15'),
(1, 'OS', 88, '2026-01-16'),
(2, 'DBMS', 76, '2026-01-15'),
(2, 'OS', 91, '2026-01-16'),
(3, 'DBMS', 85, '2026-01-15'),
(3, 'OS', 67, '2026-01-16'),
(4, 'DBMS', 72, '2026-01-15'),
(5, 'DBMS', 95, '2026-01-15'),
(5, 'OS', 82, '2026-01-16'),
(6, 'DBMS', 89, '2026-01-15'),
(7, 'OS', 74, '2026-01-16'),
(8, 'DBMS', 58, '2026-01-15'),
(10, 'DBMS', 91, '2026-01-15'),
(10, 'OS', 86, '2026-01-16'),
(13, 'DBMS', 44, '2026-01-15');
INSERT INTO ... VALUES without listing columns, values must be in the exact column order. Specifying columns explicitly (like we did for marks) is safer and clearer.
4. SELECT — Reading Data
SELECT is the most used SQL command. It's how you read data from tables.
-- Get everything
SELECT * FROM students;
-- Get specific columns only
SELECT name, city, department FROM students;
-- Give columns a temporary alias
SELECT name AS 'Student Name', city AS 'Hometown' FROM students;
WHERE — Filtering Rows
WHERE lets you filter which rows are returned.
-- Students in CS department
SELECT * FROM students WHERE department = 'CS';
-- Students older than 21
SELECT name, age FROM students WHERE age > 21;
-- AND — both conditions must be true
SELECT * FROM students
WHERE department = 'CS' AND city = 'Chhatrapati Sambhajinagar';
-- OR — at least one condition must be true
SELECT * FROM students
WHERE city = 'Pune' OR city = 'Nashik';
-- IN — match any value in a list
SELECT * FROM students
WHERE city IN ('Pune', 'Nashik', 'Jalna');
-- BETWEEN — range of values (inclusive)
SELECT * FROM marks
WHERE score BETWEEN 70 AND 90;
-- LIKE — pattern matching (% = any chars, _ = one char)
SELECT * FROM students WHERE name LIKE 'S%'; -- starts with S
SELECT * FROM students WHERE name LIKE '%ar'; -- ends with ar
SELECT * FROM students WHERE name LIKE '%sh%'; -- contains sh
SELECT * FROM students WHERE name LIKE '_a%'; -- 2nd char is a
-- NOT — negate a condition
SELECT * FROM students WHERE NOT department = 'IT';
-- IS NULL / IS NOT NULL
SELECT * FROM students WHERE email IS NULL;
SELECT * FROM students WHERE email IS NOT NULL;
= checks for equality. IS NULL checks for NULL. You cannot use = NULL — it won't work. Always use IS NULL.
5. UPDATE — Modifying Existing Data
UPDATE changes values in existing rows.
-- Update Sagar's age
UPDATE students
SET age = 22
WHERE roll_no = 1;
-- Give a raise to everyone with 3+ years experience
UPDATE company_team
SET salary = salary + 500000
WHERE experience >= 3;
-- Update multiple columns at once
UPDATE students
SET city = 'Mumbai', age = 22
WHERE roll_no = 3;
UPDATE without WHERE! It will modify every single row in the table. Always double-check your WHERE clause before executing.
6. DELETE — Removing Rows
DELETE removes rows from a table. The table structure stays intact.
-- Delete one student
DELETE FROM students
WHERE roll_no = 13;
-- Delete all IT department students
DELETE FROM students
WHERE department = 'IT';
-- Delete ALL rows (dangerous!)
DELETE FROM students;
DELETE FROM students; without WHERE deletes every row. Omkar once did this in a practice session — the panic was real. Always double-check.
7. ALTER TABLE — Changing Table Structure
ALTER lets you modify a table after creation — add columns, change types, rename columns, or drop them.
-- Add a new column
ALTER TABLE students ADD phone VARCHAR(15);
-- Change a column's data type
ALTER TABLE students MODIFY email VARCHAR(200);
-- Rename a column
ALTER TABLE students CHANGE city hometown VARCHAR(80);
-- Drop a column (permanent!)
ALTER TABLE students DROP COLUMN phone;
-- Rename the entire table
ALTER TABLE students RENAME TO cse_students;
8. ORDER BY — Sorting Results
ORDER BY sorts your result set. ASC = ascending (default), DESC = descending.
-- Alphabetical order by name
SELECT * FROM students ORDER BY name ASC;
-- Highest salary first
SELECT * FROM company_team ORDER BY salary DESC;
-- Sort by department first, then by age within each department
SELECT name, department, age FROM students
ORDER BY department ASC, age DESC;
-- Top marks in DBMS
SELECT roll_no, score FROM marks
WHERE subject = 'DBMS'
ORDER BY score DESC;
9. LIMIT & OFFSET
LIMIT controls how many rows you get back. OFFSET skips rows — useful for pagination.
-- Get first 5 students
SELECT * FROM students ORDER BY roll_no LIMIT 5;
-- Skip first 5, get next 5 (page 2)
SELECT * FROM students ORDER BY roll_no LIMIT 5 OFFSET 5;
-- Top 3 highest scores
SELECT roll_no, subject, score FROM marks
ORDER BY score DESC LIMIT 3;
10. DISTINCT — Removing Duplicates
DISTINCT returns only unique values.
-- All unique cities
SELECT DISTINCT city FROM students;
-- All unique departments
SELECT DISTINCT department FROM students;
-- Count of unique cities
SELECT COUNT(DISTINCT city) AS 'Unique Cities' FROM students;
11. Aggregate Functions — COUNT, SUM, AVG, MAX, MIN
Aggregate functions perform calculations on a set of rows and return a single value.
-- COUNT — total number of rows
SELECT COUNT(*) AS 'Total Students' FROM students;
-- COUNT with condition
SELECT COUNT(*) AS 'CS Students' FROM students
WHERE department = 'CS';
-- SUM — total of a numeric column
SELECT SUM(salary) AS 'Total Payroll' FROM company_team;
-- AVG — average value
SELECT AVG(score) AS 'Avg DBMS Score' FROM marks
WHERE subject = 'DBMS';
-- MAX and MIN
SELECT
MAX(score) AS 'Highest Score',
MIN(score) AS 'Lowest Score'
FROM marks;
-- MAX salary in the team
SELECT name, salary FROM company_team
ORDER BY salary DESC LIMIT 1;
12. GROUP BY & HAVING
GROUP BY groups rows that share a value, then you can run aggregates on each group. HAVING filters groups (like WHERE, but for groups).
-- Students per city
SELECT city, COUNT(*) AS 'total'
FROM students
GROUP BY city;
-- Students per department
SELECT department, COUNT(*) AS 'count'
FROM students
GROUP BY department;
-- Average score per subject
SELECT subject, AVG(score) AS 'avg_score'
FROM marks
GROUP BY subject;
-- HAVING — only show cities with more than 1 student
SELECT city, COUNT(*) AS 'total'
FROM students
GROUP BY city
HAVING COUNT(*) > 1;
-- Subjects where average score is above 80
SELECT subject, AVG(score) AS 'avg'
FROM marks
GROUP BY subject
HAVING AVG(score) > 80;
WHERE filters individual rows before grouping. HAVING filters groups after grouping. You can't use aggregate functions in WHERE — use HAVING instead.
13. String Functions
MySQL has built-in functions to manipulate text data.
-- UPPER / LOWER — change case
SELECT UPPER(name) FROM students;
SELECT LOWER(email) FROM students;
-- LENGTH — number of characters
SELECT name, LENGTH(name) AS 'name_length' FROM students;
-- CONCAT — join strings together
SELECT CONCAT(name, ' (', department, ')') AS 'student_info'
FROM students;
-- SUBSTRING — extract part of a string
SELECT SUBSTRING(name, 1, 5) AS 'short_name' FROM students;
-- REPLACE — swap text
SELECT REPLACE(city, 'Chhatrapati Sambhajinagar', 'CSN') AS 'short_city'
FROM students;
-- TRIM — remove leading/trailing spaces
SELECT TRIM(name) FROM students;
-- REVERSE — reverse a string
SELECT name, REVERSE(name) FROM students LIMIT 3;
14. Date Functions
MySQL has powerful date functions for working with dates and timestamps.
-- CURDATE() — current date
SELECT CURDATE(); -- 2026-02-18
-- NOW() — current date + time
SELECT NOW(); -- 2026-02-18 14:30:00
-- YEAR(), MONTH(), DAY() — extract parts of a date
SELECT name,
YEAR(enrollment_date) AS 'year',
MONTH(enrollment_date) AS 'month',
DAY(enrollment_date) AS 'day'
FROM students;
-- DATEDIFF() — difference between two dates (in days)
SELECT name,
DATEDIFF(CURDATE(), enrollment_date) AS 'days_since_enrollment'
FROM students;
-- DATE_FORMAT() — format a date
SELECT name,
DATE_FORMAT(enrollment_date, '%d-%m-%Y') AS 'formatted_date'
FROM students;
-- DATE_ADD / DATE_SUB — add or subtract time
SELECT DATE_ADD(CURDATE(), INTERVAL 30 DAY) AS '30_days_from_now';
SELECT DATE_SUB(CURDATE(), INTERVAL 1 YEAR) AS 'one_year_ago';
-- Filter by date
SELECT * FROM marks
WHERE exam_date > '2026-01-15';
-- Students enrolled in August 2023
SELECT * FROM students
WHERE MONTH(enrollment_date) = 8
AND YEAR(enrollment_date) = 2023;
%d = day, %m = month number, %M = month name, %Y = 4-digit year, %y = 2-digit year, %H = hour, %i = minutes, %s = seconds.
15. Transactions — COMMIT, ROLLBACK & SAVEPOINT
A transaction is a group of SQL statements that execute as one unit. Either all succeed, or none do. This is critical for data integrity.
Basic Transaction Flow
-- Start a transaction
START TRANSACTION;
-- Make changes
UPDATE company_team SET salary = salary - 10000 WHERE emp_id = 1;
UPDATE company_team SET salary = salary + 10000 WHERE emp_id = 5;
-- If everything is correct, save permanently
COMMIT;
ROLLBACK — Undo Changes
START TRANSACTION;
DELETE FROM students WHERE department = 'IT';
-- Oops! That was a mistake. Undo everything.
ROLLBACK;
-- The IT students are still there. Crisis averted.
SAVEPOINT — Partial Rollback
START TRANSACTION;
UPDATE company_team SET salary = 100000 WHERE emp_id = 1;
SAVEPOINT sp1;
UPDATE company_team SET salary = 0 WHERE emp_id = 5;
-- Undo only the second update, keep the first
ROLLBACK TO sp1;
COMMIT; -- emp_id 1 has 100000, emp_id 5 is unchanged
SET autocommit = 0
By default, MySQL auto-commits every statement. You can disable this to manually control when changes are saved.
-- Disable auto-commit
SET autocommit = 0;
-- Now every change needs an explicit COMMIT
UPDATE students SET age = 23 WHERE roll_no = 1;
COMMIT; -- saved
UPDATE students SET age = 99 WHERE roll_no = 2;
ROLLBACK; -- NOT saved, age remains unchanged
-- Re-enable auto-commit
SET autocommit = 1;
16. JOINs — Combining Data from Multiple Tables
JOINs let you combine rows from two or more tables based on a related column. This is one of the most important SQL concepts — real-world data is always spread across multiple tables.
We'll use our students and marks tables. Remember, marks.roll_no references students.roll_no.
INNER JOIN
Returns only the rows that have matching values in both tables. If a student has no marks, they won't appear. If a mark has no matching student, it won't appear either.
SELECT s.name, s.department, m.subject, m.score
FROM students s
INNER JOIN marks m ON s.roll_no = m.roll_no;
s and m are aliases for the table names. Writing students s lets you use s.name instead of students.name — shorter and cleaner.
LEFT JOIN (LEFT OUTER JOIN)
Returns all rows from the left table (students), and the matching rows from the right table (marks). If there's no match, the right side columns show NULL.
SELECT s.name, m.subject, m.score
FROM students s
LEFT JOIN marks m ON s.roll_no = m.roll_no;
Students like Siddhali and Isha who have no marks will still appear — with NULL in the subject and score columns.
RIGHT JOIN (RIGHT OUTER JOIN)
Opposite of LEFT JOIN — returns all rows from the right table, and matching rows from the left. Rarely used in practice because you can just swap the table order and use LEFT JOIN.
SELECT s.name, m.subject, m.score
FROM students s
RIGHT JOIN marks m ON s.roll_no = m.roll_no;
This returns all marks, even if a student record was deleted. In our case, both tables have matching data so the result looks similar to INNER JOIN.
FULL OUTER JOIN
Returns all rows from both tables. Where there's no match, NULL fills in. MySQL doesn't support FULL OUTER JOIN directly, but you can simulate it using UNION.
-- Simulating FULL OUTER JOIN in MySQL
SELECT s.name, m.subject, m.score
FROM students s
LEFT JOIN marks m ON s.roll_no = m.roll_no
UNION
SELECT s.name, m.subject, m.score
FROM students s
RIGHT JOIN marks m ON s.roll_no = m.roll_no;
CROSS JOIN
Returns the Cartesian product — every row from the first table paired with every row from the second. If students has 13 rows and marks has 15, you get 13 x 15 = 195 rows. Use with caution.
SELECT s.name, m.subject
FROM students s
CROSS JOIN marks m
LIMIT 10;
Self JOIN
A Self JOIN is when a table is joined with itself. You use two aliases to treat the same table as if it were two separate tables. This is useful when rows in a single table have relationships with other rows in the same table — like a manager and their employees both living in the same company_team table, or finding students from the same city in the same students table.
When to use a Self JOIN:
- Employee-manager hierarchy (both stored in the same table)
- Finding pairs of rows that share a common value
- Comparing rows within the same table (e.g., salaries, scores)
Example 1 — Find students from the same city:
-- Find students from the same city (no duplicates, no self-pairs)
SELECT
a.name AS 'Student 1',
b.name AS 'Student 2',
a.city
FROM students a
INNER JOIN students b
ON a.city = b.city
AND a.roll_no < b.roll_no;
The a.roll_no < b.roll_no condition prevents both duplicate pairs (Sagar-Atharva AND Atharva-Sagar) and self-pairs (Sagar-Sagar).
Example 2 — Employee and manager hierarchy (company_team table):
The company_team table has a manager_id column that references the id of another row in the same table. A Self JOIN lets us show each employee alongside their manager's name.
-- Show each employee with their manager's name
SELECT
emp.name AS 'Employee',
emp.role AS 'Role',
mgr.name AS 'Manager'
FROM company_team emp
LEFT JOIN company_team mgr
ON emp.manager_id = mgr.id
ORDER BY mgr.name ASC;
We use LEFT JOIN here so the top-level manager (Sagar, who has no manager) still appears in the result — with NULL in the Manager column. An INNER JOIN would exclude them.
Example 3 — Find employees earning more than the average salary of their own team:
-- Compare each employee's salary against the company average
SELECT
a.name,
a.salary,
ROUND(AVG(b.salary), 2) AS 'company_avg'
FROM company_team a
CROSS JOIN company_team b
GROUP BY a.name, a.salary
HAVING a.salary > AVG(b.salary);
JOINs with Aggregates
JOINs become really powerful when combined with GROUP BY and aggregate functions.
-- Average score per student (with their name)
SELECT s.name, AVG(m.score) AS 'avg_score'
FROM students s
INNER JOIN marks m ON s.roll_no = m.roll_no
GROUP BY s.name
ORDER BY avg_score DESC;
Quick JOIN Summary
| JOIN Type | Returns |
|---|---|
| INNER JOIN | Only matching rows from both tables |
| LEFT JOIN | All rows from left table + matching from right (NULL if no match) |
| RIGHT JOIN | All rows from right table + matching from left (NULL if no match) |
| FULL OUTER JOIN | All rows from both tables (NULL where no match) |
| CROSS JOIN | Every row paired with every row (Cartesian product) |
| Self JOIN | Table joined with itself |
17. Views — Virtual Tables You Can Query Like Real Ones
A View is a saved SQL query that you can treat exactly like a table. It does not store data itself — every time you query a view, MySQL runs the underlying query behind the scenes. Views are one of the most powerful tools for simplifying complex queries, restricting column access, and keeping your SQL clean and reusable.
Why Use Views?
| Reason | Explanation |
|---|---|
| Simplify complex queries | Write a JOIN+GROUP BY once, query the view every time |
| Security | Expose only specific columns — hide salary, password, etc. |
| Consistency | One definition used everywhere — change it in one place |
| Readability | Meaningful view names make SQL self-documenting |
CREATE VIEW — Basic Syntax
Create a view using CREATE VIEW view_name AS <your SELECT query>. After that, querying the view is identical to querying a table.
-- Create a view that shows only Pune students
CREATE VIEW pune_students AS
SELECT roll_no, name, age, marks
FROM students
WHERE city = 'Pune';
-- Now query the view exactly like a table
SELECT * FROM pune_students;
CREATE VIEW with JOINs — Complex Views
Views really shine when they wrap complex JOIN queries. Instead of writing a long JOIN every time, you define it once in a view.
-- View: student name + subject + score (joins students and marks)
CREATE VIEW student_report AS
SELECT
s.name AS 'student',
s.city,
m.subject,
m.score
FROM students s
INNER JOIN marks m ON s.roll_no = m.roll_no;
-- Use the view — no JOIN needed anymore!
SELECT * FROM student_report
WHERE subject = 'DBMS'
ORDER BY score DESC;
CREATE VIEW with Aggregates
Views can also wrap GROUP BY logic so you get a pre-aggregated summary table on demand.
-- View: average score per student across all subjects
CREATE VIEW avg_scores AS
SELECT
s.name,
ROUND(AVG(m.score), 2) AS 'average_score',
COUNT(m.subject) AS 'subjects_taken'
FROM students s
LEFT JOIN marks m ON s.roll_no = m.roll_no
GROUP BY s.name;
-- Instantly get the top 3 students by average
SELECT * FROM avg_scores
ORDER BY average_score DESC
LIMIT 3;
CREATE OR REPLACE VIEW — Update a View Definition
If you need to change what a view does, use CREATE OR REPLACE VIEW. This updates the definition without dropping and recreating it.
-- Update pune_students view to also include grade column
CREATE OR REPLACE VIEW pune_students AS
SELECT roll_no, name, age, marks, grade
FROM students
WHERE city = 'Pune';
SHOW FULL TABLES — List Views in the Database
To see all views in your database, filter the SHOW FULL TABLES result by table_type.
-- Show all views in current database
SHOW FULL TABLES
WHERE Table_type = 'VIEW';
DROP VIEW — Remove a View
Dropping a view only removes the view definition — the underlying table data is completely untouched.
-- Drop a single view
DROP VIEW pune_students;
-- Drop multiple views at once
DROP VIEW avg_scores, student_report;
-- Drop only if it exists (no error if it doesn't)
DROP VIEW IF EXISTS pune_students;
Key Points About Views
| Feature | Detail |
|---|---|
| Stores data? | No — runs the underlying query each time |
| Can you SELECT from it? | Yes — exactly like a table |
| Can you UPDATE through a view? | Only simple views (no GROUP BY, no JOIN, no DISTINCT) |
| Can you DROP TABLE from it? | No — use DROP VIEW |
| Does DROP VIEW affect base table? | No — base table data is safe |
| Update view definition? | Use CREATE OR REPLACE VIEW |
18. TRUNCATE vs DELETE vs DROP
These three all remove data, but they work very differently.
| Feature | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| What it removes | Specific rows (or all rows) | All rows | Entire table (structure + data) |
| WHERE clause | Yes | No | No |
| Can ROLLBACK? | Yes (within transaction) | No | No |
| Speed | Slower (row by row) | Faster (drops & recreates) | Instant |
| AUTO_INCREMENT | Keeps counter | Resets counter to 1 | N/A — table is gone |
| Table exists after? | Yes (empty) | Yes (empty) | No |
-- DELETE — removes rows, table stays
DELETE FROM marks;
-- TRUNCATE — removes all rows fast, resets auto_increment
TRUNCATE TABLE marks;
-- DROP — completely removes the table
DROP TABLE IF EXISTS marks;
-- DROP a database
DROP DATABASE IF EXISTS test_db;
19. EXPLAIN — Understanding Query Performance
EXPLAIN shows you how MySQL plans to execute a query. It tells you whether it's using indexes, scanning the full table, or doing something inefficient.
EXPLAIN SELECT * FROM students WHERE city = 'Pune';
Key things to look for:
type = ALL— Full table scan (slow on big tables). Consider adding an index.type = refortype = const— Using an index (fast).key = NULL— No index being used.rows— How many rows MySQL expects to scan.
To speed things up, you can add an index:
-- Create an index on the city column
CREATE INDEX idx_city ON students(city);
-- Now EXPLAIN will show it uses the index
EXPLAIN SELECT * FROM students WHERE city = 'Pune';
20. Stored Procedures — Reusable SQL Routines
A Stored Procedure is a named block of SQL code saved in the database that you can call by name whenever you need it. Instead of writing the same query repeatedly, you write it once as a procedure and call it anywhere. Procedures can accept parameters, contain logic (IF/ELSE, loops), and execute multiple statements.
Why Use Stored Procedures?
| Benefit | Explanation |
|---|---|
| Reusability | Write once, call many times from anywhere |
| Less network traffic | One call executes many statements server-side |
| Security | Grant EXECUTE permission without exposing table structure |
| Maintainability | Change logic in one place, all callers benefit instantly |
DELIMITER — Why We Need It
MySQL uses ; to know where a statement ends. But inside a procedure, there are many ; characters. To prevent MySQL from ending the procedure early, we temporarily change the delimiter to something else (like //), write the full procedure body, then set it back.
-- Change delimiter so MySQL doesn't end the procedure early
DELIMITER //
CREATE PROCEDURE procedure_name()
BEGIN
-- SQL statements here
END //
DELIMITER ; -- Reset back to semicolon
Basic Procedure — No Parameters
A simple procedure that gets all students from Pune.
DELIMITER //
CREATE PROCEDURE GetPuneStudents()
BEGIN
SELECT name, age, marks
FROM students
WHERE city = 'Pune';
END //
DELIMITER ;
-- Call the procedure
CALL GetPuneStudents();
IN Parameter — Pass Values Into a Procedure
IN parameters let you pass a value into the procedure. The procedure uses it but cannot modify the caller's variable.
DELIMITER //
CREATE PROCEDURE GetStudentsByCity(IN p_city VARCHAR(100))
BEGIN
SELECT name, age, marks
FROM students
WHERE city = p_city;
END //
DELIMITER ;
-- Call with Nashik
CALL GetStudentsByCity('Nashik');
-- Call with Mumbai
CALL GetStudentsByCity('Mumbai');
OUT Parameter — Return a Value from a Procedure
OUT parameters let the procedure send a value back to the caller. You declare a user variable with @ and pass it in.
DELIMITER //
CREATE PROCEDURE GetStudentCount(OUT total INT)
BEGIN
SELECT COUNT(*) INTO total
FROM students;
END //
DELIMITER ;
-- Call it and read the OUT value
CALL GetStudentCount(@count);
SELECT @count AS 'Total Students';
IF / ELSEIF / ELSE — Conditional Logic Inside Procedures
SQL supports full conditional logic inside procedures. Use IF...ELSEIF...ELSE...END IF to branch based on conditions.
-- Grade a student based on marks: A / B / C / Fail
DELIMITER //
CREATE PROCEDURE GetGrade(IN p_marks INT, OUT p_grade VARCHAR(10))
BEGIN
IF p_marks >= 90 THEN
SET p_grade = 'A';
ELSEIF p_marks >= 75 THEN
SET p_grade = 'B';
ELSEIF p_marks >= 60 THEN
SET p_grade = 'C';
ELSE
SET p_grade = 'Fail';
END IF;
END //
DELIMITER ;
-- Test it
CALL GetGrade(92, @g); SELECT @g; -- A
CALL GetGrade(78, @g); SELECT @g; -- B
CALL GetGrade(45, @g); SELECT @g; -- Fail
WHILE Loop — Repeat Logic Inside a Procedure
Use WHILE...DO...END WHILE to repeat statements. This example counts up and inserts test rows.
-- Procedure that uses a WHILE loop counter
DELIMITER //
CREATE PROCEDURE CountUp(IN max_num INT)
BEGIN
DECLARE counter INT DEFAULT 1;
WHILE counter <= max_num DO
SELECT counter AS 'Step';
SET counter = counter + 1;
END WHILE;
END //
DELIMITER ;
CALL CountUp(3);
Practical Procedure — Student Promotion
A real-world example: promote all students with marks above a threshold to the next year.
DELIMITER //
CREATE PROCEDURE PromoteStudents(IN min_marks INT, OUT promoted_count INT)
BEGIN
UPDATE students
SET grade = 'Promoted'
WHERE marks >= min_marks;
SELECT ROW_COUNT() INTO promoted_count;
END //
DELIMITER ;
CALL PromoteStudents(70, @cnt);
SELECT @cnt AS 'Students Promoted';
SHOW + DROP PROCEDURE
-- Show all procedures in the current database
SHOW PROCEDURE STATUS WHERE Db = 'college_db';
-- See the definition of a procedure
SHOW CREATE PROCEDURE GetPuneStudents;
-- Drop a procedure
DROP PROCEDURE IF EXISTS GetPuneStudents;
Key Points About Stored Procedures
| Feature | Detail |
|---|---|
| Called with | CALL procedure_name(args) |
| IN parameter | Passes value in; procedure cannot modify caller's variable |
| OUT parameter | Returns a value back to the caller via user variable |
| INOUT parameter | Both reads and writes the caller's variable |
| Logic support | IF/ELSEIF/ELSE, WHILE, LOOP, CASE, DECLARE, SET |
| DELIMITER | Must temporarily change delimiter when writing procedures |
| Drop | DROP PROCEDURE IF EXISTS name |
21. Triggers — Auto-Execute SQL on Table Events
A Trigger is a special procedure that MySQL runs automatically when a specific event (INSERT, UPDATE, or DELETE) happens on a table. You don't call triggers manually — they fire on their own. Triggers are perfect for automatic logging, validation, and enforcing business rules without touching application code.
Trigger Anatomy
| Part | Options | Meaning |
|---|---|---|
| Timing | BEFORE / AFTER | Run before or after the event completes |
| Event | INSERT / UPDATE / DELETE | Which DML operation fires the trigger |
| NEW | Available in INSERT, UPDATE | The new row being inserted/updated |
| OLD | Available in UPDATE, DELETE | The old row before the change/deletion |
BEFORE INSERT — Validate or Modify Data Before It's Saved
A BEFORE INSERT trigger fires before a new row is added. You can use NEW.column to read or even change the values that are about to be inserted.
-- Ensure marks never exceed 100 before inserting a student
DELIMITER //
CREATE TRIGGER before_student_insert
BEFORE INSERT ON students
FOR EACH ROW
BEGIN
IF NEW.marks > 100 THEN
SET NEW.marks = 100;
END IF;
END //
DELIMITER ;
-- Test: try to insert a student with marks = 105
INSERT INTO students (name, city, marks)
VALUES ('Test', 'Pune', 105);
-- marks will be saved as 100 automatically
AFTER INSERT — Log Every New Record Automatically
An AFTER INSERT trigger runs after the row is saved. Great for audit logging.
-- First, create a log table
CREATE TABLE student_log (
log_id INT AUTO_INCREMENT PRIMARY KEY,
action VARCHAR(20),
student VARCHAR(100),
logged_at DATETIME DEFAULT NOW()
);
-- Trigger: log every new student insertion
DELIMITER //
CREATE TRIGGER after_student_insert
AFTER INSERT ON students
FOR EACH ROW
BEGIN
INSERT INTO student_log (action, student)
VALUES ('INSERT', NEW.name);
END //
DELIMITER ;
-- Every INSERT into students now auto-logs to student_log
SELECT * FROM student_log;
BEFORE UPDATE — Capture the Old Value
Use OLD.column to see what the value was before the update, and NEW.column for what it's being changed to.
-- Log salary changes: save old and new salary when updated
CREATE TABLE salary_log (
emp_name VARCHAR(100),
old_salary DECIMAL(12,2),
new_salary DECIMAL(12,2),
changed_at DATETIME DEFAULT NOW()
);
DELIMITER //
CREATE TRIGGER before_salary_update
BEFORE UPDATE ON company_team
FOR EACH ROW
BEGIN
IF OLD.salary != NEW.salary THEN
INSERT INTO salary_log (emp_name, old_salary, new_salary)
VALUES (OLD.name, OLD.salary, NEW.salary);
END IF;
END //
DELIMITER ;
BEFORE DELETE — Prevent Deletion of Critical Records
A BEFORE DELETE trigger can block deletions matching certain conditions by raising an error with SIGNAL SQLSTATE.
-- Prevent deletion of the Lead Developer (Sagar)
DELIMITER //
CREATE TRIGGER prevent_lead_delete
BEFORE DELETE ON company_team
FOR EACH ROW
BEGIN
IF OLD.role = 'Lead Developer' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Cannot delete the Lead Developer!';
END IF;
END //
DELIMITER ;
-- This DELETE will fail with an error
DELETE FROM company_team
WHERE role = 'Lead Developer';
AFTER DELETE — Archive Before Deleting
Move the row to an archive table automatically before it's gone.
CREATE TABLE deleted_students AS
SELECT * FROM students WHERE 1=0; -- Empty copy of structure
DELIMITER //
CREATE TRIGGER archive_deleted_student
AFTER DELETE ON students
FOR EACH ROW
BEGIN
INSERT INTO deleted_students
VALUES (OLD.roll_no, OLD.name, OLD.age, OLD.city,
OLD.marks, OLD.grade, OLD.email,
OLD.enrollment_date, OLD.department);
END //
DELIMITER ;
SHOW + DROP TRIGGER
-- See all triggers in the current database
SHOW TRIGGERS;
-- Drop a specific trigger
DROP TRIGGER IF EXISTS before_student_insert;
Trigger Quick Reference
| Trigger Type | Fires When | OLD available? | NEW available? | Can modify row? |
|---|---|---|---|---|
| BEFORE INSERT | Before row is added | No | Yes | Yes (set NEW.*) |
| AFTER INSERT | After row is added | No | Yes | No |
| BEFORE UPDATE | Before row is changed | Yes | Yes | Yes (set NEW.*) |
| AFTER UPDATE | After row is changed | Yes | Yes | No |
| BEFORE DELETE | Before row is removed | Yes | No | No (can block with SIGNAL) |
| AFTER DELETE | After row is removed | Yes | No | No |
22. Complete SQL Cheat Sheet
| Category | Command | Example |
|---|---|---|
| Database | CREATE DATABASE | CREATE DATABASE mydb; |
| USE | USE mydb; |
|
| DROP DATABASE | DROP DATABASE IF EXISTS mydb; |
|
| Table | CREATE TABLE | CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(100)); |
| DESC | DESC students; |
|
| DROP TABLE | DROP TABLE IF EXISTS t; |
|
| CRUD | INSERT | INSERT INTO t VALUES (1, 'Sagar'); |
| SELECT | SELECT * FROM t WHERE id = 1; |
|
| UPDATE | UPDATE t SET name = 'Updated' WHERE id = 1; |
|
| DELETE | DELETE FROM t WHERE id = 1; |
|
| Filter | WHERE | WHERE age > 21 AND city = 'Pune' |
| IN / BETWEEN | WHERE city IN ('Pune','Nashik') |
|
| LIKE | WHERE name LIKE 'S%' |
|
| Sort & Limit | ORDER BY | ORDER BY name ASC |
| LIMIT / OFFSET | LIMIT 5 OFFSET 10 |
|
| DISTINCT | SELECT DISTINCT city FROM t; |
|
| Aggregates | COUNT / SUM / AVG | SELECT COUNT(*), SUM(salary), AVG(salary) FROM t; |
| MAX / MIN | SELECT MAX(score), MIN(score) FROM marks; |
|
| GROUP BY / HAVING | GROUP BY dept HAVING COUNT(*) > 2 |
|
| String | UPPER / LOWER | SELECT UPPER(name) FROM t; |
| CONCAT / LENGTH | SELECT CONCAT(a, ' ', b) FROM t; |
|
| SUBSTRING / REPLACE | SELECT SUBSTRING(name, 1, 3) FROM t; |
|
| Date | CURDATE / NOW | SELECT CURDATE(), NOW(); |
| DATEDIFF | SELECT DATEDIFF(CURDATE(), date_col) FROM t; |
|
| DATE_FORMAT | DATE_FORMAT(date_col, '%d-%m-%Y') |
|
| YEAR / MONTH / DAY | SELECT YEAR(date_col), MONTH(date_col) FROM t; |
|
| Transactions | START / COMMIT | START TRANSACTION; ... COMMIT; |
| ROLLBACK | ROLLBACK; |
|
| SAVEPOINT | SAVEPOINT sp1; ... ROLLBACK TO sp1; |
|
| autocommit | SET autocommit = 0; |
|
| Other | ALTER TABLE | ALTER TABLE t ADD col INT; |
| TRUNCATE | TRUNCATE TABLE t; |
|
| EXPLAIN | EXPLAIN SELECT * FROM t WHERE col = 1; |
|
| CREATE INDEX | CREATE INDEX idx ON t(col); |
23. Practice Questions
Theory without practice is useless. Try writing these queries yourself before checking the answers.
SELECT name, city FROM students WHERE department = 'CS';
SELECT COUNT(*) FROM students
WHERE city = 'Chhatrapati Sambhajinagar';
SELECT MAX(score) AS 'Highest DBMS'
FROM marks WHERE subject = 'DBMS';
SELECT * FROM students
WHERE name LIKE 'A%' OR name LIKE 'S%'
ORDER BY name ASC;
SELECT subject, AVG(score) AS 'avg_score'
FROM marks
GROUP BY subject
HAVING AVG(score) > 75;
SELECT name,
DATEDIFF(CURDATE(), enrollment_date) AS 'days_ago'
FROM students;
SELECT roll_no, score FROM marks
WHERE subject = 'DBMS'
ORDER BY score DESC LIMIT 3;
UPDATE students
SET department = 'CS'
WHERE department = 'IT';
SELECT UPPER(name) AS 'NAME',
LOWER(email) AS 'email'
FROM students;
START TRANSACTION;
UPDATE company_team
SET salary = salary + 500000
WHERE emp_id = 5;
-- Check: SELECT * FROM company_team WHERE emp_id = 5;
COMMIT; -- or ROLLBACK; if something is wrong
SELECT DISTINCT city FROM students
ORDER BY city DESC;
SELECT SUM(salary) AS 'total'
FROM company_team
WHERE experience BETWEEN 2 AND 4;
SELECT name,
DATE_FORMAT(enrollment_date, '%d-%b-%Y') AS 'formatted'
FROM students;
gpa (DECIMAL 3,2) to the students table.
ALTER TABLE students
ADD gpa DECIMAL(3,2);
SELECT department, COUNT(*) AS 'total'
FROM students
GROUP BY department
HAVING COUNT(*) > 3
ORDER BY 'total' DESC;
That's everything. From CREATE DATABASE to JOINs to Transactions — you now have a complete SQL reference you can come back to anytime. Bookmark this, practice the questions, and build something with it. SQL is one of those skills that will never go out of demand.
Keep querying.
Here are some other articles you might find interesting.

When Preparation Meets Reality: My Zensar Interview Experience
I spent a month preparing for a hackathon, received an email about 'Java/Python and MySQL Assessment', yet still made assumptions. Here's what happened, the valuable lessons from Rajesh Madhai sir, and why fundamentals and personal responsibility matter most.
Agentic AI: How Autonomous AI Agents Are Transforming Software Development
From writing code to managing entire workflows, agentic AI is changing how we build software. Here's what's actually working, what's hype, and how to leverage it.
