PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Bagaimana cara mengindeks kolom array string untuk pg_trgm `'term' % APAPUN (array_column)` kueri?

Mengapa ini tidak berhasil

Jenis indeks (yaitu kelas operator) gin_trgm_ops didasarkan pada % operator, yang bekerja pada dua text argumen:

CREATE OPERATOR trgm.%(
  PROCEDURE = trgm.similarity_op,
  LEFTARG = text,
  RIGHTARG = text,
  COMMUTATOR = %,
  RESTRICT = contsel,
  JOIN = contjoinsel);

Anda tidak dapat menggunakan gin_trgm_ops untuk array.Indeks yang ditentukan untuk kolom array tidak akan pernah berfungsi dengan any(array[...]) karena elemen array individu tidak diindeks. Pengindeksan array akan membutuhkan jenis indeks yang berbeda, yaitu indeks array gin.

Untungnya, indeks gin_trgm_ops telah dirancang dengan sangat cerdik sehingga bekerja dengan operator like dan ilike , yang dapat digunakan sebagai solusi alternatif (contoh dijelaskan di bawah).

Tabel uji

memiliki dua kolom (id serial primary key, names text[]) dan berisi 100.000 kalimat latin yang dibagi menjadi elemen array.

select count(*), sum(cardinality(names))::int words from test;

 count  |  words  
--------+---------
 100000 | 1799389

select * from test limit 1;

 id |                                                     names                                                     
----+---------------------------------------------------------------------------------------------------------------
  1 | {fugiat,odio,aut,quis,dolorem,exercitationem,fugiat,voluptates,facere,error,debitis,ut,nam,et,voluptatem,eum}

Mencari fragmen kata praesent memberikan 7051 baris dalam 2400 md:

explain analyse
select count(*)
from test
where 'praesent' % any(names);

                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=5479.49..5479.50 rows=1 width=0) (actual time=2400.866..2400.866 rows=1 loops=1)
   ->  Seq Scan on test  (cost=0.00..5477.00 rows=996 width=0) (actual time=1.464..2400.271 rows=7051 loops=1)
         Filter: ('praesent'::text % ANY (names))
         Rows Removed by Filter: 92949
 Planning time: 1.038 ms
 Execution time: 2400.916 ms

Tampilan material

Salah satu solusinya adalah dengan menormalkan model, yang melibatkan pembuatan tabel baru dengan satu nama dalam satu baris. Restrukturisasi seperti itu mungkin sulit untuk diterapkan dan terkadang tidak mungkin karena kueri, tampilan, fungsi, atau dependensi lain yang ada. Efek serupa dapat dicapai tanpa mengubah struktur tabel, menggunakan tampilan yang terwujud.

create materialized view test_names as
    select id, name, name_id
    from test
    cross join unnest(names) with ordinality u(name, name_id)
    with data;

With ordinality tidak perlu, tetapi dapat berguna saat menggabungkan nama dalam urutan yang sama seperti di tabel utama. Membuat kueri test_names memberikan hasil yang sama dengan tabel utama dalam waktu yang bersamaan.

Setelah membuat indeks waktu eksekusi menurun berulang kali:

create index on test_names using gin (name gin_trgm_ops);

explain analyse
select count(distinct id)
from test_names
where 'praesent' % name

                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=4888.89..4888.90 rows=1 width=4) (actual time=56.045..56.045 rows=1 loops=1)
   ->  Bitmap Heap Scan on test_names  (cost=141.95..4884.39 rows=1799 width=4) (actual time=10.513..54.987 rows=7230 loops=1)
         Recheck Cond: ('praesent'::text % name)
         Rows Removed by Index Recheck: 7219
         Heap Blocks: exact=8122
         ->  Bitmap Index Scan on test_names_name_idx  (cost=0.00..141.50 rows=1799 width=0) (actual time=9.512..9.512 rows=14449 loops=1)
               Index Cond: ('praesent'::text % name)
 Planning time: 2.990 ms
 Execution time: 56.521 ms

Solusinya memiliki beberapa kelemahan. Karena tampilan terwujud, data disimpan dua kali dalam database. Anda harus ingat untuk me-refresh tampilan setelah perubahan pada tabel utama. Dan kueri mungkin lebih rumit karena kebutuhan untuk menggabungkan tampilan ke tabel utama.

Menggunakan ilike

Kita bisa menggunakan ilike pada array yang direpresentasikan sebagai teks. Kami membutuhkan fungsi yang tidak dapat diubah untuk membuat indeks pada array secara keseluruhan:

create function text(text[])
returns text language sql immutable as
$$ select $1::text $$

create index on test using gin (text(names) gin_trgm_ops);

dan gunakan fungsi dalam kueri:

explain analyse
select count(*)
from test
where text(names) ilike '%praesent%' 

                                                           QUERY PLAN                                                            
---------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=117.06..117.07 rows=1 width=0) (actual time=60.585..60.585 rows=1 loops=1)
   ->  Bitmap Heap Scan on test  (cost=76.08..117.03 rows=10 width=0) (actual time=2.560..60.161 rows=7051 loops=1)
         Recheck Cond: (text(names) ~~* '%praesent%'::text)
         Heap Blocks: exact=2899
         ->  Bitmap Index Scan on test_text_idx  (cost=0.00..76.08 rows=10 width=0) (actual time=2.160..2.160 rows=7051 loops=1)
               Index Cond: (text(names) ~~* '%praesent%'::text)
 Planning time: 3.301 ms
 Execution time: 60.876 ms

60 versus 2400 ms, hasil yang cukup bagus tanpa perlu membuat hubungan tambahan.

Solusi ini tampaknya lebih sederhana dan membutuhkan lebih sedikit pekerjaan, asalkan ilike , yang merupakan alat yang kurang tepat daripada trgm % operator, sudah cukup.

Mengapa kita harus menggunakan ilike daripada % untuk seluruh array sebagai teks?Kesamaannya sangat bergantung pada panjang teks.Sangat sulit untuk memilih batas yang sesuai untuk pencarian kata dalam teks panjang dengan berbagai panjang.E.g. dengan limit = 0.3 kami memiliki hasilnya:

with data(txt) as (
values
    ('praesentium,distinctio,modi,nulla,commodi,tempore'),
    ('praesentium,distinctio,modi,nulla,commodi'),
    ('praesentium,distinctio,modi,nulla'),
    ('praesentium,distinctio,modi'),
    ('praesentium,distinctio'),
    ('praesentium')
)
select length(txt), similarity('praesent', txt), 'praesent' % txt "matched?"
from data;

 length | similarity | matched? 
--------+------------+----------
     49 |   0.166667 | f           <--!
     41 |        0.2 | f           <--!
     33 |   0.228571 | f           <--!
     27 |   0.275862 | f           <--!
     22 |   0.333333 | t
     11 |   0.615385 | t
(6 rows)


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Menghitung tanggal di Postgres dengan menambahkan bulan?

  2. Bagaimana saya bisa menghentikan skrip Postgres ketika menemukan kesalahan?

  3. Menggunakan jsonb (PostgreSQL), bagaimana cara mengambil item dengan nilai tertentu yang disimpan sebagai array?

  4. Cara memasukkan daftar python ke tabel Postgres

  5. Laravel 5 memperbarui batas baris tunggal tidak berfungsi