2012-12-22 6 views
27

Ich baue einen Activity Stream für unsere Seite und habe einige gute Fortschritte gemacht mit etwas, das ziemlich gut funktioniert.Intelligentes MySQL GROUP BY für Activity Streams

Es wird von zwei Tabellen mit Strom versorgt:

Strom:

  • id - einzigartiges Stream-Item ID
  • user_id - ID des Benutzers, der den Stream Artikel erstellt
  • object_type - Type des Objekts (derzeit "Verkäufer" oder "Produkt")
  • object_id - Interne ID des Objekts (derzeit entweder der Verkäufer-ID oder das ID-Produkt)
  • action_name - die Maßnahmen gegen das Objekt (derzeit entweder ‚kaufen‘ oder ‚Herz‘)
  • stream_date - Zeitstempel, dass die Aktion war erstellt.
  • hidden - Boolescher Wert, wenn der Benutzer das Element ausgeblendet hat.

folgt:

  • id - Einzigartige Folgen ID
  • user_id - Die ID des Benutzers, der die 'Folgen' Aktion.
  • following_user - Die ID des Benutzers, der verfolgt wird.
  • followed - Zeitstempel, dass die Folgeaktion ausgeführt wurde.

Derzeit verwende ich die folgende Abfrage Inhalt aus der Datenbank zu ziehen:

Abfrage:

SELECT stream.*, 
    COUNT(stream.id) AS rows_in_group, 
    GROUP_CONCAT(stream.id) AS in_collection 
FROM stream 
INNER JOIN follows ON stream.user_id = follows.following_user 
WHERE follows.user_id = '1' 
    AND stream.hidden = '0' 
GROUP BY stream.user_id, 
    stream.action_name, 
    stream.object_type, 
    date(stream.stream_date) 
ORDER BY stream.stream_date DESC; 

Diese Abfrage ist eigentlich ziemlich gut funktioniert, und ein wenig mit PHP zu analysieren die Daten, die MySQL zurückgibt, können wir einen netten Activity Stream mit Aktionen des gleichen Typs erstellen, indem derselbe Benutzer gruppiert wird, wenn die Zeit zwischen den Aktionen nicht zu groß ist (siehe unten).

Current Stream Output Example

Meine Frage ist, wie kann ich diese schlauer? Derzeit gruppiert es um eine Achse, "Benutzer" Aktivität, wenn es mehrere Elemente von einem bestimmten Benutzer innerhalb eines bestimmten Zeitrahmens gibt, weiß die MySQL sie zu gruppieren.

Wie kann ich dies noch intelligenter und gruppieren durch eine andere Achse, wie "object_id", also wenn mehrere Aktionen für das gleiche Objekt in Folge sind diese Elemente gruppiert, aber die Gruppierungslogik, die wir derzeit für die Gruppierung von Aktionen haben/Objekte nach Benutzer. Und das ohne Datenduplikation?

Beispiel mehrerer Objekte in der Reihenfolge erscheinen:

Multiple Objects Appearing in Sequence

I Lösungen für Probleme wie diese verstehen können sehr komplex, sehr schnell, aber ich frage mich, ob es ein elegantes und ziemlich einfache Lösung das (hoffentlich) in MySQL.

+0

Argh. Das falsche MySQL-Feature namens "GROUP BY" (verborgene Spalten) erschwert es Ihnen, Ihre Abfrage zu verstehen. Es macht es anderen schwer, es zu verstehen. Siehe hierzu: http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html –

+0

Wie möchten Sie, dass etwas gruppiert wird, wenn ein einzelner Nutzer mehr Dinge kauft, aber einen (oder mehrere)) dieser Produkte sind auch gruppiert? EG in deinem letzten Beispiel, was ist, wenn Chrision auch Treehouse Gold gekauft hat? Würde es zu seiner Gruppe, zur Baumhausgruppe oder zu beiden hinzugefügt werden? –

+0

@HugoDelsing Neben der Gruppierung ähnlicher Aktionen durch denselben Benutzer sollten auch Elemente gruppiert werden, die von verschiedenen Benutzern nebeneinander angezeigt werden und in denen diese Elemente noch nicht gruppiert sind. Z.B. Da Joe, India und Walt im obigen Beispiel Treehouse gekauft haben und diese nahe beieinander liegen, sollten diese gruppiert werden, obwohl sie von verschiedenen Benutzern stammen. –

Antwort

13

Mein Eindruck ist, dass Sie nach Benutzer gruppieren müssen, wie Sie, aber auch nach dieser Gruppierung nach Aktion.

Es sieht für mich wie Sie eine Unterabfrage wie diese benötigen:

SELECT *, -- or whatever columns 
    SUM(actions_in_group) AS total_rows_in_group, 
    GROUP_CONCAT(in_collection) AS complete_collection 
    FROM 
    (SELECT stream.*, -- or whatever columns 
      COUNT(stream.id) AS actions_in_user_group, 
      GROUP_CONCAT(stream.id) AS actions_in_user_collection 
     FROM stream 
     INNER JOIN follows 
     ON stream.user_id = follows.following_user 
     WHERE follows.user_id = '1' 
     AND stream.hidden = '0' 
     GROUP BY stream.user_id, 
      date(stream.stream_date) 
    ) 
    GROUP BY object_id, 
      date(stream.stream_date) 
    ORDER BY stream.stream_date DESC; 

