ฉันจะหา“ ช่องว่าง” ในการรันตัวนับด้วย SQL ได้อย่างไร


107

ฉันต้องการหา "ช่องว่าง" แรกในคอลัมน์ตัวนับในตาราง SQL ตัวอย่างเช่นหากมีค่า 1,2,4 และ 5 ฉันต้องการค้นหา 3

แน่นอนฉันสามารถรับค่าตามลำดับและดำเนินการด้วยตนเองได้ แต่ฉันต้องการทราบว่าจะมีวิธีดำเนินการใน SQL หรือไม่

นอกจากนี้ควรเป็น SQL มาตรฐานที่ทำงานร่วมกับ DBMS ที่แตกต่างกัน


ใน Sql server 2008 ขึ้นไปคุณสามารถใช้LAG(id, 1, null)ฟังก์ชันที่มีOVER (ORDER BY id)อนุประโยคได้
ajeh

คำตอบ:


187

ใน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

40
@vulkanino: โปรดขอให้พวกเขารักษาการเยื้อง โปรดทราบว่าใบอนุญาตครีเอทีฟคอมมอนส์กำหนดให้คุณต้องสักชื่อเล่นของฉันและคำถามURLด้วยเช่นกันแม้ว่าฉันคิดว่าอาจเป็นรหัส QR
Quassnoi

4
นี้เป็นที่ดี แต่ถ้าผมมีแล้วนี้จะพบเพียง[1, 2, 11, 12] 3สิ่งที่ฉันชอบที่จะพบคือ 3-10 แทน - โดยพื้นฐานแล้วจุดเริ่มต้นและจุดสิ้นสุดของทุกช่องว่าง ฉันเข้าใจว่าฉันอาจต้องเขียนสคริปต์ python ของตัวเองที่ใช้ประโยชน์จาก SQL (ในกรณีของฉัน MySql) แต่คงจะดีถ้า SQL ทำให้ฉันเข้าใกล้สิ่งที่ฉันต้องการมากขึ้น (ฉันมีตารางที่มี 2 ล้านแถวที่มีช่องว่าง ดังนั้นฉันจะต้องแบ่งมันเป็นชิ้นเล็ก ๆ และเรียกใช้ SQL บางส่วน) ฉันคิดว่าฉันสามารถเรียกใช้แบบสอบถามหนึ่งเพื่อค้นหาจุดเริ่มต้นของช่องว่างจากนั้นอีกอันเพื่อหาจุดสิ้นสุดของช่องว่างและพวกเขา "รวมการเรียงลำดับ" ทั้งสองลำดับ
Hamish Grubijan

1
@ HamishGrubijan: โปรดโพสต์เป็นคำถามอื่น
Quassnoi

2
@ มัลโคโคกลู: คุณจะได้รับNULLไม่ใช่0ถ้าโต๊ะว่าง ซึ่งเป็นจริงสำหรับฐานข้อมูลทั้งหมด
Quassnoi

5
สิ่งนี้จะไม่พบช่องว่างเริ่มต้นอย่างถูกต้อง ถ้าคุณมี 3,4,5,6,8 รหัสนี้จะรายงาน 7 เนื่องจากไม่มี 1 ให้ตรวจสอบด้วย ดังนั้นหากคุณไม่มีตัวเลขเริ่มต้นคุณจะต้องตรวจสอบว่า
ttomsen

13

คำตอบของคุณทำงานได้ดีหากคุณมีค่าแรก 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) 

นี่จะพบช่องว่างแรก หากคุณมี id 0, 2,3,4 คำตอบคือ 1. ฉันกำลังมองหาคำตอบเพื่อหาช่องว่างที่ใหญ่ที่สุด พูดว่าลำดับคือ 0,2,3,4, 100,101,102 ฉันต้องการหาช่องว่าง 4-99
Kemin Zhou

8

ไม่มีวิธี 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)

หรือ

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

(ออราเคิล)


หากมีช่วงช่องว่างระบบจะส่งคืนเฉพาะแถวแรกในช่วงสำหรับแบบสอบถาม postgres ของคุณ
John Haugeland

สิ่งนี้เหมาะสมกับฉันมากที่สุดการใช้การเข้าร่วมจะช่วยให้คุณเปลี่ยนค่า TOP ของคุณเพื่อแสดงผลลัพธ์ช่องว่างเพิ่มเติม
AJ_

1
ขอบคุณมันทำงานได้ดีมากและหากคุณต้องการดูทุกจุดที่มีช่องว่างคุณสามารถลบขีด จำกัด ได้
mekbib.awoke

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


LEFT OUTER JOIN t ==> LEFT OUTER JOIN t2
Eamon Nerbonne

1
ไม่ไม่เอมอนLEFT OUTER JOING t2ต้องการให้คุณมีt2โต๊ะซึ่งเป็นเพียงนามแฝง
Michael Krelin - แฮ็กเกอร์

6

ใช้งานได้ใน SQL Server - ไม่สามารถทดสอบในระบบอื่นได้ แต่ดูเหมือนว่าเป็นมาตรฐาน ...

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

นอกจากนี้คุณยังสามารถเพิ่มจุดเริ่มต้นไปยัง where clause ...

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

แนวทางแก้ไขต่อไปนี้:

  • ให้ข้อมูลการทดสอบ
  • แบบสอบถามภายในที่สร้างช่องว่างอื่น ๆ และ
  • ใช้งานได้ใน SQL Server 2012

ตัวเลขแถวที่เรียงลำดับตามลำดับในอนุประโยค " with " จากนั้นนำผลลัพธ์กลับมาใช้ซ้ำสองครั้งโดยมีการรวมภายในกับหมายเลขแถว แต่หักล้างด้วย 1 เพื่อเปรียบเทียบแถวก่อนหน้ากับแถวหลังโดยมองหา ID ที่มีช่องว่างมากกว่า 1. มากกว่าที่ขอ แต่ใช้ได้กว้างขวางกว่า

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

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) ;

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


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

1

ฉันเขียนวิธีทำอย่างรวดเร็ว ไม่แน่ใจว่านี่จะมีประสิทธิภาพมากที่สุด แต่ทำงานให้ลุล่วง โปรดทราบว่ามันไม่ได้บอกคุณถึงช่องว่าง แต่จะบอกรหัสก่อนและหลังช่องว่าง (โปรดทราบว่าช่องว่างอาจเป็นค่าหลายค่าได้เช่น 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



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);

ดูในการดำเนินการสำหรับ;


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

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

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

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"

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
     )  

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]

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

จะพบช่องว่างเท่านั้นจนกระทั่งจำนวนแถวใน "TABLE-NAME" เป็น "SELECT ROW_NUMBER () OVER () AS NUM FROM" TABLE-NAME "" จะให้รหัสจนถึงจำนวนแถวเท่านั้น
vijay shanker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.