Повна кількість / кількість / середнє значення за інтервал дати


20

У базі даних про трансакції, що охоплюють тисячі організацій протягом 18 місяців, я хотів би запустити запит, щоб згрупувати кожен можливий 30-денний період за entity_idдопомогою СУМ їх суми транзакцій та COUNT їхніх транзакцій за той 30-денний період, і повернути дані таким чином, щоб потім я міг запитати. Після багато тестування цей код виконує багато чого з того, що я хочу:

SELECT id, trans_ref_no, amount, trans_date, entity_id,
    SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
    COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
  FROM transactiondb;

І я буду використовувати в більшому запиті структуровану щось на кшталт:

SELECT * FROM (
  SELECT id, trans_ref_no, amount, trans_date, entity_id,
      SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
      COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
    FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;

У випадку, коли цей запит не охоплюється, це коли кількість підрахунків транзакцій триватиме кілька місяців, але все одно залишається протягом 30 днів один від одного. Чи можливий такий тип запиту за допомогою Postgres? Якщо так, я вітаю будь-які дані. Багато інших тем обговорюють " запуск " агрегатів, а не прокатки .

Оновлення

CREATE TABLEскрипт:

CREATE TABLE transactiondb (
    id integer NOT NULL,
    trans_ref_no character varying(255),
    amount numeric(18,2),
    trans_date date,
    entity_id integer
);

Зразкові дані можна знайти тут . Я запускаю PostgreSQL 9.1.16.

Ідеальний вихід включатиме SUM(amount)і COUNT()всі транзакції протягом 30-денного періоду. Дивіться це зображення, наприклад:

Приклад рядків, які в ідеалі будуть включені до "набору", але це не тому, що мій набір є статичним за місяцем.

Зелене виділення дати вказує, що включено в мій запит. Виділення жовтого рядка вказує на записи, які я хотів би стати частиною набору.

Попереднє читання:


1
До every possible 30-day period by entity_idвам означає , що період може почати будь-який день, так що 365 можливих періодів в (невисокосному) рік? Або ви хочете вважати дні з фактичною транзакцією як початок періоду індивідуально для будь-якого entity_id ? У будь-якому випадку вкажіть визначення таблиці, версію Postgres, деякі приклади даних та очікуваний результат для вибірки.
Ервін Брандстеттер

Теоретично я мав на увазі будь-який день, але на практиці немає потреби розглядати дні, коли немає транзакцій. Я розмістив зразкові дані та визначення таблиці.
tufelkinder

Отже, ви хочете накопичити однакові рядки у вікні entity_id30 днів, починаючи з кожної фактичної транзакції. Чи може бути кілька транзакцій за одну (trans_date, entity_id)або одна комбінація визначена унікальною? Визначення таблиці не має UNIQUEобмежень або ПК, але, здається, обмежень немає ...
Ервін Брандштеттер

Єдине обмеження на idпервинний ключ. На день може бути кілька операцій на одну особу.
tufelkinder

Про розповсюдження даних: чи є записи (на сутність_id) протягом більшості днів?
Ервін Брандстеттер

Відповіді:


26

Ваш запит

Ви можете спростити запит за допомогою WINDOWпункту, але це лише скорочення синтаксису, а не зміна плану запитів.

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date)
             ORDER BY trans_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
  • Також використовуючи трохи швидше count(*), оскільки idце, безумовно, визначено NOT NULL?
  • І вам цього не потрібно з ORDER BY entity_idтих пірPARTITION BY entity_id

Однак ви можете спростити далі:
Не додавайте ORDER BYдо визначення вікна взагалі, це не стосується вашого запиту. Тоді вам також не потрібно визначати власну рамку вікна:

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date);

Простіша, швидша, але все ж просто краща версія того, що у вас є , зі статичними місяцями.

Запит, який вам може захотіти

... не визначено чітко, тому я буду грунтуватися на цих припущеннях:

