A-Level Computer Science / Unit 8: Relational Databases and SQL

8.3.3 Grouping, Aggregates and Two-Table Queries

πŸ”’ Lesson slides are available to signed-in users. Sign in

8.3.3 Grouping, Aggregates and Two-Table Queries

Some database questions require more than displaying individual tuples. A query may need to form categories, calculate a summary value, or combine related attributes stored in two tables. This section develops those skills using GROUP BY, COUNT, SUM, AVG and INNER JOIN.

By the end of this section, you should be able to:

  • Explain how GROUP BY divides qualifying tuples into groups.
  • Use COUNT, SUM and AVG to calculate summary values.
  • Distinguish between an aggregate over the whole result set and one aggregate per group.
  • Write grouped queries that contain valid selected attributes.
  • Explain why related information may need to be obtained from two tables.
  • Use an INNER JOIN to match a foreign key with a corresponding primary key.
  • Use table-qualified attribute names to remove ambiguity.
  • Combine a two-table query with filtering and sorting where appropriate.
  • Trace the intermediate stages used to produce a grouped or joined result.
Scope note: Syllabus queries use at most two tables. Defining structures was covered in 8.3.1; one-table selection, filtering and sorting in 8.3.2; and data maintenance is covered in 8.3.4.

The EcoTrack Database

The examples continue the original environmental-monitoring database used in the preceding SQL lessons.

ResearchSite

ResearchSite(SiteID, SiteName, RegionCode)

Primary key: SiteID

Sensor

Sensor(SensorID, SiteID, SensorType, SampleInterval)

Primary key: SensorID Foreign key: SiteID

ResearchSite data

SiteID SiteName RegionCode
RS101Forest EdgeNW
RS205Estuary PointSE
RS318Hill ReserveNW
RS424Wetland SouthSW

Sensor data

SensorID SiteID SensorType SampleInterval
S-104RS101Air quality10
S-218RS205Soil moisture30
S-311RS101Air quality15
S-405RS424Water level5
S-522RS318Temperature20
S-607RS318Air quality60
S-714RS205Water level10
Relationship: each sensor belongs to one research site, while one research site may have several sensors. The shared SiteID values allow the two tables to be joined.

Forming Groups with GROUP BY

GROUP BY places tuples that share the same value into the same category. It does not sort the output; its purpose is to define the groups used by the query.

SELECT SensorType
FROM Sensor
GROUP BY SensorType;

The seven sensor tuples form four groups:

Air quality

S-104 S-311 S-607

Soil moisture

S-218

Water level

S-405 S-714

Temperature

S-522
Common mistake: GROUP BY and ORDER BY are not interchangeable. Grouping forms categories; ordering arranges the result.
Exam tip: When a task asks for one result for each site, type or category, the repeated category attribute is often the field required after GROUP BY.

Calculating Summaries with Aggregate Functions

An aggregate function processes several tuples and returns a single summary value.

Function Purpose Suitable example
COUNT(*) Counts the tuples in the qualifying result. How many sensors are stored?
SUM(attribute) Adds the values of a numeric attribute. What is the total of all sampling intervals?
AVG(attribute) Calculates the arithmetic mean of a numeric attribute. What is the average sampling interval?

COUNT

SELECT COUNT(*)
FROM Sensor;

This returns 7, because the table contains seven tuples.

SUM

SELECT SUM(SampleInterval)
FROM Sensor;

This returns 150, the total of the seven numeric interval values.

AVG

SELECT AVG(SampleInterval)
FROM Sensor;

This returns the mean sampling interval, approximately 21.4 minutes. The exact displayed precision depends on the DBMS.

Common mistake: COUNT(*) counts tuples. It does not add the values stored in an attribute. Use SUM for a total and AVG for a mean.

One Aggregate Result for Each Group

Without GROUP BY, an aggregate is calculated over the whole qualifying result. With GROUP BY, the function is calculated separately for each group.

SELECT SiteID, COUNT(*)
FROM Sensor
GROUP BY SiteID
ORDER BY SiteID;
SiteID COUNT(*)
RS1012
RS2052
RS3182
RS4241

A grouped average follows the same structure:

SELECT SiteID, AVG(SampleInterval)
FROM Sensor
GROUP BY SiteID
ORDER BY SiteID;
SiteID AVG(SampleInterval)
RS10112.5
RS20520
RS31840
RS4245

Valid grouped SELECT lists

In a grouped query, an attribute in the SELECT list should normally be:

  • the attribute named after GROUP BY; or
  • inside an aggregate function.

Valid

SELECT SiteID, COUNT(*)
FROM Sensor
GROUP BY SiteID;

SiteID defines each group; COUNT summarises the group.

Problematic

SELECT SiteID, SensorID, COUNT(*)
FROM Sensor
GROUP BY SiteID;

A site group may contain several different SensorID values.

Common mistake: Do not select an ordinary attribute that has several possible values inside one group unless it is also included in the grouping.

Clause Order in an Analytical Query

A grouped query can also filter source tuples and sort the completed summary.

SELECT SiteID, AVG(SampleInterval)
FROM Sensor
WHERE SampleInterval <= 30
GROUP BY SiteID
ORDER BY SiteID;
1

FROM

Load tuples from Sensor.

2

WHERE

Remove tuples with intervals above 30.

3

GROUP BY

Form one group for each remaining SiteID.

4

Aggregate

Calculate one average for each group.

5

ORDER BY

Arrange the summary rows.

Written order: SELECT β†’ FROM β†’ WHERE β†’ GROUP BY β†’ ORDER BY
Useful logical model: FROM β†’ WHERE β†’ GROUP BY β†’ aggregate/SELECT β†’ ORDER BY
Exam tip: WHERE filters individual tuples before groups are formed. Do not place it after GROUP BY.

