Làm thế nào để tìm khoảng trống trong đánh số tuần tự trong mysql?


119

Chúng tôi có một cơ sở dữ liệu với một bảng có các giá trị được nhập từ một hệ thống khác. Có một cột tự động tăng và không có giá trị trùng lặp, nhưng thiếu giá trị. Ví dụ: chạy truy vấn này:

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

sẽ trả về 100, nhưng thay vào đó nó trả về 87. Có bất kỳ truy vấn nào tôi có thể chạy sẽ trả về giá trị của các số bị thiếu không? Ví dụ: các bản ghi có thể tồn tại cho id 1-70 và 83-100, nhưng không có bản ghi nào có id là 71-82. Tôi muốn trả lại 71, 72, 73, v.v.

Điều này có khả thi không?


Điều này có thể không hoạt động trong MySQL, nhưng tại nơi làm việc (Oracle), chúng tôi cần một cái gì đó tương tự. Chúng tôi đã viết một Proc được lưu trữ lấy một số làm giá trị Tối đa. Sau đó, Stored Proc đã tạo một bảng tạm thời với một cột duy nhất. Bảng chứa tất cả các số từ 1 đến Max. Sau đó, nó đã tham gia KHÔNG VÀO giữa bảng tạm thời và bảng quan tâm của chúng tôi. Nếu bạn gọi nó bằng Max = Chọn max (id) từ arrc_vouchers, thì nó sẽ trả về tất cả các giá trị bị thiếu.
saunderl

2
Có gì sai khi có khoảng trống trong đánh số? Giá trị của khóa thay thế thường không có ý nghĩa; tất cả những gì quan trọng là nó độc nhất. Nếu ứng dụng của bạn không thể xử lý các ID không liền kề, đó có thể là lỗi trong ứng dụng, không phải trong dữ liệu.
Wyzard

4
Trong trường hợp này, đó là một vấn đề vì dữ liệu chúng tôi kế thừa từ hệ thống cũ đã sử dụng số tự động tăng liên kết với bản ghi làm khóa để in trên thẻ vật lý đang được phân phối cho mọi người. Đây KHÔNG phải là ý tưởng của chúng tôi. Để biết thẻ nào bị thiếu, chúng ta cần biết chỗ trống trong việc đánh số thứ tự.
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;

Bạn có thể sử dụng chuỗi tạo để tạo các số từ 1 đến id cao nhất trong bảng của bạn. Sau đó, chạy một truy vấn mà id không có trong loạt bài này.
Tsvetelin Salutski

Câu trả lời:


170

Cập nhật

ConfexianMJS đã cung cấp câu trả lời tốt hơn nhiều về mặt hiệu suất.

Câu trả lời (không nhanh nhất có thể)

Đây là phiên bản hoạt động trên bảng có kích thước bất kỳ (không chỉ trên 100 hàng):

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 đầu tiên trong khoảng trống hiện tại
  • gap_ends_at - id cuối cùng trong khoảng trống hiện tại

6
Tôi thậm chí không còn làm việc cho công ty đó nữa, nhưng đây là câu trả lời hay nhất mà tôi đã thấy và nó chắc chắn đáng ghi nhớ để tham khảo trong tương lai. Cảm ơn!
EmmyS

4
vấn đề duy nhất với điều này, là nó không "báo cáo" một khoảng trống ban đầu có thể có. Ví dụ: nếu thiếu 5 id đầu tiên (từ 1 đến 5) thì điều đó không cho thấy điều đó ... Làm thế nào chúng ta có thể chỉ ra những khoảng trống khó hiểu ngay từ đầu?
DiegoDD

Lưu ý: Truy vấn này không hoạt động trên các bảng tạm thời. Vấn đề của tôi là order numbertôi đang tìm kiếm các khoảng trống trong không khác biệt (bảng lưu trữ các dòng thứ tự, vì vậy số thứ tự chúng thuộc về lặp lại cho mỗi dòng). Truy vấn đầu tiên: 2812 hàng trong bộ (1 phút 31,09 giây) . Tạo một bảng khác bằng cách chọn các số thứ tự riêng biệt. Truy vấn của bạn mà không cần lặp đi lặp lại của tôi: 1009 hàng trong set (18,04 giây)
Chris K

1
@DiegoDD Có chuyện gì vậy SELECT MIN(id) FROM table?
Air

8
Làm việc nhưng mất khoảng 5 giờ để chạy trên bàn với 700000 bản ghi
Matt

98

Điều này chỉ giúp tôi tìm ra khoảng trống trong một bảng có hơn 80 nghìn hàng:

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;

Kết quả:

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

Lưu ý rằng thứ tự của các cột expectedgotrất quan trọng.

Nếu bạn biết rằng điều YourColđó không bắt đầu từ 1 và điều đó không quan trọng, bạn có thể thay thế

(SELECT @rownum:=0) AS a

với

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

Kết quả mới:

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

Nếu bạn cần thực hiện một số loại tác vụ shell script trên các ID bị thiếu, bạn cũng có thể sử dụng biến thể này để tạo trực tiếp một biểu thức mà bạn có thể lặp lại trong 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;

