sql >> Databáze >  >> RDS >> PostgreSQL

Funkce okna nebo běžné tabulkové výrazy:počítejte předchozí řádky v rozsahu

Nemyslím si, že to můžete udělat levně pomocí prostého dotazu, CTE a okenních funkcí - jejich definice rámce je statická, ale potřebujete dynamický rámec (v závislosti na hodnotách sloupců).

Obecně budete muset pečlivě definovat spodní a horní hranici okna:Následující dotazy vylučují aktuální řádek a zahrnout spodní okraj.
Je zde ještě malý rozdíl:funkce zahrnuje předchozí partnery aktuálního řádku, zatímco korelovaný poddotaz je vylučuje ...

Testovací případ

Pomocí ts místo vyhrazeného slova date jako název sloupce.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM – Romanův dotaz

Používejte CTE, agregujte časová razítka do pole, zrušte vnoření, počítejte ...
I když je to správné, výkon se drasticky zhoršuje s více než rukou plnou řádků. Je zde pár zabijáků výkonu. Viz níže.

ARR - počítání prvků pole

Vzal jsem Romanův dotaz a pokusil se ho trochu zefektivnit:

  • Odstraňte 2. CTE, což není nutné.
  • Převeďte 1. CTE na poddotaz, což je rychlejší.
  • Přímý count() místo reagregace do pole a počítání pomocí array_length() .

Manipulace s poli je ale drahá a výkon se stále závažně zhoršuje s více řádky.

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 – korelovaný dílčí dotaz

Mohli byste vyřešte to jednoduchým korelovaným poddotazem. Mnohem rychlejší, ale stále ...

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 – funkce

Opakujte řádky v chronologickém pořadí pomocí row_number() ve funkci plpgsql a zkombinujte to s kurzorem přes stejný dotaz v požadovaném časovém rámci. Pak můžeme jen odečíst čísla řádků:

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$;

Volání s výchozím intervalem jedné hodiny:

SELECT * FROM running_window_ct();

Nebo s libovolným intervalem:

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

db<>zde hrajte
Starý sqlfiddle

Srovnávací

S tabulkou shora jsem spustil rychlý benchmark na mém starém testovacím serveru:(PostgreSQL 9.1.9 na Debianu).

-- 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

Změnil jsem tučné část pro každý běh a získal nejlepší z 5 pomocí EXPLAIN ANALYZE .

100 řádků
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms

1000 řádků
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms

5 000 řádků
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100 000 řádků
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

Funkce je jasným vítězem. Je o řád nejrychlejší a má nejlepší měřítko.
Ovládání pole nemůže konkurovat.



  1. mySQL - Vytvořte novou tabulku pomocí dat a sloupců ze tří tabulek

  2. 5 úloh, které vyžadují Microsoft Access

  3. alter table upravit sloupec v databázi Oracle

  4. Jak spouštět a spravovat zálohy MySQL pro Oracle DBA