Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Database Management Systems (DBMS)  Module 5 – SQL semantics: how queries really evaluate

How SQL evaluates a query: logical order

The logical order of a SELECT as relational algebra on bags: what each clause computes, how duplicates are counted and where SQLite and PostgreSQL differ.

  • Beginner
  • 25 minutes
  • Examples run with sql.js 1.14.2
  • By MySmartCoPilot
Free: 25 minutes

What you will learn

  • Apply the logical order FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, LIMIT to predict a result
  • Map a SELECT-FROM-WHERE block to relational algebra under bag semantics
  • Count the rows that UNION, INTERSECT and EXCEPT return, with and without ALL
  • Predict where SQLite and PostgreSQL differ on aliases in WHERE, set-operator precedence and INTERSECT ALL

Before you start

What you’ll learn · Before you start

What you will learn

  • Apply the logical order FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, LIMIT to predict a result
  • Map a SELECT-FROM-WHERE block to relational algebra under bag semantics
  • Count the rows that UNION, INTERSECT and EXCEPT return, with and without ALL
  • Predict where SQLite and PostgreSQL differ on aliases in WHERE, set-operator precedence and INTERSECT ALL

Before you start

On this page

A SQL query describes a result, and SQL defines that result through a fixed logical order. FROM builds the input rows, WHERE keeps some of them, GROUP BY forms groups and computes aggregates, HAVING keeps some groups, SELECT computes the output columns, DISTINCT removes duplicate rows, UNION, INTERSECT and EXCEPT combine whole results, ORDER BY sorts, and LIMIT keeps a slice. Almost every step is an operator of relational algebra applied to a bag of rows, a collection in which the same row may appear several times. The order decides what a query means, which names each clause can see and how many rows come out. It does not decide how the engine computes the answer: any plan that returns the same rows is allowed.

This lesson maps each clause to an operator of relational algebra, counts rows through the steps of a real query, and shows where SQLite and PostgreSQL read the same text differently. Alias errors are drilled in the SQL track’s lesson on the order of execution; this one looks at the algebra underneath.

The logical order, clause by clause

The clauses of a SELECT as a chain of algebra operators: product, selection, grouping, projection, set operators, sort and slice.FROM× product (and joins)tables in, one bag of rows outWHEREσ selectionkeeps the rows whose condition is TRUEGROUP BY with aggregatesγ groupingone row per groupHAVINGσ on the groupskeeps the groups whose condition is TRUESELECTπ projection on a bagcomputes columns, keeps duplicatesDISTINCTδ duplicate eliminationthe bag becomes a setUNION, INTERSECT, EXCEPT∪ ∩ −combine whole resultsORDER BYτ sortthe result becomes a listLIMIT and OFFSETa slice of the sorted list

The logical order of a SELECT, with the operator each step applies

Text description of the diagram

The diagram is a chain of nine steps, from top to bottom. Every step takes the bag of rows the step above it produced.

  1. FROM: the product of the tables (or their join). Tables go in; one bag of combined rows comes out.
  2. WHERE: selection, written σ. It keeps the rows whose condition is TRUE.
  3. GROUP BY with the aggregates: grouping, written γ. It returns one row per group.
  4. HAVING: a selection on the groups. It keeps the groups whose condition is TRUE.
  5. SELECT: projection on a bag, written π. It computes the output columns and keeps duplicate rows.
  6. DISTINCT: duplicate elimination, written δ. The bag becomes a set.
  7. UNION, INTERSECT and EXCEPT: the set operators ∪, ∩ and −, which combine whole results.
  8. ORDER BY: sorting, written τ. The result becomes an ordered list.
  9. LIMIT and OFFSET: a slice of the sorted list.
  1. FROM, a product (×) or a join: the tables go in, one bag of combined rows comes out.
  2. WHERE, a selection (σ): it keeps the rows whose condition is TRUE.
  3. GROUP BY with the aggregates, grouping (γ): one row per group comes out, with its aggregates.
  4. HAVING, a selection on the groups: it keeps the groups whose condition is TRUE.
  5. SELECT, a projection (π): it computes the output columns and keeps duplicate rows.
  6. DISTINCT, duplicate elimination (δ): the bag becomes a set.
  7. UNION, INTERSECT and EXCEPT (∪, ∩, −): two results go in, one comes out.
  8. ORDER BY, a sort (τ): the bag becomes an ordered list.
  9. LIMIT and OFFSET: a slice of that list, with no operator of its own.

