Grosse Erweiterungen mit Postgres
Ausgangslage: Was Postgres im Projekt bereits leistet
Ich habe mich zu Beginn für PostgreSQL entschieden, weil es für meinen Anwendungsbereich die beste Grundlage zu bieten schien. Mitte Oktober hatte ich das Gefühl, das Potenzial von Postgres noch nicht wirklich auszuschöpfen, und wollte wissen, wie viel davon im Projekt tatsächlich genutzt wird. Bereits im Einsatz waren:
- Ranges und Multiranges: Ein Zeitraum wie "23. Oktober bis 25. Dezember 1888" wird als ein einziger Wert gespeichert und nicht als zwei getrennte Daten. Eine Multirange fasst mehrere solcher Zeiträume zusammen. So kann direkt gefragt werden, ob sich zwei Zeiträume überschneiden.
- GiST- und GIN-Indexe: Ein Index funktioniert wie das Register eines Buches. Statt jede Zeile einer Tabelle durchzugehen, schaut die Datenbank im Register nach. GiST eignet sich für Zeiträume und Geodaten, GIN für Listen und Texte.
- pg_trgm: Die Erweiterung zerlegt Wörter in Gruppen von je drei Buchstaben (Trigramme) und vergleicht diese. Darauf beruht die fehlertolerante Suche, die bei "Hokusia" trotzdem Hokusai findet.
- PostGIS: die Erweiterung für geografische Daten (siehe PostGIS).
- Window Functions: Berechnungen, die eine Zeile mit ihren Nachbarzeilen vergleichen, zum Beispiel einen Provenienzschritt mit dem vorherigen.
- Rekursive CTEs: Abfragen, die ihr eigenes Ergebnis wieder als Ausgangspunkt nehmen und so einer Kette folgen können, etwa einer Abfolge von Besitzer:innen.
- Deferred Constraint Triggers: Prüfungen, die erst am Ende eines Speichervorgangs laufen. Mehrere zusammenhängende Änderungen werden so gemeinsam gespeichert und erst als Ganzes geprüft.
- LISTEN/NOTIFY: Postgres kann der App mitteilen, dass sich etwas geändert hat, ohne dass die App ständig nachfragen muss.
Noch ungenutzt waren:
- Volltextsuche: Bis dahin durchsuchte die Suche der Website nur Namen und Titel, nicht die Texte.
- Exclusion Constraints: Regeln, die verhindern, dass sich zwei Einträge auf eine bestimmte Weise überschneiden.
- Materialized Views: gespeicherte Ergebnisse aufwendiger Abfragen, die nicht bei jeder Anfrage neu berechnet werden müssen.
- pg_stat_statements: eine Statistik darüber, welche Abfragen wie lange dauern. Sie war installiert, aber nicht aktiviert.
Ideen und Priorisierung
Zusammen mit Claude habe ich mögliche Erweiterungen gesammelt und nach ihrem Nutzen für das Projekt geordnet:
- Volltextsuche in allen Texten. Provenienzforschung sucht nicht nur nach Namen, sondern in den Texten: nach "Jeu de Paume", "Sicherungsübereignung", "beschlagnahmt" oder einem Händler, der nur in einer Fussnote erwähnt wird.
- Netzwerkfragen. Provenienz und Beziehungen bilden zusammen ein Netz, und Postgres kann diesem Netz folgen. Mögliche Fragen sind: Durch wessen Hände gingen Werke zwischen 1933 und 1945? Wie sind Emil Georg Bührle und Hermann Göring miteinander verbunden? Welche Werke hatten dieselben Besitzer:innen wie La Petite Irène? Solche Fragen kann die Seite eines einzelnen Museums nicht beantworten.
- Zitierbare Versionen. Jede Änderung ist bereits im Änderungsprotokoll festgehalten. Daraus lässt sich ein Eintrag so wiederherstellen, wie er an einem bestimmten Tag war. Eine Fussnote "Stand 16. Oktober 2026" bleibt damit nachprüfbar, auch wenn sich seit der Zitierung etwas am Eintrag geändert hat.
- Preise über die Zeit. Die Preise in der Provenienz sind in Francs, Dollar, Franken, Reichsmark oder Pfund angegeben, von 1880 bis heute. Mit historischen Wechselkursen und Preisindizes liessen sie sich vergleichen, zum Beispiel 200 Francs im Jahr 1904 mit rund 5000 Franken heute. Dafür braucht es aber eine verlässliche Quelle für die historischen Daten. Die Preise sind für mich persönlich wenig interessant. Hohe Preise bei Kunstwerken oder der Vergleich von historischen Preisen mit einem heutigen Preis kann öffentlichkeitswirksam sein, doch dieser Aspekt ist für mein Projekt irrelevant. Interessante Erkenntnisse sehe ich dort, wo ein Preis im Kontext seiner Zeit ausserordentlich hoch oder tief war. Solche Preise könnten Anlass sein nachzuforschen, ob es sich möglicherweise um Betrug oder Korruption gehandelt hat.
- Räumliche Geschichte mit PostGIS. Wie weit ist ein Werk gereist? Wer war wann in der Nähe von wem? Und wie lassen sich viele Orte auf der Karte zu Gruppen zusammenfassen, wenn man weit herausgezoomt ist? Zugegeben, wie weit ein Objekt gereist ist, ist nicht wirklich von Relevanz, zumal es sich nur um die Luftlinie handelt und die Zahl weniger Aussagekraft hat als die Darstellung der Provenienz mit Bewegungslinien auf der Karte. Die gereiste Distanz könnte interessant sein, wenn die Ausstellungsgeschichte betrachtet wird, um in etwa nachvollziehen zu können, was die Umweltbilanz eines Kunstwerks ist. Aber auch dieses Thema ist zum Stand von Oktober 2026 nicht Gegenstand meiner Website.
- Kleinere Punkte: die Abfragestatistik, Materialized Views, eine Erkennung von Namen, die ähnlich klingen, Exclusion Constraints und Bearbeitungsrechte pro Sammlung.
Ich habe mit den Punkten 1 und 2 begonnen, weil sie den grössten Wert für die Forschung haben und die nötigen Daten bereits vorhanden sind, und Punkt 3 gleich mitgenommen, weil die Zitierbarkeit für eine wissenschaftliche Nutzung zentral ist. Diese drei wurden am 2026-10-16 umgesetzt, die übrigen Punkte am 2026-10-17.
Volltextsuche
Die Suche durchsucht nun neben den Namen auch Biografien, Beschreibungen, Glossardefinitionen, Quellen und die einzelnen Provenienzschritte, inklusive der Art der Übergabe wie beispielsweise "confiscation". Die Suche arbeitet mit Wortstämmen: Wer nach "Sammlungen" sucht, findet auch "Sammlung", und "paintings" findet auch "painted". Das funktioniert für Englisch, Deutsch und Französisch gleichzeitig, und Akzente spielen keine Rolle. Seit 2026-10-16 werden in allen Suchen auf der Website und im CMS Akzente, Sonderbuchstaben (æ, ø, ß, ł), Ligaturen, Apostrophe und Bindestriche gleich behandelt: "é", "è" und "ê" finden dasselbe wie "e". Ganze Wortgruppen lassen sich mit Anführungszeichen suchen, Wörter mit einem Minus ausschliessen und Alternativen mit "or" verbinden. In den Resultaten wird die passende Stelle im Text angezeigt und das gesuchte Wort hervorgehoben. Ein Tippfehler in einem Namen führt weiterhin zum richtigen Treffer. Technisch erhält jede Tabelle einen GIN-Index über ihren durchsuchbaren Text. Für jede Sprache gibt es eine eigene Suchkonfiguration, welche zuerst die Akzente entfernt und dann das Wort auf seinen Stamm zurückführt. Eine Hürde war, dass Postgres in einem Index nur Funktionen erlaubt, die für dieselbe Eingabe garantiert immer dasselbe Ergebnis liefern. Einige eingebaute Funktionen erfüllen das nicht, deshalb wurden sie in kleine eigene Funktionen verpackt. Eine Einschränkung bleibt: Ein Provenienzschritt wird über seinen eigenen Text gefunden. Die Suche nach "Vollard" findet den Eintrag von Ambroise Vollard, aber nicht jedes Werk, das ihm gehört hat. Diese Frage beantworten die Netzwerkfragen.
Die Suchseite
Auf der Website wurde die Suchseite neu aufgebaut. Man erreicht sie, indem man im Suchfeld oben Enter drückt. Jedes Resultat zeigt, ob der Treffer im Namen, im Text oder in der Provenienz liegt. Die Resultate lassen sich nach Datenbank filtern. Anfangs wurden sie in Seiten zu 20 Einträgen angezeigt. Das Wechseln von Seiten empfinde ich als unnötig mühsam und veraltet, deshalb laden seit 2026-10-20 weitere Resultate beim Scrollen nach. Beim Testen ist mir aufgefallen, dass die Suche nach "confiscation" das Werk "Der Wanderer über dem Nebelmeer" anzeigt, ich das Wort auf der Seite des Werks aber zuerst nicht finden konnte. Es steht dort nur als Art der Übergabe in der kleinen grauen Zeile unter einem einzelnen Provenienzschritt, und der Link aus der Suche führte nur an den Anfang des Provenienzabschnitts. Bei Werken mit vielen Provenienzschritten muss man dann lange suchen. Seit 2026-10-19 öffnet ein Suchresultat den Eintrag direkt an der gefundenen Stelle. Bei einem Treffer in der Provenienz wird der betreffende Schritt in die Mitte des Bildschirms gerollt, mit einem Rahmen versehen und das gefundene Wort darin farbig markiert. Bei einem Treffer in einer Biografie oder Beschreibung werden alle Vorkommen des Wortes markiert und das erste wird angezeigt. Markiert werden die Wörter so, wie sie im Text stehen: Wer "paintings" sucht und "painted" findet, sieht "painted" markiert. Dafür musste die API jedem Provenienzschritt seine interne Nummer mitgeben, damit der Link genau auf diesen Schritt zeigen kann.
Ausschlüsse und Tippfehler
Ich wollte zum Beispiel alle Werke finden, die Gauguin zugeschrieben sind und sich nicht im Kunsthaus befinden. Mit der Suche "Gauguin -Kunsthaus" funktionierte das zuerst nicht. Seit 2026-10-20 versteht die Suche Ausschlüsse und einfache Sätze. "gauguin -kunsthaus" schliesst alles aus, was "Kunsthaus" enthält. "gauguin but not in kunsthaus zürich" schliesst nur die Werke aus, die sich heute im Kunsthaus befinden, und behält jene, die früher dort waren. Verstanden werden "but not", "except", "without" sowie auf Deutsch "nicht in", "aber nicht", "ohne" und "ausser". Neben dem Resultat steht, wie die Suche die Eingabe gelesen hat, zum Beispiel "gauguin not in: kunsthaus zürich". Von 14 Werken Gauguins bleiben so 2. Für Tippfehler gibt es ein Wörterbuch aller Wörter in den Namen der Einträge. Ein unbekanntes Wort wird über ähnliche Buchstabengruppen, die Anzahl nötiger Änderungen (Levenshtein-Distanz) und den Klang (Double Metaphone) dem nächsten bekannten Wort zugeordnet: "hokusia" wird zu Hokusai, "gaugin" zu Gauguin, "velasquez" zu Velázquez. Findet die Suche nichts, sucht sie automatisch nach der Korrektur und bietet "Search for … instead" an. Findet sie wenig, fragt sie "Did you mean …?".
Eine Definition für alle Suchen
Seit 2026-10-20 verwenden das CMS und die Website dieselbe Definition, welche Felder eines Eintrags durchsucht werden: bei einem Kunstwerk etwa Künstler:in, Datum, Typ, Materialien, Entstehungsort und das Museum, in dem es sich heute oder früher befand. Werke, die nur über ein früheres Museum gefunden werden, erscheinen unter einem eigenen Zwischentitel. Eine kleine Anekdote dazu: Die Liste der Kunstwerke berechnete diese Suchfelder anfangs bei jeder Anfrage neu. Unter Last überschritt sie damit die 5 Sekunden, nach denen die API eine Abfrage abbricht. Die Felder werden nun gespeichert und nach jeder Änderung aktualisiert, die Liste braucht dafür noch 0,01 statt 0,9 Sekunden.
Suche über Sprachen hinweg
Die Suche nach "beschlagnahmt" ergab zunächst kein einziges Resultat, obwohl mehrere Werke in der NS-Zeit beschlagnahmt wurden. Der Grund: Die Provenienzangaben sind fast alle auf Englisch erfasst ("confiscation", "seized"), und die Suche führt Wörter zwar auf ihren Stamm zurück, übersetzt sie aber nicht. Ein deutsches Suchwort findet nur deutschen Text. Gerade für die Provenienzforschung ist das ein Problem, weil viele Quellen wie das Kunsthaus Zürich oder SIKART auf Deutsch sind und Nutzer:innen mit deutschen Begriffen suchen.
Die Lösung ist seit 2026-10-19 eine Liste von Wortgruppen. Jede Gruppe enthält Wörter, die auf Deutsch, Englisch und Französisch dasselbe bedeuten, zum Beispiel: beschlagnahmt, Beschlagnahme, konfisziert, confiscation, confiscated, seized, saisie. Jedes Wort einer Gruppe findet alle anderen. Zum Start habe ich 19 Gruppen mit den wichtigsten Begriffen der Provenienzforschung angelegt: Beschlagnahmung, Raub, Diebstahl, Zwangsverkauf, Verfolgung, Nationalsozialismus, Restitution, Kauf, Verkauf, Versteigerung, Schenkung, Vermächtnis, Erbschaft, Tausch, Leihgabe, Auftrag, Händler, Sammler/Sammlung und Ausstellung. Bewusst weggelassen habe ich Wörter, die in einer anderen Sprache oder in der Kunstgeschichte etwas anderes bedeuten, etwa das französische "vol" (Diebstahl, aber auch die Abkürzung für Band/Volume) oder das englische "lent" (verliehen, aber auch die Fastenzeit, die in der religiösen Kunst vorkommt).
Für die Umsetzung gab es zwei Möglichkeiten. Postgres kann eine Thesaurus-Datei verwenden, die direkt in die Suchkonfiguration eingebaut wird. Diese Datei liegt jedoch ausserhalb der Datenbank in einem Systemordner von Postgres. Sie wäre nicht Teil der nächtlichen Datensicherung, müsste bei jedem Update von Postgres von Hand übertragen werden und jede Änderung würde einen Neustart und das Neuaufbauen der Suchindexe erfordern. Ich habe mich deshalb für die zweite Möglichkeit entschieden: Die Wortgruppen liegen in einer gewöhnlichen Tabelle in der Datenbank. Bevor eine Suche ausgeführt wird, schreibt Postgres mit der Funktion ts_rewrite die Suchanfrage um: Aus "beschlagnahmt" wird "beschlagnahmt oder confiscation oder seized oder …". Damit ist die Liste in der Datensicherung, jede Änderung wird in der History festgehalten und wirkt sofort.
Eine technische Hürde gab es dabei: ts_rewrite wendet seine Regeln nacheinander auf die ganze Anfrage an. Würde jedes Wort direkt durch seine ganze Gruppe ersetzt, würde die nächste Regel die Kopie ihres eigenen Wortes in der Gruppe erneut ersetzen, und die Anfrage würde sich mit jedem Wort der Gruppe verdoppeln. Deshalb läuft das Umschreiben in zwei Schritten: Zuerst wird jedes Wort durch einen Platzhalter für seine Gruppe ersetzt, danach jeder Platzhalter durch alle Wörter der Gruppe. So kann keine Regel das Ergebnis einer anderen Regel nochmals umschreiben.
Heute findet "beschlagnahmt" dieselben acht Resultate wie "confiscation", "Zwangsverkauf" findet ein Werk mit "forced sale" in der Provenienz und "Raubkunst" findet den Händler Hans Wendland, in dessen Beschreibung von "art looted by the Nazi regime" die Rede ist. In der angezeigten Textstelle und auf der Seite des Eintrags wird das englische Wort markiert, das gefunden wurde.
Zwei Einschränkungen bleiben. Erstens werden nur Wörter übersetzt, die auf der Liste stehen. Zweitens wird eine Wortgruppe aus mehreren Wörtern wie "forced sale" zwar gefunden, wenn man "Zwangsverkauf" sucht, aber die Suche nach "forced sale" findet umgekehrt nicht "Zwangsverkauf".
Die Wortliste kann im CMS unter "Search words" bearbeitet werden. Dort lassen sich Gruppen erstellen, ändern und löschen. Mit "Try a word" kann geprüft werden, in welche Gruppe ein Wort fällt, und zwar so, wie die Suche es liest: "beschlagnahmte" steht nicht auf der Liste, wird aber über seinen Wortstamm der Gruppe "confiscation" zugeordnet. Teilt eine Gruppe ein Wort mit einer anderen Gruppe, erscheint eine Warnung, damit die beiden gegebenenfalls zusammengeführt werden können.
Netzwerkfragen
Es gibt drei neue Arten von Abfragen:
- Verbindung: die kürzeste Kette zwischen zwei Einträgen. Zum Beispiel: La Petite Irène → gehörte Emil Georg Bührle → dem auch L'Offrande gehörte → das von Paul Gauguin geschaffen wurde.
- Hände: wer in einem bestimmten Zeitraum Werke besass. Für 1933–1945 waren es bei der Umsetzung 62 Besitzer:innen und 18 Übergaben. Der Einsatzstab Reichsleiter Rosenberg erscheint dabei als "Besitzer ohne Rechtstitel".
- Gemeinsame Hände: Werke, die durch dieselben Hände gingen, bis zu vier Schritte entfernt. Damit lässt sich auch beantworten, wann Kunstwerke in derselben Sammlung waren. Werke, die heute einfach im selben Museum hängen, werden standardmässig weggelassen.
Für die Verbindung zwischen zwei Einträgen habe ich keine rekursive CTE verwendet, sondern eine Funktion, die Schritt für Schritt vorgeht. Eine rekursive CTE merkt sich nicht, welche Einträge sie bereits erreicht hat, und verfolgt deshalb jeden möglichen Weg. Die erste Version der "gemeinsamen Hände" hatte genau dieses Problem und brauchte 15 Sekunden. Die überarbeitete Version braucht etwa 0,1 Sekunden. Damit jeder Schritt schnell nachgeschlagen werden kann, wird das Beziehungsnetz zusätzlich als eigene Tabelle gespeichert, die sich nach jeder Änderung aktualisiert. Auf der Website hat jeder Eintrag eine Auswahl "Connect to…", mit der sich die Verbindung zu einem beliebigen anderen Eintrag als senkrechte Kette anzeigen lässt. Gibt es keine Verbindung, steht dort "No connection within 6 steps". Kunstwerke zeigen zudem den Abschnitt "Through the same hands", gruppiert nach Besitzer:in, zum Beispiel "also held by Emil Georg Bührle". In der ersten Version stand bei Bührle rund 35 Mal "at the same time", jetzt gibt es pro Besitzer:in nur noch eine Zeile für "at the same time" und eine für "at other times". Unter arthistory.piogino.ch/provenance/1933-1945 gibt es eine Seite für die Zeit von 1933–1945. Sie listet alle Besitzer:innen und alle Übergaben dieser Jahre auf. Der Zeitraum lässt sich anpassen, zum Beispiel auf /provenance/1920-1950. Besitzer ohne Rechtstitel und markierte Personen oder Institutionen werden rot hervorgehoben, darunter der ERR, Hermann Göring, die Galerie Fischer, Hans Wendland und Gustav Rochlitz.
Zitierbare Versionen
Seit 2026-09-28 wird jede Änderung in der Datenbank protokolliert. Daraus lässt sich jeder Eintrag so wiederherstellen, wie er zu einem bestimmten Zeitpunkt war: seine Felder, seine Beziehungen und seine Provenienz. Dafür wird an die Adresse eines Eintrags ?as_of= mit einem Datum angehängt, zum Beispiel ?as_of=2026-10-12. Jede Version hat einen genauen Zeitstempel, mit dem sie zitiert werden kann. Ein Zitat funktioniert auch dann noch, wenn der Eintrag später gelöscht oder mit einem anderen zusammengeführt wird. Das Änderungsprotokoll selbst bleibt für die öffentliche API gesperrt, da es festhält, wer was geändert hat. Die neuen Funktionen geben nur den Inhalt zurück. La Petite Irène hatte zum Zeitpunkt der Umsetzung bereits 19 Versionen.
Auf der Website hat jeder Eintrag ein Feld "Cite this version" mit einem fertigen Zitiertext, einem Knopf zum Kopieren und einer Liste der älteren Versionen. Wer eine alte Version öffnet, sieht oben einen Hinweis wie "As recorded on … — N changes since" mit einem Link zur aktuellen Version. Existierte der Eintrag zum gewählten Zeitpunkt noch nicht, erscheint "Not recorded yet then" mit dem Datum, an dem er zum ersten Mal erfasst wurde.
Abfragestatistik
Mit pg_stat_statements sehe ich, welche Abfragen wie oft laufen und wie lange sie dauern. Für die Aktivierung musste Postgres für einige Sekunden neu gestartet werden. Die Statistik hat sofort etwas gezeigt: Die mit Abstand teuerste Abfrage war die Qualitätsprüfung, die nach jedem Speichern im Hintergrund läuft, mit 2–5 Sekunden pro Durchgang. Den grössten Teil davon machte die Duplikatprüfung aus, die jeden Eintrag eines Typs mit jedem anderen verglich.
Schnellere Duplikatprüfung
Alle Namen liegen nun in einer Materialized View, also einer gespeicherten Liste, mit einem Trigramm-Index. Die Prüfung vergleicht dadurch nur noch Paare, die überhaupt etwas gemeinsam haben. Sie dauerte damit 0,5 statt 3,8 Sekunden und fand dieselben möglichen Duplikate. Mit dem Wachstum der Datenbank reichte das nicht, seit 2026-10-21 wird die Duplikatprüfung nur noch für geänderte Einträge durchgeführt (siehe Skalierung). Weitere aufwendige Berechnungen wie die Abdeckung der Provenienz habe ich damals bewusst nicht so gespeichert. Sie dauerten nur 0,1–0,4 Sekunden und wurden vom Server ohnehin für eine Minute zwischengespeichert. Zudem müssten dafür rund 50 ältere Funktionen umgeschrieben werden: Seit Postgres 17 läuft die Aktualisierung einer Materialized View mit eingeschränkten Rechten bei der Suche nach Funktionen, und die alten Funktionen rufen andere Funktionen auf, ohne genau anzugeben, wo diese liegen. Die neuen Funktionen sind bereits so geschrieben, dass dieses Problem nicht auftritt.
Ähnlich klingende Namen
Mit der Erweiterung fuzzystrmatch erkennt die Duplikatprüfung auch Namen, die gleich klingen, aber anders geschrieben werden, zum Beispiel "Gustav Rochlitz" und "Gustave Rochlits". Das ist wichtig, weil das Kunsthaus und Wikidata Namen teilweise unterschiedlich schreiben. Die Prüfung hat ein neues Paar gefunden: Louis und Louise Cahen d'Anvers. Die Namen sind sich zu 87 % ähnlich und keiner der beiden Einträge hat Lebensdaten. In diesem Fall wird die Meldung mit "not the same" erledigt.
Räumliche Nähe
- Reisedistanz: Kunstwerke zeigen, wie weit sie gereist sind. La Petite Irène hat rund 2232 km zurückgelegt, berechnet über 12 der 15 Provenienzschritte; die übrigen Schritte haben keinen Ort. Die Karte und die Distanz verwenden dieselbe Liste von Stationen.
- Wer war in der Nähe von wem: Die Crossed Paths werden nun in der Datenbank berechnet statt im Browser der Besucher:innen. Das ist wichtig, weil die Berechnung im Browser mit wachsenden Daten immer langsamer würde. Bei Gauguin ergibt sich ein einziges Treffen: mit Van Gogh in Arles vom 23. Oktober bis zum 25. Dezember 1888. Bevor ich umgestellt habe, wurden die Resultate mit der alten Berechnung im Browser verglichen: Van Gogh, Gauguin und La Petite Irène ergaben dieselben Treffen. Neu gibt es die Option "nearby too (≤ 50 km)", die auch Personen anzeigt, die zur selben Zeit in der Nähe waren, mit beiden Orten und der Distanz. Die Seiten der Orte verwenden noch die alte Berechnung im Browser, weil die API die Treffen nur pro Person oder Werk und noch nicht pro Ort ausgeben kann.
- Gruppen auf der Karte: Wenn man weit herausgezoomt ist, werden Orte zu Gruppen zusammengefasst. Auf der Startkarte erscheinen unterhalb einer bestimmten Zoomstufe nummerierte Blasen statt einzelner Punkte, Europa zum Beispiel als eine Blase mit "339" und Japan mit "10". Ein Klick auf eine Blase zoomt in ihren Bereich. Die Zahl zählt alle Aufenthalte und nicht nur die ausgewählten Einträge, deshalb werden die Blasen ausgeblendet, sobald einzelne Einträge ausgewählt sind. Sonst würde eine Karte, die nur Van Gogh zeigt, Blasen mit allen Personen tragen.
Preise
Für den Preisvergleich ist vorerst nur die Struktur angelegt. Es sind 16 Währungen erfasst, inklusive ihrer festen Umrechnungen: 100 alte Francs ergeben einen neuen Franc, 10 Reichsmark eine D-Mark und 6,55957 Francs einen Euro. Französische Preise vor 1960 werden als "AFRF" (alte Francs) erfasst. Mit den Museen in Frankreich und Italien kamen am 2026-10-20 Livres, Scudi, Dukaten, Florin und Piaster dazu. Wechselkurse und Preisindizes sind noch leer. Solange sie fehlen, gibt die Umrechnung "nicht berechenbar" zurück statt einer Schätzung. Hier kommt auch ein Exclusion Constraint zum Einsatz: Für dasselbe Währungspaar dürfen sich die Gültigkeitszeiträume zweier Wechselkurse nicht überschneiden, sonst verweigert die Datenbank das Speichern. Für Referenzdaten wie Wechselkurse ist das sinnvoll. Bei der Provenienz verwende ich stattdessen Qualitätsprüfungen, die nur einen Hinweis anzeigen, weil sich historische Quellen oft widersprechen und auch widersprüchliche Angaben festgehalten werden müssen.
Bearbeitungsrechte pro Sammlung (Row-Level Security)
Ein Account kann auf eine oder mehrere Sammlungen beschränkt werden. Er kann dann nur die Kunstwerke dieser Sammlungen und deren Provenienz, Inventarnummern und Bilder bearbeiten. Das ist relevant, falls künftig Museen oder weitere Personen an der Datenbank mitarbeiten. Die Regel wird von der Datenbank selbst durchgesetzt und nicht vom Code des CMS. Selbst ein Fehler im CMS könnte die Beschränkung also nicht umgehen. Lesen können weiterhin alle Accounts alles. Aktuell ist kein Account beschränkt, da ich das Projekt allein betreibe. Eine bekannte Einschränkung gibt es: Wenn ein beschränkter Account eine Person umbenennt, zeigen Links auf diese Person in den Beschreibungen anderer Sammlungen den alten Namen, bis diese Einträge das nächste Mal gespeichert werden. Die Links funktionieren aber weiterhin.