2009-08-21 10 views
72

Mi piacerebbe trovare il primo "spazio" in una colonna contatore in una tabella SQL. Ad esempio, se ci sono i valori 1,2,4 e 5, vorrei scoprire 3.Come trovo un "gap" nel contatore di corsa con SQL?

Posso ovviamente ottenere i valori in ordine e analizzarlo manualmente, ma mi piacerebbe sapere se ci sarebbe un modo per farlo in SQL.

Inoltre, dovrebbe essere SQL abbastanza standard, che funziona con diversi DBMS.

+0

in SQL Server 2008 e fino si può usare 'LAG (id, 1, null)' funzione con la clausola 'OVER (ORDER BY id)'. – ajeh

risposta

133

Nel MySQL e 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 

In 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 (funziona ovunque, efficienza minima):

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

sistemi di supporto scorrevoli funzioni finestra:

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

super utile, tatoo questo codice :) – vulkanino

+30

@vulkanino: si prega di chiedere loro di preservare il rientro. Inoltre, tieni presente che la licenza Creative Commons richiede di tatuare anche il mio nick e la domanda "URL", anche se potrebbe essere codificato in QR. – Quassnoi

+3

Questo è grandioso, ma se avessi '[1, 2, 11, 12]', allora questo avrebbe trovato solo '3'. Quello che mi piacerebbe trovare è invece il 3-10, praticamente l'inizio e la fine di ogni spazio. Capisco che potrei dover scrivere il mio script python che sfrutta SQL (nel mio caso MySql), ma sarebbe bello se SQL potesse avvicinarmi a ciò che voglio (ho una tabella con 2 milioni di righe che ha lacune, quindi avrò bisogno di dividerlo in parti più piccole e di eseguire alcuni SQL su di esso). Suppongo che potrei eseguire una query per trovare l'inizio di una lacuna, poi un'altra per trovare la fine di una lacuna, e loro "unire ordinamento" le due sequenze. –

7

La prima cosa che mi è venuta in mente. Non sono sicuro se sia una buona idea andare in questo modo, ma dovrebbe funzionare. Supponiamo che la tabella è t e la colonna è 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

Edit: Questo potrebbe essere un segno di spunta più veloce (e più breve!):

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

+0

JOINT ESTERNO SINISTRO t ==> SINISTRA ESTERNO JOIN t2 –

+1

No-no, Eamon, 'LEFT OUTER JOING t2' richiede di avere la tabella' t2', che è solo un alias. –

6

Questo funziona in SQL Server - non possono provarlo in altri sistemi, ma sembra normale ...

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

Si potrebbe anche aggiungere un punto di partenza alla clausola in cui ...

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

Quindi, se tu avessi 2000, 2001, 2002 e 2005, dove 2003 e nel 2004 non esisteva, che sarebbe tornato 2003.

2

Join interno a una vista o sequenza che ha tutti i valori possibili.

Nessun tavolo? Crea un tavolo. Tengo sempre un tavolo finto solo per questo.

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 

Poi,

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

mia ipotesi:

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

In realtà non c'è un estremamente modo standard SQL per fare questo, ma con una qualche forma di limitazione clausola di voi può fare

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)

o

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)

o

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)

+0

se c'è un intervallo, solo la prima riga in l'intervallo verrà restituito per la query postgres. –

8

Le risposte tutti funzionano bene se si dispone di un primo valore id = 1, altrimenti questo gap non verrà rilevato. Per esempio, se i valori id tabella sono 3,4,5, le vostre domande torneranno 6.

ho fatto qualcosa di simile

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

rappresenta Questa è per tutto ciò di cui finora. Include 0 come punto di partenza, che verrà impostato di default se nessun valore esiste. Ho anche aggiunto le posizioni appropriate per le altre parti di una chiave multi-valore. Questo è stato testato solo su 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 
0
select min([ColumnName]) from [TableName] 
where [ColumnName]-1 not in (select [ColumnName] from [TableName]) 
and [ColumnName] <> (select min([ColumnName]) from [TableName]) 
1

Per PostgreSQL

Un esempio che fa uso di query ricorsiva.

Questo potrebbe essere utile se si vuole trovare un varco in un intervallo specifico (che funzionerà anche se la tabella è vuota, mentre gli altri esempi non)

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

Ecco standard di uno SQL soluzione che funziona su tutti i server di database senza modifiche:

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

Vedere in azione per;

0

Funziona per tavoli vuoti o con valori negativi pure.Appena testato in 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

Se il licenziatario utilizza Firebird 3 questo è più elegante e semplice:

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