PostgreSQL’s manual describes the processing of a SELECT in this order, with WITH queries computed first and row locks (FOR UPDATE) taken last (PostgreSQL 18: SELECT). SQLite’s manual walks through the same steps and warns that the walk is only an explanation: no engine has to follow it, as long as the result is the one it defines (SQLite: SELECT).

Two consequences follow straight from the list. A clause can only use what an earlier step produced, so WHERE cannot filter on an aggregate, which step 3 computes later. And only steps 8 and 9 know about order: everything before them works on bags, which have no first or last row.

SELECT-FROM-WHERE is π over σ over ×

The core of most queries is one block of SELECT, FROM and WHERE. In algebra it reads from the inside out: take the product of the tables, select the rows that satisfy the condition, then project the columns asked for.

δ(π𝑑𝑒𝑝𝑡(σθ(S×E)))\delta\Big(\pi_{\mathit{dept}}\big(\sigma_{\theta}(S \times E)\big)\Big)

Here θ is the condition s.roll = e.roll AND e.code LIKE 'CS%', S the students and E the enrolments. The same expression as SQL, followed by the result of each step, on four students and six enrolments:

One query, and the rows after each step SQL · pipeline.sql
-- Four students and six enrolments. Which departments have a student in a CS course?
CREATE TABLE students (roll INTEGER PRIMARY KEY, name TEXT NOT NULL, dept TEXT NOT NULL);
CREATE TABLE enrolments (roll INTEGER NOT NULL, code TEXT NOT NULL);
INSERT INTO students VALUES (101, 'Aarav', 'CSE'), (102, 'Diya', 'ECE'), (103, 'Kabir', 'CSE'), (104, 'Meera', 'ME');
INSERT INTO enrolments VALUES
  (101, 'CS201'), (101, 'CS202'), (102, 'CS201'),
  (102, 'EC210'), (103, 'CS201'), (104, 'ME105');

-- The whole query.
SELECT DISTINCT s.dept
FROM students AS s, enrolments AS e
WHERE s.roll = e.roll AND e.code LIKE 'CS%'
ORDER BY s.dept;

-- Step 1, FROM: the product of the two tables, 4 x 6 rows.
SELECT count(*) AS rows_after_from FROM students AS s, enrolments AS e;

-- Step 2, WHERE: only the pairs that satisfy the condition stay.
SELECT s.roll, s.dept, e.code
FROM students AS s, enrolments AS e
WHERE s.roll = e.roll AND e.code LIKE 'CS%';

-- Step 3, SELECT: one output row per remaining pair, duplicates kept.
SELECT s.dept
FROM students AS s, enrolments AS e
WHERE s.roll = e.roll AND e.code LIKE 'CS%';

Output

┌──────┐
│ dept │
├──────┤
│ CSE  │
│ ECE  │
└──────┘
┌─────────────────┐
│ rows_after_from │
├─────────────────┤
│ 24              │
└─────────────────┘
┌──────┬──────┬───────┐
│ roll │ dept │ code  │
├──────┼──────┼───────┤
│ 101  │ CSE  │ CS201 │
│ 101  │ CSE  │ CS202 │
│ 102  │ ECE  │ CS201 │
│ 103  │ CSE  │ CS201 │
└──────┴──────┴───────┘
┌──────┐
│ dept │
├──────┤
│ CSE  │
│ CSE  │
│ ECE  │
│ CSE  │
└──────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < pipeline.sql

The row counts tell the story:

  • After FROM: 24 rows. A product pairs every student with every enrolment, so its size is the product of the sizes, 4 × 6. Most of these pairs make no sense (Aarav paired with Meera’s enrolment), which is why the condition that matches the roll numbers matters.
  • After WHERE: 4 rows. Only the pairs with equal roll numbers and a course code starting with CS survive.
  • After SELECT: still 4 rows. Projection keeps one output row per input row. CSE appears three times because three surviving pairs belong to CSE students: Aarav twice (CS201 and CS202) and Kabir once.
  • After DISTINCT: 2 rows, CSE and ECE. Meera’s only enrolment is in ME105, so ME never appeared.

