A SQL guide for finding all cinema seats that are free and adjacent to at least one other free seat.
Problem Restatement
We are given a table called Cinema.
Each row tells us whether a seat is free or occupied.
| Column | Type | Meaning |
|---|---|---|
seat_id | int | Seat id |
free | bool | 1 means free, 0 means occupied |
We need to return all free seats that are part of at least one consecutive pair of free seats.
The result should be ordered by seat_id.
The problem note says seat_id is auto-incremented, free is a boolean, and consecutive available seats means at least two seats are consecutively available.
Input and Output
Input table:
CinemaOutput column:
seat_idA seat should be returned if:
- It is free.
- The previous seat or next seat is also free.
Example
Input:
| seat_id | free |
|---|---|
| 1 | 1 |
| 2 | 0 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
The free seats are:
| seat_id | free |
|---|---|
| 1 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
Seat 1 is free, but seat 2 is occupied, so seat 1 is not part of a consecutive free pair.
Seats 3, 4, and 5 are consecutive and free.
Output:
| seat_id |
|---|
| 3 |
| 4 |
| 5 |
First Thought: Compare Neighboring Rows
A seat belongs to the answer when it has at least one free neighbor.
For a seat a, a neighboring seat b satisfies:
ABS(a.seat_id - b.seat_id) = 1So we can join the table with itself and compare each seat with its adjacent seats.
If both seats are free, then a.seat_id should be returned.
Key Insight
We do not need to find the whole block explicitly.
A seat is part of a consecutive available block of length at least 2 exactly when:
current seat is free
and
(previous seat is free or next seat is free)For example, in the block:
3, 4, 5Seat 3 has free next seat 4.
Seat 4 has free previous seat 3 and free next seat 5.
Seat 5 has free previous seat 4.
So all three seats are returned.
Algorithm
Use a self join.
Join Cinema as a with Cinema as b.
Keep only pairs where the seats are adjacent:
ABS(a.seat_id - b.seat_id) = 1Then require both seats to be free:
a.free = 1 AND b.free = 1Return a.seat_id.
Use DISTINCT because a middle seat may have two free neighbors and could appear twice.
Finally, order by seat_id.
SQL Solution
SELECT DISTINCT
a.seat_id
FROM Cinema AS a
JOIN Cinema AS b
ON ABS(a.seat_id - b.seat_id) = 1
WHERE a.free = 1
AND b.free = 1
ORDER BY a.seat_id;Code Explanation
The self join creates pairs of seats from the same table:
FROM Cinema AS a
JOIN Cinema AS bThe join condition keeps only adjacent seats:
ON ABS(a.seat_id - b.seat_id) = 1This matches pairs such as:
a.seat_id | b.seat_id |
|---|---|
| 3 | 4 |
| 4 | 3 |
| 4 | 5 |
| 5 | 4 |
Then the WHERE clause keeps only pairs where both seats are free:
WHERE a.free = 1
AND b.free = 1The query returns a.seat_id because we want every seat that participates in at least one valid adjacent pair.
We use:
SELECT DISTINCTbecause a seat in the middle of a larger free block can match both the previous and next seats.
For example, seat 4 in the block 3, 4, 5 matches both 3 and 5.
Correctness
A seat returned by the query must satisfy two conditions.
First, a.free = 1, so the returned seat is free.
Second, the join found another row b such that ABS(a.seat_id - b.seat_id) = 1 and b.free = 1. Therefore, the returned seat has a free adjacent seat. So it belongs to a consecutive available pair.
Conversely, suppose a seat belongs to a consecutive available pair. Then it is free, and either the previous seat or the next seat is also free. The self join will match the current seat with that adjacent free seat. Therefore, the query will return it.
So the query returns exactly all consecutive available seats.
Complexity
Let n be the number of rows in Cinema.
| Metric | Value | Why |
|---|---|---|
| Time | O(n^2) worst case | A self join can compare many row pairs |
| Space | O(n) | The result and join processing may store matching rows |
With an index on seat_id, a database can optimize the adjacency lookup more efficiently.
Alternative: Window Function
We can also solve this by looking at the previous and next row.
WITH seats AS (
SELECT
seat_id,
free,
LAG(free) OVER (ORDER BY seat_id) AS prev_free,
LEAD(free) OVER (ORDER BY seat_id) AS next_free
FROM Cinema
)
SELECT
seat_id
FROM seats
WHERE free = 1
AND (
prev_free = 1
OR next_free = 1
)
ORDER BY seat_id;This version is often easier to read because it directly says:
“Return a free seat if the previous or next seat is also free.”
Testing
Sample data:
CREATE TABLE Cinema (
seat_id INT,
free BOOLEAN
);
INSERT INTO Cinema (seat_id, free) VALUES
(1, 1),
(2, 0),
(3, 1),
(4, 1),
(5, 1);Expected output:
| seat_id |
|---|
| 3 |
| 4 |
| 5 |
Additional case:
TRUNCATE TABLE Cinema;
INSERT INTO Cinema (seat_id, free) VALUES
(1, 1),
(2, 1),
(3, 0),
(4, 1),
(5, 0),
(6, 1),
(7, 1);Expected output:
| seat_id |
|---|
| 1 |
| 2 |
| 6 |
| 7 |