Как мне найти «пробел» в работе счетчика с SQL? - PullRequest
92 голосов
/ 21 августа 2009

Я бы хотел найти первый «пробел» в столбце счетчика в таблице SQL. Например, если есть значения 1,2,4 и 5, я бы хотел узнать 3.

Конечно, я могу привести значения в порядок и просмотреть их вручную, но я хотел бы знать, есть ли способ сделать это в SQL.

Кроме того, это должен быть вполне стандартный SQL, работающий с разными СУБД.

Ответы [ 18 ]

158 голосов
/ 21 августа 2009

В MySQL и PostgreSQL:

SELECT  id + 1
FROM    mytable mo
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    mytable mi 
        WHERE   mi.id = mo.id + 1
        )
ORDER BY
        id
LIMIT 1

В SQL Server:

SELECT  TOP 1
        id + 1
FROM    mytable mo
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    mytable mi 
        WHERE   mi.id = mo.id + 1
        )
ORDER BY
        id

In Oracle:

SELECT  *
FROM    (
        SELECT  id + 1 AS gap
        FROM    mytable mo
        WHERE   NOT EXISTS
                (
                SELECT  NULL
                FROM    mytable mi 
                WHERE   mi.id = mo.id + 1
                )
        ORDER BY
                id
        )
WHERE   rownum = 1

ANSI (работает везде, наименее эффективно):

SELECT  MIN(id) + 1
FROM    mytable mo
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    mytable mi 
        WHERE   mi.id = mo.id + 1
        )

Системы, поддерживающие функции раздвижных окон:

SELECT  -- TOP 1
        -- Uncomment above for SQL Server 2012+
        previd
FROM    (
        SELECT  id,
                LAG(id) OVER (ORDER BY id) previd
        FROM    mytable
        ) q
WHERE   previd <> id - 1
ORDER BY
        id
-- LIMIT 1
-- Uncomment above for PostgreSQL
12 голосов
/ 12 сентября 2012

Все ваши ответы работают нормально, если у вас первое значение id = 1, иначе этот пробел не будет обнаружен. Например, если значения идентификатора таблицы равны 3,4,5, ваши запросы будут возвращать 6.

Я сделал что-то подобное

SELECT MIN(ID+1) FROM (
    SELECT 0 AS ID UNION ALL 
    SELECT  
        MIN(ID + 1)
    FROM    
        TableX) AS T1
WHERE
    ID+1 NOT IN (SELECT ID FROM TableX) 
8 голосов
/ 21 августа 2009

Первое, что пришло мне в голову. Не уверен, стоит ли вообще идти по этому пути, но это должно сработать. Предположим, что таблица t, а столбец c:

SELECT t1.c+1 AS gap FROM t as t1 LEFT OUTER JOIN t as t2 ON (t1.c+1=t2.c) WHERE t2.c IS NULL ORDER BY gap ASC LIMIT 1

Редактировать: Этот может быть тик быстрее (и короче!):

SELECT min(t1.c)+1 AS gap FROM t as t1 LEFT OUTER JOIN t as t2 ON (t1.c+1=t2.c) WHERE t2.c IS NULL

6 голосов
/ 21 августа 2009

Это работает в SQL Server - не может проверить его в других системах, но кажется стандартным ...

SELECT MIN(t1.ID)+1 FROM mytable t1 WHERE NOT EXISTS (SELECT ID FROM mytable WHERE ID = (t1.ID + 1))

Вы также можете добавить отправную точку к предложению where ...

SELECT MIN(t1.ID)+1 FROM mytable t1 WHERE NOT EXISTS (SELECT ID FROM mytable WHERE ID = (t1.ID + 1)) AND ID > 2000

Итак, если бы у вас были 2000, 2001, 2002 и 2005 гг., Где 2003 и 2004 гг. Не существовало, он вернул бы 2003.

5 голосов
/ 21 августа 2009

На самом деле не существует чрезвычайно стандартного способа SQL для этого, но с какой-то формой ограничительного предложения вы можете сделать

SELECT `table`.`num` + 1
FROM `table`
LEFT JOIN `table` AS `alt`
ON `alt`.`num` = `table`.`num` + 1
WHERE `alt`.`num` IS NULL
LIMIT 1