Ihre erste Abfrage (jetzt die innere) Gruppen, die durch Benutzer, aber dann werden die Benutzergruppen mit gleichen Aktionen umgruppiert - das heißt, identische Produkte gekauft oder Verkäufe von einem Verkäufer würde zusammen gestellt werden.

+1

Dies ist die richtige Antwort, obwohl Sie nach Ihrer inneren Abfrage "AS AS something" hinzufügen müssen, um zu vermeiden, dass MySQL einen Fehler verursacht. –

18

Einige Beobachtungen über die gewünschten Ergebnisse:

Einige der Elemente zusammengefasst werden (Jack Sprotte sieben Verkäufer hearted) und andere sind aufgeschlüsselt (Lord Nelson die Golden Hind gechartert). Wahrscheinlich benötigen Sie in Ihrer Abfrage eine UNION, die diese beiden Objektklassen aus zwei separaten Unterabfragen zusammenfasst.

Sie verwenden eine ziemlich grobe Timestamp-Nähe-Funktion, um Ihre Artikel zu gruppieren ... DATE(). Möglicherweise möchten Sie immer ausgefeilter und tweakable Schema ... wie diese benutzen, vielleicht

GROUP BY TIMESTAMPDIFF(HOUR,CURRENT_TIME(),stream_date) DIV hourchunk 

Dies wird Ihnen Gruppe Zeug nach Alter Brocken lassen. Zum Beispiel, wenn Sie 48 für hourchunk verwenden, gruppieren Sie Sachen, die vor 0-48 Stunden zusammen sind. Wenn Sie Ihrem System Traffic und Aktionen hinzufügen, möchten Sie möglicherweise den Wert hourchunk verringern.

+0

Das ist ein interessanter Punkt in Bezug auf Timestamp-Nähe, die Hourchunk-Methode, die Sie demonstriert haben, würde gut funktionieren und könnte sogar je nach Häufigkeit der Aktivitäten der Benutzer etwas manipuliert werden, was eine interessante Aussicht ist. In Bezug auf die UNION, wie würden Sie empfehlen, über die Umsetzung davon zu gehen? Ich habe vorher noch nicht wirklich mit UNION gearbeitet, aber es wäre mein Ziel, grundsätzlich in zwei verschiedene Richtungen zu aggregieren (auf einem "Benutzer hat Aktion X mal gemacht", und "X Benutzer haben X Aktion zu Objekt X gemacht"). –

6

Drüben auf Fashiolista haben wir unseren Ansatz zum Bau von Futtersystemen geöffnet. https://github.com/tschellenbach/Feedly Es ist derzeit die größte Open-Source-Bibliothek zur Lösung dieses Problems. (aber in Python geschrieben)

Das gleiche Team, das Feedly erstellt, bietet auch eine gehostete API, die die Komplexität für Sie behandelt. Werfen Sie einen Blick auf getstream.io Es gibt Clients für PHP, Node, Ruby und Python. https://github.com/tbarbugli/stream-php Es bietet auch Unterstützung für benutzerdefinierte definierte Aggregationen, die Sie suchen.

Zusätzlich haben einen Blick auf diese hohe Skalierbarkeit Post waren wir einige der Design-Entscheidungen erklären beteiligt: ​​ http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic-feeds.html

This tutorial werden Sie Setup ein System wie Pinterest Feed mit Redis helfen. Es ist ziemlich einfach, mit anzufangen.

Um mehr über Feed-Design lernen empfehle ich einige Artikel zu lesen, die wir auf Basis Feedly auf:

6

Wir haben ähnliche Probleme gelöst, indem wir den Ansatz "materialisierte Ansicht" verwenden - wir verwenden eine dedizierte Tabelle, die beim Einfügen/Aktualisieren/Löschen aktualisiert wird. Alle Benutzeraktivitäten werden in dieser Tabelle protokolliert und für die einfache Auswahl und das Rendering vorbereitet.

Vorteil ist einfache und schnelle Auswahl, Nachteil ist ein bisschen langsamer einfügen/aktualisieren/löschen, da Protokolltabelle aktualisiert werden muss.

Wenn dieses System gut ist, ist es eine gute Lösung.

Das ist ganz einfach zu implementieren, wenn Sie ORM mit Post insert/update/delete Ereignisse verwenden (wie Lehre)

+0

Aber Sie Leute haben die Action/Activity-Definitionen in einer separaten Datei, oder? –

+0

Nicht sicher, dass ich Ihre Frage verstehe ... –

+0

Vielleicht würde dies helfen: "{Name1} aktualisiert sein Profil.", Und geben Sie es live: "Nikola aktualisiert sein Profil". Verstehst du es? –