2012-12-22 10 views
27

Sto costruendo un flusso di attività per il nostro sito e ho fatto alcuni progressi decenti con qualcosa che funziona abbastanza bene.MySQL GROUP BY intelligente per flussi di attività

E 'alimentato da due tabelle:

flusso:

  • id - Unico flusso Voce ID
  • user_id - ID dell'utente che ha creato l'oggetto flusso
  • object_type - Tipo di oggetto (attualmente "venditore" o "prodotto")
  • object_id - ID interno dell'oggetto (attualmente l'ID venditore o l'ID del prodotto)
  • action_name - L'azione intrapresa contro l'oggetto (attualmente o 'acquistare' o 'cuore')
  • stream_date - Timestamp che l'azione era creato.
  • hidden - Booleano di se l'utente ha scelto di nascondere l'elemento.

segue:

  • id - Unico Segui ID
  • user_id - L'ID utente della persona l'azione 'seguire'.
  • following_user - L'ID dell'utente che viene seguito.
  • followed - Timestamp che l'azione seguente è stata eseguita.

Attualmente sto utilizzando la seguente query per estrarre il contenuto dal database:

Query:

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; 

Questa query effettivamente funziona abbastanza bene, e con un po 'di PHP per analizzare i dati restituiti da MySQL possono creare un buon flusso di attività con azioni dello stesso tipo da parte dello stesso utente che viene raggruppato insieme se il tempo tra le azioni non è troppo grande (vedi sotto l'esempio).

Current Stream Output Example

La mia domanda è, come faccio a fare questo più intelligente? Attualmente raggruppa per asse, l'attività "utente", quando ci sono più elementi di un particolare utente entro un certo periodo di tempo, MySQL sa raggrupparli.

Come posso rendere questo ancora più intelligente e raggruppare da un altro asse, come "object_id" quindi se ci sono più azioni per lo stesso oggetto in sequenza questi elementi sono raggruppati, ma mantenere la logica di raggruppamento che abbiamo attualmente per le azioni di raggruppamento/oggetti per utente. E l'implementazione di questo senza duplicazione dei dati?

Esempio di più oggetti che appaiono in sequenza:

Multiple Objects Appearing in Sequence

Capisco soluzioni a problemi come questo può diventare molto complessa, molto rapidamente, ma mi chiedo se c'è un elegante e abbastanza semplice soluzione questo (si spera) in MySQL.

+0

Argh. La disfunzione di MySQL chiamata "colonne nascoste" GROUP BY "potrebbe rendere difficile la comprensione della query. Rende difficile per gli altri capirlo. Vedi questo: http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html –

+0

Come vorresti qualcosa da raggruppare quando un singolo utente compra più cose, ma una (o più) di questi prodotti sono anche raggruppati? Ad esempio, nel tuo ultimo esempio, cosa accadrebbe se Christion acquistasse anche l'oro Treehouse? Sarebbe stato aggiunto al suo gruppo, al gruppo della casa sull'albero o ad entrambi? –

+0

@HugoDelsing Oltre a raggruppare azioni simili dello stesso utente insieme, dovrebbe raggruppare elementi che appaiono vicini l'uno all'altro da utenti diversi in cui questi elementi non sono già raggruppati. Per esempio. Come Joe, India e Walt hanno acquistato Treehouse nell'esempio sopra, e questi sono vicini, questi dovrebbero essere raggruppati, anche se sono da utenti diversi. –

risposta

13

La mia impressione è che devi raggruppare per utente, come fai tu, ma anche, dopo quel raggruppamento, per azione.

Sembra a me come avete bisogno di una sottoquery come questo:

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; 

vostra query iniziale (ora quella interna) gruppi per utente, ma poi i gruppi di utenti vengono raggruppati da azioni identiche - cioè, prodotti identici acquistati o vendite da un venditore sarebbero messi insieme.

+1

Questa è la risposta corretta, anche se è necessario aggiungere "' AS qualcosa "dopo la query interna per evitare che MySQL lanci un errore. –

18

Alcune osservazioni circa i risultati desiderati:

Alcuni degli articoli sono aggregati (Jack Sprat Hearted sette venditori) e altri sono dettagliati (Lord Nelson ha noleggiato la Golden Hind). Probabilmente hai bisogno di avere UNION nella tua query che riunisca queste due classi di elementi da due subquery separate.

È possibile utilizzare una funzione di timestamp near-town piuttosto approssimativa per raggruppare gli articoli ... DATE(). Si consiglia di utilizzare lo schema di più sofisticato e tweakable ... così, forse

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

Questo vi permetterà di roba gruppo da pezzi di età. Ad esempio, se utilizzi 48 per hourchunk, raggrupperai elementi che vanno da 0 a 48 ore fa insieme. Man mano che aggiungi traffico e azioni al tuo sistema, potresti voler ridurre il valore hourchunk.

+0

Questo è un punto interessante sulla vicinanza del timestamp, il metodo con le ore che hai dimostrato funzionerebbe bene e potrebbe anche essere in qualche modo manipolato per utente in base alla frequenza delle attività degli utenti che seguono, il che è una prospettiva interessante. Per quanto riguarda l'UNION, come consiglieresti la realizzazione di questo? Non ho mai lavorato con UNION in precedenza, ma sarebbe stato il mio obiettivo aggregare fondamentalmente in due direzioni diverse (su un "utente ha fatto l'azione X volte" e "Gli utenti X hanno fatto X per l'oggetto X"). –

6

Oltre a Fashiolista abbiamo aperto il nostro approccio alla costruzione di sistemi di alimentazione. https://github.com/tschellenbach/Feedly Attualmente è la più grande libreria open source per risolvere questo problema. (ma scritto in Python)

Lo stesso team che ha creato Feedly offre anche un'API ospitata, che gestisce la complessità per voi. Dai uno sguardo a getstream.io Ci sono client per PHP, Node, Ruby e Python. https://github.com/tbarbugli/stream-php Offre anche il supporto per le aggregazioni definite personalizzate, che si sta cercando.

Inoltre un'occhiata a questo post elevata scalabilità erano spiegare alcune delle decisioni di progettazione coinvolti: http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic-feeds.html

This tutorial vi aiuterà a configurare un sistema come il Feed di Pinterest utilizzando Redis. È abbastanza facile iniziare.

Per saperne di più sul design di alimentazione mi raccomando di leggere alcuni degli articoli che abbiamo basato Feedly on:

6

Abbiamo risolto un problema simile usando l'approccio 'vista materializzata' - stiamo usando una tabella dedicata che viene aggiornata sull'evento insert/update/delete. Tutte le attività dell'utente sono registrate in questa tabella e preparate per la selezione e il rendering semplici.

Il vantaggio è una selezione semplice e rapida, lo svantaggio è un po 'più lento inserimento/aggiornamento/eliminazione poiché la tabella del registro deve essere aggiornata.

Se questo sistema è ben progettato, è una soluzione vincente.

Questo è abbastanza facile da implementare se si utilizza ORM con il post di inserimento/aggiornamento/eliminare gli eventi (come Dottrina)

+0

Ma voi gente avete le definizioni azione/attività in un file separato, giusto? –

+0

Non sono sicuro di aver capito la tua domanda ... –

+0

Forse questo potrebbe aiutare: "{nome1} ha aggiornato il suo profilo.", E lo ha pubblicato dal vivo: "Nikola ha aggiornato il suo profilo". Hai capito? –