Mysql'de sıralı numaralandırmadaki boşluklar nasıl bulunur?


120

Değerleri başka bir sistemden içe aktarılmış bir tablo içeren bir veritabanımız var. Bir otomatik artış sütunu var ve yinelenen değerler yok, ancak eksik değerler var. Örneğin, bu sorguyu çalıştırmak:

select count(id) from arrc_vouchers where id between 1 and 100

100 döndürmelidir, ancak bunun yerine 87 döndürür. Eksik sayıların değerlerini döndürecek çalıştırabileceğim herhangi bir sorgu var mı? Örneğin, 1-70 ve 83-100 kimlikli kayıtlar olabilir, ancak 71-82 kimlikli kayıt yoktur. 71, 72, 73 vb. İade etmek istiyorum.

Mümkün mü?


Bu MySQL'de çalışmayabilir, ancak işte (Oracle) benzer bir şeye ihtiyacımız vardı. Max değeri olarak bir sayı alan bir Stored Proc yazdık. Depolanan Proc daha sonra tek sütunlu geçici bir tablo oluşturdu. Tablo 1'den Maks'a kadar tüm sayıları içeriyordu. Sonra geçici masa ile ilgilendiğimiz tablo arasında bir NOT IN birleştirmesi yaptı. Arrc_vouchers'dan Max = Select max (id) ile çağırırsanız, tüm eksik değerleri döndürür.
saunderl

2
Numaralandırmada boşluklar olmasının nesi yanlış? Bir vekil anahtarın değeri genellikle anlamlı değildir; önemli olan tek şey, benzersiz olmasıdır. Uygulamanız bitişik olmayan kimlikleri işleyemiyorsa, bu muhtemelen verilerde değil uygulamada bir hatadır.
Wyzard

4
Bu durumda bu bir sorun çünkü eski sistemden devraldığımız veriler, insanlara dağıtılan fiziksel bir karta yazdırmak için bir kayıtla ilişkili otomatik artış numarasını anahtar olarak kullanıyordu. Bu bizim fikrimiz değildi. Hangi kartların eksik olduğunu bulmak için, sıralı numaralandırmada boşlukların nerede olduğunu bilmemiz gerekir.
EmmyS

xaprb.com/blog/2005/12/06/… select l.id + 1 as start from sequence as l left outer join sequence as r on l.id + 1 = r.id where r.id is null;

1'den tablonuzun en yüksek kimliğine kadar sayılar oluşturmak için seri oluştur'u kullanabilirsiniz. Ardından, id'nin bu seride olmadığı bir sorgu çalıştırın.
Tsvetelin Salutski

Yanıtlar:


170

Güncelleme

ConfexianMJS , performans açısından çok daha iyi yanıt verdi .

(Olabildiğince hızlı değil) cevap

İşte her boyuttaki tabloda (yalnızca 100 satırda değil) çalışan sürüm:

SELECT (t1.id + 1) as gap_starts_at, 
       (SELECT MIN(t3.id) -1 FROM arrc_vouchers t3 WHERE t3.id > t1.id) as gap_ends_at
FROM arrc_vouchers t1
WHERE NOT EXISTS (SELECT t2.id FROM arrc_vouchers t2 WHERE t2.id = t1.id + 1)
HAVING gap_ends_at IS NOT NULL
  • gap_starts_at - mevcut boşluktaki ilk kimlik
  • gap_ends_at - mevcut boşluktaki son kimlik

6
Artık o şirket için çalışmıyorum bile, ama bu gördüğüm en iyi cevap ve gelecekte referans olması için kesinlikle hatırlamaya değer. Teşekkürler!
EmmyS

4
bununla ilgili tek sorun, olası bir başlangıç ​​boşluğunu "rapor etmemesidir". Örneğin, ilk 5 kimlik eksikse (1'den 5'e kadar) bunu göstermez ... En başta nasıl acınacak boşluklar gösterebiliriz?
DiegoDD

