4

varchar[]Postgres 9.2 で列 (varchar 配列)を使用していくつかのタグを保存しています。タグで行を取得する際に、クエリで大文字と小文字を区別しないようにしたいと考えています。ただし、UI に表示する大文字と小文字を保持したい (したがって、すべてを小文字として保存することはできません)。

それで、私の質問は、varchar 配列に対して Postgres で大文字と小文字を区別しないインデックスを作成するにはどうすればよいですか? 考えられるアプローチの 1 つは、列に関数 GIN インデックスを作成することです。どうやってそれを行うのですか?他のアプローチはありますか?

4

3 に答える 3

6

@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 vs 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: FOREACH vs.WHILE 最後の実験として、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) が最も高速であることを示唆しています。

于 2013-04-15T15:56:06.890 に答える
1

カスタム pgplsql 関数を使用した 1 つの可能なアプローチが見つかりました。

最初に、入力として 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);
于 2013-04-15T15:24:43.897 に答える