使用 PostgreSQL (8.4),我正在创建一个视图,该视图总结了几个表中的各种结果(例如,在视图中创建列a
、、、),然后我需要在同一个查询中将其中一些结果组合在一起(例如,、、, ...),从而产生最终结果。我注意到的是,每次使用中间结果时都会对其进行完全计算,即使它是在同一个查询中完成的。b
c
a+b
a-b
(a+b)/c
有没有办法对此进行优化以避免每次都计算相同的结果?
这是一个重现问题的简化示例。
CREATE TABLE test1 (
id SERIAL PRIMARY KEY,
log_timestamp TIMESTAMP NOT NULL
);
CREATE TABLE test2 (
test1_id INTEGER NOT NULL REFERENCES test1(id),
category VARCHAR(10) NOT NULL,
col1 INTEGER,
col2 INTEGER
);
CREATE INDEX test_category_idx ON test2(category);
-- Added after edit to this question
CREATE INDEX test_id_idx ON test2(test1_id);
-- Populating with test data.
INSERT INTO test1(log_timestamp)
SELECT * FROM generate_series('2011-01-01'::timestamp, '2012-01-01'::timestamp, '1 hour');
INSERT INTO test2
SELECT id, substr(upper(md5(random()::TEXT)), 1, 1),
(20000*random()-10000)::int, (3000*random()-200)::int FROM test1;
INSERT INTO test2
SELECT id, substr(upper(md5(random()::TEXT)), 1, 1),
(2000*random()-1000)::int, (3000*random()-200)::int FROM test1;
INSERT INTO test2
SELECT id, substr(upper(md5(random()::TEXT)), 1, 1),
(2000*random()-40)::int, (3000*random()-200)::int FROM test1;
这是执行最耗时操作的视图:
CREATE VIEW testview1 AS
SELECT
t1.id,
t1.log_timestamp,
(SELECT SUM(t2.col1) FROM test2 t2 WHERE t2.test1_id=t1.id AND category='A') AS a,
(SELECT SUM(t2.col2) FROM test2 t2 WHERE t2.test1_id=t1.id AND category='B') AS b,
(SELECT SUM(t2.col1 - t2.col2) FROM test2 t2 WHERE t2.test1_id=t1.id AND category='C') AS c
FROM test1 t1;
SELECT a FROM testview1
产生这个计划(通过EXPLAIN ANALYZE
):Seq Scan on test1 t1 (cost=0.00..1787086.55 rows=8761 width=4) (actual time=12.877..10517.575 rows=8761 loops=1) SubPlan 1 -> Aggregate (cost=203.96..203.97 rows=1 width=4) (actual time=1.193..1.193 rows=1 loops=8761) -> Bitmap Heap Scan on test2 t2 (cost=36.49..203.95 rows=1 width=4) (actual time=1.109..1.177 rows=0 loops=8761) Recheck Cond: ((category)::text = 'A'::text) Filter: (test1_id = $0) -> Bitmap Index Scan on test_category_idx (cost=0.00..36.49 rows=1631 width=0) (actual time=0.414..0.414 rows=1631 loops=8761) Index Cond: ((category)::text = 'A'::text) Total runtime: 10522.346 ms
SELECT a, a FROM testview1
产生这个计划:Seq Scan on test1 t1 (cost=0.00..3574037.50 rows=8761 width=4) (actual time=3.343..20550.817 rows=8761 loops=1) SubPlan 1 -> Aggregate (cost=203.96..203.97 rows=1 width=4) (actual time=1.183..1.183 rows=1 loops=8761) -> Bitmap Heap Scan on test2 t2 (cost=36.49..203.95 rows=1 width=4) (actual time=1.100..1.166 rows=0 loops=8761) Recheck Cond: ((category)::text = 'A'::text) Filter: (test1_id = $0) -> Bitmap Index Scan on test_category_idx (cost=0.00..36.49 rows=1631 width=0) (actual time=0.418..0.418 rows=1631 loops=8761) Index Cond: ((category)::text = 'A'::text) SubPlan 2 -> Aggregate (cost=203.96..203.97 rows=1 width=4) (actual time=1.154..1.154 rows=1 loops=8761) -> Bitmap Heap Scan on test2 t2 (cost=36.49..203.95 rows=1 width=4) (actual time=1.083..1.143 rows=0 loops=8761) Recheck Cond: ((category)::text = 'A'::text) Filter: (test1_id = $0) -> Bitmap Index Scan on test_category_idx (cost=0.00..36.49 rows=1631 width=0) (actual time=0.426..0.426 rows=1631 loops=8761) Index Cond: ((category)::text = 'A'::text) Total runtime: 20557.581 ms
在这里,选择a, a
花费的时间是选择的两倍a
,而它们实际上可以只计算一次。例如,使用SELECT a, a+b, a-b FROM testview1
,它通过子计划a
3 次和b
两次,而执行时间可以减少到总时间的 2/5(假设 + 和 - 在这里可以忽略不计)。
这是一件好事,它不会在不需要时计算未使用的列 (b
和c
),但是有没有办法让它只从视图中计算相同的使用列一次?
编辑: @Frank Heikens 正确地建议使用上面示例中缺少的索引。虽然它确实提高了每个子计划的速度,但它不会阻止多次计算相同的子查询。抱歉,我应该在最初的问题中说明这一点。
(很抱歉回答我自己的问题,但在阅读了这个不相关的问题和答案后,我想到我应该尝试使用 CTE。它有效。)
这是另一个视图,类似于
testview1
问题中的视图,但使用了Common Table Expression:(这只是一个例子,我并不是说将视图和 CTE 结合起来一定是一个好主意:一个 CTE 可能就足够了。)
与 不同
testview1
的是,现在的查询计划SELECT a FROM testview2
还计算b
和c
,因为在 中未使用而被忽略testview1
:但是,它不会重新计算在同一查询中多次使用的结果(这是目标)。
与
testview1
whichSELECT a, a, a, a, a
花费 5 倍的时间不同SELECT a
,这里花费的时间与orSELECT a, a, a, a, a, b, c, a+b, a+c, b+c FROM testview2
一样长。它只通过,一次:SELECT a FROM testview2
SELECT a, b, c FROM testview2
a
b
c
您需要表 test2 中 test1_id 的索引,这会改变事情。