Справочник с интервалом дат

Модераторы: kdv, CyberMax

Ответить
AnViSe
Сообщения: 4
Зарегистрирован: 08 фев 2007, 10:12

Справочник с интервалом дат

Сообщение AnViSe » 10 июл 2008, 18:40

Задача:
Нужно сделать справочник процентных ставок которые действуют с какой-то даты и по какую-то дату.
В результате надо получить процентную ставку действовавшую на определенную дату.

Предполагаемое решение:
Завести два поля типа дата в которых будут занесены даты начала и окончания действия это % ставки. Затем BETWEEN -ом найти нужноую строку.

Проблема:
Как быть с % ставкой действующей на текущий момент, у нее нет даты окончания. Как вариант можно занести в это поле заоблачную дату (типа 3000 год). Но тогда при вводе новой ставки надо у предыдущих изменять эту дату на другую - реальную, а для новой опять ставить заоблачную...

Вопрос:
Может есть какое более изящное решение?

mdfv
Сообщения: 119
Зарегистрирован: 23 май 2006, 15:53

Сообщение mdfv » 10 июл 2008, 18:47

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

Код: Выделить всё

select * from stavki
where startdate<=:искомой
order by startdate desc
rows 1

AnViSe
Сообщения: 4
Зарегистрирован: 08 фев 2007, 10:12

Сообщение AnViSe » 10 июл 2008, 18:59

Т.е. для FB будет типа следующего:

Код: Выделить всё

select frist 1 * from stavki 
where startdate<=:искомой 
order by startdate desc
Правильно?

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 10 июл 2008, 19:07

Вариант без диапазона медленнее, на существенном объёме данных будет тормозить, и не предусматривает варианта, когда в какой-то период вообще нет процентной ставки.
Я бы диапазоном делал. А что апдейтить надо - ничего страшного не вижу.

У меня это выглядит примерно так (изменения вношу процедурой):

Код: Выделить всё

MaxDate = '01.01.3000';

/* находим ставку, если уже есть в архиве */
ArchValue=0; ArchDateBeg='01.01.1900'; RecID=null;
select RecID, aValue, DateBeg, DateEnd
  from Archive
  where ValId = :ValId and :CheckDate between DateBeg and DateEnd
  into RecID, ArchValue, ArchDateBeg, ArchDateEnd;

/* если значение отличается от существующей записи */
if (ArchiveValue != aValue) then begin
    /* если дата начала совпадает с найденным диапазоном, то меняем существующую запись */
    if (ArchDateBeg = CheckDate) then
      update Archive
        set aValue = :NewValue, DateBeg = :CheckDate, DateEnd = :MaxDate
        where RecID = :RecID and
              aValue != :NewValue and DateBeg != :CheckDate and DateEnd != :MaxDate;
    else
    /* иначе проверяем изменение значения */
    if (ArchDateBeg < CheckDate) then begin
      /* закрываем предыдущий диапазон */
      if (RecID is not null) then
        update Archive set DateEnd = :CheckDate-1 where RecID = :RecID;
      /* вставляем новый */
      insert into Archive (ValId, aValue, DateBeg, DateEnd)
        values (:ValId, :NewValue, :CheckDate, :MaxDate);
    end
end
Особенность такого подхода - можно сколько угодно раз в день вставлять "текущие" значения процедурой - количество диапазонов не изменится, будет запомнено последнее решение.

mdfv
Сообщения: 119
Зарегистрирован: 23 май 2006, 15:53

Сообщение mdfv » 10 июл 2008, 19:12

версия сервера какая?
Вариант без диапазона медленнее, и не предусматривает варианта, когда в какой-то период вообще нет процентной ставки.
Так ведь такая ситуация имеет место быть, что ставки когда-то небыло.

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Re: Справочник с интервалом дат

Сообщение Merlin » 10 июл 2008, 19:53

AnViSe писал(а): Может есть какое более изящное решение?
Хранить только дату начала.

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 10 июл 2008, 20:00

WildSery писал(а):Вариант без диапазона медленнее, на существенном объёме данных будет тормозить
:shock:
WildSery писал(а): и не предусматривает варианта, когда в какой-то период вообще нет процентной ставки.
Это не есть факт, мистер Дюк :wink: Нулл - секретное оружие пролетарьята :)
WildSery писал(а): Я бы диапазоном делал. А что апдейтить надо - ничего страшного не вижу.
Обеспечения условия непересекаемости диапазонов в конкурентной среде - в общем случае задача нерешаемая на вразумительных уровнях изоляции транзакций.
WildSery писал(а): Особенность такого подхода - можно сколько угодно раз в день вставлять "текущие" значения процедурой
А особеннось подхода с хранением только начала диапазона - можно сколько угодно раз в день вставлять "текущие" значения простым инсёртом :)

armagedon2007
Сообщения: 44
Зарегистрирован: 14 мар 2008, 21:01

Сообщение armagedon2007 » 10 июл 2008, 21:00

Можно так

Код: Выделить всё

CREATE TABLE NEW_TABLE (
    ADATE  TIMESTAMP NOT NULL,
    VAL    INTEGER NOT NULL
);
И выбирать

Код: Выделить всё

