4

履歴テーブルにすべての履歴イベントを記録するドキュメント管理システムがあります。特定のクライアントの特定の日付のステータスが 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 に設定されていることを確認しています。

私のやり方の欠点は、すべてのクライアントのすべての履歴レコードをスキャンして最大値を取得し、1 つのクライアントとステータスについて探しているものを見つけることです。現在のところ、これは約 1506 万行をスキャンし、私の開発サーバーでは約 90 秒かかります (これは非常に高速ではありません)。

さらに複雑なことに、これを前の週の各日、つまり実行ごとに合計 7 回行う必要があります。さらに、システム内のすべてのドキュメントは、新規を表すステータス 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 を見つける必要なく、特定のクライアントとステータス値に一致する特定のドキュメントの最大履歴レコードが見つかるまでスキャンすることです。これがウィンドウ関数 (partition by) や、現在表示されていないその他のロジックで実行できるかどうかはわかりません。

doc_history テーブル内のイベントの 1 つの例:

# 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 以下の場合、上記のレコードは status_id が 5 の最も古い「NEW」ドキュメントになりますが、2013 年 2 月 17 日以降はそうではありません。

4

3 に答える 3

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_dateidevent_datedoc_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.idNOT EXISTSidevent_dateevent_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た。

条件status_id = 5が一定の場合 (常に をチェックします5)、代わりに部分インデックスの方が高速ですが、次のようになります。

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);
于 2013-02-21T02:31:35.833 に答える
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'

私はあなたの質問を何度も読みましたが、あなたが話していることを理解できません。

于 2013-02-20T22:44:39.540 に答える
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;
于 2013-02-20T23:36:07.793 に答える