SQL for Data Science › Chapter 8

Chapter 8

DML queries: working with the data inside tables

3:00:00 → 4:08:00INSERT · SELECT · UPDATE · DELETEFunctions · ORDER BY · LIMITEvery example runs

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.

Every code block on this page runs

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;
Constraints still apply

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.

Q&A from class: MODIFY vs constraints?

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;
NULLs and comparisons

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).

FROM/JOIN WHERE GROUP BY HAVING SELECT DISTINCT ORDER BY LIMIT 1. pick the table(s) → 2. keep matching rows → 3. form groups → 4. keep matching groups 5. compute the output columns → 6. drop duplicates → 7. sort → 8. cut to N rows
Logical execution order. GROUP BY and HAVING come in Chapter 9.
✍️ Writing order: how you type it
SELECT DISTINCT column6 · 7
FROM table1
JOIN another_table ON condition2
WHERE condition3
GROUP BY column4
HAVING condition5
ORDER BY column8
LIMIT n;9
⚙️ Execution order: how it runs
FROM1
JOIN2
WHERE3
GROUP BY4
HAVING5
SELECT6
DISTINCT7
ORDER BY8
LIMIT9

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.

Memory trick: execution order
Frank John's Wicked Grave Haunts Several Dull Owls
FROMJOINWHEREGROUP BYHAVINGSELECTDISTINCTORDER BY
…and LIMIT comes last. This is the order the database runs the clauses, not the order you write them.

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 '_____';
Spot the trap

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;
Always write the WHERE first

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
FamilyDMLDDL
Which rowsYour choice (with WHERE), or allAlways all
Table structureStaysStays

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.

Why MAX(budget) can't tell you the movie's name

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.

DML lab
Ctrl/⌘ + Enter
Exercise ideas (try before peeking at the hints)
  1. How many passengers embarked at Southampton? (COUNT(*) + WHERE embarked = 'S')
  2. Names of female passengers in 3rd class who survived.
  3. Average fare paid in each class: do it three times with WHERE for now (GROUP BY makes it one query next chapter).
  4. Movies released after 2015 with a budget above ₹100 crore. (dates compare as text: release_date > '2015-12-31')
  5. 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%'.