Postgres에서 창 함수의 집계를 어떻게 얻습니까?


11

정수 배열의 순열 / 조합의 두 열과 값을 포함하는 세 번째 열을 포함하는 테이블이 있습니다.

CREATE TABLE foo
(
  perm integer[] NOT NULL,
  combo integer[] NOT NULL,
  value numeric NOT NULL DEFAULT 0
);
INSERT INTO foo
VALUES
( '{3,1,2}', '{1,2,3}', '1.1400' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0.9280' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0.8000' )

각 순열뿐만 아니라 각 조합에 대한 평균 및 표준 편차를 찾고 싶습니다. 이 쿼리로 할 수 있습니다.

SELECT
  f1.perm,
  f2.combo,
  f1.perm_average_value,
  f2.combo_average_value,
  f1.perm_stddev,
  f2.combo_stddev,
  f1.perm_count,
  f2.combo_count
FROM
(
  SELECT
    perm,
    combo,
    avg( value ) AS perm_average_value,
    stddev_pop( value ) AS perm_stddev,
    count( * ) AS perm_count
  FROM foo
  GROUP BY perm, combo
) AS f1
JOIN
(
  SELECT
    combo,
    avg( value ) AS combo_average_value,
    stddev_pop( value ) AS combo_stddev,
    count( * ) AS combo_count
  FROM foo
  GROUP BY combo
) AS f2 ON ( f1.combo = f2.combo );

그러나 데이터가 많으면 "foo"테이블 (실제로 약 4 백만 개의 행이있는 14 개의 파티션으로 구성됨)을 두 번 스캔해야하므로 쿼리 속도가 느려질 수 있습니다.

최근에 Postgres는 기본적으로 특정 열의 GROUP BY와 같은 "창 함수"를 지원한다는 것을 알았습니다. 다음과 같이 사용하도록 쿼리를 수정했습니다.

SELECT
  perm,
  combo,
  avg( value ) as perm_average_value,
  avg( avg( value ) ) over w_combo AS combo_average_value,
  stddev_pop( value ) as perm_stddev,
  stddev_pop( avg( value ) ) over w_combo as combo_stddev,
  count( * ) as perm_count,
  sum( count( * ) ) over w_combo AS combo_count
FROM foo
GROUP BY perm, combo
WINDOW w_combo AS ( PARTITION BY combo );

"combo_count"열에 대해서는 작동하지만 "combo_average_value"및 "combo_stddev"열은 더 이상 정확하지 않습니다. 평균은 각 순열에 대해 취한 다음 각 조합에 대해 두 번째 평균화되는 것으로 보입니다 (잘못된).

이 문제를 어떻게 해결할 수 있습니까? 여기서 창 기능을 최적화로 사용할 수 있습니까?


현재 버전 Postgres 9.2를 가정하십니까? 윈도우 기능은 8.4와 함께 제공됩니다.
Erwin Brandstetter

죄송합니다. 지정하지 않았습니다. 예, 최신 Postgres 9.2.4를 사용하고 있습니다.
Scott Small

답변:


9

단일 쿼리 레벨에서 집계 함수의 결과에 대해 창 함수를 가질 수 있습니다 .

이것은 수학 교장에 대한 표준 편차에 실패 한다는 점을 제외하고는 몇 가지 수정 후에도 모두 잘 작동 합니다 . 관련된 계산은 선형 적이 지 않으므로 하위 모집단의 표준 편차를 단순히 결합 할 수 없습니다.

SELECT perm
      ,combo
      ,avg(value)                 AS perm_average_value
      ,sum(avg(value) * count(*)) OVER w_combo /
       sum(count(*)) OVER w_combo AS combo_average_value
      ,stddev_pop(value)          AS perm_stddev
      ,0                          AS combo_stddev  -- doesn't work!
      ,count(*)                   AS perm_count
      ,sum(count(*)) OVER w_combo AS combo_count
FROM   foo
GROUP  BY perm, combo
WINDOW w_combo  AS (PARTITION BY combo);

위해 combo_average_value이 표현을해야합니다

sum(avg(value) * count(*)) OVER w_combo / sum(count(*)) OVER w_combo

가중 평균 이 필요하기 때문에 . (멤버가 10 명인 그룹의 평균 무게는 멤버가 2 명인 그룹의 평균보다 더 중요합니다!)

이것은 작동합니다 :

