PostgreSQL JSONB und Volltextsuche in Django

  • 11 Jan 2026
  • admin
  • 3 min
  • 288

PostgreSQL: Über die Relationale Datenbank Hinaus

PostgreSQL bietet erweiterte Funktionen, die es perfekt für moderne Anwendungen machen: semi-strukturierte Daten mit JSONB und integrierte Textsuche.

1. JSONB in Django

Modell mit JSONField

from django.db import models
from django.contrib.postgres.fields import ArrayField

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    # JSONB für flexible Daten
    metadata = models.JSONField(default=dict)
    specifications = models.JSONField(default=dict)

    # String-Array
    tags = ArrayField(
        models.CharField(max_length=50),
        default=list,
        blank=True
    )

    class Meta:
        indexes = [
            # GIN-Index für effiziente JSONB-Abfragen
            models.Index(
                name='idx_product_metadata_gin',
                fields=['metadata'],
                opclasses=['jsonb_path_ops']
            ),
        ]

JSONB-Abfragen

from django.db.models import F
from django.contrib.postgres.fields.jsonb import KeyTextTransform

# Zugriff auf bestimmte Schlüssel
products = Product.objects.filter(
    metadata__brand='Apple'
)

# Verschachtelte Schlüssel
products = Product.objects.filter(
    specifications__display__size__gte=6.0
)

# Schlüsselexistenz prüfen
products = Product.objects.filter(
    metadata__has_key='warranty'
)

# Enthält Wert
products = Product.objects.filter(
    metadata__contains={'featured': True}
)

# Enthalten in
products = Product.objects.filter(
    metadata__contained_by={'brand': 'Apple', 'category': 'phone'}
)

# Annotation mit JSON-Wert
from django.db.models.functions import Cast
from django.db.models import FloatField

products = Product.objects.annotate(
    rating=Cast(
        KeyTextTransform('rating', 'metadata'),
        FloatField()
    )
).filter(rating__gte=4.5)

Atomare JSONB-Updates

from django.db.models import F
from django.db.models.functions import JSONObject
from django.contrib.postgres.fields.jsonb import KeyTransform

# Bestimmten Schlüssel aktualisieren (Django 4.2+)
Product.objects.filter(pk=1).update(
    metadata=F('metadata') | {'updated_at': '2024-01-15'}
)

# Schlüssel entfernen
from django.contrib.postgres.expressions import ArraySubquery
Product.objects.filter(pk=1).update(
    metadata=models.Func(
        F('metadata'),
        models.Value('deprecated_key'),
        function='jsonb_delete_path'
    )
)

# Numerischen Wert in JSON inkrementieren
from django.db.models.expressions import RawSQL
Product.objects.filter(pk=1).update(
    metadata=RawSQL(
        "jsonb_set(metadata, '{views}', (COALESCE(metadata->>'views', '0')::int + 1)::text::jsonb)",
        []
    )
)

2. Volltextsuche

Modell-Setup

from django.contrib.postgres.search import (
    SearchVector, SearchQuery, SearchRank,
    SearchVectorField, TrigramSimilarity
)
from django.contrib.postgres.indexes import GinIndex

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    excerpt = models.TextField(blank=True)

    # Feld für indizierte Suche
    search_vector = SearchVectorField(null=True)

    class Meta:
        indexes = [
            GinIndex(fields=['search_vector']),
        ]

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        # Search-Vektor aktualisieren
        Article.objects.filter(pk=self.pk).update(
            search_vector=(
                SearchVector('title', weight='A', config='german') +
                SearchVector('excerpt', weight='B', config='german') +
                SearchVector('content', weight='C', config='german')
            )
        )

Volltext-Abfragen

# Einfache Suche
from django.contrib.postgres.search import SearchVector, SearchQuery

articles = Article.objects.annotate(
    search=SearchVector('title', 'content', config='german')
).filter(
    search=SearchQuery('django python', config='german')
)

# Mit Ranking
from django.contrib.postgres.search import SearchRank

query = SearchQuery('machine learning', config='german')
articles = Article.objects.annotate(
    rank=SearchRank(F('search_vector'), query)
).filter(
    search_vector=query
).order_by('-rank')

