Oracle
 sql >> Database >  >> RDS >> Oracle

Oracle SQL - Seleziona gli utenti tra due data per mese

Questa query mostra il conteggio degli utenti attivi a partire dalla fine del mese.

Come funziona:

  1. Converti ogni riga di input (con StartDate e EndDate valore) in due righe che rappresentano un momento in cui il conteggio degli utenti attivi è aumentato (su StartDate ) e decrementato (su EndDate ). Dobbiamo convertire NULL a un valore di data lontano perché NULL i valori sono ordinati prima anziché dopo non NULL valori:

    In questo modo i tuoi dati assomiglieranno a questo:

    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. Quindi semplicemente SUM OVER il Change valori (dopo l'ordinamento) per ottenere il conteggio degli utenti attivi a partire da quella data specifica:

    Quindi, prima, ordina per 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
    

    Quindi 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. Quindi PARTITION (non raggruppare!) le righe per mese e ordinarle per data in modo da poter identificare l'ultimo ActiveCount riga per quel mese (questo accade effettivamente nel WHERE della query più esterna, utilizzando ROW_NUMBER() e COUNT() per ogni mese 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. Quindi filtra su quello dove IsLastInMonth = 1 (in realtà, dove ROW_COUNT() = COUNT(*) all'interno di ogni PARTITION ) per fornirci i dati di output finali:

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

Ciò comporta "lacune" nel set di risultati perché At-end-of-month la colonna mostra solo le righe in cui il Active-count il valore è effettivamente cambiato anziché includere tutti i possibili mesi di calendario, ma è l'ideale (per quanto mi riguarda) perché esclude i dati ridondanti. È possibile riempire gli spazi vuoti all'interno del codice dell'applicazione semplicemente ripetendo le righe di output per ogni mese aggiuntivo fino a raggiungere il At-end-of-month successivo valore.

Ecco la query che utilizza T-SQL su SQL Server (non ho accesso a Oracle in questo momento). Ed ecco SQLFiddle che ho usato per trovare una soluzione: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

Questa query può essere ridotto in un minor numero di query nidificate utilizzando direttamente le funzioni di aggregazione e finestra invece di utilizzare alias (come OtdYear , ActiveCount , ecc.), ma ciò renderebbe la query molto più difficile da capire.