The third step is where SQL parts company with classical relational algebra, which works on sets, so that its π removes duplicates by itself. SQL’s SELECT works on bags and keeps every copy; only DISTINCT removes them. An exam question that asks “how many rows does this query return” is really asking whether you counted copies or values.

GATE CS

The GATE CS syllabus lists relational algebra, tuple calculus and SQL under databases (GATE 2027 syllabus). When a question gives a few rows of each table and asks how many rows a query returns, work through the clauses in their logical order and count copies, not values, unless the query says DISTINCT.

Duplicates through the set operators

Each set operator has two forms: the plain one returns a set, and the ALL form keeps copies. If a row appears m times in the first result and n times in the second, the operators return it this many times:

Operator Copies in the result
UNION ALL m + n
UNION 1 if m + n > 0
INTERSECT ALL min(m, n)
INTERSECT 1 if both are above 0
EXCEPT ALL max(m − n, 0)
EXCEPT 1 if m > 0 and n = 0

With m = 3 and n = 2 that makes 5 copies for UNION ALL, 2 for INTERSECT ALL, 1 for EXCEPT ALL, 1 each for UNION and INTERSECT, and none for EXCEPT.

The ALL rules for INTERSECT and EXCEPT are the ones PostgreSQL’s manual states, and without ALL all three operators remove duplicates (PostgreSQL 18: SELECT). Note the asymmetry with SELECT: a plain SELECT keeps duplicates unless you write DISTINCT, while a plain UNION removes them unless you write ALL.

Two bags of course codes make this concrete. Bag A holds CS201 three times, CS202 once and MA101 once; bag B holds CS201 twice and CS202 twice:

Row counts of the four set operators SQL · bag_operations.sql
-- Two bags of course codes: A holds CS201 three times, B twice.
CREATE TABLE a (code TEXT);
CREATE TABLE b (code TEXT);
INSERT INTO a VALUES ('CS201'), ('CS201'), ('CS201'), ('CS202'), ('MA101');
INSERT INTO b VALUES ('CS201'), ('CS201'), ('CS202'), ('CS202');

SELECT 'UNION ALL' AS operation, count(*) AS result_rows FROM (SELECT code FROM a UNION ALL SELECT code FROM b)
UNION ALL
SELECT 'UNION', count(*) FROM (SELECT code FROM a UNION SELECT code FROM b)
UNION ALL
SELECT 'INTERSECT', count(*) FROM (SELECT code FROM a INTERSECT SELECT code FROM b)
UNION ALL
SELECT 'EXCEPT', count(*) FROM (SELECT code FROM a EXCEPT SELECT code FROM b);

-- SQLite has no bag versions of INTERSECT and EXCEPT.
SELECT code FROM a INTERSECT ALL SELECT code FROM b;

Output (exit status 1)

┌───────────┬─────────────┐
│ operation │ result_rows │
├───────────┼─────────────┤
│ UNION ALL │ 9           │
│ UNION     │ 3           │
│ INTERSECT │ 2           │
│ EXCEPT    │ 1           │
└───────────┴─────────────┘

Printed as an error (standard error)

Parse error near line 16: near "ALL": syntax error

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < bag_operations.sql

UNION ALL returns all 9 rows. UNION returns 3 distinct codes, INTERSECT the 2 codes both bags contain, and EXCEPT only MA101, the code that B lacks. The error at the end is SQLite’s: its grammar has UNION ALL but no ALL form of INTERSECT or EXCEPT (SQLite: compound SELECT), while PostgreSQL accepts both.

Bag intersection and difference in SQLite

The multiplicity rules suggest a portable way to get the ALL forms anyway. Number the copies of each value in each bag with row_number(): the copy numbered k exists in a bag exactly when the bag holds at least k copies. Two copies with the same value and number are then ordinary rows, and the plain set operators can compare them:

INTERSECT ALL and EXCEPT ALL, rebuilt from numbered copies SQL · bag_intersect_except.sql
-- INTERSECT ALL and EXCEPT ALL in SQLite: number the copies of each value, then use the set operators.
CREATE TABLE a (code TEXT);
CREATE TABLE b (code TEXT);
INSERT INTO a VALUES ('CS201'), ('CS201'), ('CS201'), ('CS202'), ('MA101');
INSERT INTO b VALUES ('CS201'), ('CS201'), ('CS202'), ('CS202');

