SQL: Data Definition & Manipulation
SELECT in depth — filtering, sorting, aggregates, grouping, subqueries, joins, EXISTS — plus INSERT, UPDATE and DELETE.
Contents
- Purpose and importance of SQL
- Retrieving data with
SELECT: compoundWHEREconditions, sorting withORDER BY, aggregate functions, grouping withGROUP BY/HAVING, subqueries, joins - Updating the database with
INSERT,UPDATE,DELETE
DDL, DML, CREATE TABLE, INSERT INTO … VALUES, SELECT … FROM … WHERE, GROUP BY, HAVING, ORDER BY, DISTINCT, IN, NOT IN, LIKE, NULL, IS NOT NULL, COUNT, SUM, AVG, MIN, MAX, JOIN, EXISTS, NOT EXISTS, UPDATE … SET … WHERE.
Objectives of SQL
Ideally, a database language should allow a user to:
- create the database and relation structures;
- perform insertion, modification and deletion of data;
- perform simple and complex queries.
SQL is a transform-oriented language with three major components:
| Component | Purpose | Examples |
|---|---|---|
| DDL — Data Definition Language | Define database structure | CREATE, ALTER, DROP |
| DML — Data Manipulation Language | Retrieve and update data | SELECT, INSERT, UPDATE, DELETE |
| DCL — Data Control Language | Control access to data | GRANT, REVOKE (see Week 8) |
SQL consists of standard English words:
CREATE TABLE Staff (staffNo VARCHAR(5),
lName VARCHAR(15),
salary DECIMAL(7,2));
INSERT INTO Staff VALUES ('SG16', 'Brown', 8300);
SELECT staffNo, lName, salary
FROM Staff
WHERE salary > 10000;
A literal is a constant value written in a statement. Non-numeric literals are enclosed in single quotes ('SG16', 'London'); numeric literals are not (8300).
DreamHome sample data
All examples use the DreamHome case study (Connolly & Begg). Expand a table to check query results yourself.
Staff
| staffNo | fName | lName | position | sex | DOB | salary | branchNo |
|---|---|---|---|---|---|---|---|
| SL21 | John | White | Manager | M | 1-Oct-45 | 30000 | B005 |
| SG37 | Ann | Beech | Assistant | F | 10-Nov-60 | 12000 | B003 |
| SG14 | David | Ford | Supervisor | M | 24-Mar-58 | 18000 | B003 |
| SA9 | Mary | Howe | Assistant | F | 19-Feb-70 | 9000 | B007 |
| SG5 | Susan | Brand | Manager | F | 3-Jun-40 | 24000 | B003 |
| SL41 | Julie | Lee | Assistant | F | 13-Jun-65 | 9000 | B005 |
Branch
| branchNo | street | city | postcode |
|---|---|---|---|
| B005 | 22 Deer Rd | London | SW1 4EH |
| B007 | 16 Argyll St | Aberdeen | AB2 3SU |
| B003 | 163 Main St | Glasgow | G11 9QX |
| B004 | 32 Manse Rd | Bristol | BS99 1NZ |
| B002 | 56 Clover Dr | London | NW10 6EU |
PropertyForRent
| propertyNo | street | city | type | rooms | rent | ownerNo | staffNo | branchNo |
|---|---|---|---|---|---|---|---|---|
| PA14 | 16 Holhead | Aberdeen | House | 6 | 650 | CO46 | SA9 | B007 |
| PL94 | 6 Argyll St | London | Flat | 4 | 400 | CO87 | SL41 | B005 |
| PG4 | 6 Lawrence St | Glasgow | Flat | 3 | 350 | CO40 | null | B003 |
| PG36 | 2 Manor Rd | Glasgow | Flat | 3 | 375 | CO93 | SG37 | B003 |
| PG21 | 18 Dale Rd | Glasgow | House | 5 | 600 | CO87 | SG37 | B003 |
| PG16 | 5 Novar Dr | Glasgow | Flat | 4 | 450 | CO93 | SG14 | B003 |
Viewing and Client
| clientNo | propertyNo | viewDate | comment |
|---|---|---|---|
| CR56 | PA14 | 24-May-01 | too small |
| CR76 | PG4 | 20-Apr-01 | too remote |
| CR56 | PG4 | 26-May-01 | null |
| CR62 | PA14 | 14-May-01 | no dining room |
| CR56 | PG36 | 28-Apr-01 | null |
| clientNo | fName | lName | telNo | prefType | maxRent |
|---|---|---|---|---|---|
| CR76 | John | Kay | 0207-774-5632 | Flat | 425 |
| CR56 | Aline | Stewart | 0141-848-1825 | Flat | 350 |
| CR74 | Mike | Ritchie | 01475-392178 | House | 750 |
| CR62 | Mary | Tregear | 01224-196720 | Flat | 600 |
The SELECT statement
SELECT [DISTINCT | ALL] {* | [columnExpression [AS newName]] [,...]}
FROM TableName [alias] [, ...]
[WHERE condition]
[GROUP BY columnList] [HAVING condition]
[ORDER BY columnList]
| Clause | What it does |
|---|---|
SELECT | Specifies which columns appear in the output |
FROM | Specifies the table(s) to be used |
WHERE | Filters rows |
GROUP BY | Forms groups of rows with the same column value |
HAVING | Filters groups subject to some condition |
ORDER BY | Specifies the order of the output |
The order of the clauses cannot be changed. Only SELECT and FROM are mandatory.
Although written SELECT-first, the DBMS logically evaluates: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. This explains why you can’t use an aggregate in WHERE (groups don’t exist yet) but can in HAVING.
Basic retrieval
All columns, all rows
List full details of all staff.
SELECT staffNo, fName, lName, position, sex, DOB, salary, branchNo
FROM Staff;
-- * is an abbreviation for 'all columns'
SELECT * FROM Staff;
Result: all 6 rows of the Staff table.
Specific columns, all rows
List salaries for all staff, showing only staff number, first and last names, and salary.
SELECT staffNo, fName, lName, salary
FROM Staff;
Use of DISTINCT
List the property numbers of all properties that have been viewed.
SELECT propertyNo FROM Viewing; -- PA14, PG4, PG4, PA14, PG36
SELECT DISTINCT propertyNo FROM Viewing; -- PA14, PG4, PG36
DISTINCT eliminates duplicate rows from the result.
Calculated fields
Produce a list of monthly salaries for all staff.
SELECT staffNo, fName, lName, salary/12 AS monthlySalary
FROM Staff;
| staffNo | fName | lName | monthlySalary |
|---|---|---|---|
| SL21 | John | White | 2500.00 |
| SG37 | Ann | Beech | 1000.00 |
| SG14 | David | Ford | 1500.00 |
| SA9 | Mary | Howe | 750.00 |
| SG5 | Susan | Brand | 2000.00 |
| SL41 | Julie | Lee | 750.00 |
Without AS, the column gets a system name (e.g. col4). Use the AS clause to name it.
Search conditions (WHERE)
The five basic search conditions: Comparison Range Set membership Pattern match Null
1. Comparison
List all staff with a salary greater than 10,000.
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > 10000;
Result: SL21 (30000), SG37 (12000), SG14 (18000), SG5 (24000).
Compound comparison — list addresses of all branch offices in London or Glasgow:
SELECT *
FROM Branch
WHERE city = 'London' OR city = 'Glasgow';
Result: B005 (22 Deer Rd, London), B003 (163 Main St, Glasgow), B002 (56 Clover Dr, London).
2. Range — BETWEEN
List all staff with a salary between 20,000 and 30,000.
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary BETWEEN 20000 AND 30000;
-- equivalent:
WHERE salary >= 20000 AND salary <= 30000;
Result: SL21 John White (30000), SG5 Susan Brand (24000).
BETWEENincludes the endpoints of the range.- There is a negated version,
NOT BETWEEN. - BETWEEN doesn’t add expressive power, but is useful for a range of values.
3. Set membership — IN
List all managers and supervisors.
SELECT staffNo, fName, lName, position
FROM Staff
WHERE position IN ('Manager', 'Supervisor');
-- equivalent:
WHERE position = 'Manager' OR position = 'Supervisor';
Result: SL21 White (Manager), SG14 Ford (Supervisor), SG5 Brand (Manager).
- Negated version:
NOT IN.INis more efficient/readable when the set contains many values.
4. Pattern matching — LIKE
Find all owners with the string ‘Glasgow’ in their address.
SELECT ownerNo, fName, lName, address, telNo
FROM PrivateOwner
WHERE address LIKE '%Glasgow%';
Result: CO87 Carol Farrel, CO40 Tina Murphy, CO93 Tony Shaw.
| Symbol | Meaning | Example |
|---|---|---|
% | Sequence of zero or more characters | '%Glasgow%' — contains “Glasgow” |
_ | Any single character | 'H____' — exactly 5 chars starting with H |
5. NULL search condition
List details of all viewings on property PG4 where a comment has not been supplied.
SELECT clientNo, viewDate
FROM Viewing
WHERE propertyNo = 'PG4' AND comment IS NULL;
Result: CR56, 26-May-01. There are 2 viewings for PG4 — one with and one without a comment.
You must test for null with the special keyword IS NULL (or IS NOT NULL). comment = NULL never evaluates to true.
Sorting results (ORDER BY)
Single column ordering
SELECT staffNo, fName, lName, salary
FROM Staff
ORDER BY salary DESC;
Result order: SL21 (30000), SG5 (24000), SG14 (18000), SG37 (12000), SA9 (9000), SL41 (9000).
Multiple column ordering
SELECT propertyNo, type, rooms, rent
FROM PropertyForRent
ORDER BY type;
There are four flats — with no minor sort key, the system arranges them in any order it chooses. To order by rent within type:
SELECT propertyNo, type, rooms, rent
FROM PropertyForRent
ORDER BY type, rent DESC;
| propertyNo | type | rooms | rent |
|---|---|---|---|
| PG16 | Flat | 4 | 450 |
| PL94 | Flat | 4 | 400 |
| PG36 | Flat | 3 | 375 |
| PG4 | Flat | 3 | 350 |
| PA14 | House | 6 | 650 |
| PG21 | House | 5 | 600 |
type is the major sort key; rent is the minor sort key. Default direction is ASC.
Aggregate functions
The ISO standard defines five aggregate functions:
| Function | Returns | Column types |
|---|---|---|
COUNT | Number of values in the column | Numeric and non-numeric |
SUM | Sum of values | Numeric only |
AVG | Average of values | Numeric only |
MIN | Smallest value | Numeric and non-numeric |
MAX | Largest value | Numeric and non-numeric |
- Each operates on a single column and returns a single value.
- Apart from
COUNT(*), each function eliminates nulls first and operates only on the remaining non-null values. COUNT(*)counts all rows, regardless of nulls or duplicates.DISTINCTbefore the column name eliminates duplicates. It has no effect on MIN/MAX, but may change SUM/AVG.- Aggregates can be used only in the SELECT list and the HAVING clause.
If the SELECT list includes an aggregate and there is no GROUP BY, the SELECT list cannot reference a column outside an aggregate:
SELECT staffNo, COUNT(salary) -- ILLEGAL
FROM Staff;
(Which staffNo would go with the single count?)
Examples
-- How many properties cost more than £350 per month to rent?
SELECT COUNT(*) AS myCount
FROM PropertyForRent
WHERE rent > 350; -- 5
-- How many different properties were viewed in May '01?
SELECT COUNT(DISTINCT propertyNo) AS myCount
FROM Viewing
WHERE viewDate BETWEEN '1-May-01' AND '31-May-01'; -- 2 (PA14, PG4)
-- Find number of Managers and sum of their salaries.
SELECT COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
WHERE position = 'Manager'; -- 2, 54000
-- Find minimum, maximum, and average staff salary.
SELECT MIN(salary) AS myMin, MAX(salary) AS myMax, AVG(salary) AS myAvg
FROM Staff; -- 9000, 30000, 17000
Grouping (GROUP BY and HAVING)
Use GROUP BY to get sub-totals. SELECT and GROUP BY are closely integrated: each item in the SELECT list must be single-valued per group, so the SELECT clause may only contain:
- column names (that are in the GROUP BY)
- aggregate functions
- constants
- expressions combining the above
- All column names in the SELECT list must appear in the GROUP BY clause unless used only inside an aggregate.
- If WHERE is used with GROUP BY, WHERE is applied first, then groups are formed from the remaining rows.
- ISO considers two nulls to be equal for GROUP BY purposes (all nulls form one group).
Find the number of staff in each branch and their total salaries.
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
ORDER BY branchNo;
| branchNo | myCount | mySum |
|---|---|---|
| B003 | 3 | 54000 |
| B005 | 2 | 39000 |
| B007 | 1 | 9000 |
Restricted groupings — HAVING
HAVINGis designed for use with GROUP BY to restrict the groups that appear in the final result.- Similar to WHERE, but WHERE filters individual rows, HAVING filters groups.
- Column names in HAVING must also appear in the GROUP BY list or be inside an aggregate.
For each branch with more than 1 member of staff, find the number of staff and the sum of their salaries.
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
HAVING COUNT(staffNo) > 1
ORDER BY branchNo;
Result: B003 (3, 54000), B005 (2, 39000). B007 is removed because it has only 1 staff member.
Subqueries
Some SQL statements can have a SELECT embedded within them. A subselect used in the WHERE or HAVING clause of an outer SELECT is called a subquery or nested query. Subselects may also appear in INSERT, UPDATE and DELETE statements.
Subquery with equality
List staff who work in the branch at ‘163 Main St’.
SELECT staffNo, fName, lName, position
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St');
The inner SELECT finds the branch number ('B003'). The outer SELECT then becomes … WHERE branchNo = 'B003'. Result: SG37 Ann Beech, SG14 David Ford, SG5 Susan Brand.
Subquery with aggregate
List all staff whose salary is greater than the average salary, and show by how much.
SELECT staffNo, fName, lName, position,
salary - (SELECT AVG(salary) FROM Staff) AS salDiff
FROM Staff
WHERE salary > (SELECT AVG(salary) FROM Staff);
You cannot write WHERE salary > AVG(salary) — aggregates aren’t allowed in WHERE. The subquery computes the average (17000) first; the outer query then effectively runs WHERE salary > 17000.
| staffNo | fName | lName | position | salDiff |
|---|---|---|---|---|
| SL21 | John | White | Manager | 13000 |
| SG14 | David | Ford | Supervisor | 1000 |
| SG5 | Susan | Brand | Manager | 7000 |
Subquery rules
ORDER BYmay not be used in a subquery (only in the outermost SELECT).- The subquery SELECT list must consist of a single column name or expression, except for subqueries using
EXISTS. - By default, column names refer to the table in the subquery’s FROM clause; you can refer to an outer table using an alias.
- When a subquery is an operand in a comparison, it must appear on the right-hand side.
- A subquery may not be used as an operand in an expression.
Nested subquery using IN
List properties handled by staff at ‘163 Main St’.
SELECT propertyNo, street, city, postcode, type, rooms, rent
FROM PropertyForRent
WHERE staffNo IN (SELECT staffNo
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St'));
The middle query returns several staff numbers, so IN is used rather than =. Result: PG16, PG36, PG21.
Multi-table queries (joins)
- Subqueries are fine if all result columns come from the same table.
- If result columns come from more than one table, you must use a join.
- Include more than one table in the FROM clause (comma-separated) and use WHERE to specify the join column(s).
- A table alias is separated from the table name by a space and is used to qualify ambiguous column names.
Simple join
List names of all clients who have viewed a property, along with any comment supplied.
SELECT c.clientNo, fName, lName, propertyNo, comment
FROM Client c, Viewing v
WHERE c.clientNo = v.clientNo;
Equivalent to the equi-join in relational algebra: only rows with identical clientNo values in both tables are included.
| clientNo | fName | lName | propertyNo | comment |
|---|---|---|---|---|
| CR56 | Aline | Stewart | PG36 | |
| CR56 | Aline | Stewart | PA14 | too small |
| CR56 | Aline | Stewart | PG4 | |
| CR62 | Mary | Tregear | PA14 | no dining room |
| CR76 | John | Kay | PG4 | too remote |
Alternative JOIN constructs
FROM Client c JOIN Viewing v ON c.clientNo = v.clientNo
FROM Client JOIN Viewing USING (clientNo)
FROM Client NATURAL JOIN Viewing
In each case the new FROM replaces the original FROM and WHERE. However, the ON version produces a table with two identical clientNo columns; USING and NATURAL JOIN keep only one.
The slides write USING clientNo; standard SQL requires parentheses: USING (clientNo). Also, SQL Server does not support USING or NATURAL JOIN — use ON there.
Sorting a join
For each branch, list numbers and names of staff who manage properties, and the properties they manage.
SELECT s.branchNo, s.staffNo, fName, lName, propertyNo
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
ORDER BY s.branchNo, s.staffNo, propertyNo;
| branchNo | staffNo | fName | lName | propertyNo |
|---|---|---|---|---|
| B003 | SG14 | David | Ford | PG16 |
| B003 | SG37 | Ann | Beech | PG21 |
| B003 | SG37 | Ann | Beech | PG36 |
| B005 | SL41 | Julie | Lee | PL94 |
| B007 | SA9 | Mary | Howe | PA14 |
Three-table join
For each branch, list staff who manage properties, including the city of the branch and the properties they manage.
SELECT b.branchNo, b.city, s.staffNo, fName, lName, propertyNo
FROM Branch b, Staff s, PropertyForRent p
WHERE b.branchNo = s.branchNo AND s.staffNo = p.staffNo
ORDER BY b.branchNo, s.staffNo, propertyNo;
-- Alternative FROM/WHERE:
FROM (Branch b JOIN Staff s USING (branchNo)) AS bs
JOIN PropertyForRent p USING (staffNo)
Result: same rows as above plus a city column (Glasgow ×3, London, Aberdeen).
Multiple grouping columns
Find the number of properties handled by each staff member.
SELECT s.branchNo, s.staffNo, COUNT(*) AS myCount
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.branchNo, s.staffNo
ORDER BY s.branchNo, s.staffNo;
Result: B003/SG14 → 1, B003/SG37 → 2, B005/SL41 → 1, B007/SA9 → 1.
Computing a join (conceptual procedure)
- Form the Cartesian product of the tables named in the FROM clause.
- If there is a WHERE clause, apply the search condition to each row of the product, keeping rows that satisfy it.
- For each remaining row, determine the value of each item in the SELECT list to produce a single result row.
- If
DISTINCTwas specified, eliminate duplicate rows. - If there is an
ORDER BY, sort the result.
SQL has a special format for the Cartesian product:
SELECT [DISTINCT | ALL] {* | columnList}
FROM Table1 CROSS JOIN Table2;
EXISTS and NOT EXISTS
- For use only with subqueries; they produce a simple true/false result.
EXISTSis true if and only if the subquery returns at least one row; false if it returns an empty table.NOT EXISTSis the opposite.- Since they only check existence, the subquery can contain any number of columns — it is common to write
(SELECT * …).
Find all staff who work in a London branch.
SELECT staffNo, fName, lName, position
FROM Staff s
WHERE EXISTS (SELECT *
FROM Branch b
WHERE s.branchNo = b.branchNo AND city = 'London');
Result: SL21 John White (Manager), SL41 Julie Lee (Assistant).
s.branchNo = b.branchNo links each staff row to its own branch (a correlated subquery). If omitted, the subquery SELECT * FROM Branch WHERE city='London' is always true, and the query becomes … WHERE true — listing all staff.
The same query written as a join:
SELECT staffNo, fName, lName, position
FROM Staff s, Branch b
WHERE s.branchNo = b.branchNo AND city = 'London';
INSERT
INSERT INTO TableName [(columnList)]
VALUES (dataValueList);
The dataValueList must match the columnList:
- the number of items in each list must be the same;
- there must be direct correspondence in position;
- the data type of each value must be compatible with its column.
INSERT … VALUES (all columns)
INSERT INTO Staff
VALUES ('SG16', 'Alan', 'Brown', 'Assistant', 'M', DATE '1957-05-25', 8300, 'B003');
INSERT using defaults (mandatory columns only)
INSERT INTO Staff (staffNo, fName, lName, position, salary, branchNo)
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', 8100, 'B003');
-- or, listing every column and using NULL:
INSERT INTO Staff
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', NULL, NULL, 8100, 'B003');
INSERT … SELECT
A second form of INSERT copies multiple rows from one or more tables into another:
INSERT INTO TableName [(columnList)]
SELECT ...
Populate StaffPropCount(staffNo, fName, lName, propCnt) using Staff and PropertyForRent.
INSERT INTO StaffPropCount
(SELECT s.staffNo, fName, lName, COUNT(*)
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.staffNo, fName, lName)
UNION
(SELECT staffNo, fName, lName, 0
FROM Staff
WHERE staffNo NOT IN (SELECT DISTINCT staffNo
FROM PropertyForRent));
| staffNo | fName | lName | propCount |
|---|---|---|---|
| SG14 | David | Ford | 1 |
| SL21 | John | White | 0 |
| SG37 | Ann | Beech | 2 |
| SA9 | Mary | Howe | 1 |
| SG5 | Susan | Brand | 0 |
| SL41 | Julie | Lee | 1 |
If the second part of the UNION is omitted, staff who currently manage no properties (SL21, SG5) are excluded.
PG4 has a null staffNo. In many DBMSs, x NOT IN (…, NULL) evaluates to UNKNOWN, returning no rows. Safer: add WHERE staffNo IS NOT NULL inside the subquery, or use NOT EXISTS.
UPDATE
UPDATE TableName
SET columnName1 = dataValue1 [, columnName2 = dataValue2 ...]
[WHERE searchCondition];
TableNamecan be a base table or an updatable view.SETnames one or more columns to update.WHEREis optional: if omitted, the named columns are updated for all rows; if specified, only rows satisfying the condition are updated.- New values must be compatible with the column data types.
-- Give all staff a 3% pay increase.
UPDATE Staff SET salary = salary * 1.03;
-- Give all Managers a 5% pay increase.
UPDATE Staff SET salary = salary * 1.05
WHERE position = 'Manager';
-- Promote David Ford (SG14) to Manager and change his salary to £18,000.
UPDATE Staff SET position = 'Manager', salary = 18000
WHERE staffNo = 'SG14';
DELETE
DELETE FROM TableName
[WHERE searchCondition];
TableNamecan be a base table or an updatable view.- If the search condition is omitted, all rows are deleted — but the table itself is not deleted (that would be
DROP TABLE).
-- Delete all viewings that relate to property PG4.
DELETE FROM Viewing WHERE propertyNo = 'PG4';
-- Delete all records from the Viewing table.
DELETE FROM Viewing;
Quick review questions
List the objectives of SQL.
Describe the importance of SQL.
What are literals?
'London'); numeric literals don’t (350).Which commands are used for a range search condition?
BETWEEN … AND … and NOT BETWEEN (inclusive of endpoints).Which command is used for pattern matching?
LIKE / NOT LIKE, with % (zero or more chars) and _ (one char).Which clause is used for single column ordering?
ORDER BY column [ASC | DESC].Name all aggregates usable in a SELECT statement.
COUNT, SUM, AVG, MIN, MAX.List the rules for subqueries.
Differentiate EXISTS and NOT EXISTS.
EXISTS is true when the subquery returns at least one row; NOT EXISTS is true when it returns no rows. Both return only true/false.