PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Làm cách nào để lập chỉ mục một cột mảng chuỗi cho truy vấn pg_trgm `'term'% ANY (array_column)`?

Tại sao điều này không hoạt động

Loại chỉ mục (tức là lớp toán tử) gin_trgm_ops dựa trên % toán tử, hoạt động trên hai text đối số:

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

Bạn không thể sử dụng gin_trgm_ops cho mảng. Một chỉ mục được xác định cho một cột mảng sẽ không bao giờ hoạt động với any(array[...]) bởi vì các phần tử riêng lẻ của mảng không được lập chỉ mục. Việc lập chỉ mục một mảng sẽ yêu cầu một loại chỉ mục khác, cụ thể là chỉ số mảng gin.

May mắn thay, chỉ mục gin_trgm_ops đã được thiết kế thông minh đến mức nó đang hoạt động với các toán tử likeilike , có thể được sử dụng như một giải pháp thay thế (ví dụ được mô tả bên dưới).

Bảng kiểm tra

có hai cột (id serial primary key, names text[]) và chứa 100000 câu latin được chia thành các phần tử mảng.

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}

Tìm kiếm đoạn từ praesent cho 7051 hàng trong 2400 mili giây:

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

Chế độ xem cụ thể hóa

Một giải pháp là chuẩn hóa mô hình, liên quan đến việc tạo một bảng mới với một tên duy nhất trong một hàng. Việc tái cấu trúc như vậy có thể khó thực hiện và đôi khi không thể thực hiện được do các truy vấn, quan điểm, chức năng hiện có hoặc các yếu tố phụ thuộc khác. Có thể đạt được hiệu ứng tương tự mà không cần thay đổi cấu trúc bảng bằng cách sử dụng chế độ xem cụ thể hóa.

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 là không cần thiết, nhưng có thể hữu ích khi tổng hợp các tên theo thứ tự như trong bảng chính. Truy vấn test_names cho kết quả giống như bảng chính trong cùng một thời điểm.

Sau khi tạo chỉ mục, thời gian thực hiện chỉ mục giảm đi nhiều lần:

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

Giải pháp có một số hạn chế. Bởi vì chế độ xem được thực thể hóa, dữ liệu được lưu trữ hai lần trong cơ sở dữ liệu. Bạn phải nhớ làm mới chế độ xem sau khi thay đổi bảng chính. Và các truy vấn có thể phức tạp hơn vì cần phải nối chế độ xem vào bảng chính.

Sử dụng ilike

Chúng ta có thể sử dụng ilike trên các mảng được biểu diễn dưới dạng văn bản. Chúng ta cần một hàm không thay đổi để tạo chỉ mục trên toàn bộ mảng:

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

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

và sử dụng hàm trong các truy vấn:

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 so với 2400 ms, kết quả khá tốt mà không cần tạo quan hệ bổ sung.

Giải pháp này có vẻ đơn giản hơn và yêu cầu ít công việc hơn, tuy nhiên, miễn là ilike , là công cụ kém chính xác hơn trgm % toán tử, là đủ.

Tại sao chúng ta nên sử dụng ilike thay vì % đối với toàn bộ mảng dưới dạng văn bản? Sự giống nhau phụ thuộc phần lớn vào độ dài của văn bản. Rất khó để chọn một giới hạn thích hợp cho việc tìm kiếm một từ trong các văn bản dài có độ dài khác nhau. với limit = 0.3 chúng tôi có kết quả:

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. Liệt kê các ràng buộc cho tất cả các bảng có các chủ sở hữu khác nhau trong PostgreSQL

  2. Làm cách nào để chọn UUID tối thiểu với kết nối bên ngoài bên trái?

  3. CHÈN POSTGRESQL nếu tên hàng cụ thể không tồn tại?

  4. Sqlalchemy với postgres. Cố gắng lấy "DISTINCT ON" thay vì "DISTINCT"

  5. Không thể xác định kiểu đa hình vì đầu vào có kiểu không xác định