2013-08-15 2 views
0

У меня есть таблица с столбцами min и max value, которые могут иметь перекрывающиеся данные.Mysql выберите пробелы в строках с минимальными и максимальными данными

Пример

+--------+--------+ 
| Minval | Maxval | 
+--------+--------+ 
| 0000 | 1000 | 
| 1500 | 8999 | 
| 0100 | 0200 | 
| 5000 | 6999 | 
+--------+--------+ 

Возможные диапазоны для минимальных и максимальных значений являются 0000-9999.

Я ищу способ найти пробелы (данные) в данных. С вышеуказанными данными пробелы будут 1001-1499 и 9000-9999.

Я работаю с php и mysql.

+0

Что такое тип данных для min и max varchar? Почему бы не int? Почему есть ведущие нули? – user4035

+0

Datatype is char (4) – Chinaski

+3

Почему вы храните целые значения в столбцах char? – user4035

ответ

1

Результат может быть возвращен MySQL-запросом без сценария.

SELECT CONCAT(LPAD(r.lo,4,'0'),'-',LPAD(r.hi,4,'0')) AS gap 
    , r.lo 
    , r.hi 
-- , d.minval IS NULL AS gap 
-- , d.* 
    FROM (SELECT rl.lo, rh.hi 
      FROM (SELECT 0000 AS lo UNION 
       SELECT rlo.maxval+1 
        FROM example1 rlo 
        WHERE rlo.maxval < 9999 
       ) rl 
      JOIN (SELECT 9999 AS hi UNION 
        SELECT rhi.minval-1 
        FROM example1 rhi 
        WHERE rhi.minval > 0000 
       ) rh 
       ON rh.hi >= rl.lo 
      GROUP BY rl.lo, rh.hi 
     ) r 
    LEFT 
    JOIN example1 d 
    ON r.lo BETWEEN d.minval+0 AND d.maxval+0 
    OR r.hi BETWEEN d.minval+0 AND d.maxval+0 
    OR d.minval+0 BETWEEN r.lo AND r.hi 
    OR d.maxval+0 BETWEEN r.lo AND r.hi 
WHERE d.minval IS NULL 
ORDER 
    BY r.lo, r.hi 
-- , d.minval, d.maxval 

Подход, который я использовал, состоял в том, чтобы начать с набора всех возможных разрывов. Мы знаем, что каждый потенциальный разрыв будет:

  • старт на 0000 или любой maxval+1
  • конца в 9999 или любой minval-1