-- Copy k of a value exists in a bag when the bag holds at least k copies of it.
CREATE VIEW a_copies AS SELECT code, row_number() OVER (PARTITION BY code) AS k FROM a;
CREATE VIEW b_copies AS SELECT code, row_number() OVER (PARTITION BY code) AS k FROM b;

-- A INTERSECT ALL B: the copies both bags have, min(m, n) of each value.
SELECT code FROM (SELECT code, k FROM a_copies INTERSECT SELECT code, k FROM b_copies)
ORDER BY code;

-- A EXCEPT ALL B: the copies only A has, max(m - n, 0) of each value.
SELECT code FROM (SELECT code, k FROM a_copies EXCEPT SELECT code, k FROM b_copies)
ORDER BY code;

Output

┌───────┐
│ code  │
├───────┤
│ CS201 │
│ CS201 │
│ CS202 │
└───────┘
┌───────┐
│ code  │
├───────┤
│ CS201 │
│ MA101 │
└───────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < bag_intersect_except.sql

The intersection keeps copies 1 and 2 of CS201 (both bags have them) and copy 1 of CS202, which is min(m, n) copies of each code. The difference keeps copy 3 of CS201, which only A has, and MA101: max(m − n, 0) copies. PostgreSQL’s own INTERSECT ALL and EXCEPT ALL return the same rows for these two bags. row_number() without an ORDER BY inside its window numbers the copies in an arbitrary order, which does no harm here, since copies of one value are indistinguishable.

Which set operator binds first

With three results and two operators, something has to decide the grouping, and the two engines decide differently:

  • PostgreSQL gives INTERSECT a higher precedence than UNION and EXCEPT, which group from left to right. So A UNION B INTERSECT C means A UNION (B INTERSECT C) (PostgreSQL 18: SELECT). MySQL 8.4 does the same (MySQL 8.4: set operations).
  • SQLite groups every compound operator from left to right, so the same text means (A UNION B) INTERSECT C (SQLite: compound SELECT).
One text, two groupings SQL · set_precedence.sql
-- Three one-column results: who registered, who paid the fee, who submitted the form.
CREATE TABLE registered (roll INTEGER);
CREATE TABLE paid (roll INTEGER);
CREATE TABLE submitted (roll INTEGER);
INSERT INTO registered VALUES (101), (102);
INSERT INTO paid VALUES (102), (103);
INSERT INTO submitted VALUES (103), (104);

-- As SQLite reads it: (registered UNION paid) INTERSECT submitted.
SELECT roll FROM registered
UNION
SELECT roll FROM paid
INTERSECT
SELECT roll FROM submitted;

-- As PostgreSQL reads the same text: registered UNION (paid INTERSECT submitted).
SELECT roll FROM registered
UNION
SELECT roll FROM (SELECT roll FROM paid INTERSECT SELECT roll FROM submitted)
ORDER BY roll;

-- PostgreSQL also accepts parentheses around a member of a compound query; SQLite does not.
SELECT roll FROM registered
UNION
(SELECT roll FROM paid INTERSECT SELECT roll FROM submitted);

Output (exit status 1)

┌──────┐
│ roll │
├──────┤
│ 103  │
└──────┘
┌──────┐
│ roll │
├──────┤
│ 101  │
│ 102  │
│ 103  │
└──────┘

Printed as an error (standard error)

Parse error near line 23: near "(": syntax error

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < set_precedence.sql

The first query is SQLite’s reading: the students who registered or paid, among those who submitted, which is only 103. The second spells out PostgreSQL’s reading of the same text with a subquery, so that SQLite can run it too: the students who registered, plus those who both paid and submitted, which is 101, 102 and 103. Neither engine reports an error for the first text, so the difference shows up only in the result.

The third query is the usual way to state the grouping, and it is where the engines part again: PostgreSQL accepts parentheses around a member of a compound query (PostgreSQL 18: combining queries), while SQLite rejects them, because each member must be a simple SELECT. Whenever a compound query mixes INTERSECT with UNION or EXCEPT, put the inner operation in a subquery in FROM, as the second query does: both engines accept that form and read it the same way.

