PostgreSQL - 查找具有特定值的最旧记录

时间:2013-02-20 22:13:58

标签: sql postgresql group-by greatest-n-per-group distinct-on

我有一个文档管理系统,可以记录历史表中的所有历史事件。我被要求能够在给定日期为特定客户提供状态为5的最旧的doc_id。该表看起来像这样(为简单起见,截断):

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

client_id和status_id列是事件发生后文档的值。这意味着doc_id定义的文档的最大历史事件行将与文档表中的相同列匹配。通过特定事件日期限制事件,您可以查看当时文档的值。因为这些值不是静态的,所以我不能简单地搜索status_id为5的特定client_id,因为找到的结果可能与文档的max(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的最大ID,然后验证该最大历史记录项是否已分配给给定客户端,状态设置为5.

我这样做的缺点是我正在扫描所有客户的所有历史记录以获得最大值,然后找到我正在寻找的客户和状态。截至目前,这扫描了大约1506万行,并且在我的开发服务器上花了大约90秒(这不是很快速)。

为了使问题更复杂,我需要在前一周的每一天执行此操作,或者每次运行总计七次。此外,系统中的所有文档都以状态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;

我希望做的是扫描,直到找到与特定客户端和状态值匹配的特定文档的最大历史记录,而不必首先为所有客户端找到所有文档ID的最大ID。我不知道是否可以通过窗口函数(分区依据)或其他一些我目前没有看到的逻辑来完成。

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”文档,其status_id为5,但是在2013年2月17日之后它将没有。 / p>

3 个答案:

答案 0 :(得分: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的{​​{1}}行具有最大(client_id, status_id)行,其中没有其他行{{1}对于相同的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是不明智的。你应该单独依靠id

为了加快速度,您需要multicolumn index表单(已更新):

event_date

我在您的反馈后切换event_date的位置,这应该更好地适应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),则partial index应该更快,但是:

status_id = 5

5

答案 1 :(得分:1)

在给定日期为特定客户提供状态为5的最旧doc_id

这样做:

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

我不止一次地读过您的问题,无法得到您所说的内容。

答案 2 :(得分: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;