Why Query Two Tables?

A normalised database stores different kinds of facts in separate tables. Sensor stores the site identifier, but the readable site name is in ResearchSite. To display both sensor and site details, the DBMS must match related tuples.

Sensor

S-104

SiteID: RS101

Air quality

match SiteID β†’

ResearchSite

SiteID: RS101

Forest Edge

NW

combined row β†’

Query result

S-104

Forest Edge

Air quality

Join condition: the rule that states which values must match before tuples from two tables are combined.

Matching Related Tuples with INNER JOIN

An INNER JOIN returns combinations for which the join condition is true. In this database, Sensor.SiteID is matched with ResearchSite.SiteID.

SELECT ResearchSite.SiteName,
       Sensor.SensorID,
       Sensor.SensorType
FROM ResearchSite
INNER JOIN Sensor
ON ResearchSite.SiteID = Sensor.SiteID
ORDER BY ResearchSite.SiteName;
SiteName SensorID SensorType
Estuary PointS-218Soil moisture
Estuary PointS-714Water level
Forest EdgeS-104Air quality
Forest EdgeS-311Air quality
Hill ReserveS-522Temperature
Hill ReserveS-607Air quality
Wetland SouthS-405Water level

Reading the statement

Part Role
FROM ResearchSite Names the first table.
INNER JOIN Sensor Names the second table to be matched.
ON ResearchSite.SiteID = Sensor.SiteID Defines the primary-key/foreign-key match.
ResearchSite.SiteName Uses a table-qualified name to identify the source unambiguously.

Filtering a joined result

SELECT ResearchSite.SiteName,
       Sensor.SensorID,
       Sensor.SensorType
FROM ResearchSite
INNER JOIN Sensor
ON ResearchSite.SiteID = Sensor.SiteID
WHERE ResearchSite.RegionCode = 'NW'
ORDER BY ResearchSite.SiteName;

The join is formed using SiteID, then WHERE keeps only rows from sites in the NW region.

Common mistake: Without a correct join condition, unrelated tuples may be combined. Always identify the foreign key and the primary key before writing the ON clause.

Worked Example: Summary Information with Site Names

Display the name of each research site and the number of sensors installed there. Order the result alphabetically by site name.

Step 1: Identify why two tables are needed

SiteName is stored in ResearchSite, while each sensor tuple is stored in Sensor.

Step 2: Identify the join

Match ResearchSite.SiteID with Sensor.SiteID.

Step 3: Identify the groups

The request needs one result per site, so group by ResearchSite.SiteName.

Step 4: Choose the aggregate

Use COUNT(*) to count the joined sensor rows in each site group.

SELECT ResearchSite.SiteName, COUNT(*)
FROM ResearchSite
INNER JOIN Sensor
ON ResearchSite.SiteID = Sensor.SiteID
GROUP BY ResearchSite.SiteName
ORDER BY ResearchSite.SiteName;
SiteName COUNT(*)
Estuary Point2
Forest Edge2
Hill Reserve2
Wetland South1
Exam tip: For a joined summary, plan in this order: required output β†’ source tables β†’ matching keys β†’ groups β†’ aggregate β†’ final order.

Interactive: SQL Analysis Lab

Explore whole-table aggregates, grouped summaries and the process used to match two related tables.

Build a summary query

-- Build an aggregate query
Choose an aggregate, optional grouping and optional filter.
Result
No query has been run.

Common Mistakes and Misconceptions

  • Using GROUP BY when the requirement is merely to sort rows.
  • Assuming COUNT(*) adds numeric values.
  • Applying SUM or AVG to a non-numeric attribute.
  • Selecting an ungrouped, non-aggregated attribute in a grouped query.
  • Placing WHERE after GROUP BY.
  • Forgetting the ON clause of an INNER JOIN.
  • Matching unrelated attributes rather than the corresponding foreign and primary keys.
  • Leaving an attribute name unqualified when it exists in both joined tables.
  • Expecting an inner join to include a tuple that has no matching row in the other table.
  • Using more than two tables when the syllabus task is limited to at most two.

Practice

Original practice tasks

Use these two tables:

Library(LibraryID, LibraryName, District)

Book(BookID, LibraryID, Genre, PageCount)

  1. Write a query to count all tuples in Book.
  2. Write a query to calculate the total number of pages stored in Book.
  3. Write a query to calculate the average page count.
  4. Display each genre once by grouping the Book tuples.
  5. Display each LibraryID and the number of books held there.
  6. Display each genre and its average page count.
  7. Explain why this query is problematic:
    SELECT LibraryID, BookID, COUNT(*)
    FROM Book
    GROUP BY LibraryID;
  8. Write an INNER JOIN query to display LibraryName, BookID and Genre.
  9. Modify the previous query so that it returns only libraries in the 'Central' district.
  10. Display each library name and its number of books, ordered alphabetically by library name.
  11. Find and correct the errors:
    SELECT LibraryName, COUNT(*)
    FROM Library
    INNER JOIN Book
    GROUP BY LibraryName
    ON Library.LibraryID = Book.LibraryID;

Review

Feature Purpose Key reminder
GROUP BY Forms categories from equal values. It does not sort the result.
COUNT(*) Counts qualifying tuples. It does not total a numeric attribute.
SUM(attribute) Adds numeric values. Use a numeric attribute.
AVG(attribute) Calculates the arithmetic mean. With GROUP BY, one mean is produced per group.
INNER JOIN Combines matching tuples from two tables. Write a correct ON condition.
Table.Attribute Identifies an attribute's source table. Useful when joined tables share an attribute name.
Final exam tip: For a summary query, identify the grouping category and the required calculation. For a two-table query, identify the matching key before writing any SQL.