在Google App Engine上过滤和排序音乐信息
我在谷歌应用引擎上做了一些简单的应用,感觉挺不错的,但现在我遇到了一些问题,想要在这个平台上搭建一个音乐收藏整理工具。简单来说,我不知道怎么在多个属性上进行筛选,同时又能按另一个属性排序。
假设我们有一个核心模型叫做专辑,它包含几个属性,比如:
- 标题
- 艺术家
- 唱片公司
- 出版年份
- 音乐类型
- 时长
- 曲目名称列表
- 情绪列表
- 插入数据库的时间
假设我想用这些属性来筛选整个收藏,然后再按以下其中一个属性排序:
- 出版年份
- 专辑时长
- 艺术家名字
- 信息添加到数据库的时间
我不知道怎么做到这一点,而不遇到索引爆炸的问题。具体来说,我想做的事情是:
Albums.all().filter('publication_year <', 1980).order('artist_name')
我知道这样做是不可能的,但有没有其他解决办法呢?
这看起来是一个比较通用的应用类型。音乐专辑也可以是餐厅、葡萄酒瓶或者酒店。我有一堆带有描述性属性的物品,想要进行筛选和排序。
有没有我忽视的最佳实践数据模型设计?有什么建议吗?
3 个回答
正如你所说的,你不能在一个字段上使用不等式条件,而在另一个字段上进行排序(或者在两个字段上都使用不等式条件等等)。解决这个问题的方法就是先用“最合适”的不等式条件来获取数据(这里的“最合适”是指预计能得到最少数据的条件),然后再在你的应用程序中用Python代码进一步处理和排序。
Python的列表推导式(还有其他循环方式)、列表的sort
方法、内置的sorted
函数,以及标准库中的itertools
模块等等,都能大大简化在Python中执行这些任务的过程。
因为存储空间便宜,你可以自己创建一个基于列表属性的索引文件,里面的关键字可以反映你想要的排序标准。
class album_pubyear_List(db.Model):
words = db.StringListProperty()
class album_length_List(db.Model):
words = db.StringListProperty()
class album_artist_List(db.Model):
words = db.StringListProperty()
class Album(db.Model):
blah...
def save(self):
super(Album, self).save()
# you could do this at save time or batch it and do
# it with a cronjob or taskqueue
words = []
for field in ["title", "artist", "label", "genre", ...]:
words.append("%s:%s" %(field, getattr(self, field)))
word_records = []
now = repr(time.time())
word_records.append(album_pubyear_List(parent=self, key_name="%s_%s" %(self.pubyear, now)), words=words)
word_records.append(album_length_List(parent=self, key_name="%s_%s" %(self.album_length, now)), words=words)
word_records.append(album_artist_List(parent=self, key_name="%s_%s" %(self.artist_name, now)), words=words)
db.put(word_records)
当你需要搜索的时候,就可以创建一个合适的 WHERE 条件,然后调用相应的模型。
where = "WHERE words = " + "%s:%s" %(field-a, value-a) + " AND " + "%s:%s" %(field-b, value-b) etc.
aModel = "album_pubyear_List" # or anyone of the other key_name sorted wordlist models
indexes = db.GqlQuery("""SELECT __key__ from %s %s""" %(aModel, where))
keys = [k.parent() for k in indexes[offset:numresults+1]] # +1 for pagination
object_list = db.get(keys) # returns a sorted by key_name list of Albums
这里有几种选择:你可以尽量过滤数据,然后在内存中对结果进行排序,就像Alex建议的那样,或者你可以重新设计你的数据结构,使用相等的过滤条件,而不是不相等的。
举个例子,假设你只想按十年为单位来过滤数据,你可以添加一个字段,记录这首歌是在哪个十年录制的。要查找某个十年之前或之后的所有歌曲,可以使用一个IN查询,列出你想要的十年。这需要为每个包含的十年做一个基础查询,但如果记录数量很大,这样做可能比先获取所有结果再在内存中排序要便宜。