5

という名前の PostgreSQL データベースにテーブルがありますfeeds_up。次のようになります。

| feed_url | isup | hasproblems | observed timestamp with tz    | id (pk)|
|----------|------|-------------|-------------------------------|--------|
| http://b.| t    | f           | 2013-02-27 16:34:46.327401+11 | 15235  |
| http://f.| f    | t           | 2013-02-27 16:31:25.415126+11 | 15236  |

30 万行程度で、5 分ごとに最大 20 行ずつ増えています。非常に頻繁に (ページの読み込みごとに) 実行されるクエリがあります。

select distinct on (feed_url) feed_url, isUp, hasProblems
    from feeds_up
    where observed <= '2013-02-27T05:38:00.000Z'
    order by feed_url, observed desc;

そこに時間の例を入れました。その時間はパラメータ化されています。Explain Analyst はExplain.depesz.comにあります。約 8 秒かかります。クレイジー!

には約 20 の一意の値しかないためfeed_url、これは非常に非効率的です。私は愚かで、関数で FOR ループを試してみようと思いました。

CREATE OR REPLACE FUNCTION feedStatusAtDate(theTime timestamp with time zone) RETURNS SETOF feeds_up AS
$BODY$
DECLARE
    url feeds_list%rowtype;
BEGIN
FOR url IN SELECT * FROM feeds_list 
LOOP
    RETURN QUERY SELECT * FROM feeds_up
    WHERE observed <= theTime
    AND feed_url = url.feed_url
    ORDER BY observed DESC LIMIT 1;
END LOOP;
END;
$BODY$ language plpgsql;

select * from feedStatusAtDate('2013-02-27T05:38:00.000Z');

わずか307msです。

SQL で FOR ループを使用すると、最初のクエリのように効率的な適切なクエリを作成するにはどうすればよいでしょうか。それは可能ですか?それとも、これは FOR ループが本当に最適な種類のものですか?

ETA

Postgres バージョン: i686-pc-linux-gnu 上の PostgreSQL 9.1.5、gcc (SUSE Linux) 4.3.4 [gcc-4_3-branch リビジョン 152973]、32 ビットでコンパイル

feeds_up のインデックス:

CREATE INDEX feeds_up_url
  ON feeds_up
  USING btree
  (feed_url COLLATE pg_catalog."default");

CREATE INDEX feeds_up_url_observed
  ON feeds_up
  USING btree
  (feed_url COLLATE pg_catalog."default", observed DESC);

CREATE INDEX feeds_up_observed
  ON public.feeds_up
  USING btree
  (observed DESC);
4

2 に答える 2

1

「id」がシリアルであり、常にシーケンシャルであると仮定すると、次のように、サブクエリで各 feed_url の MAX(id) を見つけて、残りのデータを取得することで単純化できます。

SELECT fu.feed_url, fu.isup, fu.hasproblems, fu.observed
FROM feeds_up fu
JOIN
(
  SELECT feed_url, max(id)  AS id FROM feeds_up
  WHERE observed <= '2013-03-27T05:38:00.000Z'
  GROUP BY feed_url
) AS q USING (id);
ORDER BY fu.feed_url, fu.observed desc;

簡単なテストを行ったところ、これは「観察された」インデックスのみを使用して非常に効率的に機能します。

アップデート:

「id」の代わりに「observed」を使用するには (レコードが順番に挿入されない可能性があるため)、上記のクエリを次のように変更できます。

SELECT DISTINCT ON (fu.feed_url) fu.feed_url, fu.isup, fu.hasproblems, fu.observed
FROM feeds_up fu
JOIN
(
  SELECT feed_url, max(observed) as observed FROM feeds_up
  WHERE observed <= '2013-03-27T05:38:00.000Z'
  GROUP BY feed_url
) AS q USING (feed_url, observed)
ORDER BY fu.feed_url, fu.observed desc;

私のシステムでは、これは「観察」の1つのインデックスとほぼ同時に実行されました。YMMV

于 2013-04-15T03:52:04.173 に答える