11.2 使用SQL处理程序中的数据

    在前面几节介绍的例子中,演示了 SQL 的处理过程。我们没有在问题领域中使用任何面向对象设计。我们使用了SQLite中可以处理的数据元素:字符串、日期、浮点数和整型数值,而并没有使用BlogPost对象。我们基本在使用过程式的编程风格进行设计。

    可以看到,可以使用一些查询来完成一篇博客和该博客中的所有文章的查找,以及与这些文章相关的所有标签。这个过程可能会像如下代码这样。

    query_blog_by_title= """
    SELECT FROM BLOG WHERE TITLE=?
    """
    query_post_by_blog_id= """
    SELECT
    FROM POST WHERE BLOG_ID=?
    """
    query_tag_by_post_id= """
    SELECT TAG.*
    FROM TAG JOIN ASSOC_POST_TAG ON TAG.ID = ASSOC_POST_TAG.TAG_ID
    WHERE ASSOC_POST_TAG.POST_ID=?
    """
    for blog in database.execute( query_blog_by_title, ("2013-2014
    Travel",) ):
      print( "Blog", blog )
      for post in database.execute( query_post_by_blog_id, (blog[0],) ):
        print( "Post", post )
        for tag in database.execute( query_tag_by_post_id, (post[0],)
    ):
          print( "Tag", tag )

    我们定义了3个SQL查询语句,第1个会根据标题查询博客。对于每个博客我们都获取所有属于这个博客的文章。最终,获取与给定文章相关的所有标签。

    第2个查询隐式地重复了REFERENCES的定义,它建立在POST表和BLOG表之间的引用。由于要查询一个指定博客父对象的所有子文章,因此需要在查询中重复一些表的定义。

    第3个查询中包含了一种关系的连接,在ASSOC_POST_TAG表和TAG表之间。JOIN语句再次定义了表之间的外键引用,WHERE 语句也会在表定义中重复地定义一个 REFERENCES语句。

    由于多张表的关系是在第3个查询中连接起来的,因此使用SELECT 将会查询这些表的全部列。由于我们只关心TAG表中的属性,因此只要使用SELECT TAG.来对所需要的列进行查询就可以了。

    这些查询会返回所有的比特和数据块,然而这些查询并不会重新创建 Python 对象。如果有更复杂的类定义,我们必须基于所返回的数据块来创建对象。特别是当 Python 类定义中有很重要的方法时,为了使用更完整的 Python 类定义,我们需要一种更好的从 SQL 到Python的映射。

    在纯SQL中实现类似于类的处理方式

    现在看一个更复杂的Blog类的定义,这个定义在第9章“序列化和保存——JSON、YAML、Pickle、CSV和XML”中介绍过。这里只关心一个方法,在以下代码中已经高亮出来了。

    from collections import defaultdict
    class Blog:
      def init( self, title, posts ):
        self.title= title
        self.entries= list(posts)
      def append( self, post ):
        self.entries.append(post)
      def by_tag(self):
        tag_index= defaultdict(list)
        for post in self.entries:
          for tag in post.tags:
            tag_index[tag].append( post )
        return tag_index
      def as_dict( self ):
        return dict(
          title= self.title,
          underline= "="
    len(self.title),
          entries= [p.as_dict() for p in self.entries],
        )

    一个博客的blog.by_tag()功能在SQL查询中会变得相当复杂。因为使用的是面向对象编程,它简化了这个过程:在一个Post实例中迭代,创建defaultdict,在它里面完成了从每个标签到一个Posts序列的映射,这些文章会共享这个标签。以下代码使用SQL查询做了同样的事情。

    query_by_tag="""
    SELECT TAG.PHRASE, POST.TITLE, POST.ID
    FROM TAG JOIN ASSOC_POST_TAG ON TAG.ID = ASSOC_POST_TAG.TAG_ID
    JOIN POST ON POST.ID = ASSOC_POST_TAG.POST_ID
    JOIN BLOG ON POST.BLOG_ID = BLOG.ID
    WHERE BLOG.TITLE=?
    """

    在这个查询结果集中包含了一个由行的序列所组成的表,包括了3个属性:TAG.PHRASEPOST.TITLEPOST.ID。每个POST标题和POST ID将会和所有相关的TAG重复出现。为了使用更简单且对于HTML友好的方式来表示,我们需要将包含了相同TAG.PHRASE的行分组为同一个附属列表,如下代码所示。

    tag_index= defaultdict(list)
    for tag, post_title, post_id in database.execute( query_by_tag,
    ("2013-2014 Travel",) ):
      tag_index[tag].append( (post_title, post_id) )
    print( tag_index )

    这个额外的过程会将POST标题和POST ID的两个元组放入一个结构中,它可以被有效地用于生成RST和HTML的输出结果。SQL查询加上Python的处理过程显得非常繁琐——比内部面向对象的Python还要繁琐。

    更重要的是,SQL查询与表定义无关。SQL不是一种面向对象编程语言,并没有一个将数据和处理过程融合在一起的类。使用像SQL这样的过程式编程语言时,将完全无法使用面向对象编程。从一种严格的面向对象编程的视角来看,我们会标记为“重大失败”。

    有很多观点建议,在处理特定的问题时,使用这种大量使用SQL而且没有任何对象的编程方式比使用Python更合适。通常,这种问题包含了SQL的GROUP BY语句。然而在SQL中很方便,在Python中,通过defaultdictCounter提供的实现也同样非常高效。对于在一个小程序中查询大量行的场景,使用Python的defaultdict的实现方式会比在数据库中使用SQL的GROUP BY语句的实现方式更高效。当有不确定的情况时,最好测试一下。当数据库管理员说到使用SQL非常快时,需要测试一下才能确定。