What each step can see

The order also explains which names a clause can use. An output column’s alias is created in step 5, so clauses that run later, such as ORDER BY, can use it, and clauses that run earlier cannot. PostgreSQL holds to that with one extension: an alias works in ORDER BY and also in GROUP BY, but not in WHERE or HAVING (PostgreSQL 18: SELECT), and WHERE dbl > 100 fails with “column “dbl” does not exist”. SQLite is more lenient, as the first query below shows:

Three places where engines read SQL differently SQL · dialect_checks.sql
CREATE TABLE marks (roll INTEGER, score INTEGER);
INSERT INTO marks VALUES (101, 35), (102, 72), (103, 58);

-- An output alias in WHERE: SQLite looks the name up among the aliases when no column has it.
SELECT roll, score * 2 AS dbl FROM marks WHERE dbl > 100;

-- Integer and real division.
SELECT 7 / 2 AS int_div, 7 / 2.0 AS real_div, 7 % 2 AS remainder;

-- Bag intersection is not part of SQLite's grammar.
SELECT roll FROM marks INTERSECT ALL SELECT roll FROM marks;

Output (exit status 1)

┌──────┬─────┐
│ roll │ dbl │
├──────┼─────┤
│ 102  │ 144 │
│ 103  │ 116 │
└──────┴─────┘
┌─────────┬──────────┬───────────┐
│ int_div │ real_div │ remainder │
├─────────┼──────────┼───────────┤
│ 3       │ 3.5      │ 1         │
└─────────┴──────────┴───────────┘

Printed as an error (standard error)

Parse error near line 11: near "ALL": syntax error

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < dialect_checks.sql

  • An alias in WHERE. SQLite looks the name up among the output aliases when no input column has it, and runs the query. The same text fails in PostgreSQL, so never rely on it in code that has to move between engines.
  • Division. 7 / 2 is 3 in both engines, because both truncate the quotient of two integers towards zero (PostgreSQL 18: mathematical operators, SQLite: operators). Make either operand a decimal number, 7 / 2.0, to get 3.5.
  • INTERSECT ALL is a syntax error in SQLite, as before.

The logical order is about clauses, not about the parts of one expression. Inside a WHERE condition, the order in which the operands of AND and OR are evaluated is not defined, and the planner may rearrange a condition in any way Boolean algebra allows. PostgreSQL’s manual gives WHERE x > 0 AND y/x > 1.5 as a condition that can still divide by zero, and recommends CASE when the order really matters (PostgreSQL 18: expression evaluation rules).

The logical order is not the plan

The pipeline example built a 24-row product on paper, but no engine would build it for this query. EXPLAIN QUERY PLAN shows what SQLite actually does with the same query (SQLite: EXPLAIN QUERY PLAN):

The plan SQLite chooses for the pipeline query SQL · plan_not_order.sql
CREATE TABLE students (roll INTEGER PRIMARY KEY, name TEXT NOT NULL, dept TEXT NOT NULL);
CREATE TABLE enrolments (roll INTEGER NOT NULL, code TEXT NOT NULL);

-- The plan SQLite chooses for the query of the first example.
EXPLAIN QUERY PLAN
SELECT DISTINCT s.dept
FROM students AS s, enrolments AS e
WHERE s.roll = e.roll AND e.code LIKE 'CS%'
ORDER BY s.dept;

Output

┌────┬────────┬─────────┬──────────────────────────────────────────────┐
│ id │ parent │ notused │                    detail                    │
├────┼────────┼─────────┼──────────────────────────────────────────────┤
│ 8  │ 0      │ 216     │ SCAN e                                       │
│ 13 │ 0      │ 45      │ SEARCH s USING INTEGER PRIMARY KEY (rowid=?) │
│ 16 │ 0      │ 0       │ USE TEMP B-TREE FOR DISTINCT                 │
└────┴────────┴─────────┴──────────────────────────────────────────────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < plan_not_order.sql

