Which Pumps Appear on Either List but Are Still Unfinished?
Combine, compare and subtract query results while keeping duplicate policy, column meaning and final ordering explicit.
14 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Combining Results Needed a Duplicate Policy
BOTIE
That choice matters at the maintenance desk. Repeated alerts can be useful events in one report and unwanted repeated pump assignments in another. We need to choose the result’s meaning before deciding whether two equal rows should collapse.
Sources for this history
Two Teams Have Reported the Same Pump
Sensor alerts flag P7 and P8. Technician requests flag P8 and P9. Botie needs one candidate list, then needs to remove pumps whose inspection is already complete.
This combines whole result rows. It does not match columns from two records into a wider row as a join does. We will decide when duplicate reports are meaningful before removing them.
The records are invented maintenance examples. The workshop exposes record operations; the SQL worksheet executes a real query in an isolated SQLite database.
Step 1 of 1
Follow one shared identifier through the lists
P8 appears in both inputs and is already completed.
| Asset | Alert list | Request list | Completed list | Final candidate |
|---|---|---|---|---|
| P7 | Yes, twice | No | No | Yes, once |
| P8 | Yes | Yes | Yes | No |
| P9 | No | Yes | No | Yes, once |
P7
- Alert list
- Yes, twice
- Request list
- No
- Completed list
- No
- Final candidate
- Yes, once
P8
- Alert list
- Yes
- Request list
- Yes
- Completed list
- Yes
- Final candidate
- No
P9
- Alert list
- No
- Request list
- Yes
- Completed list
- No
- Final candidate
- Yes, once
Result rows are the objects being compared
Complete projected rows determine membership, while set difference removes duplicates and completed work.
Combine reports and remove finished work
Append compatible asset results, remove completed assets, and produce one ordered row per remaining pump.
BOTIE
Distinct unfinished asset IDs
[
{
"asset_id": "P7"
},
{
"asset_id": "P9"
}
]Follow the operations
1. Sensor alert records
[ { "asset_id": "P7" }, { "asset_id": "P7" }, { "asset_id": "P8" } ]2. Choose table columns
[ { "asset_id": "P7" }, { "asset_id": "P7" }, { "asset_id": "P8" } ]3. Technician request records
[ { "asset_id": "P8" }, { "asset_id": "P9" } ]4. Choose table columns
[ { "asset_id": "P8" }, { "asset_id": "P9" } ]5. Append compatible result rows
[ { "asset_id": "P7" }, { "asset_id": "P7" }, { "asset_id": "P8" }, { "asset_id": "P8" }, { "asset_id": "P9" } ]6. Check for a missing value
[ { "asset_id": "P7" }, { "asset_id": "P7" }, { "asset_id": "P8" }, { "asset_id": "P8" }, { "asset_id": "P9" } ]7. Completed asset records
[ { "asset_id": "P8" } ]8. Choose table columns
[ { "asset_id": "P8" } ]9. Remove rows found in another result
[ { "asset_id": "P7" }, { "asset_id": "P9" } ]10. Order rows by named fields
[ { "asset_id": "P7" }, { "asset_id": "P9" } ]11. Result
[ { "asset_id": "P7" }, { "asset_id": "P9" } ]
The records use consistent text keys. More general SQL type affinity and collation behaviour is outside this graph.
1. Align the meanings of the result columns
Each side of a compound SELECT must return the same number of columns. Corresponding positions need compatible meanings and types. Selecting asset_id on one side and site_name on the other may run while producing a meaningless mixed column.
Choose the projected columns first. If you also select source, two rows for the same asset can remain different because their source labels differ.
Duplicate elimination compares the complete selected row.
2. Use UNION ALL when repeated events matter
UNION ALL appends every row from the second result to the first. It preserves repeated alerts rather than silently changing their count.
The resulting table still has no guaranteed presentation order without an outer ORDER BY. Input order or the order in which the SELECT clauses were written is not a replacement for a sorting requirement.
Preserve event IDs or source information when individual events matter.
Retain all five reports
The asset column alone does not identify separate report events, but the repeated rows remain visible.
| asset_id |
|---|
| P7 |
| P7 |
| P8 |
| asset_id |
|---|
| P8 |
| asset_id |
|---|
| P8 |
| P9 |
Database setup for this example
CREATE TABLE alerts(asset_id TEXT);
CREATE TABLE requests(asset_id TEXT);
CREATE TABLE completed(asset_id TEXT);
INSERT INTO alerts VALUES ('P7'),('P7'),('P8');
INSERT INTO requests VALUES ('P8'),('P9');
INSERT INTO completed VALUES ('P8');SELECT asset_id FROM alerts
UNION ALL
SELECT asset_id FROM requests
ORDER BY asset_id;| asset_id |
|---|
| P7 |
| P7 |
| P8 |
| P8 |
| P9 |
P7 and P8 repeat. UNION ALL has not mistaken those rows for one pump assignment.
3. Use UNION when the result is a set of assignments
UNION removes duplicate complete output rows. With only asset_id projected, it returns one row per reported pump.
If each input is already unique, a pump can still occur in both inputs. Deduplicating each side separately is therefore insufficient for a distinct combined list.
The correct place to remove repetition depends on the result you are constructing.
Make one assignment candidate per asset
Both source lists contribute candidates, but each ID appears once.
| asset_id |
|---|
| P7 |
| P7 |
| P8 |
| asset_id |
|---|
| P8 |
| asset_id |
|---|
| P8 |
| P9 |
Database setup for this example
CREATE TABLE alerts(asset_id TEXT);
CREATE TABLE requests(asset_id TEXT);
CREATE TABLE completed(asset_id TEXT);
INSERT INTO alerts VALUES ('P7'),('P7'),('P8');
INSERT INTO requests VALUES ('P8'),('P9');
INSERT INTO completed VALUES ('P8');SELECT asset_id FROM alerts
UNION
SELECT asset_id FROM requests
ORDER BY asset_id;| asset_id |
|---|
| P7 |
| P8 |
| P9 |
The query changes the grain from reports to distinct asset IDs.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
How many P8 rows does UNION retain?
Use the information below.
Alerts contain P7, P7, P8. Requests contain P8, P9. Both SELECTs return only asset_id.4. Ask what both teams found
INTERSECT keeps distinct rows present in both results. In this example it finds the assets supported by both an alert and a technician request.
The operation does not pair every alert with every request. It compares the selected result rows, so duplicate reports do not multiply the intersection.
Use a join instead when you need columns from the individual matching reports.
Find agreement between the two lists
Only asset IDs are compared.
| asset_id |
|---|
| P7 |
| P7 |
| P8 |
| asset_id |
|---|
| P8 |
| asset_id |
|---|
| P8 |
| P9 |
Database setup for this example
CREATE TABLE alerts(asset_id TEXT);
CREATE TABLE requests(asset_id TEXT);
CREATE TABLE completed(asset_id TEXT);
INSERT INTO alerts VALUES ('P7'),('P7'),('P8');
INSERT INTO requests VALUES ('P8'),('P9');
INSERT INTO completed VALUES ('P8');SELECT asset_id FROM alerts
INTERSECT
SELECT asset_id FROM requests
ORDER BY asset_id;| asset_id |
|---|
| P8 |
P8 appears on both lists. This tells us about membership, not agreement between the underlying explanations.
5. Remove work that already has a completion record
EXCEPT keeps distinct rows from its left result that are absent from its right result. The order matters: candidates EXCEPT completed asks a different question from completed EXCEPT candidates.
Here we combine the report lists, then subtract completed asset IDs. When several operators appear together, make the intended stages clear. Engine precedence rules differ; a named subquery or CTE is often easier to review than relying on a reader to remember them.
The next lesson will give these intermediate result sets useful names.
6. Keep NULL and collation rules in view
SQLite’s compound duplicate comparisons treat NULL values in the same positions as equal for this purpose. That differs from using ordinary equality in a WHERE predicate. Text comparison also follows the chosen collation rules.
Decide whether a NULL asset ID belongs in an assignment report at all. Our exercise excludes NULL IDs explicitly because no pump can be assigned from an unknown identifier. It does not rely on an accidental NULL removal elsewhere.
A duplicate policy and a missing-identifier policy are separate decisions.
7. Check the result after each combination
Run each projection, then the combined candidates, then the subtraction. Adding another P7 alert should not create another assignment. Adding P7 to completed should remove it.
The workshop exposes those same intermediate relations. It uses append followed by set difference, whose distinct result supplies the final one-row-per-asset policy.
Different valid SQL forms can express this result. The tests check membership and ordering rather than requiring one operator sequence.
Check yourself
Questions on Parts 1 to 7
Question 1 of 2
Which operation returns the unfinished candidates?
Use the information below.
Candidates are P7, P8, P9. Completed assets are P8.Try it with Botie
Build with Botie
Write and run SQL