Sprawdzanie x kolejnych dni-podane znaczniki czasu w bazie danych

Czy ktoś mógłby mi podpowiedzieć jak można sprawdzać X kolejnych dni w tabeli bazy danych (MySQL) gdzie przechowywane są loginy (ID użytkownika, znacznik czasu)?

Robi to Stackoverflow (np. odznaki typu Enthusiast - jeśli logujesz się przez 30 kolejnych dni lub więcej...). Z jakich funkcji będziesz musiał korzystać lub jaki jest pomysł, jak to zrobić?

Coś w stylu SELECT 1 FROM login_dates WHERE ...?

Author: wildplasser, 2012-07-18

4 answers

Można to osiągnąć za pomocą przesuniętego samo-zewnętrznego połączenia w połączeniu ze zmienną. Zobacz To rozwiązanie:

SELECT IF(COUNT(1) > 0, 1, 0) AS has_consec
FROM
(
    SELECT *
    FROM
    (
        SELECT IF(b.login_date IS NULL, @val:=@val+1, @val) AS consec_set
        FROM tbl a
        CROSS JOIN (SELECT @val:=0) var_init
        LEFT JOIN tbl b ON 
            a.user_id = b.user_id AND
            a.login_date = b.login_date + INTERVAL 1 DAY
        WHERE a.user_id = 1
    ) a
    GROUP BY a.consec_set
    HAVING COUNT(1) >= 30
) a

Zwróci 1 lub 0 w zależności od tego, czy użytkownik zalogował się przez 30 kolejnych dni lub więcej w ANYTIME w przeszłości.

Ciężar tego zapytania jest naprawdę w pierwszym podwyzszeniu. Przyjrzyjmy się bliżej, abyśmy mogli lepiej zrozumieć, jak to działa: {]}

Z poniższymi przykładowymi danymi zestaw:

CREATE TABLE tbl (
  user_id INT,
  login_date DATE
);

INSERT INTO tbl VALUES
(1, '2012-04-01'),  (2, '2012-04-02'),
(1, '2012-04-25'),  (2, '2012-04-03'),
(1, '2012-05-03'),  (2, '2012-04-04'),
(1, '2012-05-04'),  (2, '2012-05-04'),
(1, '2012-05-05'),  (2, '2012-05-06'),
(1, '2012-05-06'),  (2, '2012-05-08'),
(1, '2012-05-07'),  (2, '2012-05-09'),
(1, '2012-05-09'),  (2, '2012-05-11'),
(1, '2012-05-10'),  (2, '2012-05-17'),
(1, '2012-05-11'),  (2, '2012-05-18'),
(1, '2012-05-12'),  (2, '2012-05-19'),
(1, '2012-05-16'),  (2, '2012-05-20'),
(1, '2012-05-19'),  (2, '2012-05-21'),
(1, '2012-05-20'),  (2, '2012-05-22'),
(1, '2012-05-21'),  (2, '2012-05-25'),
(1, '2012-05-22'),  (2, '2012-05-26'),
(1, '2012-05-25'),  (2, '2012-05-27'),
                    (2, '2012-05-28'),
                    (2, '2012-05-29'),
                    (2, '2012-05-30'),
                    (2, '2012-05-31'),
                    (2, '2012-06-01'),
                    (2, '2012-06-02');

To zapytanie:

SELECT a.*, b.*, IF(b.login_date IS NULL, @val:=@val+1, @val) AS consec_set
FROM tbl a
CROSS JOIN (SELECT @val:=0) var_init
LEFT JOIN tbl b ON 
    a.user_id = b.user_id AND
    a.login_date = b.login_date + INTERVAL 1 DAY
WHERE a.user_id = 1

Wyprodukuje:

Przykładowy Wynik

Jak widzisz, to co robimy to przesunięcie połączonej tabeli o +1 dzień. Dla każdego dnia, który nie jest kolejny z poprzednim dniem, wartość NULL jest generowana przez lewe połączenie.

Teraz, kiedy wiemy gdzie są kolejne dni, możemy użyć zmiennej do rozróżnienia każdego zbioru kolejnych dni poprzez wykrycie, czy przesunięta tabela jest wiersze to NULL. Jeśli są NULL, dni nie są kolejne, więc po prostu zwiększ zmienną. Jeśli są NOT NULL, to nie zwiększaj zmiennej:

