Wyszukiwanie danych to podstawowa kwestia z którą na pewno każdy się zetknie na swojej deweloperskiej drodze. Praktycznie każdy projekt, będzie wymagać jakiegoś rodzaju wyszukiwarki. Dziś zajmiemy się jednak konkretnie wyszukiwaniem pełnotekstowym. Zacznijmy jednak od początku.
Co to jest ten full text search i co nam daje?
Wyszukiwanie pełnotekstowe, daje nam możliwość przeszukania zbiorów danych uwzględniając błędy ortograficzne odmianę wyrazów czy synonimy.
Co to w praktyce oznacza?
Przykładowo, mamy bloga i chcemy znaleźć przepis na kurczaka miodowo musztardowego 😉 Jeśli użyjemy prostego/zwykłego wyszukiwania, po wpisaniu frazy „kurczak miodowo musztardowy”, ukaże nam się przepis. Super!
Problem polega na tym, że wpisując „miodowy kurczak” czy „kurczak w musztardzie” nie znajdziemy tego co chcemy, wpisanie „kurak miodowy” też nie pomoże 🙁
Dlatego chcąc zapewnić jak najlepsze możliwości odnalezienia przepisu, musimy skorzystać z jakiegoś narzędzia, które pomoże nam usprawnić wyszukiwanie.
Więc jakie mamy możliwości?
Istnieje wiele „silników wyszukiwania”, które pozwolą nam osiągnąć zamierzony rezultat. Najpopularniejsze to ElasticSearch czy Solr. Są również rozwiązania bazodanowe które umożliwiają nam wykonanie takiej operacji bez konieczności instalacji dodatkowego oprogramowania (MongoDB Atlas, PostgreSQL itd.). Integrację Django z ElasticSearch zostawimy na inny artykuł. Dziś skupimy się na wykorzystaniu bazy danych PostgreSQL. Od Django w wersji 1.10 istnieją funkcje pozwalające korzystać z wyszukiwania pełnotekstowego, jeśli użyjemy właśnie z tej bazy danych.
Jak zacząć?
Na początek potrzebujemy PostgreSQL’a i Django – raczej nic trudnego. Trzeba jednak pamiętać, że żyjemy w Polsce…jakkolwiek by to nie zabrzmiało 😛 Wspominam o tym, ponieważ chcąc zapewnić najlepszy możliwy sposób wyszukiwania, będziemy potrzebowali słowników i małej konfiguracji po stronie bazy danych, dlatego zachęcam do zatrzymania się na moment i sprawdzenia posta „Jak nauczyć słonia języka polskiego„.
W samym Django, musimy dorzucić django.contrib.postgres do listy INSTALLED_APPS. Nie zaszkodzi również stworzyć migrację (można to zrobić ręcznie ale to „średnio eleganckie” rozwiązanie), która uruchomi rozszerzenie bazy danych – konkretnie pg_trgm. Możemy to zrobić wykonując polecenie:
$ python manage.py makemigrations <nazwa_aplikacji> --empty
Polecenie to, stworzy pustą migrację, którą należy uzupełnić, importując klasę TrigramExtension i umieszczając ją w tablicy operacji. Będzie to wyglądać, mniej więcej tak:
# Generated by Django 3.2.0 on 2021-04-10 16:43
from django.db import migrations
from django.contrib.postgres.operations import TrigramExtension
class Migration(migrations.Migration):
dependencies = [
("blog", "0028_post_galery_is_main"),
]
operations = [
TrigramExtension(),
]
Teraz wystarczy tylko uruchomić migrację i gotowe!
$ python manage.py migrate
Mogę już szukać tego kurczaka?
W zasadzie tak 😉 Napiszmy sobie teraz kawałek kodu który pozwoli nam szukać po tytule oraz treści przepisu. Zakładając, że mamy przykładowy model:
class Recipe(models.Model):
title = models.CharField("Nazwa", max_length=200)
content = models.TextField("Treść przepisu", max_length=5000)
Skoro aktywowaliśmy sobie rozszerzenie pg_trgm, skorzystajmy z klas TrigramDistance oraz Greatest. Wyszukać przepis lub przepisy które będą zawierały odpowiednią frazę możemy w następujący sposób:
# Nasze zapytanie
query = "kurczak musztardowy"
# Zapytanie o przepisy
recepies = (
Recipe.objects.annotate(
distance=Greatest(
TrigramDistance("title", query),
TrigramDistance("content", query),
),
)
.filter(distance__lte=0.935)
.order_by("distance", "title")
)
W ten sposób otrzymamy wyniki które będą najbardziej pasować do naszego zapytania, niezależnie czy szukane frazy znajdą się w polu title czy content. Warto zwrócić tutaj uwagę na współczynnik distance który należy odpowiednio dostosować aby otrzymać oczekiwane rezultaty.
To wszystko?
Oczywiście, że nie 😀 Temat poruszony w tym poście, to „wierzchołek góry lodowej”. Nie powiedzieliśmy tutaj nic o wydajności ani nie poruszyliśmy zbytnio kwestii wersji językowych, wag czy tuningu zapytań. Oprócz wspomnianej klasy, mamy również inne opcje jak SearchVector, SearchRank czy TrigramSimilarity i dopiero odpowiednie połączenie sposobów wyszukiwania, pozwoli nam uzyskać oczekiwane rezultaty.
W oczekiwaniu na kolejną porcję wiedzy, zachęcam do sprawdzenia pełnej dokumentacji Django.
Photo by Christian Wiediger on Unsplash
