sql >> Base de Datos >  >> RDS >> Mysql

Verifique x días consecutivos:marcas de tiempo dadas en la base de datos

Puede lograr esto usando una auto-unión externa desplazada junto con una variable. Vea esta solución:

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

Esto devolverá un 1 o un 0 basado en si un usuario ha iniciado sesión durante 30 días consecutivos o más en ANYTIME en el pasado.

La peor parte de esta consulta está realmente en la primera subselección. Echemos un vistazo más de cerca para que podamos entender mejor cómo funciona esto:

Con el siguiente conjunto de datos de ejemplo:

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

Esta consulta:

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

Producirá:

Como puede ver, lo que estamos haciendo es cambiar la tabla unida por +1 día. Para cada día que no sea consecutivo con el día anterior, un NULL el valor es generado por LEFT JOIN.

Ahora que sabemos donde están los días no consecutivos, podemos usar una variable para diferenciar cada conjunto de días consecutivos al detectar si las filas de la tabla desplazada son o no NULL . Si son NULL , los días no son consecutivos, así que solo incrementa la variable. Si son NOT NULL , entonces no incrementes la variable:

Después de que hayamos diferenciado cada conjunto de días consecutivos con la variable incremental, solo es cuestión de agrupar por cada "conjunto" (como se define en el consec_set columna) y usando HAVING para filtrar cualquier conjunto que tenga menos de los días consecutivos especificados (30 en su ejemplo):

Finalmente, envolvemos ESO consulta y simplemente cuenta el número de conjuntos que tenían 30 o más días consecutivos. Si hubo uno o más de estos conjuntos, devuelva 1 , de lo contrario devuelve 0 .

Vea una demostración paso a paso de SQLFiddle