Riz
Riz

Reputation: 10246

SELECT that returns list of values not occurring in any row

Query:

select id from users where id in (1,2,3,4,5)

If the users table contains ids 1, 2, 3, this would return 1, 2, and 3. I want a query that would return 4 and 5. In other words, I don't want the query to return any rows that exist in the table, I want to give it a list of numbers and get the values from that list that don't appear in the table.

(updated to clarify question following several inapplicable answers)

Upvotes: 43

Views: 44116

Answers (6)

Sergey
Sergey

Reputation: 31

select missing.id
  from
(select ELT(@indx, 1,2,3,4,5) as id, @indx:=@indx+1
  from (select @indx:=1) init,
    users
  where ELT(@indx, 1,2,3,4,5) is not null
) missing
  left join users u using(id)
where u.id is null;

What you have in here is:

  • ELT together with the variable @indx allows you to 'transpose' the list into a column.
  • (select @indx:=1) is needed to initialize indx to 1
  • and users table in the inner select is needed so that MySQL has something to iterate on (so the size of your list cannot exceed the number of the rows in users table, if that's the case that you can use any other table that is big enough instead of inner users, again, table itself does not matter it's just to have something to iterate on, so only its size that matters).
  • ELT(@indx, 1,2,3,4,5) is not null condition in the nested select is to stop iteration once you are at the index exceeding your list size.

The rest is simple - left join and check for null.

Upvotes: 3

BugFinder
BugFinder

Reputation: 17858

Given the numbers are a fixed list. Quickest way I can think of is have a test table, populated with those numbers and do

untested select statement - but you will follow the princpal.

select test.number 
from test 
left join 
    users 
on 
    test.number = users.id 
where test.number <> users.id

Then you'll get back all the numbers that dont have a matching user.id and so can fill in the holes..

Upvotes: 16

LanderTaker
LanderTaker

Reputation: 55

Sorry, I cannot add comments, @Austin,

You have my +1.

Anyway, not sure if this works on mysql, but change all that atomic selects concatenated unions for a value set, so you have something like that:

SELECT id FROM (
    VALUES (1), (2), (3), (4), (5)
) AS list(id)
LEFT JOIN users USING (id)
WHERE users.id IS NULL

Upvotes: -2

Austin
Austin

Reputation: 3328

If you don't want to (explicitly) use temporary tables, this will work:

SELECT id FROM (
  (SELECT 1 AS id) UNION ALL
  (SELECT 2 AS id) UNION ALL
  (SELECT 3 AS id) UNION ALL
  (SELECT 4 AS id) UNION ALL
  (SELECT 5 AS id)
) AS list
LEFT JOIN users USING (id)
WHERE users.id IS NULL

However, it is quite ugly, quite long, and I am dubious about how it would perform if the list of IDs is long.

Upvotes: 22

Guto Araujo
Guto Araujo

Reputation: 3854

Had the same need and built on the answer by BugFinder using a temporary table in session. This way it will automatically be destroyed after I'm done with the query, so I don't have to deal with house cleaning as I will run this type of query often.

Create the temporary table:

CREATE TEMPORARY TABLE tmp_table (id INT UNSIGNED);

Populate tmp_table with the values you will check:

INSERT INTO tmp_table (id) values (1),(2),(3),(4),(5);

With the table created and populated, run the query as with any regular table:

SELECT tmp_table.id
  FROM tmp_table
  LEFT JOIN users u
  ON tmp_table.id = u.id
  WHERE u.id IS NULL;

This info on MySQL Temporary Tables was also useful

Upvotes: 17

DaveyBoy
DaveyBoy

Reputation: 2924

A different option is to use another table containing all possible ids and then do a select from there:

mysql> describe ids;
+-------+-----------------+------+-----+---------+-------+
| Field | Type            | Null | Key | Default | Extra |
+-------+-----------------+------+-----+---------+-------+
| id    | int(5) unsigned | NO   |     | 0       |       |
+-------+-----------------+------+-----+---------+-------+
1 row in set (0.05 sec)

mysql> select * from ids;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

mysql> select * from users;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)


mysql> select id from ids where id not in (select id from users);
+----+
| id |
+----+
|  4 |
|  5 |
+----+
2 rows in set (0.04 sec)

Added side effect - allows you to expand the result list by inserting into the ids table

Upvotes: 3

Related Questions