Prolog与数据库

本文探讨了Prolog与数据库的联系,指出Prolog的查询语法与SQL类似,但更简洁。文章以Turbo Prolog GEOBASE为例,提出了一种重新设计数据库的方法,强调每个谓词只表达一个概念,并通过示例展示了如何使用Prolog实现更灵活的查询。此外,还讨论了自然语言查询和数据库按列存储的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Prolog与数据库


简介

Prolog用谓词表示数据,并且用合一将不同谓词关联,这与SQL的数据查询暗合。例如,

    /* 数据库架构。
     *   student(id, name, age)
     *   course(id, name)
     *   studies(student_id, course_id)
     */

    /* 下面的查询列出学生及所学课程的名称(SName, CName)。*/
    student(S, SName, _), studies(S, C), course(C, CName).

    -- 相应的SQL查询。
    SELECT student.name, course.name
      FROM student JOIN studies ON student.id = studies.student_id
                   JOIN course  ON course.id  = studies.course_id;

不难看到,Prolog的查询语法更简练,但它有必须写出整个tuple的缺点(将作进一步讨论)。QBE(Query By Example)是另一种采用合一的数据库查询方式。

尽管查询机制相似,但Prolog的视角完全不同。上例中,Prolog的语言是说查询成功的条件是用逗号分开的各个子句都成功。

Prolog查询与SQL查询相似,都代表逻辑关系,而不是指定具体的查询实现机制,尽管二者都用简单的语法(暗示循环依次检索)来表达逻辑关系。在上述查询中,无论SQL引擎还是Prolog引擎都可以通过选择查询顺序(子句顺序)来提高查询效率。与SQL数据库相似,Prolog引擎也可以为数据建立索引或将其置入哈希表中。下面的例子则更好地说明了逻辑表达与实现机制的区别。在例中,引擎有责任优化查询,避免O(N^2)的开销。

    /* 查找年龄最大的一个或多个学生。'\+' 代表“不存在”。*/
    student(ID, Name, Age), \+ (student(_, _, Age1), Age < Age1).

    SELECT id, name FROM student
     WHERE age >= ALL (SELECT age FROM student);

    SELECT id, name FROM student
     WHERE age = (SELECT MAX(age) FROM student);