Not: Bu sorgu geçici tablolarda çalışmaz. Benim sorunum, order numberboşlukları aradığımın farklı olmamasıydı (tablo sipariş satırlarını saklar, bu nedenle ait oldukları sipariş numarası her satır için tekrar eder). 1. sorgu: sette 2812 satır (1 dakika 31.09 saniye) . Farklı sipariş numaraları seçerek başka bir tablo oluşturdu. Tekrarlarım olmadan sorgunuz: Sette 1009 satır (18.04 sn)
Chris K

1
@DiegoDD Sorun ne SELECT MIN(id) FROM table?
Hava

8
Çalıştı ancak 700.000 kayıt içeren bir masada koşmak yaklaşık 5 saat sürdü
Matta

98

Bu, 80.000'den fazla satır içeren bir tablodaki boşlukları bulmam için çalıştı:

SELECT
 CONCAT(z.expected, IF(z.got-1>z.expected, CONCAT(' thru ',z.got-1), '')) AS missing
FROM (
 SELECT
  @rownum:=@rownum+1 AS expected,
  IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got
 FROM
  (SELECT @rownum:=0) AS a
  JOIN YourTable
  ORDER BY YourCol
 ) AS z
WHERE z.got!=0;

Sonuç:

+------------------+
| missing          |
+------------------+
| 1 thru 99        |
| 666 thru 667     |
| 50000            |
| 66419 thru 66456 |
+------------------+
4 rows in set (0.06 sec)

Sütunların sırasının expectedve gotkritik olduğuna dikkat edin .

Bunun YourCol1'de başlamadığını ve önemli olmadığını biliyorsanız, yerine

(SELECT @rownum:=0) AS a

ile

(SELECT @rownum:=(SELECT MIN(YourCol)-1 FROM YourTable)) AS a

Yeni sonuç:

+------------------+
| missing          |
+------------------+
| 666 thru 667     |
| 50000            |
| 66419 thru 66456 |
+------------------+
3 rows in set (0.06 sec)

Eksik kimlikler üzerinde bir tür kabuk betiği görevi gerçekleştirmeniz gerekiyorsa, bu değişkeni doğrudan bash içinde yineleyebileceğiniz bir ifade üretmek için de kullanabilirsiniz.

SELECT GROUP_CONCAT(IF(z.got-1>z.expected, CONCAT('$(',z.expected,' ',z.got-1,')'), z.expected) SEPARATOR " ") AS missing
FROM (  SELECT   @rownum:=@rownum+1 AS expected,   IF(@rownum=height, 0, @rownum:=height) AS got  FROM   (SELECT @rownum:=0) AS a   JOIN block   ORDER BY height  ) AS z WHERE z.got!=0;

Bu, böyle bir çıktı üretir

$(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456)

Daha sonra her kimlik için bir komut yürütmek üzere bir bash terminalindeki for döngüsüne kopyalayıp yapıştırabilirsiniz.

for ID in $(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456); do
  echo $ID
  # fill the gaps
done

Yukarıdakiyle aynı şey, yalnızca hem okunabilir hem de çalıştırılabilir olması. Yukarıdaki "CONCAT" komutunu değiştirerek, diğer programlama dilleri için sözdizimi oluşturulabilir. Ya da belki SQL.


8
güzel çözüm, benim için tercih edilen cevaptan daha iyi - teşekkürler
Wee Zel

6
Onun çok daha verimli kabul cevap daha.
symcbean

1
kabul edilen cevaptan çok daha hızlı. Ekleyeceğim tek şey, CONVERT( YourCol, UNSIGNED )YourCol zaten bir tam sayı değilse daha iyi sonuçlar verecek olmasıdır.
Barton Chittenden

1
@AlexandreCassagne: Sorunuzu doğru anlamak ediyorsam, ben sadece min bulmak için gömülü gibi ayrı bir sorgu yapacağını:SELECT MAX(YourCol) FROM YourTable;
ConfexianMJS

