-
Notifications
You must be signed in to change notification settings - Fork 0
GOQL.de
🌐 English
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.
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).
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.
Ü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.
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.
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.
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 referenziertDas 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.
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.
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.
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.
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.
Gramps Connect is part of the family of Gramps-based software.
Using the app
- Overview
- Installing
- Deploying
- Messaging
- GOQL (advanced search)
- Gramplets & Add-on Store
- Data Model & Editing
- FAQ
Building & contributing