我有一个具有数百万行的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