2013-02-20 3 views
4

У меня есть система управления документами, которая записывает все исторические события в таблицу истории. Мне было предложено предоставить старейший doc_id, который имеет статус 5 для данного клиента на определенную дату. Таблица выглядит примерно так (усеченный для простоты):PostgreSQL - поиск самой старой записи с определенным значением

doc_history: 
    id integer 
    doc_id integer 
    event_date timestamp 
    client_id integer 
    status_id integer 

В client_id и status_id столбцы значение документа после того, как событие произошло. Это означает, что максимальная строка события истории для документа, определенного doc_id, будет соответствовать тем же столбцам в таблице документов. Ограничивая события определенной датой события, вы можете видеть, какие значения документа были в то время. Поскольку эти значения не являются статическими, я не могу просто просто искать конкретный client_id с status_id из 5, потому что найденный результат может не соответствовать максимальному (id) документа. Надеюсь, это имеет смысл.

То, что я нашел, чтобы работать, но медленно, заключается в следующем:

select 
    t.* 
from 
    (select 
     distinct on (doc_id), 
     * 
    from 
     doc_history 
    where 
     event_date <= '2013-02-17 23:59:59' 
    order by 
     doc_id, id desc) t 
where 
    t.client_id = 9999 and 
    t.status_id = 5 
limit 1; 

В принципе, я получаю максимальный идентификатор для конкретного документа ID до заданной максимальной даты события, а затем что этот максимальный элемент истории присвоен данному клиенту со статусом, установленным на 5.

Недостатком этого является то, что я просматриваю все записи истории для всех клиентов, чтобы получить их максимальные значения, а затем найти то, что я ищу для одного клиента и статус. На данный момент это сканирует примерно 15,06 миллионов строк и занимает около 90 секунд на моем dev-сервере (который не пылает быстро).

Чтобы усложнить ситуацию, мне нужно сделать это за каждый день предыдущей недели или семь раз за каждый прогон. Кроме того, все документы в системе начинаются со статуса 5, который представляет новый. Это делает его так, чтобы этот запрос просто возвращает первый документ, введенный для этого клиента:

select * from doc_history where client_id = 9999 and 
    status_id = 5 and 
    event_date <= '2013-02-17 23:59:59' 
    order by id limit 1; 

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

Пример одного из событий в doc_history таблице:

# select id, doc_id, event, old_value, new_value, event_date, client_id, status_id from doc_history where doc_id = 9999999 order by id; 
    id | doc_id | event | old_value | new_value |   event_date   | client_id | status_id 
----------+---------+-------+-----------+-----------+----------------------------+-----------+----------- 
25362415 | 9999999 | 13 |   |   | 2013-02-14 11:49:50.032824 |  9999 |   5 
25428192 | 9999999 | 15 |   |   | 2013-02-18 11:15:48.272542 |  9999 |   5 
25428193 | 9999999 |  7 | 5   | 1   | 2013-02-18 11:15:48.301377 |  9999 |   1 

событий 7 статус изменился, и старые и новые значения показывают, что оно было изменено с 5 до 1, что находит свое отражение в status_id. Для event_date меньше или равно 2013-02-17 23:59:59, вышеуказанная запись была бы самым старым «NEW» документом со статусом_ид 5, но после 2/17/2013 этого не было бы.

ответ

1

обеспечивают самый старый DOC _ идентификатор, который имеет статус 5 для данного клиента на определенную дату

Это будет делать это:

select 
    min(doc_id) doc_id 
from 
    doc_history 
where 
    client_id = 9999 
    and status_id = 5 
    and date event_date = '2013-02-17' 

Я прочитал ваш вопрос более чем один раз и не может понять, о чем вы говорите.

+0

Что я буду за это : что является самым старым необработанным (NEW) документом для клиента X 2/17/2013. Если я получу старое событие из таблицы истории, я получу новый документ, но мне нужно новое событие до 2/17/2013 со статусом 5 (новый) для клиента X. Значения состояния и клиента не являются static между событиями, потому что они отражают изменения, вызванные событиями (например, изменение статуса и клиента). – Adam

+0

Я добавил пример того, как выглядят строки в таблице. – Adam

0

Если бы я получил это право, эквивалент, и, вероятно, быстро, запрос ваш будет:

select t.* 
from doc_history 
where event_date <= '2013-02-17 23:59:59' and 
    t.client_id = 9999 and 
    t.status_id = 5 
