2011-01-04 10 views
4

Ich habe eine "Nachricht" -Tabelle, wo Benutzer senden und empfangen von Nachrichten, ziemlich einfach. Was ich tun möchte, ist: abrufen DISTINCT sender_ids WHERE receiver_id ist X, und haben es auf eine Weise sortiert, wo Benutzer von wem Empfänger X ungelesene Nachrichten zuerst angezeigt und Benutzer von dem Empfänger X gelesen hat Nachrichten erscheinen nach und alles nach sortiert created_at DESC.Beratung benötigt mit Mysql Abfrage, Sortierung, Gruppierung und Leistung

Irgendwelche Ideen, wie ich das erreichen kann? Hinweis: Leistung ist auch ein Problem.

Dies ist die Abfrage, die ich verwendet habe, aber es scheint, dass die Sortierung nicht wirklich richtig gemacht wird, vielleicht DISTINCT verschraubt Dinge? Ich erwarte Ergebnis 6, 5, 4, 2, 3 - aber bin immer 6, 5, 4, 3, 2

SELECT DISTINCT sender_id 
FROM message m 
WHERE receiver_id = 1 
ORDER BY read_at, created_at DESC 

Hier ist die Tabelle mit Beispieldaten:

CREATE TABLE `message` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `sender_id` bigint(20) NOT NULL, 
    `receiver_id` bigint(20) NOT NULL, 
    `message` text, 
    `read_at` datetime DEFAULT NULL, 
    `created_at` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `sender` (`sender_id`), 
    KEY `receiver` (`receiver_id`), 
    KEY `dates` (`receiver_id`,`read_at`,`created_at`) 
) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=latin1; 