1
@temuri Gerekirse GROUP_CONCAT varyantına SELECT IF((z.got-IF(z.over>0, z.over, 0)-1)>z.expected, CONCAT(z.expected,' thru ',(z.got-IF(z.over>0, z.over, 0)-1)), z.expected) AS missing FROM ( SELECT @rownum:=@rownum+1 AS expected, @target-@missing AS under, (@missing:=@missing+IF(@rownum=YourCol, 0, YourCol-@rownum))-@target AS over, IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got FROM (SELECT @rownum:=0, @missing:=0, @target:=10) AS a JOIN YourTable ORDER BY YourCol ) AS z WHERE z.got!=0 AND z.under>0;
geçin

11

Hile yapması gereken Hızlı ve Kirli sorgu:

SELECT a AS id, b AS next_id, (b - a) -1 AS missing_inbetween
FROM 
 (
SELECT a1.id AS a , MIN(a2.id) AS b 
FROM arrc_vouchers  AS a1
LEFT JOIN arrc_vouchers AS a2 ON a2.id > a1.id
WHERE a1.id <= 100
GROUP BY a1.id
) AS tab

WHERE 
b > a + 1

Bu size, üzerinde kimlikleri eksik olan kimliği ve var olan next_id'i ve aralarında kaç tanesinin eksik olduğunu gösteren bir tablo verecektir.

 
next_id eksik_inbetween kimliği
 1 4 2
68 70 1
75 87 11

1
Bu benim için harika çalıştı. Teşekkürler.! Bunu amaçlarım için kolayca değiştirebildim.
Rahim Hoca

Boşluklarda 'sonraki kimliği' ararken en iyi cevap bu gibi görünüyor. Ne yazık ki, 10K satırlık tablolar için son derece yavaştır. ~ 46K bir masada 10 dakikadan fazla bekliyordum, oysa @ConfexianMJS ile bir saniyeden daha kısa sürede sonuç aldım!
BringBackCommodore64

5

Bir MariaDBkullanıyorsanız, sıralı depolama motorunu kullanarak daha hızlı (% 800) bir seçeneğiniz vardır :

SELECT * FROM seq_1_to_50000 WHERE SEQ NOT IN (SELECT COL FROM TABLE);

2
Bu fikri üzerine genişletmek için, dizinin maksimum kullanılarak kurulabilir "SELECT MAX(column) FROM table"MAX $ söylemek sonucundan bir değişken ve ayar ... SQL deyimi sonra yazılabilir "SELECT * FROM seq_1_to_". $MAX ." WHERE seq not in (SELECT column FROM table)" benim sözdizimi php tabanlı
me_

veya SELECT @var:= max FROM ....; select * from .. WHERE seq < @max;MySQL değişkenleriyle kullanabilirsiniz .
Moshe L

2

100 satır ve 1-100 değerlerini içeren tek bir sütun içeren geçici bir tablo oluşturun.

Dış Bu tabloyu arrc_vouchers tablonuza birleştirin ve arrc_vouchers kimliğinin boş olduğu tek sütun değerlerini seçin.

Bu körü kodluyor ama işe yaramalı.

select tempid from temptable 
left join arrc_vouchers on temptable.tempid = arrc_vouchers.id 
where arrc_vouchers.id is null

Tamam, 1-100 örnek vermenin kolay bir yoluydu. Bu durumda, 20.000 - 85.000'e bakıyoruz. Öyleyse 20000 - 85000 numaralı 65.000 satırlık bir geçici tablo oluşturabilir miyim? Ve bunu nasıl yapacağım? PhpMyAdmin kullanıyorum; Sütunun varsayılan değerini 25000 olarak ayarlarsam ve otomatik artış yaparsam, sadece 65.000 satır ekleyebilir miyim ve bu otomatik artışı 25000 ile başlatır mı?
EmmyS

