Таблица автоматической регулировки диапазона - PullRequest
2 голосов
/ 29 марта 2010

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

Я использую Oracle и Java для кода своего приложения. Как мне обеспечить предотвращение перекрывающихся диапазонов дат, а также разрешить автоматическую настройку перекрывающихся диапазонов? Должен ли я создать триггер AFTER INSERT с dbms_lock для сериализации доступа, чтобы предотвратить наложение данных. Тогда в Java примените логику для автоматической настройки всего? Или эта часть должна быть в PL / SQL при вызове хранимой процедуры? Это то, что нам нужно для пары других таблиц, поэтому было бы неплохо абстрагироваться.

Если у кого-то уже есть что-то подобное, пожалуйста, поделитесь:)

Я нашел эту ссылку: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:474221407101

Вот пример того, как каждый из 4 перекрывающихся случаев должен обрабатываться для корректировки при вставке:

= Example 1 =
In DB (Start, End, Value):
(0, 10, 'X')
**(30, 100, 'Z')
(200, 500, 'Y')

Input
(20, 50, 'A')
Gives
(0, 10, 'X')
**(20, 50, 'A')
**(51, 100, 'Z')
(200, 500, 'Y')


= Example 2 =
In DB (Start, End, Value):
(0, 10, 'X')
**(30, 100, 'Z')
(200, 500, 'Y')

Input 
(40, 80, 'A')
Gives
(0, 10, 'X')
**(30, 39, 'Z')
**(40, 80, 'A')
**(81, 100, 'Z')
(200, 500, 'Y')


= Example 3 =
In DB (Start, End, Value):
(0, 10, 'X')
**(30, 100, 'Z')
(200, 500, 'Y')

Input
(50, 120, 'A')
Gives
(0, 10, 'X')
**(30, 49, 'Z')
**(50, 120, 'A')
(200, 500, 'Y')


= Example 4 =
In DB (Start, End, Value):
(0, 10, 'X')
**(30, 100, 'Z')
(200, 500, 'Y')

Input
(20, 120, 'A')
Gives
(0, 10, 'X')
**(20, 120, 'A')
(200, 500, 'Y')

Алгоритм выглядит следующим образом:

given range = g; input range  = i; output range set = o

if i.start <= g.start
  if i.end >= g.end
    o_1 = i
  else
    o_1 = i
    o_2 = (i.end + 1, g.end)
else
  if i.end >= g.end
    o_1 = (g.start, i.start - 1)
    o_2 = i
  else
    o_1 = (g.start, i.start - 1)
    o_2 = i
    o_3 = (i.end + 1, g.end)

Ответы [ 2 ]

2 голосов
/ 29 марта 2010

Я обычно видел такие модели данных, в которых отправная точка диапазона является единственной отслеживаемой, где конечная точка затем неявная. Так было бы

CREATE TABLE MY_TABLE
(START_AT    NUMBER,
 VALUE       NUMBER,
 CONSTRAINT MY_TABLE_PK (START_AT)
);

Если вам нужно представить значения в существующем формате, вы можете использовать аналитику и материализованное представление, используя LEAD(START_AT) OVER (ORDER BY START_AT) (я думаю, что это правильно, но не проверено), чтобы получить интерпретированное конечное значение.

0 голосов
/ 30 марта 2010

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

Если для вас важен параллелизм, вам нужно просто добавить столбец последовательности (с опцией ORDER, если вы используете RAC) и написать запрос, подобный этому:

SELECT  *
FROM    (
        SELECT  *, rownum AS rn
        FROM    mytable
        WHERE   start_date <= :date
                AND end_date >= :date
        ORDER BY
                seq DESC
        )
WHERE   rn = 1

чтобы узнать эффективный диапазон (и другие данные) на данную дату.

Возвращает последний вставленный диапазон, содержащий данную дату.

Вы можете сделать этот запрос немного более эффективным, запустив процедуру обслуживания, которая бы своевременно избавлялась от перекрывающихся диапазонов (как описано в посте), и переписав запрос следующим образом:

SELECT  *
FROM    (
        SELECT  *, rownum AS rn2
        FROM    (
                SELECT  *
                FROM    (
                        SELECT  *, rownum AS rn
                        FROM    mytable
                        WHERE   seq <= :lseq
                                AND start_date <= :date
                                AND end_date >= :date
                        ORDER BY
                                start_date DESC
                        )
                WHERE   rn = 1
                UNION ALL
                SELECT  *
                FROM    (
                        SELECT  *, rownum AS rn
                        FROM    mytable
                        WHERE   seq > :lseq
                                AND start_date <= :date
                                AND end_date >= :date
                        ORDER BY
                                seq DESC
                        )
                WHERE   rn = 1
                )
        ORDER BY
                seq DESC
        )
WHERE   rn2 = 1

Создайте индексы для start_date и seq, чтобы это работало быстро.

Последний запрос выберет первый подходящий диапазон из обработанных диапазонов (которые, как известно, не перекрываются), первый соответствующий диапазон из необработанных диапазонов (которых мало) и из двух записей выберет один с самым высоким seq.

...