質問する
テストテーブル:
CREATE TABLE tbl (id int, str text);
INSERT INTO tbl VALUES
(1, 'a.b.c.d.e')
, (2, 'x1.yy2.zzz3') -- different number & length of elements for testing
, (3, '') -- empty string
, (4, NULL); -- NULL
LATERALサブクエリでの再帰CTE
SELECT *
FROM tbl, LATERAL (
WITH RECURSIVE cte AS (
SELECT str
UNION ALL
SELECT right(str, strpos(str, '.') * -1) -- trim leading name
FROM cte
WHERE str LIKE '%.%' -- stop after last dot removed
)
SELECT ARRAY(TABLE cte) AS result
) r;
CROSS JOIN LATERAL
(, LATERAL
略して)サブクエリの集計結果は常に行を返すため、安全です。あなたが得る...
- ...空の文字列要素を
str = ''
持つベーステーブルの配列
- ...
str IS NULL
ベーステーブルのNULL要素を持つ配列
サブクエリで安価な配列コンストラクターでラップされているため、外部クエリで集計は行われません。
SQL機能の代表例ですが、rCTEオーバーヘッドにより、最高のパフォーマンスが妨げられる可能性があります。
取るに足らない数の要素に対する総当たり
要素の数がごく少数の場合、サブクエリを使用しない単純なアプローチの方が高速です。
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
あなたがコメントしたような最大5つの要素を想定しています。もっと簡単に拡張できます。
特定のドメインの要素が少ない場合、過剰なsubstring()
式はNULLを返し、によって削除されarray_remove()
ます。
実際、above(right(str, strpos(str, '.')
)からの式は、正規表現関数の方がコストが高いため、数回ネストされている方が(読むのは面倒ですが)高速です。
@Duduのクエリのフォーク
@Duduのスマートクエリは、次のように改善される可能性がありますgenerate_subscripts()
。
SELECT id, array_agg(array_to_string(arr[i:], '.')) AS result
FROM (SELECT id, string_to_array(str,'.') AS arr FROM tbl) t
LEFT JOIN LATERAL generate_subscripts(arr, 1) i ON true
GROUP BY id;
LEFT JOIN LATERAL ... ON true
NULL値を含む可能性のある行を保持するためにも使用します。
PL / pgSQL関数
rCTEと同様のロジック。あなたが持っているものよりも実質的にシンプルで速い:
CREATE OR REPLACE FUNCTION string_part_seq(input text, OUT result text[]) AS
$func$
BEGIN
LOOP
result := result || input; -- text[] || text array concatenation
input := right(input, strpos(input, '.') * -1);
EXIT WHEN input = '';
END LOOP;
END
$func$ LANGUAGE plpgsql IMMUTABLE STRICT;
OUT
パラメータは自動的に関数の最後に返されます。
初期化する必要はありませんresult
、のでNULL::text[] || text 'a' = '{a}'::text[]
。
これ'a'
は正しく入力された場合にのみ機能します。NULL::text[] || 'a'
(文字列リテラル)Postgresがarray || array
演算子を選択するため、エラーが発生します。
strpos()
0
ドットが見つからない場合はを返すためright()
、空の文字列を返し、ループが終了します。
これはおそらくここでのすべてのソリューションの中で最速です。
それらはすべてPostgres 9.3以降で機能します(短い配列スライス表記を除きます。フィドルに上限を追加して、9.3で機能するようにしています :) 。
arr[3:]
arr[3:999]
SQL Fiddle。
検索を最適化するための異なるアプローチ
私は@ jpmc26(そしてあなた自身)と一緒です:完全に異なるアプローチが望ましいでしょう。jpmc26さんの組み合わせのようなI reverse()
とtext_pattern_ops
。
トライグラムインデックスは、部分一致またはあいまい一致に対して優れています。ただし、興味があるのは単語全体だけなので、全文検索も選択肢の1つです。インデックスサイズが大幅に小さくなり、パフォーマンスが向上することを期待しています。
pg_trgmとFTSは大文字小文字を区別しないクエリをサポートします。
q.x.t.com
またはt.com
(インラインドットのある単語)のようなホスト名は、タイプ「ホスト」として識別され、1つの単語として扱われます。しかし、FTS にはプレフィックスマッチングもあります(見過ごされがちです)。マニュアル:
また、*
語彙素に添付して、プレフィックスの一致を指定できます。
@ jpmc26のスマートなアイデアをreverse()
で使用すると、これを機能させることができます。
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
これはインデックスでサポートされています。
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
'simple'
構成に注意してください。デフォルトの構成でステミングやシソーラスを使用しないでください'english'
。
別の方法としては(可能なクエリの種類が多ければ多いほど)、Postgres 9.6のテキスト検索の新しいフレーズ検索機能を使用できます。リリースノート:
新しい演算子<->
and を使用して、tsquery入力でフレーズ検索クエリを指定できます。前者は、前後の語彙素がこの順序で隣接している必要があることを意味します。後者は、それらが正確に語彙素離れている必要があることを意味します。<
N
>
N
クエリ:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
ドット('.'
)をスペース(' '
)に置き換えて、パーサーが「t.com」をホスト名として分類しないようにし、代わりに各単語を個別の語彙素として使用します。
そして、それに合わせて一致するインデックス:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));