time_stamp, usr_id, transaction_id 및 lives_remaining에 대한 열이있는 레코드가 포함 된 Postgres 테이블 ( “lives”라고 함)을 다루고 있습니다. 각 usr_id에 대한 가장 최근의 lives_remaining 합계를 제공하는 쿼리가 필요합니다.
- 여러 명의 사용자가 있습니다 (별도의 usr_id).
- time_stamp는 고유 식별자가 아닙니다. 때때로 사용자 이벤트 (테이블의 행별로)가 동일한 time_stamp로 발생합니다.
- trans_id는 매우 작은 시간 범위에서만 고유합니다. 시간이 지남에 따라 반복됩니다.
- (주어진 사용자에 대해) 남은 수명은 시간이 지남에 따라 증가 및 감소 할 수 있습니다.
예:
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 07:00 | 1 | 1 | 1 09:00 | 4 | 2 | 2 10:00 | 2 | 3 | 삼 10:00 | 1 | 2 | 4 11:00 | 4 | 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
주어진 각 usr_id에 대한 최신 데이터가있는 행의 다른 열에 액세스해야하므로 다음과 같은 결과를 제공하는 쿼리가 필요합니다.
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
언급했듯이 각 usr_id는 생명을 얻거나 잃을 수 있으며 때로는 이러한 타임 스탬프가있는 이벤트가 너무 가깝게 발생하여 동일한 타임 스탬프를 갖습니다! 따라서이 쿼리는 작동하지 않습니다.
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
대신 time_stamp (첫 번째)와 trans_id (두 번째)를 모두 사용하여 올바른 행을 식별해야합니다. 그런 다음 하위 쿼리의 해당 정보를 해당 행의 다른 열에 대한 데이터를 제공하는 기본 쿼리로 전달해야합니다. 이것은 내가 일하게 된 해킹 된 쿼리입니다.
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
좋아,이게 효과가 있지만 나는 그것을 좋아하지 않는다. 쿼리 내에서 쿼리, 자체 조인이 필요하며 MAX가 가장 큰 타임 스탬프와 trans_id를 가진 것으로 확인 된 행을 잡아서 훨씬 더 간단 할 수있는 것 같습니다. “lives”테이블에는 구문 분석 할 수천만 개의 행이 있으므로이 쿼리가 가능한 한 빠르고 효율적 이길 바랍니다. 특히 RDBM과 Postgres를 처음 접했기 때문에 적절한 인덱스를 효과적으로 사용해야한다는 것을 알고 있습니다. 최적화하는 방법에 대해 약간 잃었습니다.
여기 에서 비슷한 토론을 찾았 습니다 . Oracle 분석 기능에 해당하는 일부 유형의 Postgres를 수행 할 수 있습니까?
집계 함수 (예 : MAX)에서 사용하는 관련 열 정보에 액세스하고, 인덱스를 만들고, 더 나은 쿼리를 만드는 방법에 대한 조언을 주시면 감사하겠습니다!
추신 다음을 사용하여 예제 케이스를 만들 수 있습니다.
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
답변
158k 의사 랜덤 행이있는 테이블 (usr_id는 0에서 10k trans_id
사이에 균일하게 분포 , 0에서 30 사이에 균일하게 분포),
아래에서 쿼리 비용 xxx_cost
은 필요한 I / O 및 CPU 리소스에 대한 가중치 함수 추정 인 Postgres의 비용 기반 최적화 프로그램의 비용 추정 (Postgres의 기본값 포함 )을 참조합니다. PgAdminIII를 시작하고 “Query / Explain options”를 “Analyze”로 설정 한 쿼리에서 “Query / Explain (F7)”을 실행하여이를 얻을 수 있습니다.
- Quassnoy의 쿼리는 1.3 초에서 비용 745k의 추정 (!), 그리고 완료가 (에 화합물 지수 부여를 (
usr_id
,trans_id
,time_stamp
)) - Bill의 쿼리의 예상 비용은 93k이며 2.9 초 만에 완료됩니다 ((
usr_id
,trans_id
) 에 대한 복합 인덱스를 제공함 ). - 쿼리 # 1 아래 16K의 비용 추정치를 가지며, 800ms의 완료가 (화합물에 주어진 인덱스 (
usr_id
,trans_id
,time_stamp
)) - 쿼리 # 2 아래 14K의 비용 추정치를 가지며, 800ms의 완료는 (ON 화합물 함수 인덱스를 부여 (
usr_id
,EXTRACT(EPOCH FROM time_stamp)
,trans_id
))- 이것은 Postgres 전용입니다.
- 아래 쿼리 # 3 (포스트 그레스가 8.4+) 쿼리 2 비교 (또는 더 이상) 비용 추정치 및 종료 시간을 갖는다 (복합 지표 (에 기재를
usr_id
,time_stamp
,trans_id
));lives
테이블을 한 번만 스캔하는 이점이 있으며 메모리에서 정렬을 수용하기 위해 임시로 (필요한 경우) work_mem 을 늘리면 모든 쿼리 중에서 훨씬 빠릅니다.
위의 모든 시간에는 전체 10k 행 결과 집합 검색이 포함됩니다.
목표는 예상 비용에 중점을두고 최소 비용 예상 과 최소 쿼리 실행 시간입니다. 쿼리 실행은 런타임 조건 (예 : 관련 행이 이미 메모리에 완전히 캐시되었는지 여부)에 크게 좌우 될 수 있지만 비용 추정은 그렇지 않습니다. 다른 한편으로, 비용 견적은 정확히 견적이라는 것을 명심하십시오.
최적의 쿼리 실행 시간은로드없이 전용 데이터베이스에서 실행될 때 얻을 수 있습니다 (예 : 개발 PC에서 pgAdminIII로 플레이). 쿼리 시간은 실제 머신로드 / 데이터 액세스 확산에 따라 프로덕션에 따라 다릅니다. 한 쿼리가 다른 쿼리보다 약간 빠르지 만 (<20 %) 비용 이 훨씬 높은 경우 일반적으로 실행 시간 은 더 높지만 비용 은 더 낮은 쿼리 를 선택하는 것이 더 현명합니다.
쿼리가 실행될 때 프로덕션 시스템의 메모리에 대한 경쟁이 없을 것으로 예상되는 경우 (예 : RDBMS 캐시 및 파일 시스템 캐시는 동시 쿼리 및 / 또는 파일 시스템 활동에 의해 스 래싱되지 않음) 얻은 쿼리 시간 독립형 (예 : 개발 PC의 pgAdminIII) 모드가 대표적입니다. 프로덕션 시스템에 경합이있는 경우 비용이 낮은 쿼리는 캐시에 많이 의존하지 않는 반면 비용이 높은 쿼리는 동일한 데이터를 반복해서 다시 방문 하므로 쿼리 시간이 예상 비용 비율에 비례하여 저하 됩니다 (트리거링 안정적인 캐시가없는 경우 추가 I / O), 예 :
cost | time (dedicated machine) | time (under load) |
-------------------+--------------------------+-----------------------+
some query A: 5k | (all data cached) 900ms | (less i/o) 1000ms |
some query B: 50k | (all data cached) 900ms | (lots of i/o) 10000ms |
ANALYZE lives
필요한 인덱스를 만든 후 한 번 실행하는 것을 잊지 마십시오 .
쿼리 # 1
-- incrementally narrow down the result set via inner joins
-- the CBO may elect to perform one full index scan combined
-- with cascading index lookups, or as hash aggregates terminated
-- by one nested index lookup into lives - on my machine
-- the latter query plan was selected given my memory settings and
-- histogram
SELECT
l1.*
FROM
lives AS l1
INNER JOIN (
SELECT
usr_id,
MAX(time_stamp) AS time_stamp_max
FROM
lives
GROUP BY
usr_id
) AS l2
ON
l1.usr_id = l2.usr_id AND
l1.time_stamp = l2.time_stamp_max
INNER JOIN (
SELECT
usr_id,
time_stamp,
MAX(trans_id) AS trans_max
FROM
lives
GROUP BY
usr_id, time_stamp
) AS l3
ON
l1.usr_id = l3.usr_id AND
l1.time_stamp = l3.time_stamp AND
l1.trans_id = l3.trans_max
쿼리 # 2
-- cheat to obtain a max of the (time_stamp, trans_id) tuple in one pass
-- this results in a single table scan and one nested index lookup into lives,
-- by far the least I/O intensive operation even in case of great scarcity
-- of memory (least reliant on cache for the best performance)
SELECT
l1.*
FROM
lives AS l1
INNER JOIN (
SELECT
usr_id,
MAX(ARRAY[EXTRACT(EPOCH FROM time_stamp),trans_id])
AS compound_time_stamp
FROM
lives
GROUP BY
usr_id
) AS l2
ON
l1.usr_id = l2.usr_id AND
EXTRACT(EPOCH FROM l1.time_stamp) = l2.compound_time_stamp[1] AND
l1.trans_id = l2.compound_time_stamp[2]
2013/01/29 갱신
마지막으로 버전 8.4부터 Postgres는 Window 함수를 지원하므로 다음과 같이 간단하고 효율적으로 작성할 수 있습니다.
쿼리 # 3
-- use Window Functions
-- performs a SINGLE scan of the table
SELECT DISTINCT ON (usr_id)
last_value(time_stamp) OVER wnd,
last_value(lives_remaining) OVER wnd,
usr_id,
last_value(trans_id) OVER wnd
FROM lives
WINDOW wnd AS (
PARTITION BY usr_id ORDER BY time_stamp, trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
);
답변
다음을 기반으로 깨끗한 버전을 제안합니다 DISTINCT ON
( docs 참조 ).
SELECT DISTINCT ON (usr_id)
time_stamp,
lives_remaining,
usr_id,
trans_id
FROM lives
ORDER BY usr_id, time_stamp DESC, trans_id DESC;
답변
여기에 상관 된 하위 쿼리 나 GROUP BY를 사용하지 않는 또 다른 방법이 있습니다. 저는 PostgreSQL 성능 조정 전문가가 아니므로이 방법과 다른 사람들이 제공 한 솔루션을 모두 시도하여 어떤 것이 더 나은지 확인하는 것이 좋습니다.
SELECT l1.*
FROM lives l1 LEFT OUTER JOIN lives l2
ON (l1.usr_id = l2.usr_id AND (l1.time_stamp < l2.time_stamp
OR (l1.time_stamp = l2.time_stamp AND l1.trans_id < l2.trans_id)))
WHERE l2.usr_id IS NULL
ORDER BY l1.usr_id;
나는 그것이 trans_id
적어도 주어진 가치에 대해 고유 하다고 가정하고 time_stamp
있습니다.
답변
나는 당신이 언급 한 다른 페이지에서 Mike Woodhouse의 답변 스타일이 마음에 듭니다 . 그것은이 경우 하위 쿼리 그냥 사용할 수 있습니다, 단지 하나의 열이 특히 간결 것은 이상 최대화 될 때의 MAX(some_col)
와 GROUP BY
다른 열을하지만 경우에 당신 극대화 할 수있는 두 부분으로 수량이 여전히 사용하여 수행 할 수 있습니다 ORDER BY
더하기 LIMIT 1
대신 (Quassnoi가 수행 한대로) :
SELECT *
FROM lives outer
WHERE (usr_id, time_stamp, trans_id) IN (
SELECT usr_id, time_stamp, trans_id
FROM lives sq
WHERE sq.usr_id = outer.usr_id
ORDER BY trans_id, time_stamp
LIMIT 1
)
행 생성자 구문을 사용하면 WHERE (a, b, c) IN (subquery)
필요한 말의 양이 줄어들 기 때문에 좋습니다.
답변
이 문제에 대한 해키 솔루션이 있습니다. 한 지역의 각 숲에서 가장 큰 나무를 선택한다고 가정 해 보겠습니다.
SELECT (array_agg(tree.id ORDER BY tree_size.size)))[1]
FROM tree JOIN forest ON (tree.forest = forest.id)
GROUP BY forest.id
숲별로 나무를 그룹화하면 분류되지 않은 나무 목록이 있으며 가장 큰 나무를 찾아야합니다. 가장 먼저해야 할 일은 행을 크기별로 정렬하고 목록 중 첫 번째 행을 선택하는 것입니다. 비효율적으로 보일 수 있지만 수백만 개의 행이 있으면 JOIN
의 및 WHERE
조건 을 포함하는 솔루션보다 훨씬 빠릅니다 .
BTW, ORDER_BY
for array_agg
는 Postgresql 9.0에 도입되었습니다.
답변
Postgressql 9.5에는 DISTINCT ON이라는 새로운 옵션이 있습니다.
SELECT DISTINCT ON (location) location, time, report
FROM weather_reports
ORDER BY location, time DESC;
중복 행을 제거하고 ORDER BY 절에 정의 된 첫 번째 행만 남깁니다.
공식 문서 참조
답변
SELECT l.*
FROM (
SELECT DISTINCT usr_id
FROM lives
) lo, lives l
WHERE l.ctid = (
SELECT ctid
FROM lives li
WHERE li.usr_id = lo.usr_id
ORDER BY
time_stamp DESC, trans_id DESC
LIMIT 1
)
색인을 만들면 (usr_id, time_stamp, trans_id)
이 쿼리가 크게 향상됩니다.
당신은 항상 PRIMARY KEY
당신의 테이블에 어떤 종류의 것을 가지고 있어야 합니다.