Підраховуйте транзакції та суму за кожен 30-денний період протягом першої та останньої транзакції будь-якої entity_id. Виключіть періоди протікання та відключення без активності, але включіть усі можливі 30-денні періоди в межах цих зовнішніх меж.

SELECT entity_id, trans_date
     , COALESCE(sum(daily_amount) OVER w, 0) AS trans_total
     , COALESCE(sum(daily_count)  OVER w, 0) AS trans_count
FROM  (
   SELECT entity_id
        , generate_series (min(trans_date)::timestamp
                         , GREATEST(min(trans_date), max(trans_date) - 29)::timestamp
                         , interval '1 day')::date AS trans_date
   FROM   transactiondb 
   GROUP  BY 1
   ) x
LEFT JOIN (
   SELECT entity_id, trans_date
        , sum(amount) AS daily_amount, count(*) AS daily_count
   FROM   transactiondb
   GROUP  BY 1, 2
   ) t USING (entity_id, trans_date)
WINDOW w AS (PARTITION BY entity_id ORDER BY trans_date
             ROWS BETWEEN CURRENT ROW AND 29 FOLLOWING);

Тут перераховані всі 30-денні періоди для кожного entity_idз вашими агрегатами та з trans_dateпершим (включаючи) періоду. Щоб отримати значення для кожного окремого рядка, знову приєднайтесь до базової таблиці ...

Основна складність така ж, як обговорюється тут:

Визначення кадру вікна не може залежати від значень поточного рядка.

І скоріше дзвоніть generate_series()із timestampвведенням:

Запит, який ви насправді хочете

Після оновлення запитань та обговорення:
накопичіть рядки одного і того ж entity_idу вікні 30 днів, починаючи з кожної фактичної транзакції.

Оскільки ваші дані розповсюджуються рідко, слід ефективніше запускати самостійне з'єднання з умовою діапазону , тим більше що Postgres 9.1 ще не має LATERALприєднань:

SELECT t0.id, t0.amount, t0.trans_date, t0.entity_id
     , sum(t1.amount) AS trans_total, count(*) AS trans_count
FROM   transactiondb t0
JOIN   transactiondb t1 USING (entity_id)
WHERE  t1.trans_date >= t0.trans_date
AND    t1.trans_date <  t0.trans_date + 30  -- exclude upper bound
-- AND    t0.entity_id = 114284  -- or pick a single entity ...
GROUP  BY t0.id  -- is PK!
ORDER  BY t0.trans_date, t0.id

SQL Fiddle.

Прокручене вікно може мати сенс (лише щодо продуктивності) з даними протягом більшості днів.

Це не об'єднує дублікати (trans_date, entity_id)за день, але всі рядки того ж дня завжди включаються у вікно 30 днів.

Для великої таблиці індекс покриття, подібний до цього, може трохи допомогти:

CREATE INDEX transactiondb_foo_idx
ON transactiondb (entity_id, trans_date, amount);

Останній стовпець amountкорисний лише у тому випадку, якщо з нього ви отримаєте лише покажчики. Інше киньте його.

Але він не буде використовуватися, поки ви все одно виберете всю таблицю. Він підтримував би запити для невеликого підмножини.


Це виглядає дуже добре, перевіряючи його на даних зараз і намагаючись зрозуміти все, що насправді робить ваш запит ...
tufelkinder

@tufelkinder: Додано рішення для оновленого питання.
Erwin Brandstetter

Переглядаючи це зараз. Мене заінтригує той факт, що він працює в SQL Fiddle ... Коли я намагаюся запустити його прямо на своєму трансакціоні, він помиляється зcolumn "t0.amount" must appear in the GROUP BY clause...
tufelkinder

@tufelkinder: я скоротив тестовий випадок до 100 рядів. sqlfiddle обмежує розмір тестових даних. Джейк (автор) кілька місяців тому знизив ліміт лімітів, тому сайт менш легко зупиняється.
Erwin Brandstetter

1
Вибачте за затримку, необхідну для перевірки на повній базі даних. Як завжди, ваша відповідь була надзвичайно поглибленою та пізнавальною. Дякую!
tufelkinder
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.