sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur indexerar man en strängmatriskolumn för pg_trgm `'term' % NÅGON (array_column)`-fråga?

Varför detta inte fungerar

Indextypen (d.v.s. operatorklassen) gin_trgm_ops baseras på % operator, som fungerar på två text argument:

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

Du kan inte använda gin_trgm_ops för arrays.Ett index som definieras för en arraykolumn kommer aldrig att fungera med any(array[...]) eftersom enskilda element i arrayer inte indexeras. Indexering av en array skulle kräva en annan typ av index, nämligen gin array index.

Lyckligtvis är indexet gin_trgm_ops har utformats så smart att den fungerar med operatörer like och ilike , som kan användas som en alternativ lösning (exempel beskrivs nedan).

Testtabell

har två kolumner (id serial primary key, names text[]) och innehåller 100 000 latinska meningar uppdelade i arrayelement.

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}

Söker efter ordet fragment praesent ger 7051 rader på 2400 ms:

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

Materialiserad vy

En lösning är att normalisera modellen, vilket innebär att man skapar en ny tabell med ett enda namn på en rad. Sådan omstrukturering kan vara svår att implementera och ibland omöjlig på grund av befintliga frågor, vyer, funktioner eller andra beroenden. En liknande effekt kan uppnås utan att ändra tabellstrukturen, med hjälp av en materialiserad vy.

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 är inte nödvändigt, men kan vara användbart när man aggregerar namnen i samma ordning som i huvudtabellen. Frågar test_names ger samma resultat som huvudtabellen på samma tid.

Efter att ha skapat indexet minskar exekveringstiden upprepade gånger:

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

Lösningen har några nackdelar. Eftersom vyn materialiseras lagras data två gånger i databasen. Du måste komma ihåg att uppdatera vyn efter ändringar i huvudtabellen. Och frågor kan vara mer komplicerade på grund av behovet av att ansluta vyn till huvudtabellen.

Använder ilike

Vi kan använda ilike på arrayerna representerade som text. Vi behöver en oföränderlig funktion för att skapa indexet på arrayen som helhet:

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

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

och använd funktionen i frågor:

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 kontra 2400 ms, ganska bra resultat utan att behöva skapa ytterligare relationer.

Denna lösning verkar enklare och kräver mindre arbete, dock förutsatt att ilike , vilket är mindre exakt verktyg än trgm % operatör, är tillräcklig.

Varför ska vi använda ilike istället för % för hela arrayer som text?Likheten beror till stor del på längden på texterna.Det är mycket svårt att välja en lämplig gräns för sökningen ett ord i långa texter av olika längd.T.ex. med limit = 0.3 vi har resultaten:

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. hur man skapar 3 beroende dropdown-lista med PHP ajax JQUERY?

  2. Infoga uttalande med där klausul inte körs på nodejs mysql

  3. SQL MELLAN Operatör för nybörjare

  4. Mac OS X + Python + Django + MySQL