롤링 합계 / 횟수 / 날짜 간격 평균


20

18 개월 동안 1,000 개의 엔터티에 걸친 트랜잭션 데이터베이스에서 가능한 30 일마다 entity_id트랜잭션 금액의 합과 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 버전, 일부 샘플 데이터 및 샘플에 대한 예상 결과를 제공하십시오.
Erwin Brandstetter

이론적으로, 나는 하루를 의미했지만 실제로 거래가없는 날을 고려할 필요가 없습니다. 샘플 데이터 및 테이블 정의를 게시했습니다.
tufelkinder

따라서 각 실제 거래에서 시작entity_id 하여 30 일 동안 동일한 행을 누적하려고 합니다. 동일한 거래가 여러 개있을 수 있습니까? 아니면 그 조합이 고유 한 것으로 정의되어 있습니까? 테이블 정의에 PK 제약 조건이 없거나 제약 조건이 있지만 제약 조건이 누락 된 것 같습니다.(trans_date, entity_id)UNIQUE
Erwin Brandstetter

유일한 제약은 id기본 키에 있습니다. 엔터티 당 하루에 여러 트랜잭션이있을 수 있습니다.
tufelkinder

데이터 배포 정보 : 대부분의 날에 항목 (entity_id 당)이 있습니까?
Erwin Brandstetter

답변:


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

간단한, 당신이 무엇을 더 빨리,하지만 여전히 더 나은 버전 함께 정적 개월.

원하는 검색어

... 명확하게 정의되어 있지 않으므로 다음 가정을 기반으로 작성합니다.

any의 첫 번째 및 마지막 트랜잭션 내에서 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);

여기에는 각 entity_id집계에 대한 30 일 기간 과 기간 trans_date의 첫 날이 포함됩니다. 각 개별 행에 대한 값을 한 번 더 기본 테이블에 조인하려면 ...

기본 난이도는 여기에서 설명한 것과 같습니다.

창의 프레임 정의는 현재 행의 값에 의존 할 수 없습니다.

그리고 오히려 전화 generate_series()timestamp입력 :

실제로 원하는 검색어

질문 업데이트 및 토론 후 : 각 실제 거래에서 시작하여 30 일 동안
동일한 행을 누적 entity_id합니다.

데이터가 드물게 배포 되므로 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 바이올린.

롤링 창은 대부분의 날 동안 데이터에 대해서만 의미가 있습니다.

하루 에 중복을 집계 하지는 않지만(trans_date, entity_id) 같은 날의 모든 행이 항상 30 일 창에 포함됩니다.

큰 테이블의 경우 다음과 같은 커버링 인덱스가 상당히 도움이 될 수 있습니다.

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

마지막 열 amount은 인덱스 전용 스캔을 가져 오는 경우에만 유용합니다. 그렇지 않으면 떨어 뜨립니다.

그러나 어쨌든 전체 테이블을 선택하는 동안에는 사용되지 않습니다. 작은 하위 집합에 대한 쿼리를 지원합니다.


이것은 실제로보기 좋고, 데이터를 테스트하고, 쿼리가 실제로하고있는 모든 것을 이해하려고 노력합니다.
tufelkinder

@tufelkinder : 업데이트 된 질문에 대한 솔루션을 추가했습니다.
Erwin Brandstetter

지금 검토 중입니다. 나는 그것이 SQL Fiddle에서 실행된다는 것에 흥미를 느낀다 ... transactiondb에서 직접 실행하려고하면 오류가 발생한다column "t0.amount" must appear in the GROUP BY clause...
tufelkinder

@ tufelkinder : 테스트 케이스를 100 행으로 줄였습니다. sqlfiddle은 테스트 데이터의 크기를 제한합니다. 제이크 (저자)는 몇 달 전에 한도를 줄여 사이트가 쉽게 정지되지 않도록했습니다.
Erwin Brandstetter

1
지연으로 인해 전체 데이터베이스에서 테스트해야했습니다. 당신의 대답은 언제나처럼 깊이 있고 교육적이었습니다. 고맙습니다!
tufelkinder
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.