【问题标题】:Creating case-insensitive indexes on Postgres string array在 Postgres 字符串数组上创建不区分大小写的索引
【发布时间】:2013-04-07 18:22:06
【问题描述】:

我在 Postgres 9.2 中使用 varchar[] 列(varchar 数组)来存储一些标签。在按标签检索行时,我希望查询不区分大小写。但是,我想保留大小写以在 UI 中显示(因此我不能将所有内容都存储为小写)。

那么,我的问题是如何在 Postgres 中通过 varchar 数组创建不区分大小写的索引?一种可能的方法是在列上创建一个功能性 GIN 索引。如何做到这一点?还有其他方法吗?

【问题讨论】:

  • 使用数组存储标签是个坏主意。
  • @ClodoaldoNeto,你为什么这么说?
  • 搜索关系模型
  • @ClodoaldoNeto,虽然这可能不是完全规范化的,但它可以是一个完全有效的生产设计模式。我经常遇到这样的情况,即非规范化为数组在空间和速度方面都是有意义的。
  • @ErwinBrandstetter:虽然问题是关于 varchar 数组,但我最近一直在使用的一个有趣的替代方法是以 JSON 类型(使用 json_accessors 扩展/plV8)和 GIN 索引存储动态数据。这有其自身的权衡,但快速测试表明存储要求确实较低。这是张贴者可能探索的另一条路径。

标签: postgresql postgresql-9.2 database-indexes


【解决方案1】:

@Saurabh Nanda:与您发布的内容类似,您还可以创建一个简单的函数来将您的 varchar 数组转换为小写,如下所示:

CREATE OR REPLACE FUNCTION array_lowercase(varchar[]) RETURNS varchar[] AS
$BODY$
  SELECT array_agg(q.tag) FROM (
    SELECT btrim(lower(unnest($1)))::varchar AS tag
  ) AS q;
$BODY$
  language sql IMMUTABLE;

请注意,我还修剪了空格的标签。这对你来说可能不是必需的,但我通常这样做是为了保持一致。

测试:

SELECT array_lowercase(array['Hello','WOrLD']);
 array_lowercase 
-----------------
 {hello,world}
(1 row)

正如 Saurabh 所说,您可以创建一个 GIN 索引:

CREATE INDEX ix_tags ON tagtable USING GIN(array_lowercase(tags));

并查询:

SELECT * FROM tagtable WHERE ARRAY['mytag'::varchar] && array_lowercase(tags);

更新:WHILE 与 array_agg/unnest 的性能对比

我创建了 100K 10 个元素 text[] 数组(12 个字符随机混合大小写字符串)的表并测试了每个函数。

array_agg/unnest 函数返回:

EXPLAIN ANALYZE VERBOSE SELECT array_lowercase(data) FROM test;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.test  (cost=0.00..28703.00 rows=100000 width=184) (actual time=0.320..3041.292 rows=100000 loops=1)
   Output: array_lowercase((data)::character varying[])
 Total runtime: 3174.690 ms
(3 rows)

WHILE 函数返回:

EXPLAIN ANALYZE VERBOSE SELECT array_lowercase_while(data) FROM test;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.test  (cost=0.00..28703.00 rows=100000 width=184) (actual time=5.128..4356.647 rows=100000 loops=1)
   Output: array_lowercase_while((data)::character varying[])
 Total runtime: 4485.226 ms
(3 rows)

更新 2: FOREACHWHILE 作为最后的实验,我将 WHILE 函数更改为使用 FOREACH:

CREATE OR REPLACE FUNCTION array_lowercase_foreach(p_input varchar[]) RETURNS varchar[] AS $BODY$
DECLARE
    el text;
    r varchar[];
BEGIN
    FOREACH el IN ARRAY p_input LOOP
        r := r || btrim(lower(el))::varchar;
    END LOOP;
    RETURN r;
END;
$BODY$
  language 'plpgsql'

结果似乎类似于WHILE

EXPLAIN ANALYZE VERBOSE SELECT array_lowercase_foreach(data) FROM test;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.test  (cost=0.00..28703.00 rows=100000 width=184) (actual time=0.707..4106.867 rows=100000 loops=1)
   Output: array_lowercase_foreach((data)::character varying[])
 Total runtime: 4239.958 ms
(3 rows)

虽然我的测试并不严谨,但我确实对每个版本运行了多次,发现数字具有代表性,表明 SQL 方法(array_agg/unnest)是最快的。

【讨论】:

  • 在将数组转换为小写时,您对 SQL 查询与 WHILE 循环的效率有何看法?
  • @SaurabhNanda:我测试了这两种方法并更新了我的答案。 SQL 方法始终更快。我还尝试了使用FOREACH 语句(see here)的版本:我需要使用更大的样本进行测试,但结果类似于WHILE
  • 感谢您抽出时间对此进行一些基准测试。知道为什么SELECT 查询比FOREACHWHILE 快吗?
【解决方案2】:

找到了一种使用自定义 pgplsql 函数的可能方法:

首先声明一个自定义函数,该函数接受一个 varchar[] 数组作为输入,并返回一个所有元素都转换为小写的新数组。 (这是我第一次写 PL/SQL,所以这可能是非常低效的代码)。

CREATE OR REPLACE FUNCTION array_lowercase(varchar[]) RETURNS varchar[] AS $$
DECLARE
    i INTEGER;
    l INTEGER;
    r VARCHAR[];
    inp ALIAS FOR $1;
BEGIN
    i := 1;
    l := array_length($1, 1);
    WHILE i <= l LOOP
        r[i] = lower(inp[i]);
        i := i + 1;
    END LOOP;
    RETURN r;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

接下来,使用新定义的array_lowercase 函数在表达式上创建 GIN 索引:

create index hotel_bookings_tags on hotel_bookings using gin(array_lowercase(tags));

现在在查询中使用它(使用 EXPLAIN 验证它是否使用索引):

select * from posts where array[(varchar 'some_tag')] && array_lowercase(tags);

【讨论】:

    【解决方案3】:

    不确定它是否有帮助,但我正在为 text[] 搜索类似的内容,并使用了类型转换:

    select id from product where lower(tags::text)::text[] && array['tat'];
    

    【讨论】:

      猜你喜欢
      • 2014-03-25
      • 1970-01-01
      • 1970-01-01
      • 2018-07-25
      • 2013-11-05
      • 2011-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多