? Lesson 3: Introduction to SQL

Lesson 3: Introduction to SQL

This is the chapter we skipped earlier — Database System Concepts Chapter 3, "Introduction to SQL" (book lines 1945–3512). It's the foundation that Chapter 4 (now Lesson 4) builds on, so slotting it in here puts the book track in the correct reading order: Lesson 2 (relational model) → this (basic SQL) → Lesson 4 (intermediate SQL). If you only ever memorize one SQL chapter, make it this one: it's the actual vocabulary — create table, select ... from ... where, aggregation, subqueries, and the insert/update/delete statements — that every whiteboard SQL question is built from.

The chapter's organizing idea (which it shares with your Lesson 1) is that SQL is declarative: you say what you want, and the system figures out how. The book's own "meaning of a query" sequence (Cartesian product → filter → project) is explicitly not how it executes — the optimizer rewrites it, which is exactly the optimizer story from your storage lesson and Lesson 2's relational algebra. Keep that split in mind: SQL reads top-to-bottom as select/from/where, but it means from → where → select.

3.1 Overview and 3.2 Data Definition

SQL has several parts: DDL (schema definition), DML (queries + updates), integrity, view definition, transaction control, embedded/dynamic SQL, and authorization. This chapter covers basic DML and DDL. The DDL types you'll actually use:

Type Meaning Gotcha
char(n) Fixed-length string; pads with spaces to length n. Space-padding makes char vs varchar comparisons unreliable — the book says always prefer varchar.
varchar(n) Variable-length string, max length n. The safe default for text.
int / smallint Integer (machine-dependent range).
numeric(p,d) Exact fixed-point: p total digits, d after the decimal. numeric(3,1) stores 44.5 exactly but not 444.5.
real / double precision / float(n) Approximate floating point. Use for measured quantities, never for money (rounding).

create table declares the schema and inline integrity constraints. The canonical university snippet:

create table instructor (  ID varchar(5),  name varchar(20) not null,  dept_name varchar(20),  salary numeric(8,2),  primary key (ID),  foreign key (dept_name) references department);

The constraints available in basic DDL are exactly the ones from your Lesson 2: primary key (non-null + unique), foreign key ... references (values must exist in the referenced relation's primary key), and not null. The book's key practical point: SQL rejects any update that violates a constraint — inserting a course with a non-existent dept_name fails. Schema surgery uses drop table r (drops data and schema), delete from r (keeps schema, empties data), alter table r add A D (new column gets null for existing rows), and alter table r drop A (unsupported by many systems).

3.3 Basic Query Structure

Every query has three clauses. select lists output columns/expressions, from lists input relations, where filters rows. The book is emphatic about the meaning order: (1) Cartesian product of from relations, (2) apply where predicate, (3) output select columns. That's how you reason about correctness; the engine does something smarter.

Single relation

select name from instructor returns one column. SQL keeps duplicates by default — unlike the formal relational model, where a relation is a set. That default matters: select dept_name from instructor lists "Comp. Sci." once per instructor. To dedupe, add distinct: select distinct dept_name from instructor. The opposite keyword all exists but is the default, so it's rarely written. The select clause also takes arithmetic (salary * 1.1 for a hypothetical 10% raise — note this doesn't change the table). The where clause uses = <> < <= > >= plus and/or/not.

Multiple relations

Cross-relation queries list both tables in from and equate the join column in where: select name, instructor.dept_name, building from instructor, department where instructor.dept_name = department.dept_name. The from first computes the Cartesian product (12 instructors × 13 departments = 156 rows in the sample!), then where keeps only the matching pairs. The book's warning is the interview trap: forget the where predicate and you get a giant Cartesian product. With realistic counts (200 instructors, 600 enrollments) that's 120,000 rows of garbage. Instructors who taught nothing drop out — that's an inner join, and the book points forward to outer joins (Lesson 4) for keeping them.

3.4 Additional Basic Operations

Rename (as)

as renames attributes (select name as instructor_name) or relations. Relation renaming is essential for self-joins: "instructors earning more than at least one Biology instructor" needs instructor as T, instructor as S so T.salary and S.salary are distinguishable. The book notes Oracle omits as in the from clause. Those aliases are called correlation names (or table aliases / tuple variables).

String operations

