我想在SQL表的计数器列中找到第一个“缺口”。例如,如果有值1,2,4和5,我想找出3。
我当然可以按顺序获取值并手动完成它,但我想知道是否有办法在SQL中执行此操作。
此外,它应该是非常标准的SQL,使用不同的DBMS。
答案 0 :(得分:158)
在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
在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
答案 1 :(得分:12)
如果你的第一个值为id = 1,你的答案一切正常,否则将无法检测到这个差距。例如,如果您的表ID值为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)
答案 2 :(得分:8)
首先出现在我脑海中。不确定这样做是否一个好主意,但应该有效。假设该表为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
答案 3 :(得分:6)
这适用于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年。
答案 4 :(得分:5)
实际上没有极标准的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)的
答案 5 :(得分:2)
内部联接到具有所有可能值的视图或序列。
没有桌子?做一张桌子。我总是为此保留一张假桌。
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) ;
答案 6 :(得分:2)
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
答案 7 :(得分:1)
我的猜测:
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;
答案 8 :(得分:1)
这个记录到目前为止所提到的一切。它包含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
答案 9 :(得分:1)
我写了一个快速的方法。不确定这是最有效的,但是可以完成工作。请注意,它不会告诉您间隙,而是告诉您间隙之前和之后的ID(请注意,间隙可以是多个值,例如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
答案 10 :(得分:0)
这是一种以可移植和更紧凑的方式显示所有可能间隙值范围的替代方法:
假设您的表架构如下所示:
> SELECT id FROM your_table;
+-----+
| id |
+-----+
| 90 |
| 103 |
| 104 |
| 118 |
| 119 |
| 120 |
| 121 |
| 161 |
| 162 |
| 163 |
| 185 |
+-----+
要获取所有可能的间隙值的范围,您有以下查询:
lowerbound
列都小于upperbound
列,然后使用GROUP BY
和MIN(m2.id)
来减少无用记录的数量.lowerbound
正好是 upperbound - 1
的记录(YOUR_MIN_ID_VALUE, 89)
和 (186, YOUR_MAX_ID_VALUE)
,这隐式意味着 {{1} 中没有使用这两个范围中的任何数字} 到目前为止。your_table
答案 11 :(得分:0)
如果列中的数字是正整数(从1开始),那么这是如何轻松求解。 (假设ID是您的列名)
SELECT TEMP.ID
FROM (SELECT ROW_NUMBER() OVER () AS NUM FROM 'TABLE-NAME') AS TEMP
WHERE ID NOT IN (SELECT ID FROM 'TABLE-NAME')
ORDER BY 1 ASC LIMIT 1
答案 12 :(得分:0)
DECLARE @Table AS TABLE(
[Value] int
)
INSERT INTO @Table ([Value])
VALUES
(1),(2),(4),(5),(6),(10),(20),(21),(22),(50),(51),(52),(53),(54),(55)
--Gaps
--Start End Size
--3 3 1
--7 9 3
--11 19 9
--23 49 27
SELECT [startTable].[Value]+1 [Start]
,[EndTable].[Value]-1 [End]
,([EndTable].[Value]-1) - ([startTable].[Value]) Size
FROM
(
SELECT [Value]
,ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY [Value]) Record
FROM @Table
)AS startTable
JOIN
(
SELECT [Value]
,ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY [Value]) Record
FROM @Table
)AS EndTable
ON [EndTable].Record = [startTable].Record+1
WHERE [startTable].[Value]+1 <>[EndTable].[Value]
答案 13 :(得分:0)
如果您的计数器从1开始,并且您想在为空时生成序列的第一个数字(1),那么以下是对Oracle有效的第一个答案中的经过纠正的代码段:
SELECT
NVL(MIN(id + 1),1) AS gap
FROM
mytable mo
WHERE 1=1
AND NOT EXISTS
(
SELECT NULL
FROM mytable mi
WHERE mi.id = mo.id + 1
)
AND EXISTS
(
SELECT NULL
FROM mytable mi
WHERE mi.id = 1
)
答案 14 :(得分:0)
以下解决方案:
在“ with ”子句中对有序行进行顺序编号,然后将结果重复使用两次,并在行号上进行内部联接,但偏移量为1,以便将之前的行与该行进行比较之后,寻找间隔大于1的ID。要求更多,但适用范围更广。
create table #ID ( id integer );
insert into #ID values (1),(2), (4),(5),(6),(7),(8), (12),(13),(14),(15);
with Source as (
select
row_number()over ( order by A.id ) as seq
,A.id as id
from #ID as A WITH(NOLOCK)
)
Select top 1 gap_start from (
Select
(J.id+1) as gap_start
,(K.id-1) as gap_end
from Source as J
inner join Source as K
on (J.seq+1) = K.seq
where (J.id - (K.id-1)) <> 0
) as G
内部查询产生:
gap_start gap_end
3 3
9 11
外部查询产生:
gap_start
3
答案 15 :(得分:0)
发现大多数方法在mysql
中运行非常慢,非常慢。这是我对mysql < 8.0
的解决方案。在1M记录上进行了测试,间隔接近尾声〜1秒才能完成。不确定是否适合其他SQL版本。
SELECT cardNumber - 1
FROM
(SELECT @row_number := 0) as t,
(
SELECT (@row_number:=@row_number+1), cardNumber, cardNumber-@row_number AS diff
FROM cards
ORDER BY cardNumber
) as x
WHERE diff >= 1
LIMIT 0,1
我假设序列从“ 1”开始。
答案 16 :(得分:0)
-- 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
答案 17 :(得分:0)
如果你使用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
答案 18 :(得分:0)
它适用于空表或负值。刚刚在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
答案 19 :(得分:0)
这是标准的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);
请参阅以下内容;
答案 20 :(得分:0)
import UIKit
class LoadingScreen: UIViewController {
var time : Float = 0.0
var timer: NSTimer?
@IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do stuff
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
} //close viewDidLoad
func setProgress() {
time += 0.1
progressView.progress = time / 3
if time >= 3 {
timer!.invalidate()
}
}
}