# Headline (Treffer hervorheben)
from django.contrib.postgres.search import SearchHeadline

articles = Article.objects.annotate(
    headline=SearchHeadline(
        'content',
        query,
        start_sel='',
        stop_sel='',
        config='german'
    )
)

# Erweiterte Abfragen
# UND
query = SearchQuery('django') & SearchQuery('api')

# ODER
query = SearchQuery('django') | SearchQuery('flask')

# NICHT
query = SearchQuery('python') & ~SearchQuery('java')

# Phrasensuche
query = SearchQuery('machine learning', search_type='phrase')

Trigram-Suche (Fuzzy)

# Erweiterung installieren
# CREATE EXTENSION pg_trgm;

from django.contrib.postgres.search import TrigramSimilarity, TrigramDistance

# Ähnliche Artikel finden (typo-tolerant)
articles = Article.objects.annotate(
    similarity=TrigramSimilarity('title', 'djnago')  # absichtlicher Typo
).filter(
    similarity__gt=0.3
).order_by('-similarity')

# Trigram-Distanz
articles = Article.objects.annotate(
    distance=TrigramDistance('title', 'machine lerning')
).filter(
    distance__lt=0.7
).order_by('distance')

# Kombination von Volltext und Trigram
from django.db.models import Q, Value
from django.db.models.functions import Greatest

search_term = 'machin lernin'  # Mit Fehlern

articles = Article.objects.annotate(
    similarity=Greatest(
        TrigramSimilarity('title', search_term),
        TrigramSimilarity('content', search_term)
    ),
    rank=SearchRank(
        F('search_vector'),
        SearchQuery(search_term, config='german')
    )
).filter(
    Q(similarity__gt=0.2) | Q(rank__gt=0.1)
).order_by('-similarity', '-rank')

3. Optimierte Indizes

class Article(models.Model):
    # ... fields ...

    class Meta:
        indexes = [
            # GIN für Volltext
            GinIndex(
                fields=['search_vector'],
                name='article_search_gin'
            ),

            # GIN für JSONB
            GinIndex(
                fields=['metadata'],
                name='article_metadata_gin',
                opclasses=['jsonb_path_ops']
            ),

            # Partieller Index
            models.Index(
                fields=['published_at'],
                name='article_published_idx',
                condition=models.Q(status='published')
            ),

            # Trigram-Index
            GinIndex(
                fields=['title'],
                name='article_title_trgm',
                opclasses=['gin_trgm_ops']
            ),
        ]

4. Management-Command für Search-Rebuild

# management/commands/rebuild_search.py
from django.core.management.base import BaseCommand
from django.contrib.postgres.search import SearchVector
from blog.models import Article

class Command(BaseCommand):
    help = 'Search-Vektoren für alle Artikel neu erstellen'

    def handle(self, *args, **options):
        self.stdout.write('Erstelle Search-Vektoren neu...')

        Article.objects.update(
            search_vector=(
                SearchVector('title', weight='A', config='german') +
                SearchVector('excerpt', weight='B', config='german') +
                SearchVector('content', weight='C', config='german')
            )
        )

        count = Article.objects.count()
        self.stdout.write(
            self.style.SUCCESS(f'{count} Artikel aktualisiert')
        )

Best Practices

  • Verwenden Sie JSONB statt JSON für effiziente Abfragen
  • Erstellen Sie GIN-Indizes für häufig abgefragte JSONB-Felder
  • Verwenden Sie SearchVectorField für häufige Suchen
  • Kombinieren Sie Volltext und Trigram für Fuzzy-Suchen
  • Aktualisieren Sie Search-Vektoren mit Triggern oder Signals

Fazit

PostgreSQL mit JSONB und Volltextsuche macht Django zu einer leistungsstarken Plattform für Anwendungen mit erweiterten Such- und Datenflexibilitätsanforderungen.

Teilen Sie diesen Artikel

Kunden

Noch keine Kommentare. Seien Sie der Erste!

Einen Kommentar hinterlassen

Wird nicht veröffentlicht

Verwandte Artikel