sql >> Base de Datos >  >> RDS >> Oracle

Oracle SQL:seleccione usuarios entre dos fechas por mes

Esta consulta muestra el recuento de usuarios activos a partir de fin de mes.

Cómo funciona:

  1. Convierta cada fila de entrada (con StartDate y EndDate valor) en dos filas que representan un punto en el tiempo cuando se incrementó el recuento de usuarios activos (en StartDate ) y decrementado (en EndDate ). Necesitamos convertir NULL a un valor de fecha lejano porque NULL los valores se ordenan antes en lugar de después de no NULL valores:

    Esto hace que sus datos se vean así:

    OnThisDate   Change
    2018-01-01        1
    2019-01-01       -1
    2018-01-01        1
    9999-12-31       -1
    2019-01-01        1
    2019-06-01       -1
    2017-01-01        1
    2019-03-01       -1
    
  2. Entonces simplemente SUM OVER el Change valores (después de ordenar) para obtener el recuento de usuarios activos a partir de esa fecha específica:

    Primero, ordena por OnThisDate :

    OnThisDate   Change
    2017-01-01        1
    2018-01-01        1
    2018-01-01        1
    2019-01-01        1
    2019-01-01       -1
    2019-03-01       -1
    2019-06-01       -1
    9999-12-31       -1
    

    Entonces SUM OVER :

    OnThisDate   ActiveCount
    2017-01-01             1
    2018-01-01             2
    2018-01-01             3
    2019-01-01             4
    2019-01-01             3
    2019-03-01             2
    2019-06-01             1
    9999-12-31             0
    
  3. Luego PARTITION (¡no agrupe!) las filas por mes y ordénelas por su fecha para que podamos identificar el último ActiveCount fila para ese mes (esto realmente sucede en el WHERE de la consulta más externa, usando ROW_NUMBER() y COUNT() para cada mes PARTITION ):

    OnThisDate   ActiveCount    IsLastInMonth
    2017-01-01             1                1
    2018-01-01             2                0
    2018-01-01             3                1
    2019-01-01             4                0
    2019-01-01             3                1
    2019-03-01             2                1
    2019-06-01             1                1
    9999-12-31             0                1
    
  4. Luego filtre en eso donde IsLastInMonth = 1 (en realidad, donde ROW_COUNT() = COUNT(*) dentro de cada PARTITION ) para darnos los datos finales de salida:

    At-end-of-month     Active-count
    2017-01                        1
    2018-01                        3
    2019-01                        3
    2019-03                        2
    2019-06                        1
    9999-12                        0
    

Esto da como resultado "brechas" en el conjunto de resultados porque At-end-of-month columna solo muestra filas donde el Active-count el valor realmente cambió en lugar de incluir todos los meses calendario posibles, pero eso es ideal (en lo que a mí respecta) porque excluye datos redundantes. Puede completar los espacios en blanco dentro del código de su aplicación simplemente repitiendo las filas de salida para cada mes adicional hasta que llegue al siguiente At-end-of-month valor.

Aquí está la consulta usando T-SQL en SQL Server (no tengo acceso a Oracle en este momento). Y aquí está el SQLFiddle que usé para llegar a una solución:http://sqlfiddle.com/# !18/ad68b7/24

SELECT
  OtdYear,
  OtdMonth,
  ActiveCount
FROM
  (

    -- This query adds columns to indicate which row is the last-row-in-month ( where RowInMonth == RowsInMonth )
    SELECT
      OnThisDate,
      OtdYear,
      OtdMonth,
      ROW_NUMBER() OVER ( PARTITION BY OtdYear, OtdMonth ORDER BY OnThisDate ) AS RowInMonth,
      COUNT(*) OVER ( PARTITION BY OtdYear, OtdMonth ) AS RowsInMonth,
      ActiveCount
    FROM
      (
        SELECT
          OnThisDate,
          YEAR( OnThisDate ) AS OtdYear,
          MONTH( OnThisDate ) AS OtdMonth,
          SUM( [Change] ) OVER ( ORDER BY OnThisDate ASC ) AS ActiveCount
        FROM
          (
            SELECT
              StartDate AS [OnThisDate],
              1 AS [Change]
            FROM
              tbl

            UNION ALL

            SELECT
              ISNULL( EndDate, DATEFROMPARTS( 9999, 12, 31 ) ) AS [OnThisDate],
              -1 AS [Change]
            FROM
              tbl
          ) AS sq1
      ) AS sq2
  ) AS sq3
WHERE
  RowInMonth = RowsInMonth
ORDER BY
  OtdYear,
  OtdMonth

Esta consulta puede aplanarse en menos consultas anidadas usando funciones agregadas y de ventana directamente en lugar de usar alias (como OtdYear , ActiveCount , etc.), pero eso haría que la consulta fuera mucho más difícil de entender.