Repository navigation
GOQL.zh_CN
🌐 English · Deutsch · Español · Français
Gramps Connect 中的每个列表视图——成员、家庭、活动等等——都有一个搜索框。默认情况下,在其中输入内容会对该视图的几个常用字段做一次快速的纯文本匹配(成员视图:Gramps ID 和姓名;地点视图:ID、标题和地名;依此类推)——足以应付大多数日常搜索,之所以作为默认,是因为它比查询语言更简单。
搜索框旁边有一个复选框:“Use Gramps Object Query Language”(使用 Gramps 对象查询语言)。 勾选它后,同一个搜索框就改为接受一个完整的 GOQL(gramps-object-query-language)条件——这是一种专门为查询 Gramps 记录而构建的小型语言,能做到纯文本匹配完全无法企及的事情:跨越关系进行查询(用数据库术语说就是连接——“父母同姓的所有家庭”),甚至可以反向跨越关系(“不再被任何东西指向的所有摘录”)。本页写给任何想要编写这类条件的人——不需要编程经验。
GOQL 也是 Gramplet 中 filter()/db.get_number_of() 调用以及 people()/families() 等函数的 where= 参数背后的机制——勾选复选框后在搜索框中输入的语法,同样可以在插件的 Python 代码中使用。(db.get_number_of() 和较旧的单独 count() 是 Gramplet API 中用于统计匹配记录数量的函数,而不是下文介绍的 GOQL 自己的 any/len——名字相近,用途不同。)
完全不想手写?每个列表标题旁的**“筛选器”按钮**以普通复选框的形式提供了几十条常用规则——可以用 AND/OR 组合、嵌套和取反——并在底层生成完全相同的 GOQL,以只读方式显示出来,让您看到它写了什么。详见概览页的功能列表。其中的“+ 新建自定义规则…”是该对话框中唯一仍需手写原始 GOQL 条件的地方,语法与本页完全相同——一旦保存并命名,此后就作为一个普通复选框重复使用,再也不用重新输入。
查询总是在某个视图(成员、家庭、事件等)的上下文中编写,并且只是一个条件:
surname == 'Smith'它的含义是:在成员视图中,查找姓氏等于 'Smith' 的记录。您所搜索的视图决定了条件作用于哪种记录。
有几个符号会反复出现:
| 符号 | 含义 |
|---|---|
==、!=
|
等于 / 不等于 |
<、<=、>、>=
|
小于、至多、大于、至少(更早/更晚、更小/更大) |
and、or、not
|
组合多个条件,或对一个条件取反 |
in [ ... ] |
与列表中的任意一个值匹配 |
'text' in field |
只要 field 中任何位置包含 'text' 就匹配 |
like(field, 'pattern') |
匹配一个文本模式,其中 % 代表“任意内容” |
regex(field, 'pattern') |
匹配一个正则表达式,适合已经熟悉它的人 |
文本值放在单引号中('Smith');数字则不用(1968)。
and 要求两边都为真;or 只需其中一边为真。两者读起来都很自然:gender == Person.MALE and surname == 'Smith' 找出所有姓 Smith 的男性,而 given_name == 'John' or surname == 'Doyle' 找出所有名叫 John 的人,或者任何姓 Doyle 的人。两者混用时与普通算术一样,乘法先于加法——and 先于 or 计算——但无论如何,括号都能让意图一目了然:(gender == Person.MALE and surname == 'Smith') or given_name == 'Mary' 找出所有姓 Smith 的男性,外加任何名叫 Mary 的人,不论性别。
not 对一个条件取反,只要括号里的内容不成立就匹配:not (surname == 'Smith') 是所有姓氏不是 Smith 的人。比较也可以像真正的 Python 那样连写——Date('Jan 1, 1900') < birth.date.sortval < Date('Jan 1, 1950') 找出所有严格出生在这两个日期之间的人,一行搞定,而不必写成用 and 连接的两个条件。
除了普通的 ==,还有几种更宽松的文本匹配方式,足以覆盖大多数日常搜索。given_name in ['John', 'Jane'] 匹配列表中的任意一个名字——想加多少都可以。'an' in given_name 匹配名字中任何位置(Jane、Alexander、Susan……),不需要通配符。like(field, 'J%') 匹配一个模式,其中 % 代表“任意内容”,所以 like(given_name, 'J%') 会匹配 John、Jane、James 等。对于已经熟悉正则表达式的人,regex(field, 'pattern') 更加强大——regex(surname, '^[SD]') 一次就能找出所有的 Smith 和 Doyle,而 like(...) 必须为每个首字母单独写一个条件才能表达。
字段不必直接位于您正在搜索的记录上。birth 和 death 从一个人指向其出生或死亡事件,所以 birth.date.sortval 就是该事件的日期,而 birth.place.title 再往前一步,到达该事件的地点。同样的思路也适用于其他记录类型:father 和 mother 从一个家庭指向每位父母自己的成员记录(father.surname == 'Smith'),source 从一条引用指向它所引用的来源,enclosed_by 从一个地点指向包含它的地点(比如一座城市所在的县——而且它可以自我连写,所以 enclosed_by.enclosed_by.title 会向上两级)。
比较的两边都可以是这样的路径,而不只是一个固定值,正是这一点让“父母同姓的家庭”这样的查询成为可能:father.surname == mother.surname。连写和比较可以自由组合——father.birth.date.sortval < Date('Jan 1, 1850') 从一个家庭出发前进两步(到父亲,再到他的出生事件),无需事先知道他们是谁,就能找出更早的世代。
一条路径能跨越多少跳没有限制,而且每一跳都可以落在完全不同类型的记录上。从家庭视图开始,father.birth.place.title == 'Chicago, Cook, Illinois, USA' 从家庭到达父亲自己的成员记录,从那里到他的出生事件,再从那里到该事件的地点——跨越三种不同类型的记录跳了三次,最终落在地点的名称上,全部写在一行里。同样的连写可以在任何视图中使用,只要符合上述关系所允许的组合即可。
一个人只有一个出生事件,但可以有任意数量的子女、摘录、引用或附加的媒体——这些需要另一种检查方式。any(c.given_name == 'Steve' for c in children) 只要任意一个子女满足条件,就匹配该家庭;省略条件(any(n for n in notes))则只是询问是否附加了任何东西,这就是 not any(n for n in notes) 找出没有记录任何摘录的人的方式。len(...) 问的是“有多少个”而不是“至少一个”——len([c for c in children]) > 2 找出有两个以上子女的家庭,而 len([c for c in children if c.gender == Person.MALE]) > 1 则把范围缩小到只数儿子。
不过,单个查询在这个方向上最远只能到达集合。上面描述的连写(father.birth.place.title)只能通过恰好连接到一条记录的关系来实现——any/len 可以告诉您集合内部是否有某项满足条件,但查询无法再越过这个匹配项,继续连写到它自己的相关记录。比如,一个家庭的父亲有他自己的父母家庭——但由于一个人可以被记录为多个家庭的子女,这个链接本身也是一个集合,比查询目前能跟随的范围又多了一步。因此,“父亲和祖父出生在同一个县的所有家庭”目前还无法用 GOQL 表达。
上面的每个集合都是向外延伸的——一个家庭自己的子女,一个人自己的摘录。backlinks 是唯一一个反向延伸的集合:家谱中是否有其他东西指向这条记录?它在全部十种记录类型中都可用,即使是那些自身没有向外集合的类型(标签没有自己的子女或摘录,但很多记录都可以被打上标签,所以 Tag 自己的 backlinks 条件可以找出没有被任何东西使用的标签):
not any(bl for bl in backlinks) # 没有任何东西引用这条记录
any(bl._class == 'Person' for bl in backlinks) # 至少有一个成员引用它
any(bl for bl in backlinks) and len([bl for bl in backlinks]) > 1 # 被不止一个对象引用正是这一点让“不再被任何人引用的所有来源”或“没有在此记录任何事件的所有地点”这类查询成为可能——在来源或地点视图中运行 not any(bl for bl in backlinks) 即可。
_class 是 backlinks 条件唯一可以检查的字段——引用者自身的记录类型('Person'、'Family'……)。由于反向链接的引用者可以同时是十种记录类型中的任何一种,_class 无法像普通关系那样继续深入——不存在 _class.primary_name——但它确实支持对列表使用 in/not in:
any(bl._class in ['Person', 'Family'] for bl in backlinks)
any(bl._class != 'Media' for bl in backlinks)len([bl for bl in backlinks]) 的用法与上面的 len(...) 完全相同。
Date('...') 能理解普通的日期文本,所以 birth.date.sortval >= Date('Jan 1, 1968') 会找出所有在那一天或之后出生的人。有一点值得了解:sortval 始终是单一的时间点,不附带任何“大约”“之前”或“估计”之类的限定——输入为“1968 年之前”的出生日期与普通的“Jan 1, 1968”具有相同的 sortval,所以 >= 比较会把这个人算作在截止日期当天或之后出生,尽管“之前”的意思恰恰相反。当这种区别很重要时,请改为将 birth.date.modifier 与一个命名常量(例如 Date.MOD_ABOUT)进行比较。
有些字段,比如一个人的性别或一条引用的可信度,是与命名常量而不是原始数字进行比较——gender == Person.MALE 或 confidence >= Citation.CONF_HIGH——这些常量直接取自 Gramps 本身,因此永远不会与 Gramps 实际存储的内容脱节。
GOQL 是一小套固定的构建块,而不是一门完整的编程语言——任何超出上述模式的内容都会被报错拒绝,而不是去猜测其含义。
本页介绍的是日常用法;每个视图搜索框旁边的“i”按钮会列出该视图中可用的确切字段和关系。完整的语法参考——每个运算符、关系、集合和边界情况,以及各自背后的设计考虑——请参阅 gramps-object-query-language 仓库(GOQL 所基于的项目)中的 where_expr.md。
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