Benzer bir durum yaşadım (Sırayla 100 öğem var ve 100 öğede eksik öğeyi bulmam gerekiyor). Bunu yapmak için, başka bir 1-100 tablosu oluşturdum, sonra bu ifadeyi üzerinde çalıştırdım ve çok güzel çalışıyor. Bu, geçici tablolar oluşturmak için çok karmaşık bir işlevin yerini alır. Sadece benzer durumdaki biri için tavsiye, bazen bir tablo oluşturmak geçici tablolardan daha hızlıdır.
newshorts

2

Bir sorgu + biraz işlem yapan bir kod gerektiren alternatif bir çözüm şu olabilir:

select l.id lValue, c.id cValue, r.id rValue 
  from 
  arrc_vouchers l 
  right join arrc_vouchers c on l.id=IF(c.id > 0, c.id-1, null)
  left  join arrc_vouchers r on r.id=c.id+1
where 1=1
  and c.id > 0 
  and (l.id is null or r.id is null)
order by c.id asc;

Sorgunun, MySQL'in planlayıcısı tarafından performans açısından ele alınmadığını bildiğimiz herhangi bir alt seçim içermediğine dikkat edin.

Bu, daha küçük bir değere (lValue) veya daha büyük bir değere (rValue) sahip olmayan centralValue (cValue) başına bir girdi döndürecektir, yani:

lValue |cValue|rValue
-------+------+-------
{null} | 2    | 3      
8      | 9    | {null} 
{null} | 22   | 23     
23     | 24   | {null} 
{null} | 29   | {null} 
{null} | 33   | {null} 


Daha fazla ayrıntıya girmeden (sonraki paragraflarda göreceğiz) bu çıktı şu anlama gelir:

  • 0 ile 2 arasında değer yok
  • 9 ile 22 arasında değer yok
  • 24 ile 29 arasında değer yok
  • 29 ile 33 arasında değer yok
  • 33 ile MAX VALUE arasında değer yok

