it-roy-ru.com

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

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

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

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

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

94
EmmyS

Обновление

ConfexianMJS предоставил намного лучшеanswer с точки зрения производительности.

Ответ (не так быстро, как это возможно)

Вот версия, которая работает с таблицей любого размера (не только с 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 - последний идентификатор в текущем промежутке 
152
matt

Это просто помогло мне найти пробелы в таблице с более чем 80k строк:

SELECT
 CONCAT(z.expected, IF(z.got-1>z.expected, CONCAT(' thru ',z.got-1), '')) AS missing
FROM (
 SELECT
  @rownum:[email protected]+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)

Если вам нужно выполнить какую-либо задачу сценария Shell для отсутствующих идентификаторов, вы также можете использовать этот вариант для непосредственного создания выражения, которое вы можете перебирать в 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, как и ожидалось, ЕСЛИ (@ rownum = высота, 0, @rownum: = высота) AS получил FROM (SELECT @rownum: = 0), как блок JOIN ORDER BY по высоте) AS z ГДЕ 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.

53
ConfexianMJS

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

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 
8
Ben

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

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 и МАКС. ЗНАЧЕНИЕ

Таким образом, основная идея состоит в том, чтобы сделать правые и левые соединения с одной и той же таблицей, видя, есть ли у нас значения смежности на значение (то есть: если центральное значение равно «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
mgo1977

Создайте временную таблицу со 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
2
amelvin

основываясь на ответе, данном выше 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
RamRaider

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

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

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

1
Moshe L

Хотя кажется, что все это работает, результирующий набор возвращается в течение очень длительного времени, когда имеется 50000 записей.

Я использовал это, и он нашел пробел или следующий доступный (последний использованный + 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;
0
Rob

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

0
Tsvetelin Salutski

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

ВЫБЕРИТЕ МИНУТУ (l.number_sequence + 1) как nextavabile от пациентов, поскольку l ВНЕШНЕЕ ОБЪЕДИНЕНИЕ пациентов, как r на l.number_sequence + 1 = r.number_sequence ГДЕ r.number_sequence равен NULL. Несколько других сценариев и решений, обсуждаемых там, с 2005 года!

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

0
sscotti

Если есть последовательность, имеющая промежуток не более одного между двумя числами (как 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
0
PRAKHAR GUPTA

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

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, только если next, если не найдено, то подзапрос находит следующий существующий 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
lynx_74