Pertanyaan ditanyakan
Meja tes:
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
CTE rekursif dalam subquery LATERAL
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;
The CROSS JOIN LATERAL
( , LATERAL
untuk pendek) adalah aman, karena hasil agregat subquery selalu mengembalikan berturut-turut. Anda mendapatkan ...
- ... sebuah array dengan elemen string kosong
str = ''
di dalam tabel dasar
- ... sebuah array dengan elemen NULL untuk
str IS NULL
di tabel dasar
Dibungkus dengan konstruktor array murah di subquery, jadi tidak ada agregasi di kueri luar.
Pameran fitur SQL, tetapi overhead rCTE dapat mencegah kinerja terbaik.
Brute force untuk sejumlah elemen sepele
Untuk kasus Anda dengan sejumlah kecil elemen , pendekatan sederhana tanpa subquery mungkin lebih cepat:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Dengan asumsi maksimum 5 elemen seperti Anda berkomentar. Anda dapat dengan mudah memperluas untuk lebih banyak.
Jika domain tertentu memiliki lebih sedikit elemen, substring()
ekspresi berlebih mengembalikan NULL dan dihapus oleh array_remove()
.
Sebenarnya, ekspresi dari atas ( right(str, strpos(str, '.')
), bersarang beberapa kali mungkin lebih cepat (meskipun canggung untuk dibaca) karena fungsi ekspresi reguler lebih mahal.
Garpu dari permintaan @ Dudu
@ Permintaan pintar Dudu dapat ditingkatkan dengan 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;
Juga menggunakan LEFT JOIN LATERAL ... ON true
untuk mempertahankan kemungkinan baris dengan nilai NULL.
Fungsi PL / pgSQL
Logika serupa dengan rCTE. Lebih sederhana dan lebih cepat dari yang Anda miliki:
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;
The OUT
parameter dikembalikan pada akhir fungsi otomatis.
Tidak perlu menginisialisasi result
, karena NULL::text[] || text 'a' = '{a}'::text[]
.
Ini hanya berfungsi dengan 'a'
diketik dengan benar. NULL::text[] || 'a'
(string literal) akan memunculkan kesalahan karena Postgres memilih array || array
operator.
strpos()
kembali 0
jika tidak ada titik yang ditemukan, jadi right()
mengembalikan string kosong dan loop berakhir.
Ini mungkin yang tercepat dari semua solusi di sini.
Semuanya bekerja di Postgres 9.3+
(kecuali untuk notasi array pendek arr[3:]
. Saya menambahkan batas atas pada biola untuk membuatnya bekerja di halaman 9.3:. arr[3:999]
)
SQL Fiddle.
Pendekatan berbeda untuk mengoptimalkan pencarian
Saya dengan @ jpmc26 (dan diri Anda sendiri): pendekatan yang sama sekali berbeda lebih disukai. Saya suka kombinasi jpmc26 reverse()
dan a text_pattern_ops
.
Indeks trigram akan lebih baik untuk pertandingan parsial atau fuzzy. Tetapi karena Anda hanya tertarik pada seluruh kata , Pencarian Teks Lengkap adalah pilihan lain. Saya mengharapkan ukuran indeks yang jauh lebih kecil dan dengan demikian kinerja yang lebih baik.
pg_trgm serta FTS mendukung kueri tidak sensitif kasus , btw.
Nama host seperti q.x.t.com
atau t.com
(kata-kata dengan titik sebaris) diidentifikasi sebagai tipe "host" dan diperlakukan sebagai satu kata. Tetapi ada juga pencocokan awalan di FTS (yang tampaknya kadang-kadang diabaikan). Manual:
Juga, *
dapat dilampirkan ke leksem untuk menentukan pencocokan awalan:
Menggunakan ide cerdas @ jpmc26 dengan reverse()
, kita dapat membuat ini berfungsi:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Yang didukung oleh indeks:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Perhatikan 'simple'
konfigurasi: Kami tidak ingin stemming atau tesaurus digunakan dengan 'english'
konfigurasi default .
Atau (dengan variasi yang lebih besar dari kemungkinan pertanyaan) kita bisa menggunakan kemampuan pencarian frasa baru dari pencarian teks di Postgres 9.6. Catatan rilis:
Permintaan pencarian frase dapat ditentukan dalam input tsquery menggunakan operator baru <->
dan . Yang pertama berarti bahwa leksem sebelum dan sesudahnya harus tampak berdekatan satu sama lain dalam urutan itu. Yang terakhir berarti mereka harus terpisah persis leksem.<
N
>
N
Pertanyaan:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Ganti dot ( '.'
) dengan spasi ( ' '
) untuk mencegah pengurai mengklasifikasikan 't.com' sebagai nama host dan alih-alih gunakan setiap kata sebagai leksem yang terpisah.
Dan indeks yang cocok untuk digunakan:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));