Skip to content

GOQL.de

Doug Blank edited this page Sep 20, 2026 · 1 revision

🌐 English

GOQL

Jede Listenansicht in Gramps Connect — Personen, Familien, Ereignisse und so weiter — hat ein Suchfeld. Standardmäßig führt eine Eingabe darin einen schnellen reinen Textabgleich gegen eine Handvoll gemeinsamer Felder dieser Ansicht durch (bei Personen: Gramps-ID und Name; bei Ort: ID, Titel und Ortsname; und so weiter) — genug für die meisten alltäglichen Suchen, und Standard, weil es einfacher ist als eine Abfragesprache.

Neben dem Feld sitzt ein Kontrollkästchen: „Gramps Object Query Language verwenden.“ Es einschalten, und dasselbe Feld nimmt stattdessen eine vollständige GOQL-Bedingung (gramps-object-query-language) — eine kleine, eigens zum Abfragen von Gramps-Datensätzen gebaute Sprache, die zu Dingen fähig ist, an die ein reiner Textabgleich gar nicht heranreicht: über Beziehungen hinweg reichen (ein Join, in Datenbankbegriffen — „jede Familie, deren Mutter und Vater denselben Nachnamen tragen“), und sogar rückwärts über sie hinweg reichen („jede Notiz, auf die nichts mehr verweist“). Diese Seite richtet sich an alle, die eine solche Bedingung schreiben möchten — keine Programmiererfahrung nötig.

GOQL treibt außerdem die Aufrufe filter()/db.get_number_of() von Gramplets sowie das Argument where= von people()/families()/usw. an — dieselbe Syntax, die man bei eingeschaltetem Kontrollkästchen ins Suchfeld tippen würde, funktioniert auch aus dem Python-Code eines Add-ons heraus. (db.get_number_of()/das ältere, bloße count() sind Gramplet-API-Funktionen zum Zählen passender Datensätze, nicht das unten behandelte any/len von GOQL selbst — eine andere Sache, derselbe Name.)

Gar nicht von Hand schreiben wollen? Der „Filter“-Knopf neben dem Titel jeder Liste bietet Dutzende gängige Regeln als schlichte Kontrollkästchen — kombinierbar mit UND/ODER, schachtelbar und verneinbar — und baut darunter genau dasselbe GOQL, schreibgeschützt angezeigt, sodass man sehen kann, was er geschrieben hat. Siehe dazu die Funktionsliste der Übersicht-Seite. Dessen „+ Neue eigene Regel…“ ist die einzige Stelle in diesem Dialog, die weiterhin eine rohe GOQL-Bedingung von Hand entgegennimmt, dieselbe Syntax wie diese ganze Seite — einmal gespeichert und benannt, wird sie von da an als gewöhnliches Kontrollkästchen wiederverwendet, nie erneut eingetippt.

Die Grundidee

Eine Abfrage wird immer im Kontext einer Ansicht geschrieben — Person, Familie, Ereignis und so weiter — und ist einfach eine Bedingung:

surname == 'Smith'

Das liest sich als: in der Personen-Ansicht Datensätze finden, bei denen der Nachname gleich 'Smith' ist. Die Ansicht, in der gesucht wird, legt fest, für welche Art von Datensatz die Bedingung gilt.

Ein paar Symbole kommen immer wieder vor:

Symbol Bedeutung
==, != ist gleich / ist ungleich
<, <=, >, >= ist kleiner als, höchstens, größer als, mindestens (früher/später, kleiner/größer)
and, or, not Bedingungen kombinieren, oder eine umkehren
in [ ... ] trifft auf einen beliebigen Wert aus einer Liste zu
'text' in field trifft zu, wenn field irgendwo 'text' enthält
like(field, 'pattern') trifft auf ein Textmuster zu, wobei % für „irgendetwas“ steht
regex(field, 'pattern') trifft auf einen regulären Ausdruck zu, für alle, die sich damit schon auskennen

Textwerte stehen in einfachen Anführungszeichen ('Smith'); Zahlen nicht (1968).

Bedingungen kombinieren

and verlangt, dass beide Seiten wahr sind; or braucht nur eine. Beide lesen sich natürlich: gender == Person.MALE and surname == 'Smith' findet jeden Mann namens Smith, während given_name == 'John' or surname == 'Doyle' jeden namens John findet oder überhaupt jeden namens Doyle. Beides zu mischen funktioniert wie gewöhnliche Arithmetik, bei der Multiplikation vor Addition kommt — and wird vor or geprüft — aber Klammern machen die Absicht trotzdem unmissverständlich: (gender == Person.MALE and surname == 'Smith') or given_name == 'Mary' findet jeden männlichen Smith, plus jeden namens Mary jeglichen Geschlechts.