(MySQL, PostgreSQL)

или

SELECT TOP 1 `num` + 1
FROM `table`
LEFT JOIN `table` AS `alt`
ON `alt`.`num` = `table`.`num` + 1
WHERE `alt`.`num` IS NULL

(SQL Server)

или

SELECT `num` + 1
FROM `table`
LEFT JOIN `table` AS `alt`
ON `alt`.`num` = `table`.`num` + 1
WHERE `alt`.`num` IS NULL
AND ROWNUM = 1

(Oracle)

2 голосов
/ 21 августа 2009

Внутреннее присоединение к представлению или последовательности, которая имеет все возможные значения.

Нет стола? Сделать стол. Я всегда держу фиктивный стол только для этого.

create table artificial_range( 
  id int not null primary key auto_increment, 
  name varchar( 20 ) null ) ;

-- or whatever your database requires for an auto increment column

insert into artificial_range( name ) values ( null )
-- create one row.

insert into artificial_range( name ) select name from artificial_range;
-- you now have two rows

insert into artificial_range( name ) select name from artificial_range;
-- you now have four rows

insert into artificial_range( name ) select name from artificial_range;
-- you now have eight rows

--etc.

insert into artificial_range( name ) select name from artificial_range;
-- you now have 1024 rows, with ids 1-1024

Тогда

 select a.id from artificial_range a
 where not exists ( select * from your_table b
 where b.counter = a.id) ;
2 голосов
/ 10 августа 2016

Для PostgreSQL

Пример использования рекурсивного запроса.

Это может быть полезно, если вы хотите найти пробел в определенном диапазоне (это будет работать, даже если таблица пуста, тогда как другие примеры не будут)

WITH    
    RECURSIVE a(id) AS (VALUES (1) UNION ALL SELECT id + 1 FROM a WHERE id < 100), -- range 1..100  
    b AS (SELECT id FROM my_table) -- your table ID list    
SELECT a.id -- find numbers from the range that do not exist in main table
FROM a
LEFT JOIN b ON b.id = a.id
WHERE b.id IS NULL
-- LIMIT 1 -- uncomment if only the first value is needed
1 голос
/ 21 августа 2009

Мое предположение:

SELECT MIN(p1.field) + 1 as gap
FROM table1 AS p1  
INNER JOIN table1 as p3 ON (p1.field = p3.field + 2)
LEFT OUTER JOIN table1 AS p2 ON (p1.field = p2.field + 1)
WHERE p2.field is null;
1 голос
/ 26 марта 2019

Я написал быстрый способ сделать это. Не уверен, что это наиболее эффективно, но выполняет свою работу. Обратите внимание, что это не говорит вам разрыв, но говорит вам идентификатор до и после разрыва (имейте в виду, что разрыв может быть несколько значений, например 1,2,4,7,11 и т. Д.)

Я использую sqlite в качестве примера

Если это ваша структура таблицы

create table sequential(id int not null, name varchar(10) null);

а это твои ряды

id|name
1|one
2|two
4|four
5|five
9|nine

Запрос

select a.* from sequential a left join sequential b on a.id = b.id + 1 where b.id is null and a.id <> (select min(id) from sequential)
union
select a.* from sequential a left join sequential b on a.id = b.id - 1 where b.id is null and a.id <> (select max(id) from sequential);

https://gist.github.com/wkimeria/7787ffe84d1c54216f1b320996b17b7e

1 голос
/ 02 июля 2013

На этот счет приходится все упомянутое до сих пор. Он включает 0 в качестве начальной точки, к которой он будет применяться по умолчанию, если также не существует значений. Я также добавил соответствующие местоположения для других частей многозначного ключа. Это было проверено только на SQL Server.

select
    MIN(ID)
from (
    select
        0 ID
    union all
    select
        [YourIdColumn]+1
    from
        [YourTable]
    where
        --Filter the rest of your key--
    ) foo
left join
    [YourTable]
    on [YourIdColumn]=ID
    and --Filter the rest of your key--
where
    [YourIdColumn] is null
...