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)에서는 비슷한 것이 필요했습니다. Max 값으로 숫자를 사용하는 Stored Proc을 작성했습니다. 그런 다음 Stored Proc는 단일 열이있는 임시 테이블을 생성했습니다. 표에는 1부터 최대까지의 모든 숫자가 포함되어 있습니다. 그런 다음 임시 테이블과 관심 테이블 사이에 NOT IN 조인을 수행했습니다. Max = Select max (id) from arrc_vouchers로 호출하면 모든 누락 된 값이 반환됩니다.
saunderl 2010

2
번호 매기기에 공백이 있으면 무엇이 문제입니까? 일반적으로 서로 게이트 키의 값은 의미가 없습니다. 중요한 것은 그것이 독특하다는 것입니다. 애플리케이션이 비 연속 ID를 처리 할 수 ​​없다면 데이터가 아닌 애플리케이션의 버그 일 수 있습니다.
Wyzard 2010

4
이 경우 이전 시스템에서 물려받은 데이터가 레코드와 관련된 자동 증가 번호를 키로 사용하여 사람들에게 전달되는 실제 카드에 인쇄하기 때문에 문제가됩니다. 이것은 우리의 생각이 아닙니다. 누락 된 카드를 확인하려면 연속 번호 매기기에서 공백이있는 위치를 알아야합니다.
EmmyS 2010

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부터 테이블의 가장 높은 ID까지 숫자를 생성 할 수 있습니다. 그런 다음이 시리즈에 포함되지 않은 ID가있는 쿼리를 실행합니다.
Tsvetelin Salutski

답변:


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 2011

참고 :이 쿼리는 임시 테이블에서 작동하지 않습니다. 내 문제는 order number내가 틈새를 찾고 있었는데 구별되지 않았다는 것입니다 (테이블에 주문 라인이 저장되어 있으므로 각 라인에 대해 반복되는 주문 번호). 첫 번째 쿼리 : 세트의 2812 개 행 (1 분 31.09 초) . 고유 한 주문 번호를 선택하여 다른 테이블을 만들었습니다. 내 반복이없는 쿼리 : 세트의 1009 행 (18.04 초)
Chris K

1
@DiegoDD 뭐가 잘못 됐어 SELECT MIN(id) FROM table?
Air

8
700000 개 레코드 테이블에 실행하기 위해 노력하지만 5시간 걸렸다
매트

98

이것은 80k 행이 넘는 테이블에서 간격을 찾는 데 도움이되었습니다.

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)

열의 순서 expectedgot 중요합니다.

이것이 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
좋은 솔루션, 나를 위해이 선호하는 답보다 더 나은입니다 - 감사합니다
꼬마 ZEL

6
그것 훨씬 더 효율적 허용 대답보다.
symcbean 2011

1
받아 들인 대답보다 훨씬 빠릅니다. 내가 추가 할 유일한 것은 CONVERT( YourCol, UNSIGNED )YourCol이 이미 정수가 아닌 경우 더 나은 결과를 제공 한다는 것입니다.
Barton Chittenden

1
@AlexandreCassagne : 귀하의 질문을 올바르게 이해하고 있다면 최소값을 찾기 위해 포함 된 것과 같은 별도의 쿼리를 수행 할 것입니다.SELECT MAX(YourCol) FROM YourTable;
ConfexianMJS

1
@temuri 필요한 경우 GROUP_CONCAT 변형으로 전환 :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 next_id missing_inbetween
 1 4 2
68 70 1
75 87 11

1
이것은 나를 위해 잘 작동했습니다. 감사.! 내 목적에 맞게 쉽게 수정할 수있었습니다.
Rahim Khoja

틈새에서 'next id'를 찾을 때 이것이 가장 좋은 대답 인 것 같습니다. 불행히도 10K 행이있는 테이블의 경우 매우 느립니다. ~ 46K 테이블에서 10 분 이상을 기다렸지 만 @ConfexianMJS를 사용하면 1 초도 안되는 시간에 결과를 얻었습니다!
BringBackCommodore64

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 값을 포함하는 단일 열이있는 임시 테이블을 만듭니다.

외부이 테이블을 arrc_vouchers 테이블에 조인하고 arrc_vouchers ID가 널인 단일 열 값을 선택하십시오.

이 블라인드를 코딩하지만 작동합니다.

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 2014-04-09

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) 당 하나의 항목이 반환됩니다. 즉,

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, 오른쪽), 그리고 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 문이 필요하므로 0 미만으로 줄일 수 없습니다. 다음 노트에 언급 된대로 c.value> 0을 유지하는 경우 반드시 필요하지는 않지만 문서로 포함합니다.
  2. 이전 값에 관심이없고 다음 행에서 post 값을 파생 할 수 있으므로 0 중심 값을 필터링합니다.

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 가 있는지 확인하기 위해 조인을 남겼습니다. 다음 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;

이것은 질문이 요구 한 것이 아닌 첫 번째 간격을 찾습니다.
drewish

0

아마도 관련이 없을 수도 있지만 일련의 숫자에서 간격을 나열하기 위해 이와 같은 것을 찾고 있었고 정확히 찾고있는 것에 따라 여러 가지 다른 솔루션이있는이 게시물을 찾았습니다. 시퀀스에서 사용 가능한 첫 번째 간격 (즉, 다음 사용 가능한 번호)을 찾고 있었는데, 이것이 잘 작동하는 것 같습니다.

MIN (l.number_sequence + 1)을 다음과 같이 환자로부터 다음 사용 가능으로 선택하십시오. l LEFT OUTER JOIN 환자를 l on l.number_sequence + 1 = r.number_sequence WHERE r.number_sequence가 NULL입니다. 2005 년부터 논의 된 몇 가지 다른 시나리오와 솔루션!

SQL을 사용하여 시퀀스에서 누락 된 값을 찾는 방법

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.