not kehrt eine Bedingung um und trifft immer dann zu, wenn das Innere nicht wahr ist: not (surname == 'Smith') ist jeder, dessen Nachname nicht Smith ist. Vergleiche lassen sich auch verketten, wie es echtes Python erlaubt — Date('Jan 1, 1900') < birth.date.sortval < Date('Jan 1, 1950') findet jeden, der streng zwischen diesen beiden Daten geboren wurde, in einer Zeile statt zwei mit and verbundenen.

Text abgleichen

Über ein einfaches == hinaus decken ein paar lockerere Wege, Text abzugleichen, die meisten alltäglichen Suchen ab. given_name in ['John', 'Jane'] trifft auf jeden Namen in der Liste zu — beliebig viele hinzufügen. 'an' in given_name trifft überall im Namen zu (Jane, Alexander, Susan, …), ganz ohne Platzhalter. like(field, 'J%') trifft auf ein Muster zu, bei dem % für „irgendetwas“ steht, like(given_name, 'J%') trifft also auf John, Jane, James und so weiter zu. Für alle, die sich mit regulären Ausdrücken schon auskennen, ist regex(field, 'pattern') noch mächtiger — regex(surname, '^[SD]') findet jeden Smith und Doyle auf einen Schlag, etwas, das like(...) ohne eine eigene Bedingung pro Anfangsbuchstaben nicht ausdrücken kann.

Verwandte Datensätze erreichen

Ein Feld muss nicht direkt auf dem gesuchten Datensatz liegen. birth und death reichen von einer Person zu ihrem Geburts- oder Todesereignis, birth.date.sortval ist also das Datum dieses Ereignisses, und birth.place.title folgt einen Schritt weiter, zum Ort dieses Ereignisses. Dieselbe Idee funktioniert bei anderen Datensatztypen: father und mother reichen von einer Familie zum eigenen Personendatensatz jedes Elternteils (father.surname == 'Smith'), source reicht von einer Fundstelle zu dem, was sie zitiert, und enclosed_by reicht von einem Ort zum umschließenden Ort (dem Landkreis einer Stadt, etwa — und es verkettet sich mit sich selbst, enclosed_by.enclosed_by.title reicht also zwei Ebenen nach oben).

Beide Seiten eines Vergleichs können einer dieser Pfade sein, nicht nur ein fester Wert, was eine Abfrage wie „Familien, in denen Mutter und Vater denselben Nachnamen tragen“ möglich macht: father.surname == mother.surname. Verketten und Vergleichen lassen sich frei kombinieren — father.birth.date.sortval < Date('Jan 1, 1850') reicht zwei Schritte von einer Familie (zum Vater, dann zu dessen Geburtsereignis), um ältere Generationen zu finden, ohne vorher zu wissen, wer sie sind.

Es gibt keine Grenze, wie viele solcher Sprünge ein Pfad überqueren kann, und jeder Sprung kann bei einer völlig anderen Art von Datensatz landen. Von der Familien-Ansicht ausgehend reicht father.birth.place.title == 'Chicago, Cook, Illinois, USA' von der Familie zum eigenen Personendatensatz des Vaters, von dort zu dessen Geburts-Ereignis, und von dort zum Ort dieses Ereignisses — drei Sprünge durch drei verschiedene Arten von Datensatz, endend beim Namen des Orts, alles in einer Zeile. Dieselbe Verkettung funktioniert von jeder Ansicht aus, in jeder Kombination, die die obigen Beziehungen erlauben.

Sammlungen: any und len

Eine Person hat genau ein Geburtsereignis, aber beliebig viele Kinder, Notizen, Fundstellen oder angehängte Medien — die brauchen eine andere Art von Prüfung. any(c.given_name == 'Steve' for c in children) trifft auf eine Familie zu, wenn irgendein Kind die Bedingung erfüllt; die Bedingung wegzulassen (any(n for n in notes)) fragt einfach, ob überhaupt etwas angehängt ist, weshalb not any(n for n in notes) Personen ohne erfasste Notizen findet. len(...) fragt „wie viele“ statt „mindestens eins“ — len([c for c in children]) > 2 findet Familien mit mehr als zwei Kindern, und len([c for c in children if c.gender == Person.MALE]) > 1 schränkt das auf das Zählen nur der Söhne ein.

