SQL ile çalışan sayaçta nasıl bir "boşluk" bulurum?


106

Bir SQL tablosundaki bir sayaç sütunundaki ilk "boşluğu" bulmak istiyorum. Örneğin 1,2,4 ve 5 değerleri varsa 3'ü öğrenmek isterim.

Elbette değerleri sırayla alıp manuel olarak geçebilirim, ancak bunu SQL'de yapmanın bir yolu olup olmadığını bilmek istiyorum.

Ek olarak, farklı DBMS'lerle çalışan oldukça standart bir SQL olmalıdır.


Sql server 2008 ve üzeri sürümlerde LAG(id, 1, null)function with OVER (ORDER BY id)clause kullanabilirsiniz .
ajeh

Yanıtlar:


185

İçinde MySQLve 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

İçinde 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

İçinde 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 (her yerde çalışır, en az verimli):

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

Sürgülü pencere işlevlerini destekleyen sistemler:

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: lütfen girintiyi korumalarını isteyin. Ayrıca lütfen Creative Commons lisansının, benim takma adıma ve soruyu dövme yapmanızı gerektirdiğini unutmayın URL, ancak QR kodlu olabilir.
Quassnoi

4
Bu harika, ama olsaydı [1, 2, 11, 12], o zaman bu sadece bulurdu 3. Bulmak istediğim şey 3-10 - temelde her boşluğun başlangıcı ve sonu. SQL'den yararlanan kendi python betiğimi yazmam gerekebileceğini anlıyorum (benim durumumda MySql), ancak SQL beni istediğim şeye yaklaştırabilirse iyi olurdu (boşlukları olan 2 milyon satırlık bir tablom var, bu yüzden onu daha küçük parçalara ayırmam ve üzerinde biraz SQL çalıştırmam gerekecek). Sanırım bir boşluğun başlangıcını bulmak için bir sorgu çalıştırabilirim, sonra bir boşluğun sonunu bulmak için başka bir sorgu çalıştırabilir ve iki sekansı "birleştirerek sıralayabilirim".
Hamish Grubijan

1
@HamishGrubijan: Lütfen başka bir soru olarak gönderin
Quassnoi

2
@Malkocoglu: Masa boşsa NULLalmayacaksın 0. Bu, tüm veritabanları için geçerlidir.
Quassnoi

5
bu, başlangıç ​​boşluklarını düzgün bir şekilde bulamayacaktır. 3,4,5,6,8'iniz varsa. bu kod 7'yi bildirir, çünkü kontrol etmek için NO 1'e sahiptir. Bu nedenle, başlangıç ​​numaralarını kaçırırsanız, bunu kontrol etmeniz gerekecektir.
ttomsen

12

İlk değeriniz id = 1 ise yanıtlarınızın hepsi iyi sonuç verir, aksi takdirde bu boşluk tespit edilmeyecektir. Örneğin tablo kimlik değerleriniz 3,4,5 ise, sorgularınız 6 döndürür.

Bunun gibi bir şey yaptım

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) 

Bu ilk boşluğu bulacaktır. 0, 2,3,4 kimliğiniz varsa. Cevap 1. En büyük boşluğu bulmak için bir cevap arıyordum. Sıranın 0,2,3,4, 100,101,102 olduğunu söyleyin. 4-99 boşluk bulmak istiyorum.
Kemin Zhou

8

Bunu yapmanın gerçekten son derece standart bir SQL yolu yoktur, ancak bir tür sınırlayıcı cümle ile yapabilirsiniz

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)

veya

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)

veya

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)


bir boşluk aralığı varsa, postgres sorgunuz için yalnızca aralıktaki ilk satır döndürülür.
John Haugeland

Bu benim için en mantıklı olanı, bir birleşim kullanmak aynı zamanda daha fazla boşluk sonucu göstermek için TOP değerinizi değiştirmenize de izin verir.
AJ_

1
Teşekkürler, bu çok iyi çalışıyor ve boşluk olan tüm noktaları görmek isterseniz sınırı kaldırabilirsiniz.
mekbib.awoke

8

Aklıma gelen ilk şey. Bu şekilde gitmenin iyi bir fikir olup olmadığından emin değilim, ama işe yaramalı. Tablonun tve sütunun 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

Düzenleme: Bu daha hızlı (ve daha kısa!) Bir tıklama olabilir:

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


SOL DIŞ BİRLEŞTİRME t ==> SOL DIŞ BİRLEŞTİRME t2
Eamon Nerbonne

