2008-10-06 8 views
12

J'ai récemment hérité d'une base de données sur laquelle l'une des tables a la clé primaire composée de valeurs codées (Part1 * 1000 + Part2).
J'ai normalisé cette colonne, mais je ne peux pas changer les anciennes valeurs. Alors maintenant j'aiComment trouver des "trous" dans un tableau

select ID from table order by ID 
ID 
100001 
100002 
101001 
... 

Je veux trouver les « trous » dans la table (plus précisément, le premier « trou » après 100000) pour les nouvelles lignes.
J'utilise la sélection suivante, mais existe-t-il une meilleure façon de le faire?

select /* top 1 */ ID+1 as newID from table 
where ID > 100000 and 
ID + 1 not in (select ID from table) 
order by ID 

newID 
100003 
101029 
... 

La base de données est Microsoft SQL Server 2000. Je suis d'accord pour utiliser des extensions SQL.

+0

Juste par curiosité, quelle est la motivation pour trouver ces "trous"? Est-ce une clé intelligente? –

+0

La base de données est pour le système de contrôle d'accès à mon travail. Dans l'ancienne base de données, Part1 était un code d'entreprise et Part2 un code d'employé. Si une personne change d'entreprise, une nouvelle carte lui sera délivrée. Le système de lecture de cartes a besoin de 6 chiffres, donc les nouvelles cartes commencent à 100 000, excluant les cartes existantes. – pmg

Répondre

12
select ID +1 From Table t1 
where not exists (select * from Table t2 where t1.id +1 = t2.id); 

ne sais pas si cette version serait plus rapide que celui que vous avez mentionné l'origine.

+1

J'aime les looks de votre version. Je vérifierai le plan d'exécution demain matin et je vous le ferai savoir. – pmg

+1

Je ne sais pas pour vos données, mais sur ma table similaire (25.000 entrées, le premier trou est à la marque 3000), la version de gauche a pris environ une seconde de l'horloge, celle-ci a couru si longtemps que j'ai tué après 2 minutes. –

+1

Ma table de données réelles a 17500 enregistrements et BEAUCOUP de trous. Mes sélections et IronGoofy fonctionnent exactement de la même manière; La version de Santiago est un peu plus lente - mais tous les 3, sur mon système, retournent les trous en un clin d'œil. – pmg

7
SELECT (ID+1) FROM table AS t1 
LEFT JOIN table as t2 
ON t1.ID+1 = t2.ID 
WHERE t2.ID IS NULL 
+2

Cela fonctionne. Merci Santiago. L'Analyseur de requêtes, dans le plan d'exécution, indique que la version sous-sélective est meilleure car elle utilise une jointure de fusion plutôt qu'une correspondance de hachage. – pmg

+1

Oui, il est toujours préférable d'éviter les sous-requêtes si possible. Content que ça aide! –

+1

Cela ne fonctionnait pas pour moi dans PostgreSQL, peut-être à cause de la manière dont le fait de sélectionner "id + 1" fonctionne, ou peut-être parce que ce n'est pas une clé primaire sur le mien. –

4

Cette solution devrait vous donner la première et la dernière valeur d'identification des "trous" que vous recherchez. Je l'utilise dans Firebird 1.5 sur une table de 500K enregistrements, et bien que cela prenne un peu de temps, cela me donne ce que je veux.

SELECT l.id + 1 start_id, MIN(fr.id) - 1 stop_id 
FROM (table l 
LEFT JOIN table r 
ON l.id = r.id - 1) 
LEFT JOIN table fr 
ON l.id < fr.id 
WHERE r.id IS NULL AND fr.id IS NOT NULL 
GROUP BY l.id, r.id 

Par exemple, si vos données ressemble à ceci:

ID 
1001 
1002 
1005 
1006 
1007 
1009 
1011 

vous recevriez ceci:

start_id stop_id 
1003  1004 
1008  1008 
1010  1010 

Je voudrais pouvoir prendre tout le crédit pour cette solution, mais je l'ai trouvé au Xaprb.

+0

Pris un peu de temps pour courir sur une grande table sur une connexion lente avec SQL2005, mais l'attente en valait la peine! –

0

Cette solution ne donne pas tous les trous dans le tableau, seulement les suivants libres + premier nombre max disponible sur la table - fonctionne si vous voulez remplir les lacunes dans les id-es, + obtenir le numéro d'identification gratuit si vous ne le faites pas avoir un écart ..

Sélectionnez un nombre + 1 à partir de temp moins Sélectionnez un nombre de temp;

0

de How do I find a "gap" in running counter with SQL?

select 
    MIN(ID) 
from (
    select 
     100001 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

La meilleure façon est la construction d'une table temporaire avec tous les ID

que de faire une jointure gauche.

declare @maxId int 
select @maxId = max(YOUR_COLUMN_ID) from YOUR_TABLE_HERE 


declare @t table (id int) 

declare @i int 
set @i = 1 

while @i <= @maxId 
begin 
    insert into @t values (@i) 
    set @i = @i +1 
end 

select t.id 
from @t t 
left join YOUR_TABLE_HERE x on x.YOUR_COLUMN_ID = t.id 
where x.YOUR_COLUMN_ID is null 
0

Cela vous donnera l'image complète, où 'Bottom' signifie écart commence et 'Top' signifie fin de l'écart:

select * 
    from 
    ( 
     (select <COL>+1 as id, 'Bottom' AS 'Pos' from <TABLENAME> /*where <CONDITION*/> 
     except 
     select <COL>, 'Bottom' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/) 
    union 
     (select <COL>-1 as id, 'Top' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/ 
     except 
     select <COL>, 'Top' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/) 
    ) t 
    order by t.id, t.Pos 

Note: Premier et Les derniers résultats sont WRONG et ne devrait pas être considéré, mais en les sortant rendrait cette requête beaucoup plus compliquée, donc cela fera pour l'instant.

1

ont pensé à cette question récemment, et ressemble à ceci est la façon la plus élégante de le faire:

SELECT TOP(@MaxNumber) ROW_NUMBER() OVER (ORDER BY t1.number) 
FROM master..spt_values t1 CROSS JOIN master..spt_values t2 
EXCEPT 
SELECT Id FROM <your_table> 
0

Beaucoup de la réponse précédente sont très bons. Cependant ils manquent tous de retourner la première valeur de la séquence et/ou manquent de considérer la limite inférieure 100000. Ils renvoient tous des trous intermédiaires mais pas le tout premier (100001 si manquant).

une solution complète à la question est la suivante:

select id + 1 as newid from 
    (select 100000 as id union select id from tbl) t 
    where (id + 1 not in (select id from tbl)) and 
      (id >= 100000) 
    order by id 
    limit 1; 

Le numéro 100000 doit être utilisé si le premier nombre de la séquence est 100.001 (comme dans la question initiale); sinon il doit être modifié en conséquence « limite 1 » est utilisé pour avoir juste le premier numéro disponible au lieu de la séquence complète

0

Pour les personnes utilisant Oracle, les éléments suivants peuvent être utilisés:

select a, b from (
    select ID + 1 a, max(ID) over (order by ID rows between current row and 1 following) - 1 b from MY_TABLE 
) where a <= b order by a desc; 
Questions connexes