我有一個具有數百萬行的10s的表。各種複雜的過濾查詢產生行集以支持應用程序。這些行集的大小從單個行到整個表都是任意的。但是,出於特定領域的原因,它們始終保持特定關鍵點的高水平連續性。在WHERE子句中用一組文本範圍過濾行
我需要在數據庫和應用程序之間雙向傳遞這些行集,並且以某種方式進行壓縮會很好。你們中許多人可能熟悉UNIX cut
,它需要像這樣的字段規範:cut -f 2-6,7,9-21
並返回相應的列。目前,我正在使用一個稍微有限的剪切域規範版本(例如,編號爲17-
)來表示行集。因此,例如24-923817,2827711-8471362,99188271
表示佔用34個字節的唯一一組6567445行。
我已經寫了下面的程序將這些轉換爲SQL WHERE濾波器使用的語法方面
CREATE OR REPLACE FUNCTION cut_string_to_sql_filter(TEXT, TEXT) RETURNS TEXT AS $$
SELECT
CASE $1
WHEN '' THEN 'FALSE'
ELSE
(SELECT
'(' || STRING_AGG(REGEXP_REPLACE(REGEXP_REPLACE(str, '(\d+)-(\d+)', QUOTE_IDENT($2) || ' BETWEEN \1 AND \2'), '^(\d+)$', QUOTE_IDENT($2) || '=\1'), ' OR ') || ')' AS sql
FROM
REGEXP_SPLIT_TO_TABLE($1, ',') AS t(str))
END;
$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE;
第一個參數是該行集規範,第二個參數是表的鍵字段名。對於上面的例子,SELECT cut_string_to_sql_filter('24-923817,2827711-8471362,99188271', 'some_key')
回報:
(some_key BETWEEN 24 AND 923817 OR some_key BETWEEN 2827711 AND 8471362 OR some_key=99188271)
的問題,這是目前任何查詢,使得使用這樣的行集規範必須使用動態SQL,因爲我不能想辦法利用運營商定製或任何其他語法功能將此效果嵌入到普通的SQL查詢中。
我也寫了一組返流功能的行規格:
CREATE OR REPLACE FUNCTION cut_string_to_set(TEXT) RETURNS SETOF INTEGER AS $$
DECLARE
_i TEXT;
_j TEXT;
_pos INTEGER;
_start INTEGER;
_end INTEGER;
BEGIN
IF $1 <> '' THEN
FOR _i IN SELECT REGEXP_SPLIT_TO_TABLE($1, ',') LOOP
_pos := POSITION('-' IN _i);
IF _pos > 0 THEN
_start := SUBSTRING(_i FROM 1 FOR _pos - 1)::INTEGER;
_end := SUBSTRING(_i FROM _pos + 1)::INTEGER;
FOR _j IN _start.._end LOOP
RETURN NEXT _j;
END LOOP;
ELSE
RETURN NEXT _i;
END IF;
END LOOP;
END IF;
END
$$ LANGUAGE PLPGSQL IMMUTABLE STRICT PARALLEL SAFE;
此作品純SQL與WHERE some_key IN (SELECT cut_string_to_set(...))
。當然,對於規劃人員來說,作爲一系列範圍的最佳表達方式,產生噩夢和冗長的查詢計劃,在解包方面效率相對較低,並且可能或不應該阻止規劃人員使用索引。
任何人能提供上述難題的任何包裝解決方案的這個,有可能爲它自己的類型,可能與運營商定製,以允許在沒有動態SQL在列語法健全基於索引的過濾在更廣泛的參與查詢?這是不可能的嗎?
如果您有任何機會,請隨時提供改進程序的建議。謝謝!下面
EDIT 1
大答案建議使用範圍類型的數組。不幸的是,查詢規劃器似乎不願意使用這樣的查詢索引。下面的Planner輸出可以在小測試表上運行。
Gather (cost=1000.00..34587.33 rows=38326 width=45) (actual time=0.395..112.334 rows=1018 loops=1)
Workers Planned: 6
Workers Launched: 6
-> Parallel Seq Scan on test (cost=0.00..29754.73 rows=6388 width=45) (actual time=91.525..107.354 rows=145 loops=7)
Filter: (test_ref <@ ANY ('{"[24,28)","[29,51)","[999,1991)"}'::int4range[]))
Rows Removed by Filter: 366695
Planning time: 0.214 ms
Execution time: 116.779 ms
CPU成本(注意小型測試表上超過100毫秒的6名並行工作人員)太高。我看不到任何額外的索引可以在這裏幫助。
相比之下,這裏是使用BETWEEN過濾器的計劃器輸出。
Bitmap Heap Scan on test (cost=22.37..1860.39 rows=1031 width=45) (actual time=0.134..0.430 rows=1018 loops=1)
Recheck Cond: (((test_ref >= 24) AND (test_ref <= 27)) OR ((test_ref >= 29) AND (test_ref <= 50)) OR ((test_ref >= 999) AND (test_ref <= 1990)))
Heap Blocks: exact=10
-> BitmapOr (cost=22.37..22.37 rows=1031 width=0) (actual time=0.126..0.126 rows=0 loops=1)
-> Bitmap Index Scan on test_test_ref_index (cost=0.00..2.46 rows=3 width=0) (actual time=0.010..0.010 rows=4 loops=1)
Index Cond: ((test_ref >= 24) AND (test_ref <= 27))
-> Bitmap Index Scan on test_test_ref_index (cost=0.00..2.64 rows=21 width=0) (actual time=0.004..0.004 rows=22 loops=1)
Index Cond: ((test_ref >= 29) AND (test_ref <= 50))
-> Bitmap Index Scan on test_test_ref_index (cost=0.00..16.50 rows=1007 width=0) (actual time=0.111..0.111 rows=992 loops=1)
Index Cond: ((test_ref >= 999) AND (test_ref <= 1990))
Planning time: 0.389 ms
Execution time: 0.660 ms
END EDIT低於1個
編輯2
回答建議使用範圍索引。據我所知,這個問題是我不需要索引範圍類型。好的,所以也許關鍵列被轉換爲操作範圍,所以我可以應用GIST索引,規劃人員將使用它。
CREATE INDEX test_test_ref_gist_index ON test USING GIST (test_ref);
ERROR: data type integer has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
這裏不足爲奇。所以讓我們將鍵列轉換爲一個範圍和索引。
CREATE INDEX test_test_ref_gist_index ON test USING GIST (INT4RANGE(test_ref, test_ref));
這是一個110MB的指數。這很重要。但它的工作。
Gather (cost=1000.00..34587.33 rows=38326 width=45) (actual time=0.419..111.009 rows=1018 loops=1)
Workers Planned: 6
Workers Launched: 6
-> Parallel Seq Scan on test_mv (cost=0.00..29754.73 rows=6388 width=45) (actual time=90.229..105.866 rows=145 loops=7)
Filter: (test_ref <@ ANY ('{"[24,28)","[29,51)","[999,1991)"}'::int4range[]))
Rows Removed by Filter: 366695
Planning time: 0.237 ms
Execution time: 114.795 ms
沒有。我並不太驚訝。我希望這個索引適用於「包含」而不是「包含」的操作。雖然我沒有經驗。
END EDIT 2
哇,真棒!這正是我所追求的。不幸的是,有一個致命的問題。PostgreSQL拒絕爲此使用索引。請參閱上面的編輯器輸出的編輯。有什麼想法嗎? – rg6
@ rg6更新了鏈接到範圍索引 –
我承認PostgreSQL的這個特殊角落對我來說是新的,但它似乎並沒有工作。請閱讀編輯。 – rg6