order by doc_id, id desc 
limit 1; 
+0

К сожалению, это не так. Это хорошая попытка, но нет никакой гарантии, что события, обнаруженные этим запросом, будут последними событиями, записанными максимумом event_date. Например, может произойти событие после возврата сюда, где status_id или client_id отличаются. В записях вышеприведенного документа он имел статус_ид 5 из 2/14 до 2/18. Если вы выполнили свой запрос с event_date <= 2013-02-18 23:59:59, вы попадете в строку id 25428192, но это неверно, поскольку с 2/18 оно имеет статус_ид 1, а не 5, как представлено строка 25428193. – Adam

3

Это должно быть гораздо быстрее:

SELECT * 
FROM doc_history h1 
WHERE event_date < '2013-02-18 0:0'::timestamp 
AND client_id = 9999 
AND status_id = 5 
AND NOT EXISTS (
    SELECT 1 
    FROM doc_history h2 
    WHERE h2.doc_id = h1.doc_id 
    AND h2.event_date < '2013-02-18 0:0'::timestamp 
    AND h2.event_date > h1.event_date -- use event_date instead of id! 
    ) 
ORDER BY doc_id 
LIMIT 1; 

У меня был очень тяжелый время, смысл вашего описания. В принципе, как я понимаю сейчас, вам нужна строка с самым большим doc_id для данного (client_id, status_id) с event_date до заданной временной метки, где не существует другой строки с более высоким значением id (равно event_date) для того же doc_id.

Обратите внимание, как я заменил условие в вашем примере:

WHERE event_date <= '2013-02-17 23:59:59' 

с:

WHERE event_date < '2013-02-18 0:0' 

Поскольку у вас есть дробные секунды, ваше выражение потерпит неудачу для временных меток, таких как:
'2013-02-17 23:59:59.123'

Я использую h2.event_date > h1.event_date вместо h2.id > h1.id в NOT EXISTS semi-join, потому что я считаю неразумным считать больше id равно более поздним event_date. Вероятно, вам стоит полагаться только на event_date.

Чтобы сделать это быстро, вам нужно multicolumn index формы (обновлено):

CREATE INDEX doc_history_multi_idx 
ON doc_history (client_id, status_id, doc_id, event_date DESC); 

я переключился позиции doc_id, event_date DESC после обратной связи, это должно лучше приспособить ORDER BY doc_id LIMIT 1.

Если условие status_id = 5 является постоянным (всегда проверять 5), partial index вместо этого должен быть быстрее, но:

CREATE INDEX doc_history_multi_idx 
ON doc_history (client_id, doc_id, event_date DESC) 
WHERE status_id = 5; 

И:

CREATE INDEX doc_history_id_idx ON doc_history (doc_id, event_date DESC); 
+0

Приносим извинения, если я не натолкнулся. Может быть, мой нынешний холод в голове имеет какое-то отношение к этому. Мне нравится ваш момент о дробных секундах, поэтому я буду работать над включением этого (хотя это реальный край для этой системы).Во всяком случае, мне действительно нравится ваше решение здесь, и завтра я буду тестировать его. Поскольку таблица видит только вставки и никогда не обновляет/удаляет, столбец идентификатора безопасен и может быть заменен на event_date, но ваша озабоченность оправдана. Один вопрос о ваших временных отметках: есть ли причина для 0: 0 по сравнению с тем, чтобы оставить его? – Adam

+0

@Adam: ** 'date' vs.' timestamp' **: Ну, '' 2013-02-18 ':: timestamp' тоже работает, но выглядит как 'date', а' '2013-02 -18 0: 0 ':: timestamp' проясняет, что это временная метка, даже если вы не добавляете акцию явно. –

+0

@Adam: ** 'id' vs.' event_date' **: * Вы знаете, что они синхронизированы, но система не работает. Среди прочего он имеет отношение к индексам. Как вы можете видеть, я использовал 'event_date' для обоих индексов, поэтому вам нужно работать с' event_date' в запросах, чтобы их использовать. Или переключите все на 'id'. * Смешивание обоих *, как и вы, не имеет никакой пользы, но есть ряд потенциальных недостатков. Если 'id' гарантированно будет поставляться, это будет немного быстрее. 4 байта для «целого» и 8 байтов для «timestamp», который попадает в [MAXALIGN] (http://stackoverflow.com/a/7431468/939860) для обоих индексов. –

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