mysqlの連番のギャップを見つける方法は?


119

別のシステムから値がインポートされたテーブルを持つデータベースがあります。自動インクリメント列があり、重複する値はありませんが、欠損値があります。たとえば、次のクエリを実行します。

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

100を返す必要がありますが、代わりに87を返します。不足している数値の値を返すクエリを実行できますか?たとえば、ID 1-70と83-100のレコードが存在する場合がありますが、IDが71-82のレコードはありません。71、72、73などを返品したい

これは可能ですか?


これはMySQLでは機能しない可能性がありますが、職場(Oracle)では同様のものが必要でした。数値を最大値とするストアドプロシージャを記述しました。次に、ストアドプロシージャは、単一の列を持つ一時テーブルを作成しました。テーブルには、1からMaxまでのすべての数値が含まれています。次に、一時テーブルと対象のテーブルの間でNOT IN結合が行われました。Max = Select max(id)でarrc_vouchersから呼び出した場合、不足しているすべての値が返されます。
saunderl

2
番号付けにギャップがあることの何が問題になっていますか?一般に、代理キーの値は意味がありません。重要なのは、それがユニークであることです。アプリケーションが連続しないIDを処理できない場合、それはおそらくデータではなく、アプリケーションのバグです。
Wyzard

4
この場合、古いシステムから継承したデータは、レコードに関連付けられた自動インクリメント番号をキーとして使用して、人に配布される物理的なカードに印刷するため、問題になります。これは私たちの考えではありませんでした。欠けているカードを見つけるには、連番のどこにギャップがあるかを知る必要があります。
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;

generate seriesを使用して、1からテーブルの最大IDまでの数値を生成できます。次に、このシリーズに含まれていないクエリを実行します。
Tsvetelin Salutski、2017

回答:


170

更新

ConfexianMJSは、パフォーマンスの点ではるかに優れた 回答を提供しました

(可能な限り速くない)答え

これは、(100行だけでなく)任意のサイズのテーブルで機能するバージョンです。

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 -現在のギャップの最初のID
  • gap_ends_at -現在のギャップの最後のID

6
私はもうその会社で働いていませんが、これは私が見た中で最高の答えであり、将来の参考のために覚えておくことは間違いありません。ありがとう!
EmmyS、

4
これに関する唯一の問題は、起こり得る初期ギャップを「報告」しないことです。たとえば、最初の5つのIDが欠落している場合(1から5)、それはそれを示していません...どうすれば、最初の非常に大きなギャップを表示できますか?
DiegoDD 2013

注:このクエリは一時テーブルでは機能しません。私の問題は、order numberギャップを探していたところが明確ではないことでした(テーブルには注文明細行が格納されているため、それらが属する注文番号は各行の繰り返しに含まれています)。最初のクエリ:セット内の2812行(1分31.09秒)。別の注文番号を選択して別のテーブルを作成しました。リピートなしのクエリ:セット内の1009行(18.04秒)
Chris K

1
@DiegoDD何が問題になっていSELECT MIN(id) FROM tableますか?
Air

8
機能しましたが、700000レコードのテーブルで実行するのに約5時間かかりました
Matt

98

これは、8万行を超えるテーブルのギャップを見つけるのに役立ちました。

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;

結果:

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

列の順序ことに注意expectedしてはgot非常に重要です。

それがYourCol1から始まっておらず、それが問題ではないことがわかっている場合は、

(SELECT @rownum:=0) AS a

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

新しい結果:

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

不足しているIDで何らかのシェルスクリプトタスクを実行する必要がある場合は、このバリアントを使用して、bashで反復できる式を直接生成することもできます。

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;

これはそのような出力を生成します

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

次に、それをコピーしてbashターミナルのforループに貼り付け、すべてのIDに対してコマンドを実行します

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

これは上記と同じですが、読み取りと実行の両方が可能です。上記の「CONCAT」コマンドを変更することにより、他のプログラミング言語用の構文を生成できます。または多分SQLです。


8
素晴らしい解決策、私にとっては好ましい答えよりも優れています-ありがとう
Wee Zel

6
そのくらいのより効率的な受け入れ答えより。
symcbean

1
受け入れられた答えよりもはるかに速い。私が追加する唯一のことは、CONVERT( YourCol, UNSIGNED )YourColがまだ整数でない場合により良い結果をもたらすことです。
Barton Chittenden 2017

1
@AlexandreCassagne:あなたの質問を正しく理解している場合は、埋め込みクエリのような別のクエリを実行して、最小値を見つけます:SELECT MAX(YourCol) FROM YourTable;
ConfexianMJS

1
GROUP_CONCATバリアントに@temuri切り替え、必要に応じて: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;
ConfexianMJS

11

トリックを実行する必要がある迅速でダーティーなクエリ:

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

これにより、IDが上に欠落しているID、存在するnext_id、および間に欠落しているIDの数を示すテーブルが表示されます...例

 
id next_id missing_inbetween
 1 4 2
68 70 1
75 87 11

1
これは私にとってはうまくいきました。ありがとう。これを目的に合わせて簡単に変更できました。
Rahim Khoja 2016年

ギャップで「次のID」を探す場合、これが最良の答えのようです。残念ながら、行数が10Kのテーブルでは非常に遅くなります。私は〜46Kのテーブルで10分以上待っていましたが、@ ConfexianMJSでは1秒未満で結果が得られました!
BringBackCommodore64 2017

5

を使用しているMariaDB場合は、シーケンスストレージエンジンを使用してより高速な(800%)オプションがあります。

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

