Как найти пробелы в последовательной нумерации в mysql?
у нас есть база данных с таблицей, значения которых были импортированы из другой системы. Существует столбец автоматического приращения, и нет повторяющихся значений, но есть отсутствующие значения. Например, выполнение этого запроса:
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 и т. д.
это возможно?
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
- первый id в текущем зазоре -
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;
это производит вывод, как so
$(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456)
затем вы можете скопировать и вставить его в цикл for в терминале bash для выполнения команды для каждого ID
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;
обратите внимание, что запрос не содержит никакого подселекта, который мы знаем, что он не обрабатывается performantly планировщиком 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
некоторые замечания:
- оператор SQL IF в условии join необходим, если вы определяете поле " id " как UNSIGNED, поэтому он не позволит вам уменьшить его до нуля. Это не обязательно, если вы держите c.значение > 0, как указано в следующем примечании, но я включаю его просто как док.
- я фильтрую нулевое центральное значение, поскольку нас не интересует какое-либо Предыдущее значение, и мы можем получить значение post из следующей строки.
Если вы используете MariaDB
у вас есть более быстрый (800%) вариант
SELECT * FROM seq_1_to_50000 where seq not in (select col from table);
на основе ответа, данного выше 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. Затем он не присоединился между таблицей temp и нашей таблицей интересов.
Если вы вызвали его с Max = Select max (id) из arrc_vouchers, он вернет все отсутствующие значения.