Điều này tạo ra một đầu ra như vậy

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

Sau đó, bạn có thể sao chép và dán nó vào một vòng lặp for trong một thiết bị đầu cuối bash để thực hiện một lệnh cho mọi ID

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

Nó giống như ở trên, chỉ khác là nó vừa có thể đọc được vừa có thể thực thi. Bằng cách thay đổi lệnh "CONCAT" ở trên, cú pháp có thể được tạo cho các ngôn ngữ lập trình khác. Hoặc thậm chí có thể là SQL.


8
giải pháp tốt đẹp, đối với tôi nó là tốt hơn so với câu trả lời ưa thích - nhờ
Wee Zel

6
nhiều hiệu quả hơn câu trả lời được chấp nhận.
symcbean

1
xa nhanh hơn so với câu trả lời được chấp nhận. Điều duy nhất tôi muốn thêm là nó CONVERT( YourCol, UNSIGNED )sẽ cho kết quả tốt hơn nếu YourCol chưa phải là số nguyên.
Barton Chittenden

1
@AlexandreCassagne: Nếu tôi hiểu câu hỏi của bạn một cách chính xác, tôi sẽ chỉ cần làm một truy vấn riêng biệt như một nhúng cho việc tìm kiếm những phút:SELECT MAX(YourCol) FROM YourTable;
ConfexianMJS

1
@temuri Chuyển sang biến thể GROUP_CONCAT nếu cần: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

Truy vấn nhanh và bẩn sẽ thực hiện thủ thuật:

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

Điều này sẽ cung cấp cho bạn một bảng hiển thị id có id bị thiếu ở trên nó và next_id tồn tại, và số lượng bị thiếu giữa ... ví dụ:

 
id next_id thiếu_inbetween
 1 4 2
68 70 1
75 87 11

1
Nó hiệu quả tuyệt vời đối với tôi. Cảm ơn.! Tôi đã có thể dễ dàng sửa đổi điều này cho các mục đích của mình.
Rahim Khoja

Có vẻ như đây là câu trả lời tốt nhất khi tìm kiếm 'id tiếp theo' trong khoảng trống. Thật không may là nó CỰC KỲ chậm đối với các bảng có 10K hàng. Tôi đã đợi hơn 10 phút trên bàn ~ 46K trong khi với @ConfexianMJS, tôi nhận được kết quả sau chưa đầy một giây!
Mang theo Ba lô, Hàng hóa 64

5

Nếu bạn đang sử dụng, MariaDBbạn có tùy chọn nhanh hơn (800%) bằng cách sử dụng công cụ lưu trữ trình tự :

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

2
để mở rộng ý tưởng này, tối đa của chuỗi có thể được thiết lập bằng cách sử dụng "SELECT MAX(column) FROM table"và đặt một biến từ kết quả nói rằng $ MAX ... câu lệnh sql sau đó có thể được viết "SELECT * FROM seq_1_to_". $MAX ." WHERE seq not in (SELECT column FROM table)" cú pháp của tôi là php based
me_

hoặc bạn có thể sử dụng SELECT @var:= max FROM ....; select * from .. WHERE seq < @max;với các biến MySQL.
Moshe L

2

Tạo một bảng tạm thời có 100 hàng và một cột duy nhất chứa các giá trị 1-100.

Bên ngoài Nối bảng này vào bảng arrc_vouchers của bạn và chọn các giá trị cột đơn trong đó id arrc_vouchers là null.

Mã hóa điều này mù, nhưng sẽ hoạt động.

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

OK, 1 - 100 chỉ là một cách dễ dàng để đưa ra một ví dụ. Trong trường hợp này, chúng tôi đang xem xét 20.000 - 85.000. Vậy tôi có tạo một bảng tạm thời với 65.000 hàng được đánh số 20000 - 85000 không? Và làm thế nào để tôi làm điều đó? Tôi đang sử dụng phpMyAdmin; Nếu tôi đặt giá trị mặc định của cột là 25000 và làm cho cột tự động tăng lên, tôi có thể chỉ chèn 65.000 hàng và nó sẽ bắt đầu tự động tăng với 25000 không?
EmmyS

Tôi đã gặp trường hợp tương tự (tôi có 100 mặt hàng theo thứ tự và cần tìm các mặt hàng còn thiếu trong 100). Để làm điều này, tôi đã tạo một bảng 1-100 khác, sau đó thực hiện câu lệnh này trên đó và nó hoạt động rất đẹp. Điều này thay thế một chức năng rất phức tạp để tạo bảng tạm thời. Chỉ là lời khuyên cho ai đó trong tình huống tương tự, đôi khi tạo một bảng nhanh hơn bảng tạm thời.
newshorts

2

Một giải pháp thay thế yêu cầu truy vấn + một số mã thực hiện một số xử lý sẽ là:

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;

Lưu ý rằng truy vấn không chứa bất kỳ lựa chọn con nào mà chúng tôi biết rằng nó không được trình lập kế hoạch của MySQL xử lý hiệu quả.

