Как найти пробелы в последовательной нумерации в MySQL? - PullRequest
101 голосов
/ 03 декабря 2010

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

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

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

Возможно ли это?

Ответы [ 11 ]

159 голосов
/ 19 мая 2011

Обновление

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 - последний идентификатор в текущем разрыве
63 голосов
/ 20 апреля 2015

Это просто помогло мне найти пробелы в таблице, содержащей более 80 тыс. Строк:

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)

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

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

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

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

8 голосов
/ 03 декабря 2010

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

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

Это даст вам таблицу, показывающую идентификатор, у которого пропущены идентификаторы, и следующий_идентификатор, который существует, и сколько пропущено между ними...eg

id  next_id  missing_inbetween
 1        4                  2
68       70                  1
75       87                 11
3 голосов
/ 10 января 2017

Если вы используете MariaDB, у вас есть более быстрый вариант (800%) с использованием механизма хранения последовательности :

SELECT * FROM seq_1_to_50000 WHERE SEQ NOT IN (SELECT COL FROM TABLE);
2 голосов
/ 08 марта 2014

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

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 * ​​1016 *
  • Нет значений от 24 до 29
  • Нет значений от 29 до 33
  • Нет значений от 33 до МАКС. ЗНАЧЕНИЕ

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

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

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 в условии соединения необходима, если вы определили поле 'id' как UNSIGNED, поэтому оно не позволит вам уменьшить его до нуля.Это не является строго необходимым, если вы сохраняете значение c.value> 0, как указано в следующей заметке, но я включаю его так же, как и документ.
  2. Я фильтрую нулевое центральное значение, поскольку мы незаинтересованы в любом предыдущем значении, и мы можем получить значение сообщения из следующей строки.
2 голосов
/ 03 декабря 2010

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

Outer. Присоедините эту таблицу к своей таблице arrc_vouchers и выберите значения в одном столбце, где идентификатор arrc_vouchers равен нулю.

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

select tempid from temptable 
left join arrc_vouchers on temptable.tempid = arrc_vouchers.id 
where arrc_vouchers.id is null
1 голос
/ 24 августа 2018

Если есть последовательность, имеющая интервал не более одного между двумя числами (например, 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
  • имя_ столбца - id
1 голос
/ 17 февраля 2015

на основе ответа, данного выше 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
0 голосов
/ 13 апреля 2019

Вероятно, не имеет значения, но я искал что-то вроде этого, чтобы перечислить пробелы в последовательности чисел, и нашел этот пост, который имеет несколько различных решений в зависимости от того, что именно вы ищете.Я искал первый доступный пробел в последовательности (то есть следующий доступный номер), и это, кажется, работает нормально.

SELECT MIN (l.number_sequence + 1) как nextavabile от пациентов как l LEFT OUTER JOIN пациентовкак r на l.number_sequence + 1 = r.number_sequence ГДЕ r.number_sequence равен NULL.Несколько других сценариев и решений, обсуждаемых там, начиная с 2005 года!

Как найти пропущенные значения в последовательности с SQL

0 голосов
/ 09 февраля 2019

Я попробовал по-разному, и лучшая производительность, которую я нашел, - это простой запрос:

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
;

... одно соединение слева, чтобы проверить, существует ли следующий идентификатор , только если следующий, если не найден, то подзапрос находит следующий существующий идентификатор, чтобы найти конец пропуска. Я сделал это, потому что запрос с равен (=) лучше, чем больше, чем оператор (>).

Использование 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

...