私は現在、Postgres 9.2 で複雑な並べ替えの問題に取り組んでいます。この質問で使用されているソース コード (簡略化) は、http ://sqlfiddle.com/#!12/9857e/11 で見つけることができます。
さまざまなタイプのさまざまな列を含む巨大な (>>20Mio 行) テーブルがあります。
CREATE TABLE data_table
(
id bigserial PRIMARY KEY,
column_a character(1),
column_b integer
-- ~100 more columns
);
このテーブルを 2 列( ASC ) で並べ替えたいとしましょう。しかし、後で並べ替えられた出力に行を挿入する必要があり、ユーザーはおそらく(並べ替えられた出力の)一度に 100 行しか表示したくないため、単に Order By でそれを行いたくありません。
これらの目標を達成するために、私は次のことを行います。
CREATE TABLE meta_table
(
id bigserial PRIMARY KEY,
id_data bigint NOT NULL -- refers to the data_table
);
--Function to get the Column A of the current row
CREATE OR REPLACE FUNCTION get_column_a(bigint)
RETURNS character AS
'SELECT column_a FROM data_table WHERE id=$1'
LANGUAGE sql IMMUTABLE STRICT;
--Function to get the Column B of the current row
CREATE OR REPLACE FUNCTION get_column_b(bigint)
RETURNS integer AS
'SELECT column_b FROM data_table WHERE id=$1'
LANGUAGE sql IMMUTABLE STRICT;
--Creating a index on expression:
CREATE INDEX meta_sort_index
ON meta_table
USING btree
(get_column_a(id_data), get_column_b(id_data), id_data);
そして、data_table の ID を meta_table にコピーします。
INSERT INTO meta_table(id_data) (SELECT id FROM data_table);
後で、同様の単純な挿入を使用してテーブルに行を追加できます。
行 900000 ~ 900099 ( 100 行) を取得するには、次を使用できます。
SELECT get_column_a(id_data), get_column_b(id_data), id_data
FROM meta_table
ORDER BY 1,2,3 OFFSET 900000 LIMIT 100;
(すべてのデータが必要な場合は、data_table に追加の INNER JOIN を使用します。)
結果のプランは次のとおりです。
Limit (cost=498956.59..499012.03 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..554396.21 rows=1000000 width=8)
これは非常に効率的な計画です (Index Only Scans は Postgres 9.2 の新機能です)。しかし、行 20'000'000 - 20'000'099 ( 100 行
)
を取得したい場合はどうすればよいでしょうか? 同じプランで、実行時間が大幅に長くなります。さて、オフセット パフォーマンスを向上させるには ( PostgreSQL での OFFSET パフォーマンスの向上)、次のことができます (100,000 行ごとに別のテーブルに保存したと仮定します)。
SELECT get_column_a(id_data), get_column_b(id_data), id_data
FROM meta_table
WHERE (get_column_a(id_data), get_column_b(id_data), id_data ) >= (get_column_a(587857), get_column_b(587857), 587857 )
ORDER BY 1,2,3 LIMIT 100;
これははるかに高速に実行されます。結果の計画は次のとおりです。
Limit (cost=0.51..61.13 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.51..193379.65 rows=318954 width=8)
Index Cond: (ROW((get_column_a(id_data)), (get_column_b(id_data)), id_data) >= ROW('Z'::bpchar, 27857, 587857))
これまでのところ、すべてが完璧に機能し、postgres は素晴らしい仕事をしています!
2列目の順序をDESCに変更したいとしましょう。
ただし、> 演算子は両方の列 ASC を比較するため、WHERE 句を変更する必要があります。上記 (ASC Ordering) と同じクエリは、次のように書くこともできます。
SELECT get_column_a(id_data), get_column_b(id_data), id_data
FROM meta_table
WHERE
(get_column_a(id_data) > get_column_a(587857))
OR (get_column_a(id_data) = get_column_a(587857) AND ((get_column_b(id_data) > get_column_b(587857))
OR ( (get_column_b(id_data) = get_column_b(587857)) AND (id_data >= 587857))))
ORDER BY 1,2,3 LIMIT 100;
プランが変更され、クエリが遅くなります。
Limit (cost=0.00..1095.94 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..1117877.41 rows=102002 width=8)
Filter: (((get_column_a(id_data)) > 'Z'::bpchar) OR (((get_column_a(id_data)) = 'Z'::bpchar) AND (((get_column_b(id_data)) > 27857) OR (((get_column_b(id_data)) = 27857) AND (id_data >= 587857)))))
DESC-Ordering で効率的な古いプランを使用するにはどうすればよいですか?
問題を解決するためのより良いアイデアはありますか?
(私はすでに独自の演算子クラスで独自の型を宣言しようとしましたが、それは遅すぎます)