像SQL那样对SQLite表进行透视操作
我有一些数据,里面有224,000行,存储在一个SQLite数据库里。我想从中提取时间序列信息,以便用来喂给一个数据可视化工具。简单来说,数据库中的每一行都是一个事件,包含一些信息,其中有一个时间戳(从1970年开始的秒数)和一个负责这个事件的名字。我想提取每个名字在数据库中每周发生的事件数量。
这其实挺简单的:
SELECT COUNT(*),
name,
strf("%W:%Y", time, "unixepoch")
FROM events
GROUP BY strf("%W:%Y", time, "unixepoch"), name
ORDER BY time
这样我们就得到了大约六千行的数据。
count name week:year
23............ fudge.......23:2009
etc...
但是我不想要每个名字每周都有一行数据,我想要每个名字一行,每周一列,像这样:
Name 23:2009 24:2009 25:2009
fudge........23............6............19
fish.........1.............0............12
etc...
现在,这个监控过程已经运行了69周,独特的名字有502个。所以显然,我不想用硬编码的方式来处理所有的列,更不想处理所有的行。如果有办法可以遍历所有数据,比如用Python的executemany(),我也能接受,但我还是希望能有更好的解决方案。毕竟,SQL应该是基于集合的,这样做才更合理。
2 个回答
我建议你先执行你的查询。
SELECT COUNT(*),
name,
strf("%W:%Y", time, "unixepoch")
FROM events
GROUP BY strf("%W:%Y", time, "unixepoch"), name
ORDER BY time
然后再用Python进行后续处理。
这样你就不需要处理22.4万行数据,而只需要处理6千行。你可以很轻松地把这6千行数据存到内存里(用Python处理)。其实你也可以把22.4万行数据存到内存里,但那样会占用更多的内存。
不过:新的sqlite版本支持一个叫做group_concat的聚合函数。也许你可以用这个函数在SQL中进行数据透视?我不能试试,因为我用的是旧版本。
在这种情况下,一个好的做法是不要把SQL写得太复杂,这样会让人难以理解和维护。让SQL做它能方便地完成的事情,然后在Python中对查询结果进行后处理。
这里有一个我写的简单交叉表生成器的简化版。完整版本可以提供行、列和总计。
你会注意到它内置了“分组”功能——最初的用途是用Python和xlrd从Excel文件中汇总数据。
你提供的row_key
和col_key
不一定要像例子中那样是字符串;它们可以是元组——比如在你的情况下可以是(year, week)
——或者可以是整数——例如,你有一个字符串列名到整数排序键的映射。
import sys
class CrossTab(object):
def __init__(
self,
missing=0, # what to return for an empty cell. Alternatives: '', 0.0, None, 'NULL'
):
self.missing = missing
self.col_key_set = set()
self.cell_dict = {}
self.headings_OK = False
def add_item(self, row_key, col_key, value):
self.col_key_set.add(col_key)
try:
self.cell_dict[row_key][col_key] += value
except KeyError:
try:
self.cell_dict[row_key][col_key] = value
except KeyError:
self.cell_dict[row_key] = {col_key: value}
def _process_headings(self):
if self.headings_OK:
return
self.row_headings = list(sorted(self.cell_dict.iterkeys()))
self.col_headings = list(sorted(self.col_key_set))
self.headings_OK = True
def get_col_headings(self):
self._process_headings()
return self.col_headings
def generate_row_info(self):
self._process_headings()
for row_key in self.row_headings:
row_dict = self.cell_dict[row_key]
row_vals = [row_dict.get(col_key, self.missing) for col_key in self.col_headings]
yield row_key, row_vals
def dump(self, f=None, header=None, footer='', ):
if f is None:
f = sys.stdout
alist = self.__dict__.items()
alist.sort()
if header is not None:
print >> f, header
for attr, value in alist:
print >> f, "%s: %r" % (attr, value)
if footer is not None:
print >> f, footer
if __name__ == "__main__":
data = [
['Rob', 'Morn', 240],
['Rob', 'Aft', 300],
['Joe', 'Morn', 70],
['Joe', 'Aft', 80],
['Jill', 'Morn', 100],
['Jill', 'Aft', 150],
['Rob', 'Aft', 40],
['Rob', 'aft', 5],
['Dozy', 'Aft', 1],
# Dozy doesn't show up till lunch-time
['Nemo', 'never', -1],
]
NAME, TIME, AMOUNT = range(3)
xlate_time = {'morn': "AM", "aft": "PM"}
print
ctab = CrossTab(missing=None, )
# ctab.dump(header='=== after init ===')
for s in data:
ctab.add_item(
row_key=s[NAME],
col_key= xlate_time.get(s[TIME].lower(), "XXXX"),
value=s[AMOUNT])
# ctab.dump(header='=== after add_item ===')
print ctab.get_col_headings()
# ctab.dump(header='=== after get_col_headings ===')
for x in ctab.generate_row_info():
print x
输出:
['AM', 'PM', 'XXXX']
('Dozy', [None, 1, None])
('Jill', [100, 150, None])
('Joe', [70, 80, None])
('Nemo', [None, None, -1])
('Rob', [240, 345, None])