10.4 搜索、扫描和查询

    别怕,这些只是同义词。我们会交换地使用这些词。

    对于数据库搜索的设计,我们有两种选择。我们可以返回一系列的键或者是一系列的对象。由于我们的设计强调要将键保存在每个对象中,从数据库获取一系列的对象能够满足我们的需求,所以我们会主要关注这种设计。

    搜索天生就是低效的操作,我们会倾向于将更多的注意力放在索引上。在后面的章节中,我们会介绍如何创建更有用的索引。但是,暴力扫描总是有效的备用方案。

    当一个子类包含一个独立的键时,我们可以基于键创建一个简单的迭代器,这样就能很容易地扫描shelf上某些Child类的所有实例。下面是一个搜索所有元素的生成器表达式的例子。

    children = ( shelf[k] for k in shelf.keys() if key.
    startswith("Child:") )

    这段代码会扫描shelf上的所有键,然后选择所有以"Child:"作为键的开头的对象。我们可以基于这个表达式来创建一个包含更多条件的更复杂的生成器表达式。

    children_by_title = ( c for c in children if c.title == "some title")

    我们用了一个内嵌的生成器表达式向一开始的children查询中添加了条件,像这样的生成器表达式在Python中非常高效。这个表达式不会扫描数据库两次,它只会用两个条件扫描一次数据库。内层生成器的查询结果会作为外层生成器的查询条件的一部分,从而创建最终的结果。

    当子类中使用的是依赖式的键时,可以基于更复杂的匹配条件创建一个迭代器,用于搜索shelf上某个特定父对象的子对象。下面是搜索一个给定父对象的所有子对象生成器的一个表达式。

    children_of = ( shelf[k] for k in shelf.keys() if key.
    startswith(parent+":Child:") )

    这种依赖式的键结构使得用一个简单循环就能够轻松地删除父对象和它的所有子对象。

    for obj in (shelf[k] for k in shelf.keys() if key.startswith(parent)):
      del obj

    当使用"Parent:pid:Child:cid"这种分层的键时,在需要区分父对象和它们的子对象时必须非常小心。由于存在这种多部分组成的键,因此我们会有许多对象的键都以"Parent:pid"为开始。这些键中的其中一个指向的是正确的父对象,就是只包含"Parent:pid"的键。其他的键指向的是以"Parent:pid:Child:cid"为键结构的子对象。在这些强力搜索过程中,有3种条件我们会经常用到。

    • key.startswith("Parent:pid"):查询父对象和所有的子对象,不过不是一个常见的需求。
    • key.startswith("Parent:pid:Child:"):只查询给定父对象的子对象。我们可以用一个正则表达式来匹配键,例如r"^(Parent:\d+):(Child:\d+)$"。
    • key.startswith("Parent:pid")和":Child:":只查询父对象,不返回任何子对象。我们可以用一个正则表达式来匹配键,例如r"^Parent:\d+$"。

    所有的这些查询都可以通过创建索引来优化。