2009-08-21 7 views
72

J'aimerais trouver le premier "gap" dans une colonne de compteur dans une table SQL. Par exemple, s'il y a des valeurs 1, 2, 4 et 5, j'aimerais en savoir 3.Comment trouver un "écart" dans le fonctionnement du compteur avec SQL?

Je peux bien sûr obtenir les valeurs dans l'ordre et passer manuellement, mais j'aimerais savoir si il y aurait un moyen de le faire en SQL.

En outre, il devrait être assez standard SQL, en travaillant avec différents DBMS.

+0

Dans le serveur SQL Server 2008 et jusqu'à vous pouvez utiliser 'LAG (id, 1, null)' fonctionne avec la clause OVER (ORDER BY id) '. – ajeh

Répondre

133

Dans MySQL et 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 

En 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 

En 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 (fonctionne partout, moins efficace):

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

systèmes prenant en charge les fonctions de fenêtres coulissantes:

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, je vais tatoo ce code :) – vulkanino

+30

@vulkanino: s'il vous plaît leur demander de préserver l'indentation. Veuillez également noter que la licence creative commons vous oblige à tatouer mon pseudo et la question 'URL', bien qu'il puisse s'agir d'un code QR. – Quassnoi

+3

C'est génial, mais si j'avais [1, 2, 11, 12] ', alors cela ne trouverait que' 3'. Ce que j'adorerais trouver, c'est plutôt 3-10 - essentiellement le début et la fin de chaque trou. Je comprends que je pourrais avoir à écrire mon propre script python qui utilise SQL (dans mon cas MySQL), mais ce serait bien si SQL pouvait me rapprocher de ce que je veux (j'ai une table avec 2 millions de lignes qui a des lacunes, donc je vais devoir le découper en morceaux plus petits et faire un peu de SQL dessus). Je suppose que je pourrais lancer une requête pour trouver le début d'un écart, puis un autre pour trouver la fin d'un écart, et les "fusionner" les deux séquences. –

7

La première chose qui m'est venue à l'esprit. Je ne sais pas si c'est une bonne idée d'aller de cette façon, mais ça devrait marcher. Supposons que la table est t et la colonne est 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: Celui-ci peut être une tique plus rapide (et plus court!):

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

GAUCHE OUTER JOIN t ==> GAUCHE OUTER JOIN t2 –

+1

Non-non, Eamon, 'LEFT OUTER JOING t2' vous obligerait à avoir une table 't2', qui est juste un alias. –

6

Cela fonctionne dans SQL Server - ne peuvent le tester dans d'autres systèmes, mais il semble ... norme

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

Vous pouvez également ajouter un point de départ à la clause where ...

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

donc, si vous aviez 2000, 2001, 2002 et 2005 où 2003 et 2004 n'existait pas, il retournerait 2003.

2

Jointure interne à une vue ou à une séquence ayant toutes les valeurs possibles.

Pas de tableau? Faire une table. Je garde toujours une table fictive juste pour ça.

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 

Ensuite,

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

Je pense:

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

Il n'y a pas vraiment une extrêmement manière standard SQL pour le faire, mais avec une certaine forme de limitation clause vous peut faire

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)

ou

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)

ou

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

s'il y a une plage d'écart, seule la première rangée de la plage sera renvoyée pour votre requête postgres. –

8

Vos réponses fonctionnent tous très bien si vous avez une première valeur id = 1, sinon cet écart ne sera pas détecté. Par exemple, si vos valeurs id de table sont 3,4,5, vos requêtes retourneront 6.

Je l'ai fait quelque chose comme ça

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

Celui-ci représente tout mentionné jusqu'à présent. Il inclut 0 comme point de départ, auquel il sera par défaut si aucune valeur n'existe également. J'ai également ajouté les emplacements appropriés pour les autres parties d'une clé multi-valeur. Cela a seulement été testé sur 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

Pour PostgreSQL

Un exemple qui utilise requête récursive.

Cela pourrait être utile si vous voulez trouver un espace dans une gamme spécifique (il fonctionnera même si la table est vide, alors que les autres exemples ne)

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

Voici la norme d'un SQL solution qui s'exécute sur tous les serveurs de base de données sans modification:

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

Voir dans l'action pour;

0

Il fonctionne pour les tables vides ou avec des valeurs négatives aussi bien.Juste testé dans 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

Si vous utilisez Firebird 3 c'est le plus élégant et simple:

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 
Questions connexes