SQL & PostgreSQL MCQs multiple-choice questions with answers & explanations
All 21 SQL & PostgreSQL quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.
- 1.mid
What does this query return?
-- customers.id: 1, 2, 3 -- orders.customer_id: 1, 1, NULL SELECT count(*) FROM customers WHERE id NOT IN (SELECT customer_id FROM orders);- A2
- B3
- C0
- DAn error, because the subquery returns NULL
Show answer
Answer: C (0)
id NOT IN (1, 1, NULL)meansid <> 1 AND id <> 1 AND id <> NULL. The last comparison is always unknown, so the condition is never true and no rows qualify.NOT EXISTSwould return 2, because it isn't affected by theNULL. - 2.easy
What does the final
SELECTreturn?CREATE TABLE t (bonus int); INSERT INTO t VALUES (100), (NULL), (200); SELECT count(*), count(bonus), avg(bonus) FROM t;- A3, 3, 100
- B3, 2, 150
- C2, 2, 150
- D3, 2, 100
Show answer
Answer: B (3, 2, 150)
count(*)counts rows (3), whilecount(bonus)counts only non-NULL values (2).avgalso ignoresNULLs, so it is (100 + 200) / 2 = 150, not 300 / 3 = 100. - 3.mid
Salaries are 300, 200, 200 and 100. Ordering by salary descending, what do
RANK()andDENSE_RANK()return for the row with salary 100?- A3 and 3
- B4 and 4
- C3 and 4
- D4 and 3
Show answer
Answer: D (4 and 3)
Both functions give the two 200s rank 2.
RANKthen skips a number, so 100 gets 4;DENSE_RANKleaves no gaps, so 100 gets 3.ROW_NUMBERwould also give 4, but with no ties. - 4.easy
Which condition returns the rows where
emailhas no value (isNULL)?- A
WHERE email IS NULL - B
WHERE email = NULL - C
WHERE email = '' - D
WHERE email IN (NULL)
Show answer
Answer: A (
WHERE email IS NULL)Any comparison with
NULL, includingemail = NULL, evaluates to unknown, andWHEREkeeps only rows where the condition is true, so it matches nothing.IN (NULL)is just= NULLin disguise. An empty string is a real value, notNULL(except in Oracle). - A
- 5.easy
How many rows does this return?
SELECT 1 UNION SELECT 1 UNION ALL SELECT 1;- A1
- B2
- C3
- DAn error: UNION and UNION ALL can't be mixed
Show answer
Answer: B (2)
Set operations of the same precedence are evaluated left to right.
SELECT 1 UNION SELECT 1removes the duplicate, leaving one row, andUNION ALLthen appends another 1 without deduplicating, giving two rows. - 6.easy
Which of these queries fails in PostgreSQL?
- A
SELECT dept_id, count(*) FROM emp GROUP BY dept_id HAVING count(*) > 1 - B
SELECT dept_id AS d, count(*) FROM emp GROUP BY d - C
SELECT dept_id, count(*) FROM emp WHERE count(*) > 1 GROUP BY dept_id - D
SELECT count(*) FROM emp HAVING count(*) > 1
Show answer
Answer: C (
SELECT dept_id, count(*) FROM emp WHERE count(*) > 1 GROUP BY dept_id)WHEREfilters rows before grouping, so aggregates aren't allowed there ("aggregate functions are not allowed in WHERE"); the condition belongs inHAVING. PostgreSQL accepts aSELECTalias inGROUP BY, andHAVINGwithoutGROUP BYtreats the whole table as one group. - A
- 7.mid
Which rows does this query return?
-- departments: (1, 'Eng'), (2, 'HR') HR has no employees -- employees: ('Ada', dept 1, 200), ('Bob', dept 1, 90) SELECT d.name, e.name FROM departments d LEFT JOIN employees e ON e.dept_id = d.id WHERE e.salary > 100;- AOnly Eng / Ada
- BEng / Ada and HR / NULL
- CEng / Ada, Eng / Bob and HR / NULL
- DEng / Ada and Eng / Bob
Show answer
Answer: A (Only Eng / Ada)
The
WHEREruns after the join. HR's NULL-padded row hase.salary = NULL, soNULL > 100is unknown and the row is dropped, just like Bob's. To keep HR, move the salary condition into theONclause. - 8.mid
In PostgreSQL,
ordershas 500 rows. What does the final query return?BEGIN; TRUNCATE orders; ROLLBACK; SELECT count(*) FROM orders;- A0, because TRUNCATE commits implicitly
- BAn error: TRUNCATE is not allowed in a transaction
- C0, because TRUNCATE bypasses the transaction
- D500, because the TRUNCATE is rolled back
Show answer
Answer: D (500, because the TRUNCATE is rolled back)
In PostgreSQL,
TRUNCATEis transactional, so rolling back restores the table. The "implicit commit" behavior is true of MySQL and Oracle, whereTRUNCATEis treated as DDL that commits and can't be rolled back. - 9.easy
What is PostgreSQL's default transaction isolation level?
- ARead Uncommitted
- BRead Committed
- CRepeatable Read
- DSerializable
Show answer
Answer: B (Read Committed)
PostgreSQL defaults to Read Committed: each statement sees a snapshot taken when that statement starts. Repeatable Read is MySQL InnoDB's default, and Serializable is the SQL standard's nominal default, but neither is PostgreSQL's.
- 10.hard
In PostgreSQL, which anomaly is impossible at every isolation level, even when you request Read Uncommitted?
- APhantom read
- BNon-repeatable read
- CDirty read
- DSerialization anomaly
Show answer
Answer: C (Dirty read)
PostgreSQL implements Read Uncommitted as Read Committed, so a transaction never sees another transaction's uncommitted changes. Non-repeatable reads, phantoms and serialization anomalies are all possible at Read Committed.
- 11.mid
What is
runningon the two Jan 2 rows?-- sales(day, amount): (Jan 1, 10), (Jan 2, 20), (Jan 2, 5) SELECT day, amount, sum(amount) OVER (ORDER BY day) AS running FROM sales;- A30 and 35
- B20 and 5
- C35 and 35
- D25 and 25
Show answer
Answer: C (35 and 35)
With
ORDER BYand no explicit frame, the default frame isRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which includes all peers with the sameday. Both Jan 2 rows therefore see 10 + 20 + 5. AROWSframe with a unique order would give 30 and 35. - 12.mid
A B-tree index exists on
orders (customer_id, created_at). Which query can it help the least?- A
WHERE customer_id = 7 - B
WHERE customer_id = 7 AND created_at > '2024-01-01' - C
WHERE customer_id = 7 ORDER BY created_at - D
WHERE created_at > '2024-01-01'
Show answer
Answer: D (
WHERE created_at > '2024-01-01')The index is sorted by
customer_idfirst, so without a condition on the leading column, matchingcreated_atvalues are scattered across the whole index. The other three use the leftmost prefix, and theORDER BYquery can read rows already sorted. - A
- 13.easy
What does this return?
SELECT avg(x) FROM (VALUES (10), (NULL), (20)) AS v(x);- A10
- B15
- CNULL
- DAn error
Show answer
Answer: B (15)
Aggregate functions skip
NULLs, so the average is (10 + 20) / 2 = 15. You would get 10 only ifNULLwere treated as 0, which would requireavg(COALESCE(x, 0)). - 14.mid
In
INSERT ... ON CONFLICT (sku) DO UPDATE SET qty = EXCLUDED.qty, what doesEXCLUDEDrefer to?- AThe row that was proposed for insertion
- BThe existing row that caused the conflict
- CA system table that logs rejected rows
- DRows skipped by an earlier DO NOTHING
Show answer
Answer: A (The row that was proposed for insertion)
EXCLUDEDis the row you tried to insert, which was excluded because of the conflict. The existing row is referenced by the table name or its alias, as inSET qty = inventory.qty + EXCLUDED.qty. - 15.mid
For a
jsonbcolumnpayload, what are the types ofpayload -> 'user'andpayload ->> 'user'?- Ajsonb and jsonb
- Btext and jsonb
- Cjsonb and text
- Djson and text
Show answer
Answer: C (jsonb and text)
->returns the field asjsonb(so you can keep navigating), and->>returns it astext. That's why comparisons against string literals usually use->>, and chains look likepayload -> 'user' ->> 'plan'. - 16.mid
Which PostgreSQL index type can speed up
WHERE payload @> '{"type": "buy"}'on ajsonbcolumn?- AGIN
- BB-tree
- CHash
- DBRIN
Show answer
Answer: A (GIN)
GIN is an inverted index over the keys and values inside each document, and its
jsonboperator classes support containment (@>). B-tree and hash indexes compare whole values, and BRIN only stores block-range summaries. - 17.hard
A multi-terabyte, append-only log table is queried by
created_atranges, and rows arrive in time order. Which index is by far the smallest while still useful?- AHash
- BGIN
- CGiST
- DBRIN
Show answer
Answer: D (BRIN)
BRIN stores only a min/max summary per range of table blocks, so it's tiny, and it works well when values correlate with physical order, as timestamps do in an append-only table. Hash can't do ranges, and GIN and GiST index every row.
- 18.hard
Jobs 1 and 2 are queued. Worker A runs this and keeps its transaction open. What does worker B get when it runs the same statements?
BEGIN; SELECT id FROM jobs WHERE status = 'queued' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;- AJob 1, the same row as A
- BJob 2
- CIt waits until A commits
- DAn error: the row is locked
Show answer
Answer: B (Job 2)
SKIP LOCKEDsilently skips rows locked by other transactions, so B gets the next unlocked job. PlainFOR UPDATEwould make B wait, andFOR UPDATE NOWAITwould raise an error instead. - 19.mid
In PostgreSQL, which ids does the final query return?
CREATE TABLE t (id serial PRIMARY KEY, v text); INSERT INTO t (v) VALUES ('a'); BEGIN; INSERT INTO t (v) VALUES ('b'); ROLLBACK; INSERT INTO t (v) VALUES ('c'); SELECT id FROM t;- A1, 2
- B1, 2, 3
- C1, 3
- D2, 3
Show answer
Answer: C (1, 3)
nextvalis never rolled back, so the aborted insert still consumed 2 and 'c' gets 3. Gaps in sequence-generated ids are normal and shouldn't be relied on either way. - 20.mid
What does
REFRESH MATERIALIZED VIEW CONCURRENTLYrequire in PostgreSQL?- AA unique index on the materialized view
- BA primary key on every base table
- CAutovacuum enabled on the materialized view
- DThe view to be defined WITH CHECK OPTION
Show answer
Answer: A (A unique index on the materialized view)
A concurrent refresh computes the new result and applies only the differences, so readers aren't blocked. To match old and new rows it needs a unique index on the materialized view, using plain columns and no
WHEREclause; without one the command fails. - 21.hard
In PostgreSQL, what happens physically when you
UPDATEa row?- AIt is overwritten in place, and the old value goes to an undo log
- BThe whole page is locked and rewritten in place
- CThe change is queued and applied later by VACUUM
- DA new row version is written, and the old one is expired
Show answer
Answer: D (A new row version is written, and the old one is expired)
PostgreSQL's MVCC writes a new tuple and sets
xmaxon the old one, which stays for snapshots that still need it and later becomes a dead tuple forVACUUMto reclaim. In-place updates with an undo log are how Oracle and MySQL InnoDB do it.