MongoDB

Datenbanken
Data Wrangling
R
Python
Dokumentdatenbank ohne festes Schema: wann sie passt und wie man sie abfragt.

Kernideen

  • Speichert Dokumente statt Zeilen, ohne festes Schema
  • Verschachtelte Strukturen bleiben erhalten, statt auf Tabellen verteilt zu werden
  • Abgefragt wird nicht mit SQL, sondern über Filterdokumente und eine Pipeline
  • Kein Schema heisst nicht keine Struktur, sondern Struktur ohne Prüfung
  • Für die Auswertung wird meist flach gemacht, was verschachtelt gespeichert ist
  • Relationale Systeme können JSON inzwischen auch; der Vergleich lohnt sich

Erklärung

Vorwissen: Datenmodellierung als Gegenpol und Datenimport und Formate für JSON. Welches System wann passt, steht in der Übersicht.

Dokumente statt Zeilen

Ein Dokument ist eine JSON-ähnliche Struktur, die auch Listen und Unterdokumente enthalten darf. Eine Bestellung liegt damit als Ganzes an einem Ort, statt auf mehrere Tabellen verteilt zu sein:

{
  "bestell_id": 4711,
  "kunde": { "name": "Meier AG", "ort": "Chur" },
  "positionen": [
    { "artikel": "Ventil 3/2", "menge": 10, "preis": 24.0 },
    { "artikel": "Sensor induktiv", "menge": 2, "preis": 128.0 }
  ],
  "datum": "2026-03-15"
}

Der Vorteil ist der Zugriff: Wer die Bestellung liest, hat alles beisammen, ohne zu verbinden. Der Nachteil zeigt sich, sobald man quer auswerten will, etwa den Umsatz je Artikel über alle Bestellungen. Dann muss die Struktur aufgebrochen werden, und das ist genau die Arbeit, die ein relationales Modell einem abnimmt.

Kein Schema heisst nicht keine Struktur

Ohne Schemaprüfung nimmt die Datenbank jedes Dokument an, auch eines mit fehlendem Feld, falschem Typ oder abweichender Schreibweise. Die Struktur existiert trotzdem, sie steht nur nicht mehr in der Datenbank, sondern verteilt in allen Programmen, die darauf zugreifen.

Für die Datenarbeit hat das eine unmittelbare Folge: Die erste Frage an eine unbekannte Sammlung ist nicht “was steht drin”, sondern “welche Felder gibt es überhaupt, und in welchen Typen”. Prüfungen, die bei PostgreSQL die Datenbank übernimmt, gehören hier ins eigene Vorgehen, siehe Datenqualität.

MongoDB kann eine Schemaprüfung erzwingen; genutzt wird sie in gewachsenen Beständen selten.

Abfragen ohne SQL

Gefiltert wird mit einem Dokument, das die Bedingungen beschreibt. Aggregationen laufen über eine Pipeline aus Stufen, die nacheinander angewendet werden; jede Stufe entspricht ungefähr einer SQL-Klausel.

SQL MongoDB
WHERE $match
GROUP BY $group
SELECT Spalten $project
ORDER BY $sort
LIMIT $limit
JOIN $lookup
Liste aufbrechen $unwind
pipeline = [
    {"$match": {"datum": {"$gte": "2026-01-01"}}},
    {"$unwind": "$positionen"},
    {"$group": {"_id": "$positionen.artikel",
                "umsatz": {"$sum": {"$multiply": ["$positionen.menge",
                                                  "$positionen.preis"]}}}},
    {"$sort": {"umsatz": -1}},
    {"$limit": 10},
]

Die Stufe $unwind hat kein direktes Gegenstück in SQL: Sie macht aus einem Dokument mit einer Liste von drei Positionen drei Dokumente. Erst danach lässt sich über Positionen gruppieren.

Wann eine Dokumentdatenbank passt

Fall Passend
Struktur variiert je Datensatz und ändert sich häufig MongoDB
Verschachtelte Objekte werden als Ganzes gelesen und geschrieben MongoDB
Querauswertungen über viele Datensätze sind der Normalfall Relationale Systeme
Fremdschlüssel und Konsistenz sollen erzwungen werden PostgreSQL
Nur ein paar Felder sind halbstrukturiert PostgreSQL mit JSON-Spalte
Grosse Auswertungen über Dateien DuckDB

Typische Aufgaben

Verbinden

library(mongolite)
sammlung <- mongo(collection = "bestellung", db = "shop",
                  url = Sys.getenv("MONGO_URL"))
