Die folgende Benutzerverlaufstabelle enthält einen Datensatz für jeden Tag, an dem ein bestimmter Benutzer auf eine Website zugegriffen hat (innerhalb von 24 Stunden UTC). Es hat viele tausend Datensätze, aber nur einen Datensatz pro Tag und Benutzer. Wenn der Benutzer an diesem Tag nicht auf die Website zugegriffen hat, wird kein Datensatz generiert.
ID UserId CreationDate ------ ------ ------------ 750997 12 2009-07-07 18: 42: 20.723 750998 15 2009-07-07 18: 42: 20.927 751000 19 2009-07-07 18: 42: 22.283
Was ich suche, ist eine SQL-Abfrage in dieser Tabelle mit guter Leistung , die mir sagt, welche Benutzer-IDs für (n) aufeinanderfolgende Tage auf die Website zugegriffen haben, ohne einen Tag zu verpassen.
Mit anderen Worten, wie viele Benutzer haben (n) Datensätze in dieser Tabelle mit aufeinander folgenden (Tag vor oder Tag nach) Daten ? Wenn ein Tag in der Sequenz fehlt, ist die Sequenz unterbrochen und sollte bei 1 erneut gestartet werden. Wir suchen Benutzer, die hier eine kontinuierliche Anzahl von Tagen ohne Lücken erreicht haben.
Jede Ähnlichkeit zwischen dieser Abfrage und einem bestimmten Stapelüberlauf-Abzeichen ist natürlich rein zufällig. :)
quelle
Antworten:
Die Antwort lautet offensichtlich:
BEARBEITEN:
Okay, hier ist meine ernsthafte Antwort:
BEARBEITEN:
[Jeff Atwood] Dies ist eine großartige schnelle Lösung und verdient es, akzeptiert zu werden, aber Rob Farleys Lösung ist auch ausgezeichnet und wahrscheinlich sogar noch schneller (!). Bitte probieren Sie es auch aus!
quelle
ON uh2.CreationDate >= uh1.CreationDate AND uh2.CreationDate < DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate) + @days, 0)
, was "Noch nicht am 31. Tag später" bedeutet. Dies bedeutet auch, dass Sie die Berechnung von @seconds überspringen können.Wie wäre es (und stellen Sie bitte sicher, dass die vorherige Aussage mit einem Semikolon endet):
Die Idee ist, dass, wenn wir eine Liste der Tage (als Zahl) und eine Zeilennummer haben, verpasste Tage den Versatz zwischen diesen beiden Listen etwas größer machen. Wir suchen also nach einem Bereich mit einem konsistenten Versatz.
Sie können am Ende "ORDER BY NumConsecutiveDays DESC" verwenden oder "HAVING count (*)> 14" für einen Schwellenwert sagen ...
Ich habe das allerdings nicht getestet - schreibe es einfach von oben auf meinen Kopf. Funktioniert hoffentlich in SQL2005 und weiter.
... und würde sehr durch einen Index für Tabellennamen (UserID, CreationDate) unterstützt werden
Bearbeitet: Es stellt sich heraus, dass Offset ein reserviertes Wort ist, daher habe ich stattdessen TheOffset verwendet.
Bearbeitet: Der Vorschlag, COUNT (*) zu verwenden, ist sehr gültig - ich hätte das zuerst tun sollen, aber nicht wirklich nachgedacht. Zuvor wurde stattdessen dateiff (Tag, min (CreationDate), max (CreationDate)) verwendet.
rauben
quelle
Wenn Sie das Tabellenschema ändern können, würde ich vorschlagen
LongestStreak
, der Tabelle eine Spalte hinzuzufügen , die Sie auf die Anzahl der aufeinander folgenden Tage festlegen, die bis zum endenCreationDate
. Es ist einfach, die Tabelle zum Zeitpunkt der Anmeldung zu aktualisieren (ähnlich wie Sie es bereits tun: Wenn am aktuellen Tag keine Zeilen vorhanden sind, überprüfen Sie, ob für den vorherigen Tag eine Zeile vorhanden ist. Wenn true, erhöhen Sie dieLongestStreak
in neue Zeile, sonst setzen Sie es auf 1.)Die Abfrage wird nach dem Hinzufügen dieser Spalte offensichtlich:
quelle
Einige gut ausdrucksstarke SQL in Anlehnung an:
Angenommen, Sie haben eine benutzerdefinierte Aggregatfunktion in der Art von (Vorsicht, dies ist fehlerhaft):
quelle
Scheint, als könnten Sie die Tatsache ausnutzen, dass für eine Kontinuität über n Tage n Zeilen erforderlich sind.
Also so etwas wie:
quelle
Dies mit einer einzigen SQL-Abfrage zu tun, scheint mir zu kompliziert. Lassen Sie mich diese Antwort in zwei Teile aufteilen.
Führen Sie einen täglichen Cron-Job aus, der für jeden Benutzer prüft, ob er sich heute angemeldet hat, und dann einen Zähler erhöht, wenn er ihn hat, oder ihn auf 0 setzt, wenn er dies nicht hat.
- Exportieren Sie diese Tabelle auf einen Server, auf dem Ihre Website nicht ausgeführt wird und der für eine Weile nicht benötigt wird. ;)
- Sortiere es nach Benutzer, dann Datum.
- nacheinander durchgehen, einen Zähler behalten ...
quelle
Wenn dies für Sie so wichtig ist, geben Sie dieses Ereignis an und erstellen Sie eine Tabelle, um diese Informationen zu erhalten. Keine Notwendigkeit, die Maschine mit all diesen verrückten Fragen zu töten.
quelle
Sie können einen rekursiven CTE (SQL Server 2005+) verwenden:
quelle
Joe Celko hat ein vollständiges Kapitel dazu in SQL für Smarties (Runs and Sequences genannt). Ich habe das Buch nicht zu Hause, also wenn ich zur Arbeit komme ... werde ich das tatsächlich beantworten. (Angenommen, die Verlaufstabelle heißt dbo.UserHistory und die Anzahl der Tage ist @Days.)
Ein weiterer Hinweis stammt aus dem Blog von SQL Team über Läufe
Die andere Idee, die ich hatte, aber keinen SQL-Server zur Hand habe, ist die Verwendung eines CTE mit einer partitionierten ROW_NUMBER wie folgt:
Das Obige ist wahrscheinlich viel härter als es sein muss, aber es bleibt als Gehirnkitzel übrig, wenn Sie eine andere Definition von "einem Lauf" haben als nur Daten.
quelle
Einige SQL Server 2012-Optionen (unter der Annahme von N = 100 unten).
Mit meinen Beispieldaten hat sich Folgendes jedoch als effizienter erwiesen
Beide stützen sich auf die in der Frage angegebene Einschränkung, dass es höchstens einen Datensatz pro Tag und Benutzer gibt.
quelle
Etwas wie das?
quelle
Ich habe eine einfache mathematische Eigenschaft verwendet, um zu identifizieren, wer nacheinander auf die Site zugegriffen hat. Diese Eigenschaft besagt, dass die Tagesdifferenz zwischen dem ersten und dem letzten Zugriff gleich der Anzahl der Datensätze in Ihrem Zugriffstabellenprotokoll sein sollte.
Hier sind SQL-Skripte, die ich in Oracle DB getestet habe (es sollte auch in anderen DBs funktionieren):
Tabellenvorbereitungsskript:
quelle
Die Aussage
cast(convert(char(11), @startdate, 113) as datetime)
entfernt den Zeitteil des Datums, sodass wir um Mitternacht beginnen.Ich würde auch davon ausgehen, dass die
creationdate
unduserid
Spalten indiziert sind.Ich habe gerade festgestellt, dass dies nicht alle Benutzer und ihre gesamten aufeinander folgenden Tage anzeigt. Sie erfahren jedoch, welche Benutzer ab einem Datum Ihrer Wahl eine bestimmte Anzahl von Tagen besucht haben.
Überarbeitete Lösung:
Ich habe dies überprüft und es wird nach allen Benutzern und allen Daten abgefragt. Es basiert auf Spencers 1. (Scherz?) Lösung , aber meine funktioniert.
Update: Die Datumsverarbeitung in der zweiten Lösung wurde verbessert.
quelle
Dies sollte tun, was Sie wollen, aber ich habe nicht genügend Daten, um die Effizienz zu testen. Das verschlungene CONVERT / FLOOR-Zeug besteht darin, den Zeitanteil aus dem Datum / Uhrzeit-Feld zu entfernen. Wenn Sie SQL Server 2008 verwenden, können Sie CAST (x.CreationDate AS DATE) verwenden.
Erstellungsskript
quelle
Spencer hätte es fast geschafft, aber dies sollte der Arbeitscode sein:
quelle
Aus dem Kopf, MySQLish:
Ungetestet und braucht mit ziemlicher Sicherheit eine Konvertierung für MSSQL, aber ich denke, das gibt einige Ideen.
quelle
Wie wäre es mit einer Tally-Tabelle? Es folgt einem algorithmischeren Ansatz, und der Ausführungsplan ist ein Kinderspiel. Füllen Sie die TallyTable mit Zahlen von 1 bis 'MaxDaysBehind', mit denen Sie die Tabelle scannen möchten (dh 90 wird 3 Monate zurückbleiben usw.).
quelle
Bills Abfrage ein wenig optimieren. Möglicherweise müssen Sie das Datum vor der Gruppierung abschneiden, um nur eine Anmeldung pro Tag zu zählen ...
BEARBEITET, um DATEADD (dd, DATEDIFF (dd, 0, CreationDate), 0) anstelle von convert (char (10), CreationDate, 101) zu verwenden.
@IDisposable Ich wollte Datepart früher verwenden, war aber zu faul, um die Syntax nachzuschlagen, also dachte ich, ich würde stattdessen convert verwenden. Ich weiß nicht, dass es einen signifikanten Einfluss hatte. Danke! jetzt weiß ich.
quelle
Angenommen, ein Schema lautet wie folgt:
Dadurch werden zusammenhängende Bereiche aus einer Datumssequenz mit Lücken extrahiert.
quelle