Read the detail column from top to bottom. SQLite scans the enrolments once (SCAN e), finds the student of each enrolment through the primary key on roll (SEARCH s USING INTEGER PRIMARY KEY), and removes duplicate departments with a temporary B-tree, the sorted structure SQLite uses for DISTINCT, GROUP BY and ORDER BY. There is no product step at all, and no separate sort for ORDER BY either. The plan is a faster route to exactly the rows the logical order defines, which is the only promise an optimiser has to keep. SQLite’s manual warns that the format of this output may change between releases, so read it as a description, not as an interface. The track’s query-processing module looks at how such plans are chosen.

ORDER BY and LIMIT, the only steps that see order

Up to step 7 a result is a bag, and a bag has no order. Without ORDER BY, SQLite’s manual calls the order of the returned rows undefined, and PostgreSQL returns them in whatever order is fastest to produce. Three rules follow:

  • ORDER BY applies to the whole compound query. In SELECT … UNION SELECT … ORDER BY x, the sort runs after the union, on its result, in both engines.
  • LIMIT keeps a slice of the sorted list, so without ORDER BY it returns some rows, not particular ones. PostgreSQL’s manual advises an ORDER BY that makes the order unique whenever you use LIMIT.
  • Ties are a choice too. ORDER BY marks DESC LIMIT 1 returns one of the students with the top marks, and which one may change from run to run. Add a column that breaks ties, such as roll, when the answer must be repeatable.
SQL Formatter Lay out a long query with one clause per line, so its logical steps are easy to follow from FROM to LIMIT. SQLite Online (SQL Playground) Load your own tables in the browser and check the row counts of a query step by step.

Interview questions

Fresher · In what logical order does SQL evaluate the clauses of a SELECT, and why does that order matter?

The order is FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, then the set operators, ORDER BY and LIMIT. It defines what the query means: each clause works on the output of the one before it, so WHERE filters rows before any group exists and therefore cannot use an aggregate, HAVING filters whole groups, and an alias defined in SELECT is visible in ORDER BY but not in WHERE. It also explains row counts: SELECT keeps duplicates and only DISTINCT removes them. A good answer adds that the order is logical, not physical: the optimiser may use indexes, reorder joins or stop early, as long as the result is the same.

Key takeaways

  • A SELECT means the pipeline FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, set operators, ORDER BY, LIMIT, whatever order the clauses are written in.
  • SELECT a FROM r, s WHERE p is π over σ over ×, but on bags: SQL’s projection keeps every copy and only DISTINCT removes them.
  • With m and n copies of a row, UNION ALL returns m + n, INTERSECT ALL min(m, n) and EXCEPT ALL max(m − n, 0); the plain forms return sets.
  • SQLite has no INTERSECT ALL or EXCEPT ALL; numbering copies with row_number() rebuilds them from the plain operators.
  • INTERSECT binds first in PostgreSQL and MySQL; SQLite groups compound operators left to right. Use a subquery to make the grouping explicit.
  • The logical order defines the answer, not the plan: engines skip products, use indexes and sort with B-trees, as long as the rows are the same.

Exercise

Exercise · Medium · SQL

Keep the copies that both lab sessions share

Two lab sessions recorded the course code of every student who attended, one row per student: the morning session in the table morning(code) and the evening session in evening(code). A code can appear many times in each table.

Write the query that PostgreSQL would answer with SELECT code FROM morning INTERSECT ALL SELECT code FROM evening, but in SQLite, which has no INTERSECT ALL: every code as many times as the smaller of its two counts. If CS201 appears 3 times in the morning and twice in the evening, it appears twice in the result. Return one column named code; the order of the rows does not matter.

The starter query uses INTERSECT, which returns each shared code only once.

Starter code · common_copies.sql

