En lugar de un prefacio
Todo comenzó con el hecho de que me ofrecieron participar en el proyecto en el marco de la asignatura "Conceptos básicos de programación web", en lugar de hacer trabajo de laboratorio y cursos, ya que dije que quería hacer algo alejado del curso general (y por lo tanto ya había suficiente conocimiento en un montón de DRF + Vue, quería algo nuevo). Y así, en uno de mis RP en github, decidí usar la búsqueda de texto completo (la tarea insinuó esto) para filtrar el contenido, lo que me hizo recurrir a la documentación de Djangoen busca de la mejor manera de implementar este negocio. Creo que conoce la mayoría de los métodos sugeridos allí (contiene, icontains, trigram_similar). Todos ellos son adecuados para algunas tareas específicas, pero no muy buenos para, a saber, la búsqueda de texto completo. Al desplazarme un poco hacia abajo, me encontré con una sección que hablaba sobre la interacción de Django y Pgsql para implementar la búsqueda basada en documentos, lo que me atrajo, ya que postgre tiene una herramienta incorporada para implementar esta búsqueda [de texto completo]. Y decidí que lo más probable es que django simplemente proporcione una API para esta búsqueda, sobre la base de la cual tal solución debería funcionar y de manera más precisa y rápida que cualquier otra opción. El profesor no me creyó demasiado, discutimos con él y se ofreció a investigar sobre este tema. Y aquí estoy yo.
Comienzo de trabajo
El primer problema que surgió ante mí fue la búsqueda de una maqueta de la base de datos, para no encontrarme con cosas incomprensibles, y fui a google y leí el wiki de postgres . Como resultado, me decidí por su base de demostración sobre vuelos a través de Rusia.
, . , . , , , search django.contrib.postgres.search. — contains ( ) icontains ( , , : "Helen" : <Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hélène Joy>), django. postgresql. tickets small 366733 . passenger_name, , , . .
django
— django . django , , :
$ python manage.py inspectdb > models.py
, , settings.py. . , . , ( ), , 300+ , 10, , . , , curl. .
, , , curl, , . , ( ).
django
, — , queryset - . .
Un QuerySet es iterable, y ejecuta su consulta de base de datos la primera vez que itera sobre él. Por ejemplo, esto imprimirá el título de todas las entradas en la base de datos:
for e in Entry.objects.all(): print(e.headline)```
class TicketListView(g.ListAPIView):
serializer_class = TicketSerializer
def get_queryset(self):
queryset = ''
params = self.request.query_params
name = params.get('name', None)
if name:
start_time = d.datetime.now()
queryset = queryset.filter(passenger_name__contains=name)
print('len of result is {} rows'.format(len(queryset)))
end_time = d.datetime.now()
time_diff = (end_time - start_time)
execution_time = time_diff.total_seconds() * 1000
print("Filter execution time {} ms".format(execution_time))
return queryset
Contiene
Comencemos con contiene, básicamente funciona como un WHERE LIKE.
queryset = queryset.filter(passenger_name__contains=name)
SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE "tickets"."passenger_name"::text LIKE %IVAN%
Para obtener el resultado de curl, ejecuté la solicitud de la siguiente manera (contada en segundos):
$ curl -w "%{time_total}\n" -o /dev/null -s http://127.0.0.1:8000/api/tickets/?name=IVAN
1,242888
Puse todo en una mesa en la hoja correspondiente.
— , 140 1400 . , . ORM 73 600 , 55 100 .
Icontains
Icontains - ( , ). , contains — icontains. .
queryset = queryset.filter(passenger_name__icontains=name)
SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE UPPER("tickets"."passenger_name"::text) LIKE UPPER(%IVAN%)
, , ( 300 ), 200 1500 . ORM — 200 700 .
Full text search ( django.contrib.postgres)
, full text search . 1300 , 1000 1700 . , ORM — 1000 1450 .
class TicketListView(g.ListAPIView):
serializer_class = TicketSerializer
def get_queryset(self):
# queryset = Tickets.objects.all()
queryset = ''
params = self.request.query_params
name = params.get('name', None)
if name:
start_time = d.datetime.now()
queryset = Tickets.objects.filter(passenger_name__search=name)
end_time = d.datetime.now()
time_diff = (end_time - start_time)
execution_time = time_diff.total_seconds() * 1000
print("Filter execution time {} ms".format(execution_time))
f = open('results.txt', 'a')
f.write('{}'.format(execution_time))
f.write('\n')
f.close()
return queryset
Full text search ( rest_framework.filters, — SearchFilter)
FTS, FTS , , contains icontains. 200 1710 .
FTS , . , 800 1120 .
...
from rest_framework import filters as f
class TicketListView(g.ListAPIView):
queryset = Tickets.objects.all()
serializer_class = TicketSerializer
filter_backends = [f.SearchFilter]
search_fields = ['@passenger_name']
django-filter
contains icontains, . , django-filter - Django ORM.
?
— (, , ) , . — . , ( , , contains/icontains) , , , , .
En general, mi comprensión de algunos de los funcionamientos internos de django se ha establecido gracias a esta investigación. Y finalmente se dio cuenta de la diferencia entre la búsqueda de subcadenas y la búsqueda de texto completo. La diferencia en su implementación a través del Django ORM.