11.2 使用SQL处理程序中的数据
在前面几节介绍的例子中,演示了 SQL 的处理过程。我们没有在问题领域中使用任何面向对象设计。我们使用了SQLite中可以处理的数据元素:字符串、日期、浮点数和整型数值,而并没有使用Blog和Post对象。我们基本在使用过程式的编程风格进行设计。
可以看到,可以使用一些查询来完成一篇博客和该博客中的所有文章的查找,以及与这些文章相关的所有标签。这个过程可能会像如下代码这样。
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.PHRASE、POST.TITLE和POST.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中,通过defaultdict和Counter提供的实现也同样非常高效。对于在一个小程序中查询大量行的场景,使用Python的defaultdict的实现方式会比在数据库中使用SQL的GROUP BY语句的实现方式更高效。当有不确定的情况时,最好测试一下。当数据库管理员说到使用SQL非常快时,需要测试一下才能确定。