Điều đó sẽ trả về một mục nhập cho mỗi CentralValue (cValue) không có giá trị nhỏ hơn (lValue) hoặc giá trị lớn hơn (rValue), tức là:

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


Nếu không đi sâu vào chi tiết (chúng ta sẽ xem chúng trong các đoạn tiếp theo), kết quả này có nghĩa là:

  • Không có giá trị nào từ 0 đến 2
  • Không có giá trị nào từ 9 đến 22
  • Không có giá trị nào từ 24 đến 29
  • Không có giá trị nào từ 29 đến 33
  • Không có giá trị nào từ 33 đến MAX VALUE

Vì vậy, ý tưởng cơ bản là thực hiện kết hợp RIGHT và LEFT với cùng một bảng để xem liệu chúng ta có các giá trị phụ trên mỗi giá trị hay không (nghĩa là: nếu giá trị trung tâm là '3' thì chúng ta kiểm tra 3-1 = 2 ở bên trái và 3 + 1 ở phải), và khi ROW có giá trị NULL tại RIGHT hoặc LEFT thì chúng ta biết không có giá trị liền kề.

Đầu ra thô hoàn chỉnh của bảng của tôi là:

select * from arrc_vouchers order by id asc;

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

Một số lưu ý:

  1. Câu lệnh SQL IF trong điều kiện nối là cần thiết nếu bạn xác định trường 'id' là UNSIGNED, do đó nó sẽ không cho phép bạn giảm nó xuống dưới 0. Điều này không hoàn toàn cần thiết nếu bạn giữ giá trị c.value> 0 như được nêu trong ghi chú tiếp theo, nhưng tôi đưa nó vào chỉ như doc.
  2. Tôi đang lọc giá trị trung tâm bằng 0 vì chúng tôi không quan tâm đến bất kỳ giá trị nào trước đó và chúng tôi có thể lấy giá trị bài đăng từ hàng tiếp theo.

2

Nếu có một chuỗi có khoảng cách lớn nhất giữa hai số (như 1,3,5,6) thì truy vấn có thể được sử dụng là:

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);
  • tên_bảng - source1
  • tên cột dọc - id

1

dựa trên câu trả lời được đưa ra ở trên bởi Lucek, thủ tục được lưu trữ này cho phép bạn chỉ định tên bảng và cột mà bạn muốn kiểm tra để tìm các bản ghi không liền nhau - do đó trả lời câu hỏi ban đầu và cũng chứng minh cách người ta có thể sử dụng @var để biểu diễn bảng & / hoặc các cột trong một thủ tục được lưu trữ.

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

Tôi đã thử nó theo các cách khác nhau và hiệu suất tốt nhất mà tôi tìm thấy là truy vấn đơn giản sau:

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
;

... một phép nối bên trái để kiểm tra xem id tiếp theo có tồn tại hay không, chỉ khi tiếp theo nếu không được tìm thấy, sau đó truy vấn con tìm id tiếp theo tồn tại để tìm cuối khoảng trống. Tôi đã làm điều đó vì truy vấn với bằng (=) có hiệu suất tốt hơn toán tử lớn hơn (>).

Sử dụng sqlfiddle, nó không hiển thị quá khác biệt so với các truy vấn khác nhưng trong cơ sở dữ liệu thực, truy vấn trên cho kết quả nhanh hơn 3 lần so với các truy vấn khác.

Lược đồ:

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

Thực hiện theo tất cả các truy vấn mà tôi đã thực hiện để so sánh hiệu suất:

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
;

Có thể nó sẽ giúp ích cho ai đó và hữu ích.

Bạn có thể xem và kiểm tra truy vấn của tôi bằng sqlfiddle này :

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


0

Mặc dù tất cả những điều này dường như hoạt động, tập kết quả trả về trong một thời gian rất dài khi có 50.000 bản ghi.

Tôi đã sử dụng điều này và nó tìm thấy khoảng trống hoặc khoảng trống tiếp theo có sẵn (được sử dụng lần cuối + 1) với lợi nhuận nhanh hơn nhiều từ truy vấn.

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;

điều này tìm ra lỗ hổng đầu tiên không phải là những gì câu hỏi đã yêu cầu.
nhạt nhẽo

0

Có lẽ không liên quan, nhưng tôi đã tìm kiếm một cái gì đó như thế này để liệt kê các khoảng trống trong một dãy số và tìm thấy bài đăng này, có nhiều giải pháp khác nhau tùy thuộc vào chính xác những gì bạn đang tìm kiếm. Tôi đang tìm khoảng trống có sẵn đầu tiên trong chuỗi (tức là số có sẵn tiếp theo) và điều này có vẻ hoạt động tốt.

CHỌN MIN (l.number_sequence + 1) làm nextavabile từ bệnh nhân với tư cách là bệnh nhân LEFT OUTER THAM GIA với tư cách r trên l.number_sequence + 1 = r.number_sequence TRONG ĐÓ r.number_sequence là NULL. Một số kịch bản và giải pháp khác đã được thảo luận ở đó, từ năm 2005!

Làm thế nào để tìm các giá trị bị thiếu trong một chuỗi với SQL

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.