Как найти "пробел" в работе счетчика с SQL?
Я хотел бы найти первый "пробел" в столбце счетчика в таблице SQL. Например, если есть значения 1,2,4 и 5, я хотел бы узнать 3.
Я могу, конечно, получить значения в порядке и пройти через него вручную, но я хотел бы знать, если бы был способ сделать это в SQL.
кроме того, это должен быть вполне стандартный SQL, работающий с разными СУБД.
14 ответов:
In
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 1In
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 idIn
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
все ваши ответы работают нормально, если у вас есть первое значение 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)
первое, что пришло в голову. Не уверен, что это хорошая идея, чтобы пойти этим путем вообще, но должно работать. Предположим, что таблица
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 1Edit: это может быть ТИК быстрее (и короче!):
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
это работает в 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
на самом деле нет очень стандартный способ 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)
внутреннее соединение с представлением или последовательностью, которая имеет все возможные значения.
нет стола? Сделайте стол. Я всегда держу манекен вокруг стола только для этого.
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) ;
мои предположения:
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;
на
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
Это объясняет все, что упоминалось до сих пор. Он включает в себя 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
select min([ColumnName]) from [TableName] where [ColumnName]-1 not in (select [ColumnName] from [TableName]) and [ColumnName] <> (select min([ColumnName]) from [TableName])
вот стандартное решение SQL, которое работает на всех серверах баз данных без изменений:
select min(counter + 1) FIRST_GAP from my_table a where not exists (select 'x' from my_table b where b.counter = a.counter + 1) and a.counter <> (select max(c.counter) from my_table c);посмотреть в действии;
- PL / SQL через Oracle livesql,
- MySQL через sqlfiddle,
- PostgreSQL через sqlfiddle
- MS Sql через sqlfiddle
Он работает для пустых таблиц или с отрицательными значениями, а также. Только что протестирован в SQL Server 2012
select min(n) from ( select case when lead(i,1,0) over(order by i)>i+1 then i+1 else null end n from MyTable) w
Если вы используете Firebird 3 это самый элегантный и простой:
select RowID from ( select `ID_Column`, Row_Number() over(order by `ID_Column`) as RowID from `Your_Table` order by `ID_Column`) where `ID_Column` <> RowID rows 1
-- PUT THE TABLE NAME AND COLUMN NAME BELOW -- IN MY EXAMPLE, THE TABLE NAME IS = SHOW_GAPS AND COLUMN NAME IS = ID -- PUT THESE TWO VALUES AND EXECUTE THE QUERY DECLARE @TABLE_NAME VARCHAR(100) = 'SHOW_GAPS' DECLARE @COLUMN_NAME VARCHAR(100) = 'ID' DECLARE @SQL VARCHAR(MAX) SET @SQL = 'SELECT TOP 1 '+@COLUMN_NAME+' + 1 FROM '+@TABLE_NAME+' mo WHERE NOT EXISTS ( SELECT NULL FROM '+@TABLE_NAME+' mi WHERE mi.'+@COLUMN_NAME+' = mo.'+@COLUMN_NAME+' + 1 ) ORDER BY '+@COLUMN_NAME -- SELECT @SQL DECLARE @MISSING_ID TABLE (ID INT) INSERT INTO @MISSING_ID EXEC (@SQL) --select * from @MISSING_ID declare @var_for_cursor int DECLARE @LOW INT DECLARE @HIGH INT DECLARE @FINAL_RANGE TABLE (LOWER_MISSING_RANGE INT, HIGHER_MISSING_RANGE INT) DECLARE IdentityGapCursor CURSOR FOR select * from @MISSING_ID ORDER BY 1; open IdentityGapCursor fetch next from IdentityGapCursor into @var_for_cursor WHILE @@FETCH_STATUS = 0 BEGIN SET @SQL = ' DECLARE @LOW INT SELECT @LOW = MAX('+@COLUMN_NAME+') + 1 FROM '+@TABLE_NAME +' WHERE '+@COLUMN_NAME+' < ' + cast( @var_for_cursor as VARCHAR(MAX)) SET @SQL = @sql + ' DECLARE @HIGH INT SELECT @HIGH = MIN('+@COLUMN_NAME+') - 1 FROM '+@TABLE_NAME +' WHERE '+@COLUMN_NAME+' > ' + cast( @var_for_cursor as VARCHAR(MAX)) SET @SQL = @sql + 'SELECT @LOW,@HIGH' INSERT INTO @FINAL_RANGE EXEC( @SQL) fetch next from IdentityGapCursor into @var_for_cursor END CLOSE IdentityGapCursor; DEALLOCATE IdentityGapCursor; SELECT ROW_NUMBER() OVER(ORDER BY LOWER_MISSING_RANGE) AS 'Gap Number',* FROM @FINAL_RANGE
Comments