SELECT DISTINCT ON (perm, combo)
       perm
      ,combo
      ,avg(value)        OVER wpc AS perm_average_value
      ,avg(value)        OVER wc  AS combo_average_value
      ,stddev_pop(value) OVER wpc AS perm_stddev
      ,stddev_pop(value) OVER wc  AS combo_stddev
      ,count(*)          OVER wpc AS perm_count
      ,count(*)          OVER wc  AS combo_count
FROM   foo
WINDOW wc  AS (PARTITION BY combo)
      ,wpc AS (PARTITION BY perm, combo);

여기서 두 개의 다른 창을 사용하고 DISTINCT있으며 창 기능 후에도 적용되는 행을 줄입니다 .

그러나 나는 그것이 원래의 쿼리보다 빠를 것이라고 의심합니다. 나는 그렇지 않다고 확신합니다.

변경된 테이블 레이아웃으로 성능 향상

배열의 오버 헤드는 24 바이트입니다 (유형에 따라 약간 씩 다름). 또한 배열 당 꽤 적은 수의 항목과 많은 반복이있는 것처럼 보입니다. 당신과 같은 거대한 테이블의 경우 스키마 를 정규화 하는 것이 좋습니다. 레이아웃 예 :

CREATE TABLE combo ( 
  combo_id serial PRIMARY KEY
 ,combo    int[] NOT NULL
);

CREATE TABLE perm ( 
  perm_id  serial PRIMARY KEY
 ,perm     int[] NOT NULL
);

CREATE TABLE value (
  perm_id  int REFERENCES perm(perm_id)
 ,combo_id int REFERENCES combo(combo_id)
 ,value numeric NOT NULL DEFAULT 0
);

참조 무결성이 필요하지 않은 경우 외래 키 제약 조건을 생략 할 수 있습니다.

에 대한 연결 combo_id도 테이블에 배치 할 수 perm있지만이 시나리오 value에서는 더 나은 성능 을 위해 연결을 약간 비정규 화했습니다 .

이렇게하면 행 크기가 32 바이트 (튜플 헤더 + 패딩 : 24 바이트, 2 x int (8 바이트), 패딩 없음)와 알 수없는 numeric열 크기가 됩니다. (정밀도가 필요하지 않은 경우 열 double precision또는 real열이 필요할 수도 있습니다.)

SO 또는 여기 에 대한 관련 답변 에서 실제 스토리지에 대한 추가 정보 :
읽기 성능을 위해 PostgreSQL 구성

어쨌든, 그것은 현재 가지고있는 것의 일부에 불과하며 크기만으로 쿼리를 훨씬 빠르게 만들 수 있습니다. 간단한 정수로 그룹화하고 정렬하는 것도 훨씬 빠릅니다.

당신은 것 첫째 하위 쿼리에서 집계하고 다음 에 가입 perm하고 combo최적의 성능을 위해.


명확하고 간결한 답변에 감사드립니다. 이 방법으로 부분 집합 모집단의 표준 편차를 얻는 방법이없는 것 같습니다. 즉, 나는 당신의 솔루션의 단순함을 좋아합니다. GROUP BY를 제거하면 결과 쿼리가 훨씬 더 읽기 쉽습니다. 불행히도 성능이 하위 수준이라고 생각한 것처럼. 30 분 이상 실행 한 후 쿼리를 종료해야했습니다.
Scott Small

@ ScottSmall : 성능을 위해 무언가 를 할 수 있습니다 ... 답변을 보려면 업데이트를 참조하십시오.
Erwin Brandstetter 5

내 질문을 단순화하기 위해 foo관련이없는 테이블 에서 열을 제거했습니다 . 실제로이 쿼리에서 사용되지 않는 열이 몇 개 더 있기 때문에 순열과 조합을 정규화하면이 특정 사용 사례에서 속도가 크게 향상 될 것이라고 확신 할 수 없습니다.
Scott Small

또한 각 순열과 조합을 구성하는 정수 값은 DB의 다른 테이블에서 나옵니다. 이 데이터를 미리 생성하면 계산 비용이 많이 듭니다. perm / combo의 최대 길이는 5이지만 5Pn과 5Cn은 n의 큰 값 (현재 약 1000이지만 매일 증가)에 대해 상당히 커집니다 ... 어쨌든 그것은 또 다른 날의 문제입니다. Erwin의 모든 도움에 다시 한번 감사드립니다.
Scott Small
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.