Как найти пробелы в последовательной нумерации в mysql?


у нас есть база данных с таблицей, значения которых были импортированы из другой системы. Существует столбец автоматического приращения, и нет повторяющихся значений, но есть пропущенные значения. Например, выполнение этого запроса:

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

должен вернуть 100, но вместо этого он возвращает 87. Есть ли запрос, который я могу запустить, который вернет значения отсутствующих чисел? Например, записи могут существовать для идентификаторов 1-70 и 83-100, но нет записей с идентификаторами 71-82. Я хочу вернуться 71, 72, 73 и т. д.

это возможно?

11 93

11 ответов:

обновление

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 - первый идентификатор в текущем промежутке
  • gap_ends_at - последний идентификатор в текущем промежутке

это просто сработало для меня, чтобы найти пробелы в таблице с более чем 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)

обратите внимание, что порядок столбцов expected и got имеет решающее значение.

если вы знаете, что YourCol не начинается с 1, и это не имеет значения, вы можете заменить

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

Если вам нужно выполнить какую-то задачу скрипт на отсутствует IDs, вы также можете использовать этот вариант для того, чтобы непосредственно создать выражение, которое вы можете перебирать в bash.

SELECT GROUP_CONCAT(IF(z.got-1>z.expected, CONCAT('$(',z.expected,' ',z.got-1,')'), z.expected) SEPARATOR " ") AS missing

(выбираете @rownum:=@rownum+1 как и следовало ожидать, если(@параметр rownum=высота, 0, @параметр rownum:=высота), как получил от (выберите @параметр rownum:=0), как вступить заблокировать порядке по высоте ) как Z, где z.достали!=0;

это производит такой output

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

вы можете затем скопировать и вставить его в цикл for в bash терминале выполнить команду для каждого Идентификатор

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

это то же самое, что и выше, только что он читается и исполняется. Изменяя команду" CONCAT " выше, синтаксис может быть сгенерирован для других языков программирования. Или, может быть, даже SQL.

быстрый и грязный запрос, который должен сделать трюк:

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

это даст вам таблицу, показывающую идентификатор, который имеет идентификаторы, отсутствующие над ним, и next_id, который существует, и сколько их отсутствует между ними...например,

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

создайте временную таблицу со 100 строками и одним столбцом, содержащим значения 1-100.

внешний присоедините эту таблицу к вашей таблице arrc_vouchers и выберите значения одного столбца, где идентификатор arrc_vouchers равен null.

кодирование это слепое, но должно работать.

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

альтернативное решение, которое требует запроса + некоторый код, выполняющий некоторую обработку, будет:

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.

это вернет одну запись на centralValue (cValue), которая не имеет меньшего значения (lValue) или большего значения (rValue), т. е.:

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

таким образом, основная идея состоит в том, чтобы сделать правое и левое соединения с той же таблицей, видя, если у нас есть значения смежности на значение (т. е.: если центральное значение '3', то мы проверяем 3-1=2 слева и 3+1 справа), и когда строка имеет нулевое значение справа или слева, то мы знаем, что нет соседнего значения.

полный сырой выход моей таблицы:

select * from arrc_vouchers order by id asc;

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

некоторые замечания:

  1. оператор SQL IF в условии join необходим, если вы определяете поле 'id' как UNSIGNED, поэтому он не позволит вам уменьшить его до нуля. Это не является строго необходимым, если вы сохраняете c. value > 0, как указано в следующей заметке, но я включаю его просто как док.
  2. я фильтрую нулевое центральное значение, поскольку нас не интересует какое-либо Предыдущее значение, и мы можем получить значение post из следующей строки.

Если вы используете MariaDB у вас есть более быстрый (800%) вариант

SELECT * FROM seq_1_to_50000 where seq not in (select col from table);

https://mariadb.com/kb/en/mariadb/sequence/

на основе ответа, данного выше 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

хотя все это, кажется, работает, результирующий набор возвращается в очень долгое время, когда есть 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;

вы можете использовать generate series для генерации чисел от 1 до самого высокого идентификатора вашей таблицы. Затем запустите запрос, где id не в этой серии.

Если есть последовательность, имеющая разрыв не более одного между двумя числами (например 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);
  • имя_таблицы -source1
  • column_name -id

Это может не работать в MySQL, но на работе (Oracle) нам нужно было что-то подобное.

мы написали хранимый Proc, который взял число в качестве максимального значения. Затем сохраненный Proc создал временную таблицу с одним столбцом. В таблице содержатся все числа от 1 до Max. Затем он сделал не в соединении между временной таблицей и нашей таблицей интереса.

Если вы вызвали его с помощью Max = Select max (id) из arrc_vouchers, он вернет все отсутствующие значения.