Oracle
 sql >> Base de données >  >> RDS >> Oracle

Oracle SQL - Sélectionnez les utilisateurs entre deux dates par mois

Cette requête affiche le nombre d'utilisateurs actifs en vigueur à la fin du mois.

Comment ça marche :

  1. Convertir chaque ligne d'entrée (avec StartDate et EndDate valeur) en deux lignes qui représentent un point dans le temps lorsque le nombre d'utilisateurs actifs a augmenté (le StartDate ) et décrémenté (le EndDate ). Nous devons convertir NULL à une valeur de date lointaine car NULL les valeurs sont triées avant au lieu d'après non-NULL valeurs :

    Cela donne à vos données l'aspect suivant :

    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. Ensuite, nous avons simplement SUM OVER le Change valeurs (après tri) pour obtenir le nombre d'utilisateurs actifs à cette date spécifique :

    Alors d'abord, triez par 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
    

    Puis 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. Ensuite, nous PARTITION (ne pas regrouper !) les lignes par mois et les trier par date afin que nous puissions identifier le dernier ActiveCount ligne pour ce mois (cela se produit en fait dans le WHERE de la requête la plus externe, en utilisant ROW_NUMBER() et COUNT() pour chaque mois 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. Filtrez ensuite sur celui où IsLastInMonth = 1 (en fait, où ROW_COUNT() = COUNT(*) à l'intérieur de chaque PARTITION ) pour nous donner les données de sortie finale :

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

Cela entraîne des "écarts" dans le jeu de résultats car le At-end-of-month la colonne affiche uniquement les lignes où le Active-count la valeur a en fait changé plutôt que d'inclure tous les mois civils possibles - mais c'est idéal (en ce qui me concerne) car cela exclut les données redondantes. Remplir les lacunes peut être fait à l'intérieur de votre code d'application en répétant simplement les lignes de sortie pour chaque mois supplémentaire jusqu'à ce qu'il atteigne le prochain At-end-of-month valeur.

Voici la requête utilisant T-SQL sur SQL Server (je n'ai pas accès à Oracle pour le moment). Et voici le SQLFiddle que j'ai utilisé pour trouver une solution :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

Cette requête peut être aplati en moins de requêtes imbriquées en utilisant directement les fonctions d'agrégation et de fenêtre au lieu d'utiliser des alias (comme OtdYear , ActiveCount , etc.), mais cela rendrait la requête beaucoup plus difficile à comprendre.