2013-04-09 3 views
1

В таблице SQL person_rate мы сохранили значение с плавающей ставкой, которое изменяется во времени. Колонки:Скорость изменения хранилища SQL во времени и скорость SELECT, действительная для определенного момента времени

id (serial, PK) 
person_id (int) 
date_from (date) 
rate (float) 

(person_id, date_from) уникален, потому что в большинстве одно изменение в день допускается (возможно, это может быть ПК, но это не важно)

Оценить значение для данного person_id действует на временном интервале от date_from Дата следующей записи с успешным date_from, или к будущему будущему, если такой записи нет. Пример:

id person_id date_from rate 
101 1   2011-01-01 100.0 
145 1   2012-01-01 180.0 
193 1   2012-05-01 140.0 

Теперь мне нужен запрос SELECT, который для каждого person_id возвращающую rate действителен в течение некоторого заданного момента $. Скажем, на 2012-03-01 это 180; на 2012-05-02 это 140 и так.

Solutions Я протестированные:

1) состояние date_from <= $date + использовать оконную функцию rank() OVER (PARTITION BY person_id ORDER BY date_from DESC + в superselect WHERE rank = 1

2) аналогично 1), но использовать SELECT DISTINCT ON (person_id) вместо ограничения ранга 1

Оба 1) и 2) не работает хорошо, EXPLAIN показывает, что db должен сортировать все записи для каждого person_id, а затем ограничивать 1 первым. Вероятно, этот тип запроса не может полностью использовать индекс на date_from?

IDEA - добавить date_to столбец, который будет немного излишним, так как значение будет «date_from из succesive записи, минус 1 день» (или + infty, если нет succesive записи). Но тогда запрос мог бы быть с date_from <= $date AND date_to >= $date - который, вероятно, имел бы хорошую производительность с индексами на date_from и date_to.

Но я немного боюсь, как управлять целостностью данных в этом случае - как установить ограничение, которое [date_from .. date_to] интервалов для одного person_id shoud не перекрывается?

Какое оптимальное решение для postgresql для этого типа запросов? Загрузка наиболее читается, а не много пишет в таблицу person_rate. Типичный запрос был бы внутренне необходим для получения ставки за каждый день в месяце ...

Возможно, это SQL query for index/primary key ordinal с новыми индексами на стр. 9.2 может как-то помочь?

ответ

0

вы можете использовать LEAD для генерации этого to_date столбца

with scd_table (
    select a.* , 
      lead(from_date,1,to_date('31/12/9999','dd/mm/yyyy')) over (partition by a order by from_date asc) as to_date 
    from YOUR_TABLE a 
) 
select * 
from scd_table 
where :d >= from_date 
and  :d < to_date 

(это синтаксис оракула, но lead является стандарт ANSI)

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

+0

Спасибо для этого «медленно меняйте таблицы размеров», похоже, это то, что мне нужно, подобно ИДЕИ, которую я объяснил. Ограничение ненасыщения может быть реализовано с помощью триггера insert. – vlastik

+0

триггеры плохой дизайн. вам нужно задать себе один важный вопрос - ваша система делает больше DML или выбирает? если ваша система делает больше DML, вы можете отказаться от 'to_date', потому что для каждого обновления/вставки потребуется два обновления (текущая строка и to_date предыдущего). если ваша система делает больше выбора - вы можете рассмотреть возможность уплаты штрафа во время вставки, чтобы облегчить выбор. я дам вам эмпирическое правило - * если у вас есть триггеры, синонимы или слишком много просмотров, вы делаете что-то неправильно *. – haki

+0

По сравнению с выборами обновление/вставка почти не будет. Таким образом, два обновления, безусловно, в порядке. Я думаю о триггерах, вероятно, только для проверки ограничений последовательности, например. не допускать вставки [от, до] интервала, который перекрывает другой. – vlastik

0

Используйте этот простой запрос:

SELECT person_id, date_from, rate FROM person_rate where date_from in (SELECT MAX(date_from) FROM person_rate WHERE date_from <= 'provided Date' and person_id = provided_id) 

Чтобы получить все ставки Person использовать.

SELECT a.person_id, a.date_from, a.rate FROM person_rate a JOIN (SELECT person_id, MAX(date_from) as date_from FROM person_rate where date_from <= 'provided Date') b ON(a.date_from = b.date_from and a.person_id = b.person_id) 
0

SQL Fiddle

select distinct on (person_id) person_id, date_from, rate 
from person_rate 
where date_from <= '2012-03-01' 
order by person_id, date_from desc 

Если (person_id, date_from) является уникальным, то создать этот индекс:

create table person_rate (
    id serial primary key, 
    person_id int, 
    date_from date, 
    rate float, 
    unique (person_id, date_from) 
); 

Если это уже таблица производства изменяет его:

alter table person_rate add 
constraint constraint_name unique (person_id, date_from); 

Do не забудьте запустить analyze person_rate после этого. Он будет использовать индекс только в том случае, если будут выполнены правильные условия. Это включает достаточно большой стол.

0
SELECT person_id, rate FROM person_rate WHERE date_from <= '2012-05-02' ORDER BY date_from DESC LIMIT 0,1; 

Где вторая цифра LIMIT составляет не более person_id вы хотите использовать (например LIMIT 0,5 для первых 5 person_id) и дата идет здесь: date_from < = '2012-05-02'

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