PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Функции на прозореца или общи таблични изрази:пребройте предишните редове в рамките на диапазона

Не мисля, че можете да направите това евтино с обикновена заявка, CTE и функции на прозореца - тяхната дефиниция на рамката е статична, но имате нужда от динамична рамка (в зависимост от стойностите на колоните).

По принцип ще трябва внимателно да дефинирате долната и горната граница на прозореца си:Следните заявки изключват текущия ред и включи долната граница.
Все още има малка разлика:функцията включва предишни партньори на текущия ред, докато свързаната подзаявка ги изключва ...

Тестов случай

Използване на ts вместо запазена дума date като име на колона.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM - заявка на Роман

Използвайте CTE, агрегирайте времеви печати в масив, unnest, count ...
Докато е правилно, производителността се влошава драстично с повече от ръка, пълна с редове. Тук има няколко убийци на представяне. Вижте по-долу.

ARR - брой елементи на масива

Взех заявката на Роман и се опитах да я опростя малко:

  • Отстранете 2-ри CTE, който не е необходим.
  • Преобразувайте 1-ви 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$;

Обаждане с интервал по подразбиране от един час:

SELECT * FROM running_window_ct();

Или с произволен интервал:

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

db<>цигулка тук
Стар sqlfiddle

Сравнение

С таблицата отгоре направих бърз бенчмарк на моя стар тестов сървър:(PostgreSQL 9.1.9 на Debian).

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

Разнообразих судебелитете част за всяко изпълнение и взе най-доброто от 5 с EXPLAIN ANALYZE .

100 реда
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms

1000 реда
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms

5000 реда
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100 000 реда
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

Функцията е ясният победител. Той е най-бърз с порядък на величина и се мащабира най-добре.
Обработката на масив не може да се конкурира.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. С sqlalchemy как динамично да се свързвате към машината на базата данни на база заявка

  2. Защо функциите PL/pgSQL могат да имат страничен ефект, докато SQL функциите не могат?

  3. Агрегирани колони с допълнителни (отличителни) филтри

  4. Хибернация, Postgresql:Колона x е от тип oid, но изразът е от тип байт

  5. Не може да се свърже с postgres от отдалечен хост