DDL shaped the structure. DML (Data Manipulation Language) works on the data: INSERT, SELECT, UPDATE, DELETE. These are the CRUD operations from Chapter 2, now written by hand. Of the four, SELECT (reading) is what you'll use most as a data scientist, so it gets most of the time.
Press Run ▶ on any block to execute it against small sample datasets (a slice of Kaggle's Titanic data, and a Bollywood movies table with approximate figures in ₹ crore). Edit in lab copies it into the SQL lab so you can change it. The class used the full datasets; they're linked in the video description.
INSERT: adding rows
Continuing with the students table from the DDL chapter (student_id auto-increment, name, college, email unique):
-- 1) values for ALL columns, in table order (NULL lets auto-increment fill the id)
INSERT INTO students VALUES (NULL, 'Ankit', 'IIT Delhi', 'ankit@gmail.com');
-- 2) name the columns you're filling; skip the auto-increment one
INSERT INTO students (name, college, email) VALUES ('Neha', 'NIT Trichy', 'neha@gmail.com');
-- 3) column order is up to you, as long as the values follow it
INSERT INTO students (email, name, college) VALUES ('rahul@gmail.com', 'Rahul', 'BITS Pilani');
SELECT * FROM students;
Several rows at once: one VALUES, many brackets (Q1 of Practice 1):
INSERT INTO students (name, college, email) VALUES
('Riya', 'IIT Bombay', 'riya@gmail.com'),
('Kabir', 'IIT Kanpur', 'kabir@gmail.com'),
('Sara', 'DTU', 'sara@gmail.com');
SELECT * FROM students;
Try inserting 'nitish@gmail.com' again: the UNIQUE email constraint rejects it with a duplicate entry error. Without a column list you must supply a value for every column, in order.
ALTER TABLE … MODIFY COLUMN changes a column's definition (its data type and inline rules). ADD/DROP CONSTRAINT adds or removes a named rule. Two different tools.
SELECT: reading data
For reading, Nitish imports the Titanic dataset as a table named train. Its columns: passenger_id, survived (1/0), pclass (1st, 2nd or 3rd class, like train coaches: 1st is the best), name, sex, age, sibsp (siblings + spouse aboard), parch (parents + children aboard), fare and embarked (port: Southampton, Cherbourg, Queenstown).
-- everything: * means "all columns"
SELECT * FROM train;
-- only some columns, in the order you list them
SELECT name, sex, survived FROM train;
Renaming columns with AS
SELECT name AS passenger_name, sex AS gender, survived
FROM train;
Computed columns
You can create new columns on the fly with arithmetic. Family members aboard = siblings/spouse + parents/children:
SELECT name, sibsp + parch AS family FROM train;
Nitish's playful examples: how old would each passenger be today? (The Titanic sank in 1912; he added 106 because the class was in 2018.) And a constant column, say a ₹1 lakh compensation for every passenger:
SELECT name, age + 106 AS current_age FROM train;
SELECT name, 100000 AS compensation FROM train;
In MySQL you could compute the year gap live with YEAR(CURDATE()) - 1912.
DISTINCT: unique values
SELECT DISTINCT sex FROM train; -- 2 values
SELECT DISTINCT pclass FROM train; -- 3 values
-- on several columns: every unique COMBINATION
SELECT DISTINCT sex, pclass FROM train;
WHERE: filtering rows
-- all 1st-class passengers
SELECT * FROM train WHERE pclass = 1;
-- combine conditions with AND / OR
-- 1st-class passengers who died
SELECT * FROM train WHERE pclass = 1 AND survived = 0;
-- passengers aged 10 to 20 (BETWEEN includes both ends)
SELECT name, age FROM train WHERE age BETWEEN 10 AND 20;
Two passengers have no recorded age (NULL). They never satisfy age BETWEEN … or age = …. To find them use WHERE age IS NULL.
The order SQL actually runs a query
You write SELECT … FROM … WHERE …, but the database executes clauses in a different order. Knowing this explains many errors (for example, why a WHERE can't use an alias defined in SELECT).
✍️ Writing order: how you type it
⚙️ Execution order: how it runs
The orange number on each written clause is its execution step. Hover (or tap) a clause to see where it sits in the other list. SELECT is written first but runs 6th, which is why WHERE can't use a SELECT alias but ORDER BY can.
IN and NOT IN
For the next topics Nitish switches to a Bollywood movies dataset: title, actor, director, genre, budget, india_gross, worldwide, screens (opening screens), runtime (minutes), release_date.
Movies that are either Action, Horror or Drama? With OR you'd write the condition three times (and five times for five genres). IN is shorter and clearer:
-- the long way
SELECT title, genre FROM movies
WHERE genre = 'Action' OR genre = 'Horror' OR genre = 'Drama';
-- the same with IN
SELECT title, genre FROM movies
WHERE genre IN ('Action', 'Horror', 'Drama');
-- everything else
SELECT title, genre FROM movies
WHERE genre NOT IN ('Action', 'Horror', 'Drama');
Wildcards with LIKE
%
Any number of characters (including none).
_
Exactly one character.
-- titles starting with 'A'
SELECT title FROM movies WHERE title LIKE 'A%';
-- 'Tiger' anywhere in the title
SELECT title FROM movies WHERE title LIKE '%Tiger%';
-- titles ending with 'a'
SELECT title FROM movies WHERE title LIKE '%a';
-- actor surname Khan OR Kapoor
SELECT title, actor FROM movies
WHERE actor LIKE '%Khan' OR actor LIKE '%Kapoor';
-- titles of exactly 5 characters
SELECT title FROM movies WHERE title LIKE '_____';
Look at the Khan/Kapoor result: Aashiqui 2 is missing because its star is Aditya Roy Kapur, a different spelling. Wildcards match text exactly; they don't understand names. Also note that you must repeat LIKE for each pattern; IN doesn't do wildcards.
UPDATE: changing rows
For updates Nitish uses the small passengers table (id, name, email) so the real datasets stay untouched.
SELECT * FROM passengers;
-- WITHOUT a WHERE, every row changes (rarely what you want!)
-- UPDATE passengers SET name = 'Rahul';
-- only rows that match: Gmail users become 'Rohit'
UPDATE passengers SET name = 'Rohit' WHERE email LIKE '%gmail%';
-- several columns at once: Yahoo users get a new name and email
UPDATE passengers
SET name = 'Anant', email = 'anant@gmail.com'
WHERE email LIKE '%yahoo%';
SELECT * FROM passengers;
UPDATE passengers SET name = 'Rahul' with no WHERE renames everyone (Nitish ran it as the "most stupid" example). A good habit: run the WHERE as a SELECT first, check the rows, then turn it into an UPDATE.
DELETE: removing rows
-- one row
DELETE FROM passengers WHERE id = 1;
-- rows matching several conditions
DELETE FROM passengers WHERE id > 2 AND email LIKE '%yahoo%';
SELECT * FROM passengers;
-- every row (the table stays, now empty)
-- DELETE FROM passengers;
DELETE FROM t WHERE … | TRUNCATE TABLE t | |
|---|---|---|
| Family | DML | DDL |
| Which rows | Your choice (with WHERE), or all | Always all |
| Table structure | Stays | Stays |
Built-in functions
From here SQL gets more powerful: functions, sorting and (next chapter) grouping. Functions come in two kinds:
Work on each row's value and return one value per row.
-- ABS: drop the minus sign (profit of a flop becomes positive)
SELECT title, worldwide - budget AS profit, ABS(worldwide - budget) AS abs_profit
FROM movies;
-- ROUND: runtime in hours, rounded (optionally to N decimals)
SELECT title, runtime / 60 AS hours,
ROUND(runtime / 60) AS rounded,
ROUND(runtime / 60, 1) AS one_decimal
FROM movies;
-- CEIL rounds up, FLOOR rounds down
SELECT title, CEIL(runtime / 60) AS up, FLOOR(runtime / 60) AS down FROM movies;
-- text functions
SELECT UPPER(title), LOWER(title) FROM movies;
SELECT CONCAT(title, ' - ', actor) AS movie_with_star FROM movies;
SELECT title, LENGTH(title) AS chars FROM movies;
-- SUBSTRING(text, start, how_many): positions start at 1, not 0!
SELECT title, SUBSTRING(title, 1, 5) AS first5, SUBSTRING(title, 3, 5) AS from3 FROM movies;
Collapse many rows into one value.
SELECT MAX(budget) FROM movies; -- costliest budget
SELECT MIN(india_gross) FROM movies; -- lowest India collection
SELECT SUM(india_gross) FROM movies; -- everything earned in India
SELECT AVG(india_gross) FROM movies; -- average per movie
SELECT COUNT(*) FROM movies; -- number of rows
SELECT COUNT(DISTINCT actor) FROM movies; -- number of different actors
COUNT vs COUNT DISTINCT: for the list 1, 2, 3, 4, 2, 1, COUNT = 6 but COUNT(DISTINCT …) = 4.
SELECT title, MAX(budget) FROM movies looks tempting, but an aggregate collapses the whole table into one row, so which title should appear? MySQL either errors or returns an arbitrary title. To get the name of the costliest movie you need a subquery (Chapter 12) or ORDER BY … LIMIT 1. Similarly, "how many movies did each actor do?" needs GROUP BY (next chapter).
ORDER BY and LIMIT
Profit = worldwide − budget. Most profitable first:
SELECT title, worldwide - budget AS profit
FROM movies
ORDER BY profit DESC; -- ASC is the default
-- just the top 5
SELECT title, worldwide - budget AS profit
FROM movies
ORDER BY profit DESC
LIMIT 5;
-- the 5 biggest flops
SELECT title, worldwide - budget AS profit
FROM movies
ORDER BY profit
LIMIT 5;
Sorting on several columns: the first column decides, ties are broken by the next.
-- genres A→Z, and inside each genre titles Z→A
SELECT genre, title FROM movies
ORDER BY genre, title DESC;
Notice that ORDER BY profit may use the alias from SELECT. That works because ORDER BY runs after SELECT (see the execution-order diagram). WHERE profit > 0 would not.
SQL lab
All four datasets are loaded: train (Titanic), movies (Bollywood), students, passengers. Write anything; Reset restores the original data.
Exercise ideas (try before peeking at the hints)
- How many passengers embarked at Southampton? (
COUNT(*)+WHERE embarked = 'S') - Names of female passengers in 3rd class who survived.
- Average fare paid in each class: do it three times with WHERE for now (GROUP BY makes it one query next chapter).
- Movies released after 2015 with a budget above ₹100 crore. (dates compare as text:
release_date > '2015-12-31') - The three longest movies, with runtime in hours to one decimal.
Conceptual questions
These check whether you understand why, not just what. Several go one step beyond the video.
Why does SELECT title, worldwide - budget AS profit FROM movies WHERE profit > 0 fail, while … ORDER BY profit works?
Repeat the expression in WHERE (WHERE worldwide - budget > 0) or use a subquery.
Why is SELECT * discouraged in production code, even though it's handy for exploring?
Name the columns you need.
You want passengers whose age is NOT between 10 and 20, including those with unknown age. Which works?
Comparisons with NULL are "unknown", never true, so NULL rows must be added explicitly.
Without an ORDER BY, is SELECT title FROM movies LIMIT 5 guaranteed to return the same 5 rows every time?
Tables are sets; only ORDER BY defines an order. Always pair LIMIT with ORDER BY when "top" matters.
Why doesn't WHERE genre IN ('Act%', 'Dra%') find Action and Drama movies?
Use genre LIKE 'Act%' OR genre LIKE 'Dra%'.