2017-02-28 4 views
1

Got 2 Tische - Kontakte und Nachrichten:MySQL - Anzeige/verknüpften Datensätze zählen, wenn "normale" Join ist unmöglich

contact_id | contact_email 
1   | [email protected] 
2   | [email protected] 
3   | [email protected] 

message_id | message_recipients 
1   | 1,2,3 
2   | 3 

message_recipients Feld enthält ID (s) des Kontakts (n) Nachricht zugewiesen wurde. Jeder Nachricht können eine oder mehrere IDs zugewiesen werden, sodass sie durch das Symbol , getrennt sind.

Ich muss alle Kontakte anzeigen, und Anzahl der Nachrichten werden jedem Kontakt zugewiesen. Da message_recipients Feld mehrere IDs enthalten kann, kann ich keine Abfrage wie SELECT * FROM contacts, messages WHERE contacts.contact_id=messages.message_recipients ausführen, da es nicht ordnungsgemäß funktioniert.

Wenn ich SELECT * FROM contacts FULL JOIN messages ausführen, gibt es viele doppelte Zeilen aus contacts Tabelle. Sicher, ich kann SELECT * FROM contacts FULL JOIN messages GROUP BY contact_id ausführen, aber dieser gibt nur die erste Nachricht von messages Tabelle zurück.

Ich weiß, dass ich wahrscheinlich message_recipients Feld aus jeder Zeile in Array explodieren und Code wie if (in_array($contact_id, $message_recipients_array)) {$total++;} oder ähnliches verwenden muss, um zu zählen, wie viele Nachrichten jedem Kontakt zugewiesen wurde. Jetzt geht es mir hauptsächlich darum, wie ich alles benötige, indem ich so einfach wie möglich schreibe.

+1

Fixieren Sie Ihre Tabellenstruktur. ** Speichern Sie nicht mehrere Werte in einer Zelle **. Siehe [** Normalisierung **] (https://en.wikipedia.org/wiki/Datenbank_normierung) – GurV

+0

Kommentare wie sollte es aussehen? Sicher, ich kann immer eine ID im Feld message_recipients speichern, aber in diesem Fall wird die Nachrichtentabelle mit Hunderten von doppelten Datensätzen gefüllt (wobei nur das ID-Feld unterschiedlich ist, während Felder mit Nachrichtentext und anderen Details identisch sind). Ich denke nicht, dass es eine gute Übung ist. –

Antwort

1

Fixieren Sie Ihre Tabellenstruktur. Speichern Sie nicht mehrere Werte in einer Zelle. Siehe Normalization

Vorerst Sie FIND_IN_SET verwenden können:

select c.contact_id, 
    c.contact_email, 
    count(*) no_of_messages 
from messages m 
join contacts c on find_in_set(c.contact_id, m.message_recipients) > 0 
group by c.contact_id, 
    c.contact_email 

Aber dies wird langsam, da es keinen Index für die contact_id oder message_recipients verwenden kann.

Um die Probleme tatsächlich zu beheben, nehmen Sie die Empfänger-ID nicht in die Nachrichtentabelle auf.

Sie sollten einen einzelnen Empfänger in einer Zeile in einer separaten Mapping-Tabelle mit vielen bis vielen Beziehungen mit (vielleicht) der folgenden Struktur gespeichert haben.

messages_recipients (
    id int PK, 
    message_id int FK referring message(message_id), 
    message_recipient_id int FK referring contacts(contact_id) 
) 

Dann alles, was Sie tun musste, war:

select c.contact_id, 
    c.contact_email, 
    count(*) no_of_messages 
from messages_recipients m 
join contacts c on c.contact_id = m.message_recipient_id 
group by c.contact_id, 
    c.contact_email 

Diese Abfrage Sargable und wird schneller sein.

+0

Meinst du, dass Tausende von doppelten Einträge in Nachrichten Tabelle noch besser ist? –

+0

Danke. Ich glaube jedoch, dass es in meinem Szenario nicht funktionieren wird, weil das spätere Bearbeiten von Nachrichten Kopfschmerzen bereiten kann. Jetzt, wenn ich eine Nachricht bearbeite, wird sie für alle zugewiesenen Empfänger zur gleichen Zeit aktualisiert. Wenn also eine Nachricht 100 Empfänger hat, bearbeite ich die Nachricht nur einmal. Wenn ich 100 separate Datensätze in der Nachrichtentabelle habe, muss ich 100 Nachrichten bearbeiten. Sicher genug, ich könnte es automatisch tun, indem ich einen Hash-Wert für jede erstellte Nachricht zuweist (also wenn eine Nachricht bearbeitet wird, wird sie automatisch andere Nachrichten mit demselben Hash verarbeiten), aber ich weiß einfach nicht ... –

+0

Wenn Sie haben Nachrichtendetails usw. halten die Nachrichtentabelle getrennt. Geben Sie keine Empfänger-ID in diese Tabelle ein. Erstellen Sie eine separate Tabelle mit Zuordnung zwischen message_id und recipient_id. Ich habe es auch in meiner Antwort aktualisiert. Bitte überprüfen Sie – GurV

0

Korrigieren Sie Ihre Datenstruktur! Das Speichern von IDs in Strings ist eine wirklich schlechte Idee. Warum?

  • Die Nummern sollten als Nummern nicht Zeichenfolgen gespeichert werden.
  • SQL bietet keine sehr guten String-Funktionen.
  • Fremdschlüsseleinschränkungen sollten korrekt ausgedrückt werden.
  • Der Abfrageoptimierer kann keine Indizes oder Partitionen verwenden.
  • SQL hat eine großartige Methode zum Speichern von Listen: es heißt eine "Tabelle".
  • Manchmal sind wir fest mit anderen Leute wirklich, wirklich schlechte Design-Entscheidungen. MySQL bietet eine Methode, um das zu tun, was Sie wollen, find_in_set(). Dies ist ein Hack, um die Unzulänglichkeiten eines fehlerhaften Datenlayouts zu umgehen:

    select . . . 
    from contacts c join 
        messages m 
        on find_in_set(c.contact_id, m.message_recipients) > 0 
    
    +0

    Danke für die Eingabe. Ein Beispiel dafür, wie eine richtige Struktur aussehen sollte? Denken Sie daran, dass eine Nachricht HUNDERTE von Kontakten zugewiesen haben kann. Wenn ich also für jeden Kontakt in der Nachrichtentabelle einen separaten Datensatz erstelle, wird er mit tausenden identischen Datensätzen überflutet (wobei nur die ID unterschiedlich ist). –