Поворотная таблица SQLite, заданная как SQL должна быть - PullRequest
4 голосов
/ 02 декабря 2009

У меня есть некоторые данные. 224 000 строк в базе данных SQLite. Я хочу извлечь из него информацию о временных рядах для подачи в инструмент визуализации данных. По сути, каждая строка в базе данных - это событие, которое имеет (среди прочего, не строго соответствующие) группу времени и даты в секундах с начала эпохи и имя, ответственное за нее. Я хочу извлечь, сколько событий имеет каждое имя за каждую неделю в БД.

Это достаточно просто:

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. Ясно, что мне далеко не нравится любое решение, которое включает жесткое кодирование всех столбцов и тем более строк. Я менее заинтересован во всем, что включает в себя итерации по всему, скажем, с помощью executemany () в python, но я готов принять это при необходимости. SQL предназначен для установки, черт побери.

Ответы [ 2 ]

4 голосов
/ 03 декабря 2009

Хороший подход в подобных случаях заключается в том, чтобы не продвигать SQL до такой степени, чтобы он становился запутанным и трудным для понимания и поддержки. Позвольте SQL делать то, что ему удобно, и постобработать результаты запроса в Python.

Вот урезанная версия простого генератора кросс-таблицы, который я написал. Полная версия предоставляет строки / столбца / общие итоги.

Вы заметите, что он имеет встроенную функцию «group by» - исходный вариант использования был для обобщения данных, полученных из файлов Excel с использованием Python и xlrd.

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])
1 голос
/ 04 декабря 2009

Я бы сначала сделал ваш запрос

SELECT COUNT(*), 
       name, 
       strf("%W:%Y", time, "unixepoch") 
  FROM events 
 GROUP BY strf("%W:%Y", time, "unixepoch"), name 
 ORDER BY time

и затем выполните постобработку с помощью python.

Таким образом, вам не нужно перебирать более 224 000 строк, но более 6000 строк. Вы можете легко сохранить эти 6000 строк в памяти (для обработки с помощью Python). Я думаю, что вы можете хранить 224 000 строк в памяти, но это занимает гораздо больше памяти.

Однако: новые версии sqlite поддерживают функцию агрегирования group_concat. Может быть, вы можете использовать эту функцию для поворота с SQL? Я не могу попробовать, потому что я использую старую версию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...