Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Oracle SQL - Wählen Sie Benutzer zwischen zwei Datum nach Monat aus

Diese Abfrage zeigt die Anzahl der aktiven Benutzer zum Monatsende.

Wie es funktioniert:

  1. Konvertieren Sie jede Eingabezeile (mit StartDate und EndDate Wert) in zwei Zeilen, die einen Zeitpunkt darstellen, an dem die Anzahl der aktiven Benutzer erhöht wurde (am StartDate ) und dekrementiert (am EndDate ). Wir müssen NULL konvertieren zu einem fernen Datumswert, weil NULL Werte werden vor statt nach Nicht-NULL sortiert Werte:

    Dadurch sehen Ihre Daten wie folgt aus:

    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. Dann SUM OVER Sie einfach die Change Werten (nach dem Sortieren), um die Anzahl der aktiven Benutzer zu diesem bestimmten Datum zu erhalten:

    Sortieren Sie also zuerst nach 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
    

    Dann 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. Dann PARTITION (nicht gruppieren!) die Zeilen nach Monat und sortieren sie nach ihrem Datum, damit wir den letzten ActiveCount identifizieren können Zeile für diesen Monat (das passiert eigentlich im WHERE der äußersten Abfrage mit ROW_NUMBER() und COUNT() für jeden Monat 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. Dann filtern Sie nach dem, wo IsLastInMonth = 1 ist (eigentlich, wo ROW_COUNT() = COUNT(*) innerhalb jeder PARTITION ), um uns die endgültigen Ausgabedaten zu geben:

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

Dies führt zu "Lücken" in der Ergebnismenge, da der At-end-of-month Spalte zeigt nur Zeilen, in denen der Active-count Wert tatsächlich geändert, anstatt alle möglichen Kalendermonate einzubeziehen - aber das ist (meiner Meinung nach) ideal, weil es redundante Daten ausschließt. Das Ausfüllen der Lücken kann in Ihrem Anwendungscode erfolgen, indem Sie einfach die Ausgabezeilen für jeden weiteren Monat wiederholen, bis der nächste At-end-of-month erreicht wird Wert.

Hier ist die Abfrage mit T-SQL auf SQL Server (ich habe derzeit keinen Zugriff auf Oracle). Und hier ist das SQLFiddle, mit dem ich zu einer Lösung gekommen bin: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

Diese Abfrage kann in weniger verschachtelte Abfragen reduziert werden, indem Aggregat- und Fensterfunktionen direkt verwendet werden, anstatt Aliase (wie OtdYear) zu verwenden , ActiveCount , usw.), aber das würde die Abfrage viel schwerer verständlich machen.