INSERT INTO `message` (id, sender_id, receiver_id, message, read_at, created_at) 
VALUES 
    (1,2,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:01'), 
    (2,1,2,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:02'), 
    (3,2,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:03'), 
    (4,3,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:04'), 
    (5,3,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:05'), 
    (6,1,4,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:06'), 
    (7,4,1,NULL,NULL,'2011-01-01 01:01:07'), 
    (8,5,1,NULL,NULL,'2011-01-01 01:01:08'), 
    (9,5,1,NULL,NULL,'2011-01-01 01:01:09'), 
    (10,1,6,NULL,NULL,'2011-01-01 01:01:10'), 
    (11,6,1,NULL,NULL,'2011-01-01 01:01:11'); 
+0

Die knifflige Situation ist, wenn sowohl ungelesene als auch gelesene Nachrichten von demselben Benutzer vorliegen und sichergestellt wird, dass der Absender in die erste Gruppe und nicht in die zweite Gruppe gestellt wird. – outis

+0

Wenn mehrere Nachrichten von einem Absender vorhanden sind, welche "created_at" möchten Sie zum Sortieren verwenden, die neueste oder die älteste? – outis

+0

@outis, ich möchte die neueste für created_at. Ihre Lösung in der Antwort unten funktioniert, ich frage mich, ob es etwas gibt, was ich tun kann, um die Leistung zu verbessern, obwohl ich befürchte, dass es viele Nachrichten zwischen vielen verschiedenen Sender/Empfänger geben wird. – BugBusterX

Antwort

0

Die folgenden gibt das Ergebnis auf den Beispieldaten gewünscht:

SELECT sender_id 
    FROM message AS m 
    WHERE receiver_id=? 
    GROUP BY sender_id 
    ORDER BY COUNT(*)=COUNT(read_at), MAX(created_at) DESC; 

Wenn Sie die älteste Nachricht beim Sortieren von created_at, MAX ändern MIN verwenden möchten.

COUNT(read_at) ignoriert Nullen, während COUNT(*) nicht, so dass die beiden ungleich sein werden, wenn es ungelesene Nachrichten gibt. Wenn es nicht zu viele Nachrichten an einen Geberempfänger gibt, sollte es ziemlich schnell funktionieren (der Index auf receiver_id wird helfen). Profilieren Sie die Abfrage, bevor Sie entscheiden, ob weitere Optimierungen erforderlich sind.

Mit ein paar Feinabstimmungen kann der Gesamtausdruck des Scrum Meisters zum Funktionieren gebracht werden. Versuchen Sie MIN(IF(read_at IS NULL, 0, 1)) als Ersatz für COUNT(*)=COUNT(read_at). Ich denke nicht, dass es die Ausführungszeit verbessern wird, aber es gibt zumindest eine kleine Chance (wie bei der Optimierung, hängt es von den Interna von MySQL ab).

Ergebnis EXPLAIN auf der Testtabelle:

+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra          | 
+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 
| 1 | SIMPLE  | m  | ref | receiver,dates | receiver | 8  | const | 7 | Using where; Using temporary; Using filesort | 
+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 

Erste der Aggregatfunktionen auf die message Zeilen angewandt befreien:

SELECT sender_id 
    FROM ((SELECT sender_id, 0 AS all_read, MAX(created_at) AS recent 
      FROM message AS m 
      WHERE receiver_id=:receiver AND read_at IS NULL 
      GROUP BY sender_id) 
     UNION 
     (SELECT sender_id, 1 AS all_read, MAX(created_at) AS recent 
      FROM message AS m 
      WHERE receiver_id=:receiver AND read_at IS NOT NULL 
      GROUP BY sender_id) 
     ) AS t 
    GROUP BY sender_id 
    ORDER BY MIN(all_read), recent DESC; 

Blick Boden zu verlieren. Diese Abfrage arbeitet mit konstanten Werten (die separaten Abfragen ermöglichen dies) für die Spalte, die angibt, ob eine der Nachrichten des Absenders ungelesen ist oder nicht, und nicht die Aggregatausdrücke. Hier ist die output of EXPLAIN für diese Abfrage:

+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra          | 
+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL   | NULL | NULL | NULL | 5 | Using temporary; Using filesort    | 
| 2 | DERIVED  | m   | ref | receiver,dates | dates | 17  |  | 4 | Using where; Using temporary; Using filesort | 
| 3 | UNION  | m   | range | receiver,dates | dates | 17  | NULL | 3 | Using where; Using temporary; Using filesort | 
|NULL| UNION RESULT | <union2,3> | ALL | NULL   | NULL | NULL | NULL | NULL |            | 
+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+
+0

Diese Abfrage scheint genau das zu tun, was ich brauche, ich frage mich nur, ob es eine Möglichkeit gibt, das Gleiche effektiver zu tun, da zwischen den Benutzern viele Nachrichten hin- und hergeschickt werden. Gibt es eine alternative Methode, die ich untersuchen sollte? Übrigens, was wäre ein Faktor bei langsamer Leistung: viele Absender oder viele Nachrichten pro Absender? oder beides? – BugBusterX

+0

@BugBusterX: Ein bisschen von beidem. Bei den früheren (vielen Absendern) kann man nicht viel machen. Viele Absender haben den größten Einfluss auf die Sortierung selbst (da dies mehr Zeilen in der Ergebnistabelle zum Sortieren bedeutet), was auch für andere Abfragen gilt. Viele Nachrichten pro Absender beeinflussen die Berechnung der aggregierten Statistiken, die bei der Sortierung verwendet werden (das Verschieben in eine Ergebnisspalte kann oder kann nicht hilfreich sein, je nachdem, wie MySQL sie intern behandelt). – outis

+0

WOW, vielen Dank für Ihre Hilfe!Ich schätze wirklich die ganze Zeit, die du damit verbracht hast, mir zu helfen! Ich werde die zweite Version der Abfrage testen, sobald ich mehr Testdaten geladen habe, um zu sehen, welche schneller ist. Nochmals, vielen Dank für Ihre Mühe, Sie sind fantastisch! – BugBusterX

1

Wie wäre es ein GROUP BY:

SELECT sender_id 
FROM message m 
WHERE receiver_id = 1 
GROUP BY sender_id 
ORDER BY MAX(IFNULL(read_at,'9999-01-01')) DESC 
+0

Es sortiert nicht nach ASC (ich habe es hinzugefügt) und ich bin mir nicht sicher, ob dies die beste Leistung wäre. – BugBusterX

+0

Noch +1 für Gruppierungs- und Aggregatfunktionen. – outis

0

Zuerst ein kleiner Tisch Optimalisierung in der Art, wie ich es tun sollte:

create table messages 
(
    message_id bigint unsigned not null auto_increment primary key, 
    sender_id begint unsigned not null, 
    receiver_id bigint unsigned not null, 
    read_at datetime default null, 
    created_at datetime 
) engine=innodb; 

create table message_body 
(
    message_id bigint unsigned not null, 
    message varchar(32000) not null 
) engine=innodb; 

ich ein varchar verwenden statt einem Text, denn wenn Sie eine kleine Nachricht in einem Textfeld haben, werden Sie 2 Bytes. Und eine Nachricht wird manchmal weniger als 255 Zeichen haben, so dass Sie nur 1 Byte statt 2 speichern. Uhr here.

So gibt es nicht so viel Gewicht für eine Zeile laden, wenn Ihre Nachrichten nicht in der gleichen Tabelle sind. und wenn Sie eine Menge Daten loswerden wollen, wird es sehr nützlich sein!

Meine Suche u fragen Sie nach würde wie folgt aussehen:

select distinct(sender_id) 
from messages 
where receiver_id = x 
group by sender_id 
order by read_at desc 
0

Ich verstehe nicht wirklich der Teil „alles von created_at ab sortiert ist“.

Wenn die ungelesenen Nachrichten zuerst angezeigt werden sollen, können Sie "alles" nicht nach created_at sortieren.

Aber wenn Sie zum ersten Liste alle ungelesenen Nachrichten (von created_at sortiert) bedeuten, dann listen Sie alle gelesenen Nachrichten (wieder durch created_at sortiert), dann das Folgende tun:

 
SELECT * 
FROM message m 
WHERE receiver_id = 1 
ORDER BY 
    CASE 
     WHEN read_at IS NULL THEN 0 
     ELSE 1 
    END ASC, 
    created_at DESC; 

Dies erzeugt eine etwas andere Ordnung, als Sie erwarten, aber mit Blick auf die Beispieldaten denke ich, dass es korrekt sein sollte.

+0

Ja, ich wollte sagen "zuerst alle ungelesenen Nachrichten auflisten (sortiert nach Erstellter_At), dann alle gelesenen Nachrichten auflisten (wieder nach Erstellter_At sortiert)" Vorschlag von @outis funktioniert, dein Wille zu, wenn ich MAX (created_at) und Gruppe nach Absender_ID hinzufüge , aber ich mache mir Sorgen um die Leistung, da es zwischen vielen Benutzern viele Nachrichten gibt. – BugBusterX

+0

Ah, tut mir leid. Ich habe die Anforderung übersehen, eine Gruppierung nach Absender vorzunehmen –

Verwandte Themen