1
Hayır-hayır, Eamon, sadece bir takma ad olan tabloya LEFT OUTER JOING t2sahip olmanı gerektirir t2.
Michael Krelin - hacker

6

Bu, SQL Server'da çalışır - diğer sistemlerde test edemez, ancak standart görünür ...

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

Ayrıca where cümlesine bir başlangıç ​​noktası da ekleyebilirsiniz ...

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

Yani, 2003 ve 2004'ün olmadığı 2000, 2001, 2002 ve 2005'e sahipseniz, 2003'e dönecektir.


3

Aşağıdaki çözüm:

  • test verilerini sağlar;
  • başka boşluklar oluşturan bir iç sorgu; ve
  • SQL Server 2012'de çalışır.

Sıralı satırları " with " cümlesinde sıralı olarak numaralandırır ve ardından, satır numarasındaki bir iç birleşim ile sonucu iki kez yeniden kullanır, ancak önceki satırı sonraki satırla karşılaştırmak için uzaklığı 1 ile, şundan büyük boşluklu kimlikleri arar. 1. Talep edilenden daha fazlası, ancak daha yaygın olarak uygulanabilir.

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

İç sorgu şunları üretir:

gap_start   gap_end

3           3

9           11

Dış sorgu şunları üretir:

gap_start

3

2

Tüm olası değerlere sahip bir görünüme veya diziye iç birleşim.

Dikkate değer? Bir masa yapın. Sırf bunun için hep ortalıkta sahte bir masa tutarım.

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

Sonra,

 select a.id from artificial_range a
 where not exists ( select * from your_table b
 where b.counter = a.id) ;

2

İçin PostgreSQL

Özyinelemeli sorgudan yararlanan bir örnek.

Belirli bir aralıkta bir boşluk bulmak istiyorsanız bu yararlı olabilir (tablo boş olsa bile çalışacaktır, oysa diğer örnekler işe yaramayacaktır)

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

Tahminim:

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

Bu, şimdiye kadar bahsedilen her şeyi açıklıyor. Başlangıç ​​noktası olarak 0'ı içerir ve hiçbir değer yoksa varsayılan olarak ayarlayacaktır. Çok değerli bir anahtarın diğer bölümleri için uygun konumları da ekledim. Bu yalnızca SQL Server'da test edilmiştir.

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

Bunu yapmanın hızlı bir yolunu yazdım. Bunun en verimli olduğundan emin değilim, ancak işi bitirir. Size boşluğu söylemediğini, ancak boşluktan önceki ve sonraki kimliği söylediğini unutmayın (boşluğun birden fazla değer olabileceğini, örneğin 1,2,4,7,11 vb.)

Örnek olarak sqlite kullanıyorum

Bu sizin masa yapınızsa

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

ve bunlar senin sıraların

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

Sorgu

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
select min([ColumnName]) from [TableName]
where [ColumnName]-1 not in (select [ColumnName] from [TableName])
and [ColumnName] <> (select min([ColumnName]) from [TableName])

0

İşte tüm veritabanı sunucularında değişiklik olmadan çalışan standart bir SQL çözümü:

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

Eylemde görün;


0

Boş tablolar için veya negatif değerlerle de çalışır. SQL Server 2012'de yeni test edildi

 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 kullanıyorsanız, bu çok zarif ve basittir:

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

Yaklaşımların çoğunun çok, çok yavaş çalıştığını gördük mysql. İşte benim çözümüm mysql < 8.0. Bitirmek için ~ 1 saniye sonuna yakın bir boşlukla 1 milyon kayıtta test edilmiştir. Diğer SQL çeşitlerine uyup uymadığından emin değilim.

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
Sıranın "1" den başladığını varsayıyorum.

0

Sayacınız 1'den başlıyorsa ve boşken birinci sıra numarasını (1) oluşturmak istiyorsanız, Oracle için geçerli olan ilk yanıttan düzeltilmiş kod parçası burada:

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

Sütundaki sayılar pozitif tamsayılarsa (1'den başlayarak), işte o zaman nasıl kolayca çözüleceği. (ID'nin sütun adınız olduğu varsayılır)

    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" içindeki satır sayısına kadar boşlukları bulacaktır, çünkü "SEÇ_SATI_SAYISI () ÜZERİNDEN SEÇİN () 'TABLE-
ADI'DAN NUMARASI OLARAK SEÇİN"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.