2017-04-17 42 views
0

我有一個具有數百萬行的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

回答

1

傳遞範圍的數組:用於範圍類型

select * 
from t 
where 
    k <@ any (array[ 
     '[24,923817]','[2827711,8471362]','[99188271,99188271]' 
    ]::int4range[]) 

檢查索引:https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-INDEXING

在情況下在合適的範圍索引是不可能做了一個連接以物化範圍:

select * 
from 
    t 
    inner join 
    (
     select generate_series(lower(a),upper(a) - 1) as k 
     from unnest(array[ 
      '[24,27]','[29,50]','[999,1990]' 
     ]::int4range[]) a(a) 
    ) s using (k) 

可以避免加入所有範圍值。比較範圍的上限和下限:

select * 
from 
    t 
    cross join 
    (
     select lower(a) as l, upper(a) - 1 as u 
     from unnest(array[ 
      '[24,27]','[29,50]','[999,1990]' 
     ]::int4range[]) a(a) 
    ) s 
where k between l and u 
+0

哇,真棒!這正是我所追求的。不幸的是,有一個致命的問題。PostgreSQL拒絕爲此使用索引。請參閱上面的編輯器輸出的編輯。有什麼想法嗎? – rg6

+0

@ rg6更新了鏈接到範圍索引 –

+0

我承認PostgreSQL的這個特殊角落對我來說是新的,但它似乎並沒有工作。請閱讀編輯。 – rg6

0

根本不可能的。運營商不這樣做。他們稱職能。如果他們在這裏調用函數,那麼函數將不得不使用動態SQL。

爲了不使用動態SQL,你必須破解PostgreSQL詞法分析器。 PostgreSQL是一個SQL數據庫。你的語法不是SQL。你可以做兩件事,

  1. 使用SQL。
  2. 編譯SQL。

我更喜歡第一種可能的選擇。如果我需要製作DSL,我不會在PostgreSQL中完成。我在應用程序中執行此操作。

+0

運算符調用函數,但是它們提供的與現有功能接口的功能並不總是顯而易見的。考慮一個模式匹配字符串數組的界面,它依次涉及1)一個反轉LIKE參數順序的函數,2)一個使用顛倒順序的運算符,3)一個使用new運算符來使用的函數ANY結構,以及4)最後的@> ~~或@> ~~ *運算符。當然,這可以用不同的方式完成,但是我花了一段時間才意識到操作員如何允許與任何接口連接,例如ANY。 – rg6