sql >> データベース >  >> RDS >> PostgreSQL

ウィンドウ関数または共通テーブル式:範囲内の前の行をカウントします

    プレーンクエリ、CTE、ウィンドウ関数でこれを安く行うことはできないと思います。これらのフレーム定義は静的ですが、動的フレームが必要です。 (列の値によって異なります)。

    通常、ウィンドウの下限と上限を慎重に定義する必要があります。次のクエリは除外します。 現在の行とinclude 下の境界線。
    まだ小さな違いがあります。関数には現在の行の以前のピアが含まれますが、相関サブクエリではそれらが除外されます...

    テストケース

    tsの使用 予約語の代わりにdate 列名として。

    CREATE TABLE test (
      id  bigint
    , ts  timestamp
    );
    

    ROM-ローマの質問

    CTEを使用し、タイムスタンプを配列に集約し、ネストを解除し、カウントします...
    正しい一方で、パフォーマンスは大幅に低下します 行でいっぱいの手以上で。ここにはパフォーマンスキラーが2人います。以下を参照してください。

    ARR-配列要素をカウント

    私はRomanのクエリを受け取り、それを少し合理化しようとしました:

    • 不要な2番目のCTEを削除します。
    • 最初のCTEをサブクエリに変換します。これはより高速です。
    • 直接count() 配列に再集計してarray_length()でカウントする代わりに 。

    ただし、配列の処理にはコストがかかり、パフォーマンスは依然として大幅に低下します より多くの行があります。

    SELECT id, ts
         , (SELECT count(*)::int - 1
            FROM   unnest(dates) x
            WHERE  x >= sub.ts - interval '1h') AS ct
    FROM (
       SELECT id, ts
            , array_agg(ts) OVER(ORDER BY ts) AS dates
       FROM   test
       ) sub;
    

    COR-相関サブクエリ

    あなたはできた 単純な相関サブクエリでそれを解決します。はるかに高速ですが、それでも...

    SELECT id, ts
         , (SELECT count(*)
            FROM   test t1
            WHERE  t1.ts >= t.ts - interval '1h'
            AND    t1.ts < t.ts) AS ct
    FROM   test t
    ORDER  BY ts;
    

    FNC-機能

    row_number()を使用して、行を時系列でループします plpgsql関数で それをカーソルと組み合わせます 同じクエリで、目的の時間枠にまたがります。次に、行番号を引くだけです。

    CREATE OR REPLACE FUNCTION running_window_ct(_intv interval = '1 hour')
      RETURNS TABLE (id bigint, ts timestamp, ct int)
      LANGUAGE plpgsql AS
    $func$
    DECLARE
       cur   CURSOR FOR
             SELECT t.ts + _intv AS ts1, row_number() OVER (ORDER BY t.ts) AS rn
             FROM   test t ORDER BY t.ts;
       rec   record;
       rn    int;
    
    BEGIN
       OPEN cur;
       FETCH cur INTO rec;
       ct := -1;  -- init
    
       FOR id, ts, rn IN
          SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
          FROM   test t ORDER BY t.ts
       LOOP
          IF rec.ts1 >= ts THEN
             ct := ct + 1;
          ELSE
             LOOP
                FETCH cur INTO rec;
                EXIT WHEN rec.ts1 >= ts;
             END LOOP;
             ct := rn - rec.rn;
          END IF;
    
          RETURN NEXT;
       END LOOP;
    END
    $func$;
    

    デフォルトの間隔1時間で電話をかける:

    SELECT * FROM running_window_ct();
    

    または任意の間隔で:

    SELECT * FROM running_window_ct('2 hour - 3 second');
    

    db<>ここでフィドル
    古いsqlfiddle

    ベンチマーク

    上記の表を使用して、古いテストサーバーで簡単なベンチマークを実行しました:(Debian上のPostgreSQL 9.1.9)。

    -- TRUNCATE test;
    INSERT INTO test
    SELECT g, '2013-08-08'::timestamp
             + g * interval '5 min'
             + random() * 300 * interval '1 min' -- halfway realistic values
    FROM   generate_series(1, 10000) g;
    
    CREATE INDEX test_ts_idx ON test (ts);
    ANALYZE test;  -- temp table needs manual analyze
    

    太字を変更しました 各実行の一部であり、EXPLAIN ANALYZEで5つのうちのベストを取りました 。

    100行
    ROM:27.656ミリ秒
    ARR:7.834ミリ秒
    COR:5.488ミリ秒
    FNC:1.115ミリ秒

    1000行
    ROM:2116.029ミリ秒
    ARR:189.679ミリ秒
    COR:65.802ミリ秒
    FNC:8.466ミリ秒

    5000行
    ROM:51347ミリ秒!!
    ARR:3167ミリ秒
    COR:333ミリ秒
    FNC:42ミリ秒

    100000行
    ROM:DNF
    ARR:DNF
    COR:6760ミリ秒
    FNC:828ミリ秒

    機能は明確な勝利者です。桁違いに高速で、最適なスケーリングが可能です。
    配列処理は競合できません。



    1. リンクサーバーMySQLを作成する方法

    2. PostgreSQLで数値を通貨としてフォーマットする方法

    3. SQLで複数のテーブルを結合する

    4. MySQLデータベースが破損しています...どうすればよいですか?