es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Contar valores recurrentes consecutivos.

Después de un par de horas de búsqueda, ensayo y error, no encuentro información en internet. Tenemos la siguiente estructura de tabla:

Name EventDateTime Mark
Dave 2021-03-24 09:00:00 Presente
Dave 2021-03-24 14:00:00 Ausente
Dave 2021-03-25 09:00:00 Ausente
Dave 2021-03-26 09:00:00 Ausente
Dave 2021-03-27 09:00:00 Presente
Dave 2021-03-27 14:00:00 Ausente
Dave 2021-03-28 09:00:00 Ausente
Dave 2021-03-29 10:00:00 Ausente
Dave 2021-03-30 13:00:00 Ausente
Jane 2021-03-30 13:00:00 Ausente

Básicamente, se registran personas para eventos. Necesitamos generar un informe para ver a quiénes no hemos tenido contactos durante más de x días consecutivos. Consecutivo significa que deben estar de manera consecutiva en los días en que tengan eventos en los datos, no días calendario consecutivos. Además, si hubo un día en que estuvieron presentes en el evento y otros en que estuvieron ausentes, el conteo debe comenzar nuevamente desde el siguiente día en que estuvieron ausentes.

El primer problema que tengo es obtener las fechas distintas donde solo hay ausencias, luego el segundo es obtener el número de días consecutivos de ausencia. Ya he hecho esto último en MySQL con variables, pero tengo problemas para migrar esto a PostgreSQL, donde se realiza el informe.

Un ejemplo de la salida que deseo es:

Nombre EventDateTime Marca ConsecCount
Dave 2021-03-24 09:00:00 Presente 0
Dave 2021-03-24 14:00:00 Ausente 0
Dave 2021-03-25 09:00:00 Ausente 1
Dave 2021-03-26 09:00:00 Ausente 2
Dave 2021-03-27 09:00:00 Presente 0
Dave 2021-03-27 14:00:00 Ausente 0
Dave 2021-03-28 09:00:00 Ausente 1
Dave 2021-03-29 10:00:00 Ausente 2
Dave 2021-03-30 13:00:00 Ausente 3
Jane 2021-03-30 13:00:00 Ausente 0

Esta tabla actualmente tiene 639931 registros y se han generado desde el 1 de octubre y continuará creciendo a esta tasa.

Cualquier ayuda o recomendación sobre cómo empezar sería genial.

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Solución

    Para resolver este problema, debemos realizar dos pasos:

    1. Obtener una lista de fechas distintas donde sólo hay ausencias para cada persona.
    2. Calcular el número de días consecutivos de ausencia para cada persona.

    Paso 1: Obtener una lista de fechas distintas donde sólo hay ausencias para cada persona.

    Para obtener una lista de fechas distintas donde sólo hay ausencias, podemos utilizar la siguiente consulta SQL:

    SELECT Name, EventDateTime::date
    FROM (
        SELECT Name, EventDateTime::date, array_agg(Mark ORDER BY EventDateTime) AS marks
        FROM my_table
        GROUP BY 1, 2
        ) t
    WHERE NOT 'Present' = ANY (marks) AND array_length(marks, 1) > 1;
    

    Esta consulta nos dará una lista de fechas distintas donde cada persona tiene sólo ausencias. Primero estamos agrupando los datos por Name y EventDateTime::date. Luego estamos agregando los valores de Mark en un array usando array_agg(). Luego filtramos cualquier fecha donde hay un Present en el array usando NOT 'Present' = ANY (marks) y donde hay más de 1 valor en el array usando array_length(marks, 1) > 1.

    Paso 2: Calcular el número de días consecutivos de ausencia para cada persona.

    Para calcular el número de días consecutivos de ausencia, podemos utilizar la siguiente consulta SQL:

    WITH cte AS (
        SELECT Name, EventDateTime::date, Mark,
               ROW_NUMBER() OVER (PARTITION BY Name ORDER BY EventDateTime) rn
        FROM my_table
        WHERE Mark = 'Absent'
    ), cte2 AS (
        SELECT Name, date_trunc('day', EventDateTime::timestamp) AS dt, rn,
               ROW_NUMBER() OVER (PARTITION BY Name ORDER BY dt) - rn AS grp
        FROM cte
    ), cte3 AS (
        SELECT Name, MIN(dt) AS start_dt, COUNT(*) AS consec_count
        FROM cte2
        WHERE Mark = 'Absent'
        GROUP BY 1, grp
    ), cte4 AS (
        SELECT Name, EventDateTime::date, Mark,
               count(*) OVER (PARTITION BY Name ORDER BY EventDateTime) - consec_count AS consec_count
        FROM my_table
        LEFT JOIN cte3 USING (Name)
        WHERE Mark = 'Absent'
    )
    SELECT *
    FROM my_table
    LEFT JOIN cte4 USING (Name, EventDateTime, Mark);
    

    Esta consulta calculará el número de días consecutivos de ausencia para cada persona. Primero estamos utilizando una expresión de tabla común (cte) para obtener el Name, EventDateTime y Mark de cada ausencia, junto con un número de fila (rn) ordenado por EventDateTime. Luego estamos utilizando otra expresión de tabla común (cte2) para agrupar las ausencias en grupos consecutivos, en función de la fecha (dt). Estamos utilizando ROW_NUMBER() OVER (PARTITION BY Name ORDER BY dt) - rn para obtener el número de grupo consecutivo. Luego estamos utilizando otra expresión de tabla común (cte3) para obtener la fecha mínima (start_dt) y el recuento de ausencias (consec_count) para cada grupo consecutivo. Luego estamos utilizando otra expresión de tabla común (cte4) para unir la tabla original con la columna consec_count, que se calcula como count(*) OVER (PARTITION BY Name ORDER BY EventDateTime) - consec_count. Estamos restando la consec_count del número de fila para obtener el número de ausencias consecutivas hasta e incluyendo esa fila.

    La salida final será la tabla original con una columna adicional ConsecCount. La columna ConsecCount contendrá el número de ausencias consecutivas para cada fila. Si la fila no es una ausencia, la columna ConsecCount será 0.

    Optimización del rendimiento

    Si la tabla es muy grande, podemos optimizar el rendimiento de estas consultas creando índices en las columnas Name, EventDateTime, y Mark, y particionando la tabla por Name. También podemos utilizar el comando EXPLAIN para analizar el plan de ejecución de la consulta y optimizar la consulta en consecuencia. También podemos considerar utilizar una solución de base de datos distribuida como Amazon Aurora para mejorar el rendimiento y la escalabilidad.

Comments are closed.