2009-12-02 5 views
4

J'ai quelques données. 224 000 lignes, dans une base de données SQLite. Je veux en extraire des informations de séries temporelles pour alimenter un outil de visualisation de données. Essentiellement, chaque ligne de la base de données est un événement qui a (entre autres choses non strictement pertinentes) un groupe date-heure en secondes depuis l'époque et un nom qui en est responsable. Je veux extraire combien d'événements chaque nom a pour chaque semaine dans la DB.Table SQLite pivotante, setwise comme SQL doit être

C'est assez simple:

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

et nous obtenons environ six mille lignes de données.

count   name  week:year 
23............ fudge.......23:2009 
etc... 

Mais je ne veux pas une ligne pour chaque nom de chaque semaine - je veux une ligne pour chaque nom, et une colonne pour chaque semaine, comme ceci:

Name  23:2009  24:2009 25:2009 
fudge........23............6............19 
fish.........1.............0............12 
etc... 

Maintenant, la Le processus de surveillance fonctionne depuis 69 semaines, et le nombre de noms uniques est 502. Donc, clairement, je suis loin d'être friand de toute solution qui implique le codage en dur de toutes les colonnes et encore moins des lignes. Je suis moins indifférent sur tout ce qui implique l'itération sur le terrain, disons avec executemany() de python, mais je suis prêt à l'accepter si nécessaire. SQL est censé être défini par les paramètres, bon sang.

+0

@yorksranter: prévoyez-vous d'upvote et d'accepter une réponse? –

+0

Je n'ai pas les 15 points de réputation. – user106514

+0

@yorkstranter: C'est bon, si vous aimez vraiment le code, vous pouvez revisiter quand votre représentant est plus haut :-) –

Répondre

4

Une bonne approche dans des cas comme celui-ci n'est pas de pousser SQL au point où il devient compliqué et difficile à comprendre et à maintenir. Laisser SQL faire ce qu'il peut commodément et post-traiter les résultats de la requête en Python.

Voici une version réduite d'un simple générateur de tableau croisé que j'ai écrit. La version complète fournit les totaux de ligne/colonne/grand.

Vous remarquerez qu'il a intégré "group by" - l'utilisation d'origine était pour résumer les données obtenues à partir de fichiers Excel en utilisant Python et xlrd. Les row_key et col_key que vous fournissez n'ont pas besoin d'être des chaînes comme dans l'exemple; ils peuvent être des tuples - par ex. (year, week) dans votre cas - ou ils pourraient être des entiers - par ex. vous avez un mappage du nom de colonne de chaîne sur la clé de tri d'entier.

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 

Sortie:

['AM', 'PM', 'XXXX'] 
('Dozy', [None, 1, None]) 
('Jill', [100, 150, None]) 
('Joe', [70, 80, None]) 
('Nemo', [None, None, -1]) 
('Rob', [240, 345, None]) 
1

Je voudrais tout d'abord faire votre requête

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

et ne post-traitement avec python.

Vous n'avez donc pas à parcourir plus de 224 000 lignes mais plus de 6 000 lignes. Vous pouvez facilement stocker ces 6 000 lignes en mémoire (pour le traitement avec Python). Je pense que vous pouvez stocker 224 000 lignes en mémoire, mais cela prend beaucoup plus de mémoire.

Cependant: Les nouvelles versions de sqlite prennent en charge la fonction d'agrégation group_concat. Peut-être que vous pouvez utiliser cette fonction pour pivoter avec SQL? Je ne peux pas essayer parce que j'utilise une version plus ancienne.

Questions connexes