Eine Sammlung ist aber so weit, wie eine einzelne Abfrage in diese Richtung reichen kann. Das oben beschriebene Verketten (father.birth.place.title) funktioniert nur über Beziehungen, die zu genau einem Datensatz führen — any/len können sagen, ob etwas innerhalb einer Sammlung einer Bedingung entspricht, aber die Abfrage kann danach nicht weiter über dieses Treffer hinaus zu einem seiner eigenen verwandten Datensätze verketten. Der Vater einer Familie hat zum Beispiel seine eigene Elternfamilie — aber da eine Person als Kind von mehr als einer Familie erfasst sein kann, ist auch diese Verbindung eine Sammlung, einen Schritt weiter, als eine Abfrage derzeit folgen kann. „Jede Familie, deren Vater und Großvater im selben Landkreis geboren wurden“ lässt sich mit GOQL also noch nicht ausdrücken.

Rückverweise: backlinks

Jede Sammlung oben reicht nach außen — die eigenen Kinder einer Familie, die eigenen Notizen einer Person. backlinks ist die eine Sammlung, die andersherum reicht: verweist irgendetwas anderes im Stammbaum auf diesen Datensatz? Sie ist bei allen zehn Datensatztypen verfügbar, sogar bei solchen ohne eigene nach außen gerichtete Sammlungen (ein Etikett hat keine eigenen Kinder oder Notizen, aber viele Datensätze können damit etikettiert werden, die eigene backlinks-Bedingung von Tag findet also Etiketten, die niemand verwendet):

not any(bl for bl in backlinks)                                    # nichts verweist überhaupt auf diesen Datensatz
any(bl._class == 'Person' for bl in backlinks)                     # mindestens eine Person verweist darauf
any(bl for bl in backlinks) and len([bl for bl in backlinks]) > 1  # von mehr als einem Ding referenziert

Das macht eine Abfrage wie „jede Quelle, die niemand mehr zitiert“ oder „jeder Ort ohne erfasste Ereignisse dort“ möglich — not any(bl for bl in backlinks), ausgeführt in der Quellen- oder Orts-Ansicht.

_class ist das eine Feld, das eine backlinks-Bedingung prüfen kann — der eigene Datensatztyp des Verweisenden ('Person', 'Family', …). Da der Verweisende eines Backlinks jeder der zehn Datensatztypen zugleich sein kann, lässt sich _class nicht wie eine gewöhnliche Beziehung tiefer verfolgen — es gibt kein _class.primary_name — aber es akzeptiert in/not in gegen eine Liste:

any(bl._class in ['Person', 'Family'] for bl in backlinks)
any(bl._class != 'Media' for bl in backlinks)

len([bl for bl in backlinks]) funktioniert genau wie len(...) oben.

Daten

Date('...') versteht gewöhnlichen Datumstext, birth.date.sortval >= Date('Jan 1, 1968') findet also jeden, der an oder nach diesem Tag geboren wurde. Eine Sache, die man wissen sollte: sortval ist immer ein einzelner Zeitpunkt, ohne angehängten Zusatz wie „etwa“, „vor“ oder „geschätzt“ — ein als „vor 1968“ eingegebenes Geburtsdatum hat denselben sortval wie das schlichte „Jan 1, 1968“, ein >=-Vergleich würde diese Person also als an oder nach dem Stichtag geboren zählen, obwohl „vor“ das Gegenteil bedeutet. Wo dieser Unterschied wichtig ist, stattdessen birth.date.modifier gegen eine benannte Konstante vergleichen, etwa Date.MOD_ABOUT.

Benannte Konstanten

Manche Felder, wie das Geschlecht einer Person oder die Vertrauenswürdigkeit einer Fundstelle, vergleichen gegen eine benannte Konstante statt gegen eine rohe Zahl — gender == Person.MALE oder confidence >= Citation.CONF_HIGH — direkt aus Gramps selbst übernommen, damit sie nie aus dem Gleichschritt mit dem geraten, was Gramps tatsächlich speichert.

Was GOQL nicht kann

GOQL ist ein kleines, festes Set an Bausteinen, keine vollständige Programmiersprache — alles außerhalb der obigen Muster wird mit einem Fehler abgelehnt statt geraten.

Mehr erfahren

Diese Seite deckt die alltäglichen Muster ab; der eigene „i“-Knopf des Suchfelds, neben dem Suchfeld jeder Ansicht, listet die genauen Felder und Beziehungen auf, die für diese Ansicht verfügbar sind. Für die vollständige Syntaxreferenz — jeden Operator, jede Beziehung, jede Sammlung und jeden Sonderfall, samt der Überlegung dahinter — siehe where_expr.md im Repository gramps-object-query-language, dem Projekt, auf dem GOQL aufbaut.

Clone this wiki locally