Yani temel fikir, değer başına bitişik değerlere sahip olup olmadığımızı görmek için aynı tablo ile SAĞ ve SOL birleşimler yapmaktır (yani, merkezi değer '3' ise, solda 3-1 = 2 ve solda 3 + 1'i kontrol ederiz. sağ) ve bir SATIR, SAĞ veya SOL'da NULL değerine sahipse, bitişik değer olmadığını biliriz.

Tablomun tam ham çıktısı:

select * from arrc_vouchers order by id asc;

0  
2  
3  
4  
5  
6  
7  
8  
9  
22 
23 
24 
29 
33 

Bazı notlar:

  1. Birleştirme koşulundaki SQL IF deyimi, 'id' alanını UNSIGNED olarak tanımlarsanız gereklidir, bu nedenle sıfırın altına düşürmenize izin vermez. Bir sonraki notta belirtildiği gibi c.value> 0'ı tutarsanız, bu kesinlikle gerekli değildir, ancak bunu tıpkı doc olarak ekliyorum.
  2. Önceki herhangi bir değerle ilgilenmediğimizden sıfır merkezi değeri filtreliyorum ve sonraki satırdan son değeri türetebiliyoruz.

2

İki sayı arasında en fazla bir boşluk olan bir dizi varsa (1,3,5,6 gibi), kullanılabilecek sorgu şu şekildedir:

select s.id+1 from source1 s where s.id+1 not in(select id from source1) and s.id+1<(select max(id) from source1);
  • Tablo ismi - source1
  • sütun adı - id

1

Yukarıda Lucek tarafından verilen yanıta dayalı olarak bu saklı yordam, bitişik olmayan kayıtları bulmak için test etmek istediğiniz tablo ve sütun adlarını belirlemenize olanak tanır - böylece orijinal soruyu yanıtlar ve ayrıca @var'ı tabloları temsil etmek için nasıl kullanabileceğinizi gösterir. / veya saklı yordamdaki sütunlar.

create definer=`root`@`localhost` procedure `spfindnoncontiguous`(in `param_tbl` varchar(64), in `param_col` varchar(64))
language sql
not deterministic
contains sql
sql security definer
comment ''
begin
declare strsql varchar(1000);
declare tbl varchar(64);
declare col varchar(64);

set @tbl=cast(param_tbl as char character set utf8);
set @col=cast(param_col as char character set utf8);

set @strsql=concat("select 
    ( t1.",@col," + 1 ) as starts_at, 
  ( select min(t3.",@col,") -1 from ",@tbl," t3 where t3.",@col," > t1.",@col," ) as ends_at
    from ",@tbl," t1
        where not exists ( select t2.",@col," from ",@tbl," t2 where t2.",@col," = t1.",@col," + 1 )
        having ends_at is not null");

prepare stmt from @strsql;
execute stmt;
deallocate prepare stmt;
end

1

Bunu farklı şekillerde denedim ve bulduğum en iyi performans şu basit sorguydu:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;

... bir sonraki kimliğin var olup olmadığını kontrol etmek için bir sol birleşim , ancak eğer bir sonraki bulunamazsa, alt sorgu, boşluğun sonunu bulmak için var olan bir sonraki kimliği bulur. Bunu yaptım çünkü eşittir (=) ile sorgu (>) operatöründen daha iyi performans .

Sqlfiddle kullanıldığında, diğerlerinden çok farklı performans göstermez, ancak gerçek bir veritabanında yukarıdaki bu sorgu diğerlerinden 3 kat daha hızlı sonuçlanır.

Şema:

CREATE TABLE arrc_vouchers (id int primary key)
;
INSERT INTO `arrc_vouchers` (`id`) VALUES (1),(4),(5),(7),(8),(9),(10),(11),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29)
;

Performansı karşılaştırmak için yaptığım tüm sorguları aşağıdan izleyin:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;
select *, (gapEnd-gapIni) qt
    from (
        select id+1 gapIni
        ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
        from arrc_vouchers a
        order by id
    ) a where gapEnd <> gapIni
;
select id+1 gapIni
    ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
    #,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    where id+1 <> (select x.id from arrc_vouchers x where x.id>a.id limit 1)
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),concat('*** GAT *** ',(select x.id from arrc_vouchers x where x.id>a.id limit 1))) gapEnd
    from arrc_vouchers a
    order by id
;

Belki birine yardımcı olur ve faydalıdır.

Bu sqlfiddle'ı kullanarak sorgumu görebilir ve test edebilirsiniz :

http://sqlfiddle.com/#!9/6bdca7/1


0

Bunların hepsi işe yarıyor gibi görünse de, 50.000 kayıt olduğunda sonuç kümesi çok uzun bir süre içinde geri döner.

Bunu kullandım ve sorgudan çok daha hızlı bir dönüşle boşluğu veya bir sonraki mevcut (son kullanılan + 1) buluyor.

SELECT a.id as beforegap, a.id+1 as avail
FROM table_name a
where (select b.id from table_name b where b.id=a.id+1) is null
limit 1;

bu, sorunun sorduğu şey olmayan ilk boşluğu bulur.
2014

0

Muhtemelen alakalı değil, ancak bir sayı dizisindeki boşlukları listelemek için böyle bir şey arıyordum ve tam olarak aradığınıza bağlı olarak birden fazla farklı çözümü olan bu yazıyı buldum. Sıradaki ilk mevcut boşluğu arıyordum (yani bir sonraki mevcut sayı) ve bu iyi çalışıyor gibi görünüyor.

Hastalardan LEFT OUTER JOIN olarak r.number_sequence + 1 = r.number_sequence NULL olarak MİN (l.sayı_sırası + 1) 'i sonraki kullanılabilir olarak SEÇİN. 2005'ten beri orada tartışılan birkaç başka senaryo ve çözüm var!

SQL İle Bir Sıradaki Eksik Değerler Nasıl Bulunur

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.