Wynik Z Zaznaczonymi Grupami

Po zróżnicowaniu każdego zbioru kolejnych dni ze zmienną zwiększającą, jest to po prostu prosta sprawa grupowania przez każdy "zestaw" (zdefiniowany w kolumnie consec_set) i za pomocą HAVING aby odfiltrować dowolny zestaw, który ma mniej niż określone kolejne dni (30 w kolumnie). przykład):

Wynik z grupowaniem według kolumny consec_set

W końcu owijamy że zapytanie i po prostu policz liczbę zestawów, które miały 30 lub więcej kolejnych dni. Jeśli był jeden lub więcej z tych zestawów, to return 1, w przeciwnym razie return 0.


Zobacz sqlfiddle krok po kroku demo

 28
Author: Zane Bien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-18 19:24:52

Możesz dodać X do daty znacznika czasu i chech, jeśli distinct (daty ) w tym zakresie dat wynosi = = X:

Przynajmniej raz dziennie z tych 30 dni:

SELECT distinct 1 
FROM 
   login_dates l1 
inner join
   login_dates l2
      on l1.user = l2.user and 
         l2.timestamp between l1.timestamp and  
                              date_add( l1.timestamp, Interval X day )
where l1.user = some_user
group by 
   DATE(l1.timestamp)
having 
   count( distinct DATE(l1.timestamp) ) = X

(nie mówisz o wymaganiach wydajnościowych ... ;) )

* edytowane * zapytanie tylko dla ostatnich X dni: Wschód raz dziennie z tych 30 dni

SELECT distinct 1 
FROM 
   login_dates l1 
where l1.user = some_user
      and l1.timestamp >  date_add( CURDATE() , Interval -X day )
group by
    l1.user
having 
   count( distinct DATE(l1.timestamp) ) = X
 5
Author: dani herrera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-18 10:53:04

To trudny problem do rozwiązania z samym SQL.

Rdzeń problemu polega na tym, że musisz porównać dynamiczne zestawy wyników ze sobą w jednym zapytaniu. Na przykład, musisz pobrać wszystkie loginy/identyfikatory sesji dla jednej daty, a następnie połączyć je lub połączyć z listą do grupy loginów z date () (którą możesz użyć DATE_ADD do określenia). Można to zrobić dla N liczba kolejnych dat. Jeśli masz jeszcze jakieś wiersze, wtedy te sesje zostały zalogowane kropka.

Załóżmy następującą tabelę:

Sessionid int, Data utworzenia

To zapytanie zwraca wszystkie identyfikatory sesji, które mają wiersze z ostatnich dwóch dni:

select t1.sessionid from logins t1 
  join logins t2 on t1.sessionid=t2.sessionid 
  where t1.created = DATE(date_sub(now(), interval 2 day)) 
    AND t2.created = DATE(date_sub(now(), interval 1 day));

Jak widać, SQL będzie gnarly przez 30 dni. Niech skrypt go wygeneruje. :- D

To dodatkowo zakłada, że codziennie tabela logowania jest aktualizowana o sesję.

Nie wiem, czy to rzeczywiście rozwiązuje twój problem, ale mam nadzieję, że pomogłem w oprawieniu problemu.

Dobrze szczęście.

 1
Author: jjohn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-18 10:49:53

Czy nie byłoby prościej mieć dodatkową kolumnę consecutive_days w tabeli login_dates z domyślną wartością 1. Oznaczałoby to długość kolejnych dat kończących się w tym dniu.

Tworzysz insert po wyzwalaniu na login_dates, gdzie sprawdzasz, czy nie ma wpisu z poprzedniego dnia.

Jeśli nie ma żadnego, To pole będzie miało wartość domyślną 1, co oznacza, że nowa sekwencja zostanie uruchomiona w tym dniu.

Jeśli tutaj jest wpis na poprzedni dzień to Ty zmień wartość days_logged_in z domyślnego 1 na 1 większą od wartości poprzedniego dnia.

Ex:

| date       | consecutive_days |
|------------|------------------|
| 2013-11-13 | 5                |
| 2013-11-14 | 6                |
| 2013-11-16 | 1                |
| 2013-11-17 | 2                |
| 2013-11-18 | 3                |
 0
Author: user3004556,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-18 12:07:03