2012-04-04 8 views
5

I mit der Struktur eine MySQL Tabelle haben:für eine maximale Länge von aufeinanderfolgenden Tagen überprüft, die spezifische Bedingung erfüllen

beverages_log (id, users_id, beverages_id, Zeitstempel)

Ich versuche, die maximale Strähne zu berechnen an aufeinanderfolgenden Tagen, an denen ein Benutzer (mit der ID 1) mindestens fünfmal täglich ein Getränk (mit der ID 1) anmeldet. Ich bin mir ziemlich sicher, dass diese Ansichten getan werden kann, wie folgt verwendet:

CREATE or REPLACE VIEW daycounts AS 
SELECT count(*) AS n, DATE(timestamp) AS d FROM beverages_log 
WHERE users_id = '1' AND beverages_id = 1 GROUP BY d; 

CREATE or REPLACE VIEW t AS SELECT * FROM daycounts WHERE n >= 5; 

SELECT MAX(streak) AS current FROM (SELECT DATEDIFF(MIN(c.d), a.d)+1 AS streak 
FROM t AS a LEFT JOIN t AS b ON a.d = ADDDATE(b.d,1) 
LEFT JOIN t AS c ON a.d <= c.d 
LEFT JOIN t AS d ON c.d = ADDDATE(d.d,-1) 
WHERE b.d IS NULL AND c.d IS NOT NULL AND d.d IS NULL GROUP BY a.d) allstreaks; 

jedoch wiederholt Erstellen von Ansichten für verschiedene Benutzer jedes Mal, wenn ich diese Prüfung laufe ziemlich ineffizient scheint. Gibt es eine Möglichkeit in MySQL, diese Berechnung in einer einzigen Abfrage durchzuführen, ohne Ansichten zu erstellen oder die gleichen Unterabfragen mehrmals aufzurufen?

Antwort

6

Diese Lösung recht gut, so lange zu führen scheint, als es ein zusammengesetzter Index für users_id und beverages_id -

SELECT * 
FROM (
    SELECT t.*, IF(@prev + INTERVAL 1 DAY = t.d, @c := @c + 1, @c := 1) AS streak, @prev := t.d 
    FROM (
     SELECT DATE(timestamp) AS d, COUNT(*) AS n 
     FROM beverages_log 
     WHERE users_id = 1 
     AND beverages_id = 1 
     GROUP BY DATE(timestamp) 
     HAVING COUNT(*) >= 5 
    ) AS t 
    INNER JOIN (SELECT @prev := NULL, @c := 1) AS vars 
) AS t 
ORDER BY streak DESC LIMIT 1; 
+0

Ja, das funktioniert gut - danke! Ich bin mir nicht sicher, ob ich den "INNER JOIN (SELECT @prev: = NULL, @c: = 1) AS vars" vollständig verstehe - sollen die Variablen nur gelöscht werden? –

+0

Es ist nur sicherzustellen, dass sie keine Werte aus einer vorherigen Ausführung der Abfrage haben. – nnichols

+0

Gefallen. Noch eine Frage - wie kommt es, dass @prev sich tatsächlich auf das Datum für die vorherige Zeile bezieht? Es scheint, dass, wenn es als "@prev: = t.d" definiert ist, es nur das Datum für die aktuelle Zeile angibt. –

0

Warum nicht Benutzer-ID in die Tageszählung aufnehmen und nach Benutzer-ID und Datum gruppieren.

Auch Benutzer-ID in Sicht t enthalten.

Dann, wenn Sie gegen queering sind, fügen Sie die user_id zur where-Klausel hinzu.

Dann müssen Sie Ihre Ansichten nicht für jeden einzelnen Benutzer neu erstellen, den Sie sich nur merken müssen, um ihn in Ihre where-Klausel aufzunehmen.

0

Das ist ein wenig schwierig. Ich würde mit Blick Startereignisse für Tag zusammenfassen:

CREATE VIEW BView AS 
    SELECT UserID, BevID, CAST(EventDateTime AS DATE) AS EventDate, COUNT(*) AS NumEvents 
    FROM beverages_log 
    GROUP BY UserID, BevID, CAST(EventDateTime AS DATE) 

Ich würde dann eine Daten verwenden Tabelle (nur ein Tisch mit einer Zeile pro Tag, sehr handlich zu haben), alle möglichen Datumsbereiche zu prüfen und Wirf jede mit einer Lücke raus. Dies wird wahrscheinlich langsam wie die Hölle, aber es ist ein Anfang:

SELECT 
    UserID, BevID, MAX(StreakLength) AS StreakLength 
FROM 
    (
    SELECT 
     B1.UserID, B1.BevID, B1.EventDate AS StreakStart, DATEDIFF(DD, StartDate.Date, EndDate.Date) AS StreakLength 
    FROM 
     BView AS B1 
     INNER JOIN Dates AS StartDate ON B1.EventDate = StartDate.Date 
     INNER JOIN Dates AS EndDate ON EndDate.Date > StartDate.Date 
    WHERE 
      B1.NumEvents >= 5 
     -- Exclude this potential streak if there's a day with no activity 
     AND NOT EXISTS (SELECT * FROM Dates AS MissedDay WHERE MissedDay.Date > StartDate.Date AND MissedDay.Date <= EndDate.Date AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND MissedDay.Date = B2.EventDate)) 
     -- Exclude this potential streak if there's a day with less than five events 
     AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND B2.EventDate > StartDate.Date AND B2.EventDate <= EndDate.Date AND B2.NumEvents < 5) 
    ) AS X 
GROUP BY 
    UserID, BevID 
+0

Wie geschrieben, diese zählt 04/02 bis 04/04 als zweitägige Folge, weil der Unterschied der Daten zwei ist. Wenn Sie dies als drei Tage betrachten, fügen Sie einfach einen hinzu. Eigentlich sollte man mit 'COUNT (*) 'anstelle von' DATEDIFF() 'auch dieses Ergebnis angeben. –

Verwandte Themen