很明显,无论SQL还是Prolog在表达此查询时都较为笨拙。不过,语法的改进并非不可能(gprolog支持自定义运算符):

    student(ID, Name, Age), \+ Age < student(_, _, #).
    student(ID, Name, Age), Age >=all student(_, _, #). /* >=all 为自定义算符 */

Prolog崇尚用简单的元素组合出漂亮的功能,而不引入多余的复杂度。下例中简单的list append的递归定义,可以支持不同方向的查询。

    app([], L, L).
    app([A|L1], L2, [A|L12]) :- app(L1, L2, L12).

    ?- app([a], [b], L).
    L = [a,b]

    ?- app(X, [b], [a,b]).
    X = [a]

    ?- app(X, Y, [a,b]).
    X = [], Y = [a,b]
    X = [a], Y = [b]
    X = [a,b], Y = []

重新设计Turbo Prolog GEOBASE的数据库

Trubo Prolog 2.0(Borland公司1988年发行)中包含一个美国地理数据库示例程序,它支持用简单的自然语言(英语)作查询(程序在80x25的文本窗口中运行,仅需要520K的内存)。这里仅讨论数据库的设计与查询。

GEOBASE数据库的架构如下:

    /* 依据最近下载的版本,源代码有很多更改过的痕迹。稍有改动。*/
    state(NAME, ABBREVIATION, CAPITAL, AREA, POPULATION)
    city(STATE, ABBREVIATION, NAME, POPULATION) 
    river(NAME, LENGTH, STATE_LIST)
    border(STATE, ABBREVIATION, STATE_LIST) 
    highlow(STATE, ABBREVIATION, POINT, HEIGHT, POINT, HEIGHT) 
    mount(STATE, ABBREVIATION, NAME, HEIGHT) 
    lake(NAME, AREA, STATE_LIST) 
    road(NUMBER, STATE_LIST) 

它支持的英语查询例句如下:

   - give me the cities in california.

   - what is the biggest city in california?

   - what is the longest river in the usa?

   - which rivers are longer than 1 thousand miles?

   - what is the name of the state with the lowest point?

   - which states border alabama?

   - which rivers do not run through texas?

   - which rivers run through states that border the state
     with the capital austin?

尽管自然语言查询很有趣,但在我看来,数据库首先应当支持用简洁清晰的Prolog语句作灵活的(较复杂的)查询。为此,数据库可以按照下述原则重新设计:不再使用长的数据tuple,每个谓词仅表达一个特定的概念,并仅包含与之相关的项。下面的设计用gprolog实现。

实体谓词声明地理数据库中的实体对象,它的参数是对象的ID,在同类实体中唯一。实体对象可以另外指定一个或多个名称,ID是缺省的名称。

    实体谓词  示例
    --------  ----------------------------------------
    state     state(ny).
              names(state(ny), 'new york').
    city      city('new york, ny').
              names(city('new york, ny'), 'new york').
              names(city('new york, ny'), nyc).
    river     river(delaware).
    lake      lake(ontario).

    查询示例                说明
    ----------------------  --------------------------
    state(X).               列出所有的州。
    river(delaware).        确认delaware河存在。
    names(X, 'new york').   列出名为new york的实体。
    names(city(X), nyc).    列出名为nyc的城市。

必须说明,在names声明中用到的state()与city()仅代表数据tuple,Prolog并不强制检查所指的state或city已经存在。Prolog也不会检查ID的唯一性。这些功能需要由额外的用户层或者由扩展的Prolog来支持。

属性谓词为实体标出某一性质。同一谓词可用于多种实体。

    属性谓词      示例
    ------------  ------------------------------------------
    area          area(state(ny), 49100.0).
    population    population(state(ny), 17558000).
                  population(city('new york, ny'), 7071639).

    查询示例                      说明
    ----------------------------  --------------------------
    population(city(X), N),
        N > 1000000.              列出人口大于一百万的城市。

关系谓词表示不同实体之间的某种关联。同一关系谓词可用于不同的实体组合。

    关系谓词      示例
    ------------  ------------------------------------------
    in            in(city('new york, ny'), state(ny)).
                  in(river(delaware), state(ny)).
                  in(river(delaware), state(pa)).
                  in(river(delaware), state(nj)).
                  in(river(delaware), state(de)).
    capital       capital(state(ny), city('albany, ny')).

    查询示例                      说明
    ----------------------------  --------------------------------
    population(city(X), N),       列出人口大于一百万的城市。
        N > 1000000.
    C = city(_), S = state(_),    列出占州中人口比例大于0.5的城市,
        in(C, S),                   有washington与honolulu。
        population(C, A),
        population(S, B),
        A / B > 0.5.

仍须指出,以上设计的严格实现有赖更多的支持。例如,应当约束谓词所接受的参数”类型“(如capital只能用在state与city之间),这在Turbo Prolog中可以用domain来声明,但gprolog缺少对domain的支持。又如,应当声明关系各方的数量比例。不过,即使缺少这样的支持,用户代码仍不难自行声明约束,并在用户查询层中确认约束的有效性。例如,

    dom(state).
    dom(city).
    dom(river).

    prop(area, float).
    prop(population, integer).

    rel(capital).
    rel(in).

    req(area, state).
    opt(area, D) :- dom(D).
    req(population, state).
    req(population, city).

    req(capital, state, city, 1:1).
    req(in, city, state, 1:1).
    req(in, river, state, 1:"+").

上述用户自行声明的约束条件有不少好处。一方面,它便于用简单方式列出约束:既能查出某一实体所属关系,也能查出与某一关系相关的实体。另一方面,它支持聪明的声明方式,如根据规则判断in关系能发生在哪些实体之间。(实际的地理关系远比示例中复杂,如支流与主流的关系,又如流域与流经的区别。)

与SQL数据库的存储方式比较,上述Prolog数据库设计实际上是按列存储。它不是按实体保存数据,而是按谓词保存数据。在具体实现时,实体声明如state(ny)、river(delaware)可被转换为同一谓词的声明:ent(state(ny))、ent(river(delaware)),而Prolog会自动将state(ny)等转换为整数ID,并在存储事实规则时仅使用ID。无论Turbo Prolog还是gprolog都未直接提供真正的数据库功能,而只能将关系数据以文本形式写入文件。

为了方便查询,我扩展了属性谓词与关系谓词,使它们接受按名称查询:

    查询示例                      说明
    ----------------------------  ------------------------------------
    in(ontario, X).               返回state(ny)与state(ca),
                                    ontario既是湖名也是加州的城市名。
    in(cities(X), ny).            用cities而不是city将返回城市名,
                                    而不是城市ID。
    in(X, states('new york')).    按名称查询,state('new york')不存在,
                                    但states('new york')存在。

此外,很容易将state、city等声明为算符,这样使用它们时就不必带括号,如 state ny。


评论

为不同领域的知识建立数据库是非常有趣的事。百科全书并不足够,应当有更方便准确的方法查询知识,这甚至包括查询文学、电影中的人物关系、事件情节。例如Google Earth的地图数据中按地点标出了新发生的新闻事件(最近联不上了)。

用自然语言查询也很有趣,而比之更有趣的是解析自然语言文档,将资料”同化“入数据库中。例如,从百科全书网站或地理杂志中获取地理知识,并录入数据库中。指数增长的知识需要更多的人关注、分享。Emacs中有一个小游戏Adventure,让人根据文本的场景描述想像一个空间环境,并下指令移动位置、作收集物品等动作,最后杀死怪物;更有趣的工作则是反过来,让计算机根据场景的文字描述绘制出空间位置关系图。Emacs中的另一个游戏则用自然语言假装心理医生与人交流。(Emacs编辑器有一个显眼的红牛骑在蓝色字样上的标志,牛头很接近汉字的”公“字;而vim编辑器(vi的一个版本)的欢迎页面中则有”帮助乌干达的可怜儿童“广告语。)很显然,无论分享知识还是享受游戏都需要宽松的心态与经济条件。

无论知识的传承还是与之相关的工具的研发,都必须属于民间。一方面,知识没有标准版本、不可能由权威发布考核,而国家必须依赖民间的知识积累;另一方面,知识被垄断并且被随意篡改是不可容忍的。(根据en.wikipedia.org,美国《独立宣言》的第一个签名人叫John Hancock,他的签名明显比他人的大,不知是否属实。)软件工具不能用来侵犯人,包括隐私权。

我们的软件行业(包括学校教育)有明显的问题。程序员或学生缺少好的文档与教材,而被有意置于摸索的状态。这是人的贬值,是不可接受的,编程与学习理当是享受(由他人分享的知识的快乐)。程序员与学生常被有意捉弄,看不到正确的版本或知识而猜测”标准答案“,这更是不可容忍的。考试不能无视人的尊严,而现实中有非常多的有趣的题目。西方的开源软件如Emacs、vim、gprolog都带有大量文档,不过,入乡随俗,这些文档大都可读性很差,亟待补充(学习、改进它们是更好的考试)。软件资料很差并非”一日之寒“,早在Turbo Prolog时代或更早就是这样,似乎这是天然的(控制、捉弄的)权力,而这剥夺了很多(享受知识与生活的)快乐。


补充

两个小建议:

Emacs编辑器的复杂按键组合如Ctrl-x Ctrl-f可以简化为x-f(同时按下两键)。英文中似乎仅在Oxford一词里两个字母连续出现。

vi编辑器应能自然地嵌入到其他环境中,如shell、mail命令及prolog、python的解释环境中,并且能编辑特定的命令/函数/方法。也应能将其嵌入IDE或Office环境中,以便享用定制的按键组合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值