2
このアイデアをさらに詳しく説明するには、シーケンスの最大値を使用"SELECT MAX(column) FROM table"して、結果から変数を設定し、$ MAXを設定します。次に、SQLステートメントを記述でき"SELECT * FROM seq_1_to_". $MAX ." WHERE seq not in (SELECT column FROM table)" ます。私の構文はphpベースです
me_

またはSELECT @var:= max FROM ....; select * from .. WHERE seq < @max;、MySQL変数で使用でき ます。
Moshe L

2

100行と1〜100の値を含む1列の一時テーブルを作成します。

このテーブルをarrc_vouchersテーブルに外部結合し、arrc_vouchers IDがnullである単一列の値を選択します。

このブラインドのコーディングは機能するはずです。

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

わかりました。1〜100は、例を示す簡単な方法にすぎません。この場合は、20,000〜85,000です。それでは、20000〜85000の番号が付いた65,000行の一時テーブルを作成しますか?そして、どうすればそれを実行できますか?私はphpMyAdminを使用しています。列のデフォルト値を25000に設定して自動インクリメントにした場合、65,000行を挿入するだけで自動インクリメントが25000で開始されますか?
EmmyS 2010

私も同様の状況でした(注文した商品が100個あり、100個の不足している商品を見つける必要がある)。これを行うには、別のテーブル1-100を作成してから、このステートメントを実行します。これは、一時テーブルを作成するための非常に複雑な関数を置き換えます。似たような状況にある人へのアドバイスとして、一時テーブルよりもテーブルを作成する方が速い場合があります。
newshorts 14

2

クエリ+いくつかの処理を実行するいくつかのコードを必要とする代替ソリューションは次のようになります:

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;

クエリには、MySQLのプランナーでは実行されないことがわかっている副選択が含まれていないことに注意してください。

これは、より小さい値(lValue)またはより大きい値(rValue)を持たないcentralValue(cValue)ごとに1つのエントリを返します。

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


詳細は省きます(次の段落で説明します)この出力は、次のことを意味します。

  • 0と2の間の値はありません
  • 9から22までの値はありません
  • 24〜29の値はありません
  • 29から33までの値はありません
  • 33からMAX VALUEまでの値はありません

したがって、基本的な考え方は、値ごとに隣接する値があるかどうかを確認しながら、同じテーブルでRIGHTおよびLEFT結合を実行することです(つまり、中央値が '3'の場合、左で3-1 = 2で、3 + 1で右)、およびROWのRIGHTまたはLEFTにNULL値がある場合、隣接する値がないことがわかります。

私のテーブルの完全な生の出力は:

select * from arrc_vouchers order by id asc;

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

いくつかのメモ:

  1. 'id'フィールドをUNSIGNEDとして定義する場合、結合条件のSQL IFステートメントが必要です。そのため、ゼロより小さくすることはできません。c.value> 0を維持する場合、これは厳密には必要ありませんが、次の注記で説明されていますが、私はドキュメントとしてそのまま含めています。
  2. 以前の値には関心がなく、次の行から投稿の値を導出できるため、中央のゼロ値をフィルタリングしています。

2

2つの数値の間に最大1のギャップがあるシーケンス(1、3、5、6など)がある場合、使用できるクエリは次のとおりです。

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);
  • table_name- source1
  • column_name- id

1

Lucekによる上記の回答に基づいて、このストアドプロシージャを使用すると、隣接しないレコードを見つけるためにテストするテーブルと列の名前を指定できます。つまり、元の質問に答え、@ varを使用してテーブルを表す方法と/またはストアドプロシージャの列。

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

私はさまざまな方法でそれを試しました、そして私が見つけた最高のパフォーマンスはこの単純なクエリでした:

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
;

...次のIDが存在するかどうかを確認するための1つの左結合。次のIDが見つからない場合にのみ、サブクエリはギャップの終わりを見つけるために存在する次のIDを見つけます。等しい(=)を使用したクエリは、より大きい(>)演算子よりもパフォーマンスが優れているため、これを行いました。

sqlfiddleを使用すると、他のクエリとそれほど異なるパフォーマンスは表示されませんが、実際のデータベースでは、上記のクエリは他のクエリより3倍高速です。

スキーマ:

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

パフォーマンスを比較するために行ったすべてのクエリを以下に示します。

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
;

多分それは誰かを助け、役に立つでしょう。

次のsqlfiddleを使用して、クエリを表示およびテストできます。

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


0

これらはすべて機能しているように見えますが、50,000件のレコードがある場合、結果セットは非常に長い時間で返されます。

私はこれを使用しましたが、クエリからの戻りがはるかに速いギャップまたは次の使用可能な(最後に使用された+ 1)を見つけました。

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;

これは、質問が求めていたものではない最初のギャップを見つけます。
2014

0

おそらく関連性はありませんが、一連の数値のギャップをリストするためにこのようなものを探していましたが、この投稿を見つけました。あなたが探しているものに応じて複数の異なる解決策があります。シーケンスで最初に利用可能なギャップ(つまり、次に利用可能な番号)を探していましたが、これは問題なく動作するようです。

SELECT MIN(l.number_sequence + 1)を患者から次のように使用可能lとしてl左外部結合患者をrとしてl.number_sequence + 1 = r.number_sequence WHERE r.number_sequenceがNULLです。2005年から、そこで議論された他のいくつかのシナリオとソリューション!

SQLを使用してシーケンスの欠損値を見つける方法

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.