-- Meant to keep every shared copy, but INTERSECT returns each code once.
SELECT code FROM morning
INTERSECT
SELECT code FROM evening;
The sample tests · tests.yaml
# Sample tests: each one builds the two tables, runs your query and compares the rows it returns (in any order,
# counting duplicates).
tests:
  - name: keeps the smaller count of every shared code
    setup: |
      CREATE TABLE morning (code TEXT);
      CREATE TABLE evening (code TEXT);
      INSERT INTO morning VALUES ('CS201'), ('CS201'), ('CS201'), ('CS202'), ('MA101');
      INSERT INTO evening VALUES ('CS201'), ('CS201'), ('CS202'), ('CS202');
    expected:
      columns: [code]
      rows:
        - [CS201]
        - [CS201]
        - [CS202]
  - name: drops the codes that only one session has
    setup: |
      CREATE TABLE morning (code TEXT);
      CREATE TABLE evening (code TEXT);
      INSERT INTO morning VALUES ('PH110'), ('PH110');
      INSERT INTO evening VALUES ('CH120');
    expected:
      columns: [code]
      rows: []
  - name: works when the evening session has more copies
    setup: |
      CREATE TABLE morning (code TEXT);
      CREATE TABLE evening (code TEXT);
      INSERT INTO morning VALUES ('EC210'), ('EC210'), ('ME105');
      INSERT INTO evening VALUES ('EC210'), ('EC210'), ('EC210'), ('EC210'), ('ME105'), ('ME105');
    expected:
      columns: [code]
      rows:
        - [EC210]
        - [EC210]
        - [ME105]
A hint

Number the copies of each code in each table with row_number() OVER (PARTITION BY code). Copy number k of a code exists in a table exactly when the table holds at least k copies, so the pairs (code, copy number) that both tables have are the copies an INTERSECT ALL keeps. Plain INTERSECT can compare those pairs.

The sample tests run on this device, in your browser (sql.js): nothing is sent to mysmartcopilot.com. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 Put these clauses of a SELECT in the logical order in which SQL evaluates them.

    Give each item its position, from 1 (first).

    Show the answer to question 1

    Answer:

    1. FROM
    2. WHERE
    3. GROUP BY
    4. HAVING
    5. SELECT
    6. DISTINCT
    7. ORDER BY
    8. LIMIT

    FROM builds the input rows, WHERE filters them, GROUP BY forms groups and computes aggregates, HAVING filters the groups, SELECT computes the output columns, DISTINCT removes duplicate rows, ORDER BY sorts and LIMIT keeps a slice of the sorted rows. Set operators such as UNION would sit between DISTINCT and ORDER BY.

  2. Question 2 of 6 A table of 30 students and a table of 200 enrolments are listed in FROM, separated by a comma. How many rows does the FROM step produce, before WHERE runs?

    Type a number.

    Show the answer to question 2

    Answer: 6000 rows

    A comma in FROM is a cross join: every student is paired with every enrolment, so the product has 30 × 200 = 6,000 rows. The WHERE condition that matches roll numbers then keeps only the meaningful pairs.

  3. Question 3 of 6 Ishaan and Zoya are both in CSE; Ishaan takes three CS courses and Zoya two. What does SELECT s.dept FROM students AS s, enrolments AS e WHERE s.roll = e.roll AND e.code LIKE 'CS%' return for these two students?

    Choose one answer.

    Show the answer to question 3

    Answer: CSE five times

    SELECT is a projection on a bag: it keeps one output row per row that passed WHERE, and five (student, course) pairs passed. Only SELECT DISTINCT would reduce them to a single CSE.

  4. Question 4 of 6 A row appears 4 times in the first result and 6 times in the second. How many times does it appear in first INTERSECT ALL second?

    Type a number.

    Show the answer to question 4

    Answer: 4 times

    INTERSECT ALL keeps min(m, n) copies of each row: min(4, 6) = 4. UNION ALL would keep 10, plain INTERSECT 1, and EXCEPT ALL max(4 − 6, 0) = 0.

  5. Question 5 of 6 A row appears 5 times in the first result and twice in the second. How many times does it appear in first EXCEPT ALL second?

    Type a number.

    Show the answer to question 5

    Answer: 3 times

    EXCEPT ALL removes one copy from the first result for each copy in the second: max(5 − 2, 0) = 3. Plain EXCEPT would return the row not at all, because the second result contains it.

  6. Question 6 of 6 In PostgreSQL, how is SELECT … FROM a UNION SELECT … FROM b INTERSECT SELECT … FROM c grouped?

    Choose one answer.

    Show the answer to question 6

    Answer: a UNION (b INTERSECT c)

    PostgreSQL gives INTERSECT a higher precedence than UNION and EXCEPT, as MySQL 8.4 does. SQLite groups every compound operator from left to right and would read the same text as (a UNION b) INTERSECT c, which can return different rows without any error.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.