2017-06-29 89 views
7

Nowa funkcja TrigramSimilarity w django.contrib.postgres była świetna z powodu mojego problemu. Używam go do paska wyszukiwania, aby znaleźć trudne do przeliterowania łacińskie nazwy. Problem polega na tym, że istnieje ponad 2 miliony nazw, a wyszukiwanie trwa dłużej, niż chcę.Tworzenie indeksu ginu z trygramem (gin_trgm_ops) w modelu Django

Chciałbym utworzyć indeks na trygramów jak descibed w dokumentacji postgres https://www.postgresql.org/docs/9.6/static/pgtrgm.html

ale nie jestem pewien, jak to zrobić w taki sposób, że API Django korzysta z niego. W przypadku wyszukiwania tekstu postgresów znajduje się opis tworzenia indeksu. Ale nie dla trygrafii similarety. https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/search/#performance

To co mam teraz:

class NCBI_names(models.Model): 
tax_id   = models.ForeignKey(NCBI_nodes, on_delete=models.CASCADE, default = 0) 
name_txt  = models.CharField(max_length=255, default = '') 
name_class  = models.CharField(max_length=32, db_index=True, default = '') 
class Meta: 
    indexes = [GinIndex(fields=['name_txt'])] 

a potem w get_queryset w Vieuw jest zrobić:

class TaxonSearchListView(ListView): 

#form_class=TaxonSearchForm 
template_name='collectie/taxon_list.html' 
paginate_by=20 
model=NCBI_names 
context_object_name = 'taxon_list' 

def dispatch(self, request, *args, **kwargs): 
    query = request.GET.get('q') 
    if query: 
     try: 
      tax_id = self.model.objects.get(name_txt__iexact=query).tax_id.tax_id 
      return redirect('collectie:taxon_detail', tax_id) 
     except (self.model.DoesNotExist, self.model.MultipleObjectsReturned) as e: 
      return super(TaxonSearchListView, self).dispatch(request, *args, **kwargs) 
    else: 
     return super(TaxonSearchListView, self).dispatch(request, *args, **kwargs) 

def get_queryset(self): 
    result = super(TaxonSearchListView, self).get_queryset() 
    # 
    query = self.request.GET.get('q') 
    if query:    
     result = result.exclude(name_txt__icontains = 'sp.') 
     result = result.annotate(similarity=TrigramSimilarity('name_txt', query)).filter(similarity__gt=0.3).order_by('-similarity') 
    return result 

edit umieścić całą klasę widok w

+0

dodał indeksu z opcją z PostgreSQL przednim końcu, nie wydaje się, aby cokolwiek zmienić.Czy ma to związek ze sposobem, w jaki jest wykonywane zapytanie? – Allcor

Odpowiedz

4

I miał podobny problem, próbując użyć rozszerzenia pg_tgrm do obsługi wydajnych contains i icontains Wyszukiwanie pól Django.

Nie może być bardziej elegancki sposób, ale zdefiniowanie nowego typu indeksu jak to pracował dla mnie:

from django.contrib.postgres.indexes import GinIndex 

class TrigramIndex(GinIndex): 
    def get_sql_create_template_values(self, model, schema_editor, using): 
     fields = [model._meta.get_field(field_name) for field_name, order in self.fields_orders] 
     tablespace_sql = schema_editor._get_index_tablespace_sql(model, fields) 
     quote_name = schema_editor.quote_name 
     columns = [ 
      ('%s %s' % (quote_name(field.column), order)).strip() + ' gin_trgm_ops' 
      for field, (field_name, order) in zip(fields, self.fields_orders) 
     ] 
     return { 
      'table': quote_name(model._meta.db_table), 
      'name': quote_name(self.name), 
      'columns': ', '.join(columns), 
      'using': using, 
      'extra': tablespace_sql, 
     } 

Sposób get_sql_create_template_values jest kopiowany z Index.get_sql_create_template_values(), z tylko jednym modyfikacji: dodanie + ' gin_trgm_ops'.

W przypadku użycia należy wtedy zdefiniować indeks na name_txt, używając tego TrigramIndex zamiast GinIndex. Następnie uruchom makemigrations, który wygeneruje migrację, która generuje wymagany kod SQL CREATE INDEX.

UPDATE:

Widzę, że też robi kwerendę za pomocą icontains:

result.exclude(name_txt__icontains = 'sp.') 

PostgreSQL backend okaże, że na coś takiego:

UPPER("NCBI_names"."name_txt"::text) LIKE UPPER('sp.') 

a następnie Wskaźnik trygram nie będzie używany z powodu UPPER().

miałem ten sam problem, a skończyło się instacji backend bazy danych, aby obejść:

from django.db.backends.postgresql import base, operations 

class DatabaseFeatures(base.DatabaseFeatures): 
    pass 

class DatabaseOperations(operations.DatabaseOperations): 
    def lookup_cast(self, lookup_type, internal_type=None): 
     lookup = '%s' 

     # Cast text lookups to text to allow things like filter(x__contains=4) 
     if lookup_type in ('iexact', 'contains', 'icontains', 'startswith', 
          'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'): 
      if internal_type in ('IPAddressField', 'GenericIPAddressField'): 
       lookup = "HOST(%s)" 
      else: 
       lookup = "%s::text" 

     return lookup 


class DatabaseWrapper(base.DatabaseWrapper): 
    """ 
     Override the defaults where needed to allow use of trigram index 
    """ 
    ops_class = DatabaseOperations 

    def __init__(self, *args, **kwargs): 
     self.operators.update({ 
      'icontains': 'ILIKE %s', 
      'istartswith': 'ILIKE %s', 
      'iendswith': 'ILIKE %s', 
     }) 
     self.pattern_ops.update({ 
      'icontains': "ILIKE '%%' || {} || '%%'", 
      'istartswith': "ILIKE {} || '%%'", 
      'iendswith': "ILIKE '%%' || {}", 
     }) 
     super(DatabaseWrapper, self).__init__(*args, **kwargs) 
1

W przypadku gdy ktoś chce mieć indeks na wielu kolumnach dołączył (łączone) z przestrzeni można wykorzystać moje modicitaion wbudowanego indeksu.

Tworzy indeks jak gin (("column1" || ' ' || "column2" || ' ' || ...) gin_trgm_ops)

class GinSpaceConcatIndex(GinIndex): 

    def get_sql_create_template_values(self, model, schema_editor, using): 

     fields = [model._meta.get_field(field_name) for field_name, order in self.fields_orders] 
     tablespace_sql = schema_editor._get_index_tablespace_sql(model, fields) 
     quote_name = schema_editor.quote_name 
     columns = [ 
      ('%s %s' % (quote_name(field.column), order)).strip() 
      for field, (field_name, order) in zip(fields, self.fields_orders) 
     ] 
     return { 
      'table': quote_name(model._meta.db_table), 
      'name': quote_name(self.name), 
      'columns': "({}) gin_trgm_ops".format(" || ' ' || ".join(columns)), 
      'using': using, 
      'extra': tablespace_sql, 
     }