WITH RECURSIVE
    DD (SDATE, EDATE, VAL) AS
    (SELECT FIRST 1 B.ADATE, (SELECT FIRST 1 DATEADD(SECOND, -1, D.ADATE) FROM NEW_TABLE D WHERE D.ADATE > B.ADATE), VAL
    FROM NEW_TABLE B
    UNION ALL
    SELECT FIRST 1 A.ADATE, (SELECT FIRST 1 DATEADD(SECOND, -1, D.ADATE) FROM NEW_TABLE D WHERE D.ADATE > A.ADATE), A.VAL
    FROM NEW_TABLE A, DD
    WHERE A.ADATE > DD.SDATE
    )
SELECT * FROM DD
WHERE :ADATE BETWEEN SDATE AND  COALESCE(EDATE, '01.01.5000')
только сколько раз оно по справочнику пробежит чтобы выбрать одно зачете смотрите сами

armagedon2007
Сообщения: 44
Зарегистрирован: 14 мар 2008, 21:01

Сообщение armagedon2007 » 10 июл 2008, 21:43

WildSery писал(а): У меня это выглядит примерно так (изменения вношу процедурой):
А что будет в твоем примере если между 01.01.2008 и 31.01.2008
добавить новое значение допустим на 20.01.2008
DateEnd на 20.01.2008 станет 01.01.3000?

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 11 июл 2008, 11:44

Merlin писал(а)::shock:
Я не претендую на идеальную прямоту рук, но в моих тестах заметно быстрее было диапазоном.
Ты меня засмущал своим авторитетом, пойду ещё потестирую.
Merlin писал(а):Нулл - секретное оружие пролетарьята :)
В поле значения? Нда, действительно вариант. (Не люблю я эти нуллы...)
Merlin писал(а):Обеспечения условия непересекаемости диапазонов в конкурентной среде - в общем случае задача нерешаемая на вразумительных уровнях изоляции транзакций.
Задача задаче рознь. Я наивно считаю, что выставление почти справочных величин, типа ставок чего-нибудь, или курсов валют, или торговых цен, дело сугубо монопольное.
Merlin писал(а):А особеннось подхода с хранением только начала диапазона - можно сколько угодно раз в день вставлять "текущие" значения простым инсёртом :)
...А лучше опять-таки проверять существование нужной записи, потому как "select value from" в конкретную дату писать всегда удобнее, чем "select first 1 value from" ;) Кроме того, это ещё и инстрУмент, чтобы донести до разработчика что с БД что-то не так, если должно быть ограничение на хранение одного значения в конкретную дату, экстремальным методом "multiple rows..."
armagedon2007 писал(а):А что будет в твоем примере...
У меня так не будет :)
Нужна дополнительный алгоритм для внесения таких данных. Типа, чего вообще хотим получить такой вставкой.
И, кстати, не факт, что такая "вставка задним числом" в структуру хранения "датой начала" будет соответствовать ожиданиям. Не во всех задачах требуется тупо делить диапазон на два.

Gera
Сообщения: 53
Зарегистрирован: 12 мар 2008, 17:34

Сообщение Gera » 11 июл 2008, 13:22

Как быть с % ставкой действующей на текущий момент, у нее нет даты окончания
У нас в базе для этих целей используется NULL.

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

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 11 июл 2008, 15:50

Gera писал(а):У нас в базе для этих целей используется NULL.
А каким образом выбирается этот "открытый" диапазон? Через coalesce(dateEnd, '01.01.3000')?

Gera
Сообщения: 53
Зарегистрирован: 12 мар 2008, 17:34

Сообщение Gera » 11 июл 2008, 16:00

А каким образом выбирается этот "открытый" диапазон?
Через Begin_Date <= :Date AND (End_Date IS NULL OR End_Date >= :Date)

Просто NULL - это же значение "Неизвестно", что в данном случае, по моему, логически правильно.

Или же тогда использовать совсем запредельную дату 31.12.9999

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 11 июл 2008, 18:43

WildSery писал(а):лучше опять-таки проверять существование нужной записи, потому как "select value from" в конкретную дату писать всегда удобнее, чем "select first 1 value from" ;)
И ты, Брутто, про эстетику :)
WildSery писал(а): если должно быть ограничение на хранение одного значения в конкретную дату
Ты тут про курсы вспоминал... А я чо-то про Кириенковский дефолт вспомнил ;)

avenger
Сообщения: 141
Зарегистрирован: 25 окт 2005, 11:53

Сообщение avenger » 17 июл 2008, 08:25

mdfv писал(а):Хранить дату начала действия ставки,
а потом выбирать ближайшую к искомой вниз
Именно так и делаю. А объединяю данные в процедуре с for select по данным и курсором по процентным ставкам за указанную дату.

Примерно так:

Код: Выделить всё

  WHILE (DATE_CURRENT <= DATE_END) DO
  BEGIN
    IF (DATE_CURRENT > DATE_TMP AND ROW_COUNT > 0) THEN
    BEGIN
      REST_CURRENT = COALESCE(REST_CURRENT, 0) + REST_TMP;
      IF (REST_CURRENT IS NOT NULL AND REST IS NOT NULL AND REST_CURRENT <> REST) THEN
        INTERVAL_ID = INTERVAL_ID + 1;
      FETCH SUM_OPERATIONS INTO :DATE_TMP, :REST_TMP;
    END

    REST   = REST_CURRENT;
    "DATE" = DATE_CURRENT;
    IF (REST IS NOT NULL) THEN
      SUSPEND;

    DATE_CURRENT = DATE_CURRENT + 1;
  END

Ответить