from pymongo import MongoClient
import os

klient = MongoClient(os.environ["MONGO_URL"])
sammlung = klient["shop"]["bestellung"]

Sich einen Überblick verschaffen

print(klient.list_database_names())
print(klient["shop"].list_collection_names())
print(sammlung.estimated_document_count())
sammlung.find_one()                       # ein Beispieldokument

Welche Felder überhaupt vorkommen, verrät eine kleine Auszählung, weil es kein Schema gibt, das man ansehen könnte:

from collections import Counter

felder = Counter()
for dok in sammlung.find().limit(1000):
    felder.update(dok.keys())
print(felder.most_common())

Filtern und in einen Data Frame holen

df <- sammlung$find(
  query = '{"datum": {"$gte": "2026-01-01"}}',
  fields = '{"bestell_id": 1, "kunde.ort": 1, "datum": 1, "_id": 0}'
)
import pandas as pd

df = pd.DataFrame(sammlung.find(
    {"datum": {"$gte": "2026-01-01"}},
    {"bestell_id": 1, "kunde.ort": 1, "datum": 1, "_id": 0},
))

Verschachtelte Felder flach machen

df = pd.json_normalize(
    sammlung.find({}, {"_id": 0}),
    record_path="positionen",
    meta=["bestell_id", "datum", ["kunde", "ort"]],
)

Damit entsteht die Tabelle, mit der sich weiterarbeiten lässt, siehe Tidy Data.

Aggregieren in der Datenbank

ergebnis = list(sammlung.aggregate([
    {"$unwind": "$positionen"},
    {"$group": {"_id": "$positionen.artikel",
                "umsatz": {"$sum": {"$multiply": ["$positionen.menge",
                                                  "$positionen.preis"]}}}},
    {"$sort": {"umsatz": -1}},
]))
df = pd.DataFrame(ergebnis).rename(columns={"_id": "artikel"})

Index anlegen

sammlung.create_index("datum")
sammlung.create_index([("kunde.ort", 1), ("datum", -1)])
print(sammlung.index_information())

Zum Ausprobieren aus einem Container

docker run --name mongo -p 27017:27017 -d mongo
docker exec -it mongo mongosh

Verständnisfragen

Im Build dieser Website läuft kein MongoDB-Server, deshalb sind die Beispiele hier Listings. Wie verschachtelte Strukturen in eine Tabelle kommen, zeigt Datenimport und Formate, Beispiel 4, mit echten Ausgaben.

Eine Sammlung enthält Bestellungen mit einer Liste von Positionen. Gesucht ist der Umsatz je Artikel. Was ist der erste Schritt in der Pipeline?

$group über den Artikel
Das geht noch nicht: Der Artikel steckt in einer Liste, und die Gruppierung sähe ganze Dokumente.
$unwind auf die Positionen
Richtig. Erst wird aus jedem Dokument je Position ein eigenes Dokument, danach lässt sich gruppieren. Das ist der Schritt, den SQL nicht braucht, weil dort Positionen ohnehin eigene Zeilen sind.
$lookup auf die Artikeltabelle
Das verbindet mit einer anderen Sammlung und löst das Problem der Liste nicht.

“Kein Schema” heisst, dass in derselben Sammlung Dokumente mit unterschiedlichen Feldern liegen können. Was folgt daraus für eine Auswertung?

Die Struktur muss beim Lesen geprüft werden, denn niemand hat sie erzwungen
Richtig. Ein Feld kann fehlen, anders heissen oder einen anderen Typ haben. Die Prüfung, die eine relationale Datenbank beim Schreiben macht, verschiebt sich auf die Auswertung.
Die Daten sind unstrukturiert
Sie haben Struktur, nur keine erzwungene.
Man kann keine Kennzahlen berechnen
Man kann, muss aber zuerst sicherstellen, dass die Felder überall vorhanden sind.

Wann ist eine Dokumentdatenbank gegenüber PostgreSQL mit einer JSON-Spalte im Vorteil?

Wenn die Dokumente das eigentliche Datenmodell sind und stark variieren
Richtig. Sind es einzelne halbstrukturierte Felder in einem sonst relationalen Modell, genügt der JSON-Typ von PostgreSQL, siehe PostgreSQL.
Immer, wenn JSON im Spiel ist
Relationale Systeme beherrschen JSON inzwischen ebenfalls.
Wenn Transaktionen gebraucht werden
Das ist eher ein Argument für das relationale System.

Verlinkte Ressourcen