Mysql
 sql >> Database >  >> RDS >> Mysql

Verifica x giorni consecutivi - data la data e l'ora nel database

Puoi farlo usando un self-outer-join spostato insieme a una variabile. Vedi questa soluzione:

SELECT IF(COUNT(1) > 0, 1, 0) AS has_consec
FROM
(
    SELECT *
    FROM
    (
        SELECT IF(b.login_date IS NULL, @val:[email protected]+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

Questo restituirà un 1 o un 0 in base al fatto che un utente abbia effettuato l'accesso per 30 giorni consecutivi o più a IN QUALSIASI MOMENTO nel passato.

Il peso di questa query è davvero nella prima sottoselezione. Diamo un'occhiata più da vicino in modo da poter capire meglio come funziona:

Con il seguente set di dati di esempio:

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');

Questa domanda:

SELECT a.*, b.*, IF(b.login_date IS NULL, @val:[email protected]+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

Produrrà:

Come puoi vedere, quello che stiamo facendo è cambiare il tavolo unito di +1 giorno. Per ogni giorno non consecutivo al giorno precedente, un NULL il valore è generato dal LEFT JOIN.

Ora che sappiamo dove sono i giorni non consecutivi, possiamo usare una variabile per differenziare ogni insieme di giorni consecutivi rilevando se le righe della tabella spostata sono NULL . Se sono NULL , i giorni non sono consecutivi, quindi basta aumentare la variabile. Se sono NOT NULL , quindi non incrementare la variabile:

Dopo aver differenziato ogni serie di giorni consecutivi con la variabile incrementale, è solo questione di raggruppare per ogni "serie" (come definito in consec_set colonna) e utilizzando HAVING per filtrare qualsiasi set che ha meno di giorni consecutivi specificati (30 nel tuo esempio):

Quindi, infine, avvolgiamo QUESTO interrogare e contare semplicemente il numero di set che hanno avuto 30 o più giorni consecutivi. Se c'erano uno o più di questi set, restituisci 1 , altrimenti restituisci 0 .

Guarda una demo passo passo di SQLFiddle