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 BYdivides qualifying tuples into groups. - Use
COUNT,SUMandAVGto 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 JOINto 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.
The EcoTrack Database
The examples continue the original environmental-monitoring database used in the preceding SQL lessons.
ResearchSite
ResearchSite(SiteID, SiteName, RegionCode)
Sensor
Sensor(SensorID, SiteID, SensorType, SampleInterval)
ResearchSite data
| SiteID | SiteName | RegionCode |
|---|---|---|
| RS101 | Forest Edge | NW |
| RS205 | Estuary Point | SE |
| RS318 | Hill Reserve | NW |
| RS424 | Wetland South | SW |
Sensor data
| SensorID | SiteID | SensorType | SampleInterval |
|---|---|---|---|
| S-104 | RS101 | Air quality | 10 |
| S-218 | RS205 | Soil moisture | 30 |
| S-311 | RS101 | Air quality | 15 |
| S-405 | RS424 | Water level | 5 |
| S-522 | RS318 | Temperature | 20 |
| S-607 | RS318 | Air quality | 60 |
| S-714 | RS205 | Water level | 10 |
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-607Soil moisture
S-218Water level
S-405 S-714Temperature
S-522GROUP BY and ORDER BY
are not interchangeable. Grouping forms categories; ordering arranges the result.
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.
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(*) |
|---|---|
| RS101 | 2 |
| RS205 | 2 |
| RS318 | 2 |
| RS424 | 1 |
A grouped average follows the same structure:
SELECT SiteID, AVG(SampleInterval)
FROM Sensor
GROUP BY SiteID
ORDER BY SiteID;
| SiteID | AVG(SampleInterval) |
|---|---|
| RS101 | 12.5 |
| RS205 | 20 |
| RS318 | 40 |
| RS424 | 5 |
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.
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;
FROM
Load tuples from Sensor.
WHERE
Remove tuples with intervals above 30.
GROUP BY
Form one group for each remaining SiteID.
Aggregate
Calculate one average for each group.
ORDER BY
Arrange the summary rows.
SELECT β FROM β WHERE β GROUP BY β ORDER BYUseful logical model:
FROM β WHERE β GROUP BY β aggregate/SELECT β ORDER BY
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
ResearchSite
SiteID: RS101
Forest Edge
NW
Query result
S-104
Forest Edge
Air quality
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 Point | S-218 | Soil moisture |
| Estuary Point | S-714 | Water level |
| Forest Edge | S-104 | Air quality |
| Forest Edge | S-311 | Air quality |
| Hill Reserve | S-522 | Temperature |
| Hill Reserve | S-607 | Air quality |
| Wetland South | S-405 | Water 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.
ON clause.
Worked Example: Summary Information with Site Names
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 Point | 2 |
| Forest Edge | 2 |
| Hill Reserve | 2 |
| Wetland South | 1 |
Interactive: SQL Analysis Lab
Explore whole-table aggregates, grouped summaries and the process used to match two related tables.
Common Mistakes and Misconceptions
- Using
GROUP BYwhen the requirement is merely to sort rows. - Assuming
COUNT(*)adds numeric values. - Applying
SUMorAVGto a non-numeric attribute. - Selecting an ungrouped, non-aggregated attribute in a grouped query.
- Placing
WHEREafterGROUP BY. - Forgetting the
ONclause of anINNER 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)
- Write a query to count all tuples in
Book. - Write a query to calculate the total number of pages stored in
Book. - Write a query to calculate the average page count.
- Display each genre once by grouping the
Booktuples. - Display each
LibraryIDand the number of books held there. - Display each genre and its average page count.
-
Explain why this query is problematic:
SELECT LibraryID, BookID, COUNT(*) FROM Book GROUP BY LibraryID; -
Write an
INNER JOINquery to displayLibraryName,BookIDandGenre. -
Modify the previous query so that it returns only libraries in the
'Central'district. - Display each library name and its number of books, ordered alphabetically by library name.
-
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. |