Итак, мы можем создать список всех возможных «начала разрыва» и список всех возможных «пробелов». (Я думал об этом как о «диапазонах», когда писал запрос. Я использовал псевдонимы для встроенных просмотров, r для «range», rh для «высокого» конца диапазона и rl для «низкого» конца диапазон

Введенный псевдоним «r» возвращает все возможные строки, которые могут быть пробелом. (Это почти крест-соединение, но мы исключаем строки, где r.hi будет меньше, чем r.lo. I подумайте, что фактический термин, который используется для этого, - это «объединение неравенства».)

Образец антисоединения назад к исходной таблице диапазонов исключает строки, которые не являются реальными пробелами, поскольку существует некоторое совпадение с одним из диапазонов в таблицу. (Шаблон антисоединения - ЛЕВЫЙ ПРИСОЕДИНЕНИЕ, а затем предикат в предложении WHERE для исключения строк был результатом операции LEFT JOIN, и мы оставили строки, которые не имели соответствия. В качестве альтернативы, такое же устранение может быть выполнено с использованием подхода NOT EXISTS (correlated subquery).)

(Этот запрос будет также возвращать разрыв 0000-9999, когда нет ни одной строки в таблице диапазонов.)

Для тестовой установки случае (я настроил OP 0000 значения 0055, чтобы продемонстрировать, что это будет идентифицировать пробел, который начинается 0000.)

CREATE TABLE `example1` (minval INT(4), maxval INT(4)); 
INSERT INTO `example1` VALUES (0055,1000),(1500,8999),(0100,0200),(5000,6999); 


gap   lo  hi 
--------- ----- ----- 
0000-0054  0  54 
1001-1499 1001 1499 
9000-9999 9000 9999 

EDIT

Я просто заметил, что О.П. комментарий о том, что да tatype - CHAR (4), я предположил, что это целочисленный тип, вероятно, INT(4) ZEROFILL. В приведенном выше запросе также будут работать типы CHAR, но мы должны убедиться, что CHAR преобразуется в целое число, а самый простой способ сделать это - добавить «+ 0» к ссылкам столбца, эта настройка выполняется и протестирована.

CREATE TABLE `example1` (minval CHAR(4), maxval CHAR(4)); 
INSERT INTO `example1` VALUES ('0055','1000') 
    ,('1500','8999'),('0100','0200'),('5000','6999'); 
+0

Это более полно. Спасибо за объяснение. Я все еще пытаюсь разобрать, как это сделать. – Chinaski

+0

@ Chinaski: трюк заключается в том, что запрос 'r' (вы можете запускать его отдельно), чтобы генерировать все возможные диапазоны.' Rl' получает все возможные «min» для возможного разрыва, 'rh' получает всевозможные« max »для возможный разрыв, мы делаем соединение и получаем все возможные промежутки (единственные комбинации lo и hi t шляпа не будет пробелом, когда hi меньше, чем lo. следующий трюк - это «anti-join» ..., чтобы выяснить, какой из всех возможных пробелов на самом деле не является пробелом, потому что они перекрывают хотя бы одно значение в определенном диапазоне. мы выбрасываем строки, которые соответствуют, и то, что у нас осталось, - это реальные пробелы (не перекрываются с диапазоном.) – spencer7593

+0

@ Чинаски: Добро пожаловать в StackOverflow! Вы найдете здесь людей, которые хотят вам помочь; пожалуйста, не обескураживайте нисходящие голоса и голоса, чтобы закрыть вопрос (у downvoters есть причина того, что они делают, и это не о вас). – spencer7593

0

Вы, конечно, нужен PHP код

CREATE TABLE TEMP_TABLE (NUMBER_VALUE INT); 

INSERT INTO TEMP_TABLE VALUES (1,2,3,....9999); 

вы можете сделать это с помощью цикла PHP или создать постоянный запрос один раз и использовать его

Тогда для каждой строки в основной таблице сделать

DELETE FROM TEMP_TABLE WHERE NUMBER_VALUE BETWEEN MINVAL AND MAXVAL; 

После удаления у вас останутся пробелы, но все номера. Затем вы можете пропустить их, чтобы сохранить min и max в PHP

+0

- самый эффективный подход к этим действительно временным таблицам ...? – James

+0

Каков наилучший способ преобразования списка значений обратно в диапазон, например. '' 1001-1499'', а не сообщать о пробелах как «1001-1001», «1002-1002», «1003-1003» и т. Д. – spencer7593

+0

@ Джеймс - я не думаю, что это самый эффективный , но полностью решает цель – skv

3

Вам не нужен код php. Вы можете сделать это в SQL.

Зазор будет начинаться с одного максимума, чем maxval и заканчиваться на один минус минус. Затем вам просто нужно выяснить, участвует ли конкретная запись в пробеле.

select t.*, 
     t.maxval+1 as gapStart, 
     (select min(t2.minval) - 1 
     from t t2 
     where t2.minval > t.maxval 
     ) as gapEnd 
from t 
where not exists (select 1 
        from t t2 
        where t.maxval + 1 between t2.minval and t2.maxval 
       ) 
+0

Это победитель. Спасибо за это. – Chinaski

+0

+1. Я согласен, что результаты могут быть возвращены только с MySQL. Но есть краевой кейс, который я не думаю, что это ручка: промежуток, начинающийся с '0000'. (Например, если эта строка '(0000,1000) была изменена на' (0055,1000) '). Я придерживался аналогичного подхода. Я создал набор всех возможных зазоров (мы знаем, что любой зазор начнется и закончится в 0000, 9999, minval-1 или maxval + 1. Прямо получить весь набор всех «возможных зазоров» (крест join omitting minval> maxval, я должен был UNION в 0000 и 9999). Что не работает в моем запросе - это удаление всех строк, которые не являются реальными пробелами. – spencer7593

+0

@Chinaski ... В качестве примечания, если вы хотите обработать два крайних случая, вы можете сделать это с инструкциями 'union all', по сравнению с наименьшим minval и максимальным значением maxval. –

Смежные вопросы