2017-06-29 1 views
7

Das neue TrigramSimilarity-Feature des django.contrib.postgres war großartig für ein Problem, das ich hatte. Ich benutze es für eine Suchleiste, um schwer zu finden, lateinische Namen zu buchstabieren. Das Problem ist, dass es über 2 Millionen Namen gibt und die Suche länger dauert, als ich möchte.Erstellen eines Gin-Index mit Trigram (gin_trgm_ops) im Django-Modell

Ich mag einen Index für die Trigrams zu erzeugen, wie in der Postgres-Dokumentation descibed https://www.postgresql.org/docs/9.6/static/pgtrgm.html

Aber ich bin nicht sicher, wie dies in einer Art und Weise zu tun, dass der Django API Gebrauch macht. Für die Postgres-Textsuche gibt es eine Beschreibung, wie ein Index erstellt wird. Aber nicht für die Trigrammähnlichkeit. https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/search/#performance

Das ist, was ich jetzt haben:

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'])] 

und dann in der get_queryset des vieuw i tun:

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 

bearbeiten stellen die gesamte Ansichtsklasse in

+0

Added der Index mit der Option mit postgresql Frontend, schien nicht, etwas zu ändern.Könnte es mit der Art und Weise, wie die Abfrage gemacht wird, zu tun haben? – Allcor

Antwort

4

I hatte ein ähnliches Problem und versuchte, die pg_tgrm Erweiterung zu verwenden, um contains undeffizient zu unterstützenDjango Feldsuche.

Es kann ein eleganter Weg, aber die Definition eines neuen Indextyp, wie dies für mich gearbeitet:

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, 
     } 

Verfahren get_sql_create_template_values aus Index.get_sql_create_template_values() kopiert, mit nur einer Änderung: die Zugabe von + ' gin_trgm_ops'.

Für Ihren Anwendungsfall würden Sie dann den Index auf name_txt mit dieser TrigramIndex statt einer GinIndex definieren. Führen Sie dann makemigrations aus, wodurch eine Migration generiert wird, die den erforderlichen SQL-Code CREATE INDEX generiert.

UPDATE:

ich sehe, Sie sind auch eine Abfrage mit icontains tun:

result.exclude(name_txt__icontains = 'sp.') 

Das Postgresql Backend wird sich das in etwa so:

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

und dann die Trigramm-Index wird wegen der UPPER() nicht verwendet.

hatte ich das gleiche Problem, und am Ende des Datenbank-Backend Subklassen um ihn zu arbeiten:

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

Falls jemand Index für mehrere Spalten verbunden (verketteten) mit Platz haben wollen Sie meine modicitaion verwenden können des eingebauten Index.

Erstellt Index wie 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, 
     } 
Verwandte Themen