Strings are single-quoted; a literal apostrophe is two apostrophes ('It''s right'). Equality is case-sensitive per the standard, but MySQL/SQL Server ignore case by default. Pattern matching uses like with % (any substring) and _ (any single char); '%Watson%' finds buildings containing "Watson". An escape character (like 'ab\%cd%' escape '\') matches a literal percent. not like finds mismatches. Functions like upper, lower, trim, concatenation exist but vary by system.

Ordering and extra predicates

order by sorts output (desc/asc, multi-column: order by salary desc, name asc). * in select means all columns. between is sugar for a two-sided range (salary between 90000 and 100000). Row constructors compare tuples lexicographically: (ID, dept_name) = (teaches.ID, 'Biology') — handy but unsupported in Oracle.

3.5 Set Operations

union, intersect, except mirror the algebra's ∪, ∩, −. Crucial defaults:

Set ops vs the where clause. "Courses in Fall 2017 or Spring 2018" is union; "both semesters" is intersect; "Fall but not Spring" is except. Every one of these can also be written with in/not in subqueries (Section 3.8) — SQL gives you redundant ways to say the same thing, which is a feature, not a bug.

3.6 Null Values

Nulls break the neat true/false logic by adding a third value, unknown. Arithmetic with a null input is null. Any comparison with null (except is null/is not null) is unknown, not true and not false. The three-valued logic extends and/or/not: true and unknown = unknown, false and unknown = false, true or unknown = true. A where row is kept only if the predicate is truefalse or unknown both drop it. Test for null with salary is null, never salary = null (that's unknown). One subtle twist: for distinct and set operations, two nulls are treated as equal (so they collapse), even though null = null as a predicate is unknown.

3.7 Aggregate Functions

Five built-ins: avg, min, max, sum, count. They take a collection and return one value. select avg(salary) as avg_salary from instructor where dept_name = 'Comp. Sci.'. Use distinct inside an aggregate to dedupe first (count(distinct ID) counts each instructor once regardless of sections taught). count(*) counts rows; distinct with count(*) is illegal.

Grouping and having

group by partitions rows into groups; the aggregate runs per group. "Average salary per department" = select dept_name, avg(salary) from instructor group by dept_name. The hard rule: every column in select that isn't aggregated must appear in group by — otherwise the output for a group with many IDs has no defined single ID to print, and the query is rejected. having filters groups (after grouping), so it can use aggregates: ... group by dept_name having avg(salary) > 42000. The evaluation order is from → where → group by → having → select.

Nulls in aggregates. Every aggregate except count(*) ignores null inputs. So sum(salary) skips the null-salaried rows rather than becoming null. If the input is empty, count returns 0 and the rest return null. This is why a sum over a column with some nulls isn't "poisoned" — but a avg of an all-null group is just null.

3.8 Nested Subqueries

Subqueries are select-from-where expressions nested inside a larger query. They appear in where, from, and even as scalar values.

Set membership, comparison, existence

Subqueries in from and with

A subquery in the from clause is just a relation you can join and select from — this is how you avoid having when you need to filter on an aggregate: wrap the grouped query, then where avg_salary > 42000 on the outer query. Name it with as (MySQL/PostgreSQL require a name). The with clause defines a temporary named relation visible only to that query — great for readability and for reusing an intermediate result (e.g. compute department totals, then compare to their average). Viewed through Lesson 2's algebra, with is just assignment (←); a view (Lesson 4) is the same idea but persisted.

Scalar subqueries

A subquery that returns exactly one value can go anywhere an expression can — select dept_name, (select count(*) from instructor where ...) as num. If it returns more than one row at runtime, you get an error. "No from clause" queries like (select count(*) from teaches) / (select count(*) from instructor) work in SQL Server but need a dummy dual relation in Oracle.

3.9 Modification of the Database

The three write statements, each allowing a where (or nested subquery) to pick rows:

Statement Form Notes
delete delete from r where P Deletes whole tuples (never single columns). One relation at a time, but the where can reference others. All tests run before any deletion, so the average doesn't shift mid-delete.
insert insert into r values (...) or insert into r select ... Can insert a literal tuple or a query's result set. The select is fully evaluated before inserting — otherwise insert into student select * from student would loop forever. Omitted columns become null.
update update r set A = expr where P Uses case for conditional updates; a subquery in the set clause (e.g. recompute tot_cred) is common. All tests run before any update.

The case construct is the one non-obvious gem here: "3% raise above $100k, else 5%" is one statement with two ordered when branches, which avoids the ordering bug you'd hit writing two separate updates. Scalar subqueries in set (recompute a student's total credits from their grades) and coalesce (null → 0) round out the toolkit.


Interview Synthesis

When an interviewer says "write the SQL for X," this chapter is your alphabet:

  1. Shape the query: from (tables) → where (joins + filters, never forget the join predicate) → group by / having (if aggregating) → select (output) → order by.
  2. Aggregate rule: non-aggregated select columns must be in group by; filter groups with having, rows with where.
  3. "All / any" questions: reach for exists / not exists (set containment via except) or > all / > some.
  4. Nulls: test with is null; remember unknown drops rows just like false.
  5. Writes: remember the evaluate-then-apply discipline that keeps deletes/inserts/updates safe from self-reference feedback loops.

Quick Retrieval Quiz

1. In SQL's meaning of a query, the order is:

select, then from, then where from, then where, then select where, then select, then from

2. Forgetting the join predicate in a multi-table from clause gives:

A syntax error A Cartesian product of all rows Only the matched rows

3. select union vs select distinct:

Both keep duplicates union deduplicates, plain select keeps duplicates union keeps, select removes

4. A comparison like '1 < null' evaluates to:

false unknown (row dropped) true

5. 'Students who took ALL Biology courses' is best expressed with:

course_id in (Biology courses) not exists (Biology except taken) unique (subquery)

6. group by dept_name, then select dept_name, ID, avg(salary):

Erroneous: ID not in group by and not aggregated Valid: ID is just picked arbitrarily Valid only if you add a having clause

7. sum(salary) when some rows have null salary:

Ignores the null rows Becomes null Returns 0

Notes

SQL parts :-

DDL (schema) + DML (query/update) + integrity + views + txn + auth
? declarative: say what, system finds how (optimizer rewrites)

Types :-

char(n) pads spaces ; varchar(n) safe default ; numeric(p,d) exact

real/double/float = approximate ; ? money (rounding)

DDL :-

create table : PK (not null+unique), FK refs PK, not null

drop table = data+schema ; delete from = empty only

alter add A (null for old rows) ; alter drop A (? many systems)

? constraint violation = update rejected

Query shape :-

meaning: from (?) ? where (filter) ? select (project)

written order select/from/where ; engine does better

distinct = dedupe ; default keeps dups (vs union which dedupes)

? forget join pred = Cartesian product (huge!)

Strings/order :-

like % _ ; escape '\' ; is null (? = null)

order by desc/asc multi-col ; between = range sugar

Set ops :-

union / intersect / except = ? ? ? ; all dedupe (union all keeps)

MySQL ? intersect/except (use subq) ; Oracle minus = except

Null logic :-

3rd value unknown ; arithmetic w/ null = null

true?unknown=unknown ; false?unknown=false ; true?unknown=true

where keeps only true ; unknown dropped like false

distinct/set ops treat 2 nulls as equal (collapse)

Aggregates :-

avg/min/max/sum/count ; count(distinct X)

group by : non-aggregated select cols MUST be in group by

having filters groups (post-group) ; where filters rows

eval: from?where?group?having?select

null: all aggregates ? count(*) ignore nulls ; empty?count 0 else null

Subqueries :-

in/not in = membership (? except) ; >some / >all

exists/not exists = nonempty ; correlated subq re-runs per row

"all X" = not exists (X except taken)

from-subq = relation ; with = temp named rel (? view but local)

scalar subq = 1 value, anywhere expr allowed ; >1 row = runtime err

Modification :-

delete from r where P (whole tuples; 1 rel; tests before delete)

insert : values(...) or select... ; select fully eval before insert

update r set A=expr where P ; case for conditional ; subq in set

Primary source: Silberschatz, A., Korth, H. F., & Sudarshan, S. (2020). Database System Concepts, 7th ed., Chapter 3: “Introduction to SQL.” McGraw-Hill.
Note: this is the Chapter 3 lesson that was skipped earlier; it now sits between Lesson 2 (relational model) and the former Lesson 3 (now renumbered Lesson 4, intermediate SQL) so the book track reads in order.

Questions? Ask your agent — you can follow up on any concept, quiz answer, or how this maps onto a SQL whiteboard question.

← Lesson 2: The Relational Model Next: Lesson 4 — Intermediate SQL →