10

Supposed you have a table T(A) with only positive integers allowed, like:

1,1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18

In the above example, the result is 10. We always can use ORDER BY and DISTINCT to sort and remove duplicates. However, to find the lowest integer not in the list, I came up with the following SQL query:

   select list.x + 1
   from (select x from (select distinct a as x from T order by a)) as list, T
   where list.x + 1 not in T limit 1;

My idea is start a counter and 1, check if that counter is in list: if it is, return it, otherwise increment and look again. However, I have to start that counter as 1, and then increment. That query works most of the cases, by there are some corner cases like in 1. How can I accomplish that in SQL or should I go about a completely different direction to solve this problem?

Kermit
  • 33,827
  • 13
  • 85
  • 121
cybertextron
  • 10,547
  • 28
  • 104
  • 208
  • @MarcAudet: The requirement is to return 10, I think. – Jonathan Leffler Jul 05 '14 at 15:20
  • @MarcAudet I would expect the value `2` in the mentioned corner case. For the first example, I expect `10` as Jonathan. – VMai Jul 05 '14 at 15:20
  • I edited the question to show that `10` indeed is the expected result for that example. @JonathanLeffler – cybertextron Jul 05 '14 at 15:22
  • How about you make an SQL with distinct order by and in second column a row number store this result in tem table or table variable and than make a select where first column isn't the row number and take top 1. – Rand Random Jul 05 '14 at 15:23
  • 1
    @Mihai: tags? PostegresSQL, MySQL and SQL Server? – Jonathan Leffler Jul 05 '14 at 15:23
  • @Mihai I'm using `sqlite` for the example, but any valid `SQL` db can be used – cybertextron Jul 05 '14 at 15:23
  • You could combine your distinct list with a number table. See my answer here: http://stackoverflow.com/a/24551910/234415 – Tom Chantler Jul 05 '14 at 15:24
  • What is your PRIMARY KEY? – Strawberry Jul 05 '14 at 15:25
  • @Strawberry: the table has a single column with duplicates; there is no primary key. Pretend the data has no duplicates and the only column is the primary key. The solution is unlikely to be very different (maybe a DISTINCT is needed if there are several entries for 9 in the given example). – Jonathan Leffler Jul 05 '14 at 15:27
  • @philippe No, not any valid SQL can be used. Each platform handles crap like this differently. – Kermit Jul 05 '14 at 15:30
  • 2
    I would generate a table that has ints ranging from (min,max+1), right join on that table and select min(number) where there isn't a match but generating a range of ints is different from db to db – FuzzyTree Jul 05 '14 at 15:30
  • Something like a calendar table would work compare it with what you have SELECT WHERE NOT IN – Mihai Jul 05 '14 at 15:31
  • @philippe Why do you want to get that integer? I don't mind gaps. – VMai Jul 05 '14 at 15:53
  • As a matter of curiosity, what value do you want returned if the data in the table is 2, 3, 5? Candidate answers are 1 and 4 (assuming zero isn't strictly positive), but which would you expect to get? – Jonathan Leffler Jul 05 '14 at 17:24

8 Answers8

10

Because SQL works on sets, the intermediate SELECT DISTINCT a AS x FROM t ORDER BY a is redundant.

The basic technique of looking for a gap in a column of integers is to find where the current entry plus 1 does not exist. This requires a self-join of some sort.

Your query is not far off, but I think it can be simplified to:

SELECT MIN(a) + 1
  FROM t
 WHERE a + 1 NOT IN (SELECT a FROM t)

The NOT IN acts as a sort of self-join. This won't produce anything from an empty table, but should be OK otherwise.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
5

SQL Fiddle

select min(y.a) as a
from
    t x
    right join
    (
        select a + 1 as a from t
        union
        select 1
    ) y on y.a = x.a
where x.a is null

It will work even in an empty table

Clodoaldo Neto
  • 118,695
  • 26
  • 233
  • 260
3
SELECT min(t.a) - 1
FROM   t
LEFT   JOIN t t1 ON t1.a = t.a - 1
WHERE  t1.a IS NULL
AND    t.a > 1; -- exclude 0

This finds the smallest number greater than 1, where the next-smaller number is not in the same table. That missing number is returned.

This works even for a missing 1. There are multiple answers checking in the opposite direction. All of them would fail with a missing 1.

SQL Fiddle.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
2

You can do the following, although you may also want to define a range - in which case you might need a couple of UNIONs

SELECT x.id+1 
  FROM my_table x 
  LEFT 
  JOIN my_table y 
    ON x.id+1 = y.id 
 WHERE y.id IS NULL 
 ORDER 
    BY x.id LIMIT 1;
Strawberry
  • 33,750
  • 13
  • 40
  • 57
2

You can always create a table with all of the numbers from 1 to X and then join that table with the table you are comparing. Then just find the TOP value in your SELECT statement that isn't present in the table you are comparing

SELECT TOP 1 table_with_all_numbers.number, table_with_missing_numbers.number 
FROM table_with_all_numbers
    LEFT JOIN table_with_missing_numbers 
        ON table_with_missing_numbers.number = table_with_all_numbers.number
WHERE table_with_missing_numbers.number IS NULL
ORDER BY table_with_all_numbers.number ASC;
Lloyd Banks
  • 35,740
  • 58
  • 156
  • 248
2

This query ranks (starting from rank 1) each distinct number in ascending order and selects the lowest rank that's less than its number. If no rank is lower than its number (i.e. there are no gaps in the table) the query returns the max number + 1.

select coalesce(min(number),1) from (    
    select min(cnt) number
    from (
        select 
            number, 
            (select count(*) from (select distinct number from numbers) b where b.number <= a.number) as cnt
        from (select distinct number from numbers) a
    ) t1 where number > cnt
    union
    select max(number) + 1 number from numbers
) t1

http://sqlfiddle.com/#!7/720cc/3

FuzzyTree
  • 32,014
  • 3
  • 54
  • 85
2

In SQLite 3.8.3 or later, you can use a recursive common table expression to create a counter. Here, we stop counting when we find a value not in the table:

WITH RECURSIVE counter(c) AS (
  SELECT 1
  UNION ALL
  SELECT c + 1 FROM counter WHERE c IN t)
SELECT max(c) FROM counter;

(This works for an empty table or a missing 1.)

CL.
  • 173,858
  • 17
  • 217
  • 259
2

Just another method, using EXCEPT this time:

SELECT a + 1 AS missing FROM T
EXCEPT
SELECT a FROM T
ORDER BY missing
LIMIT 1;
Andriy M
  • 76,112
  • 17
  • 94
  • 154