sql >> Database >  >> RDS >> Oracle

Oracle SQL - Selecteer gebruikers tussen twee datums per maand

Deze zoekopdracht toont het aantal actieve gebruikers dat van kracht is aan het einde van de maand.

Hoe het werkt:

  1. Converteer elke invoerrij (met StartDate en EndDate waarde) in twee rijen die een tijdstip vertegenwoordigen waarop het aantal actieve gebruikers werd verhoogd (op StartDate ) en verlaagd (op EndDate ). We moeten NULL . converteren naar een verre datumwaarde omdat NULL waarden worden eerder gesorteerd in plaats van na niet-NULL waarden:

    Hierdoor zien uw gegevens er als volgt uit:

    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. Dan SUM OVER de Change waarden (na sortering) om het aantal actieve gebruikers op die specifieke datum te krijgen:

    Sorteer dus eerst op 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
    

    Dan 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. Dan PARTITION (niet groeperen!) de rijen per maand en sorteer ze op hun datum zodat we de laatste ActiveCount kunnen identificeren rij voor die maand (dit gebeurt eigenlijk in de WHERE van de buitenste zoekopdracht, met behulp van ROW_NUMBER() en COUNT() voor elke maand 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. Filter vervolgens op dat waar IsLastInMonth = 1 (eigenlijk, waar ROW_COUNT() = COUNT(*) binnen elke PARTITION ) om ons de uiteindelijke uitvoergegevens te geven:

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

Dit resulteert wel in "gaten" in de resultatenset omdat de At-end-of-month kolom toont alleen rijen waar de Active-count waarde eigenlijk veranderd in plaats van alle mogelijke kalendermaanden op te nemen - maar dat is ideaal (wat mij betreft) omdat het overbodige gegevens uitsluit. Het opvullen van de hiaten kan in uw applicatiecode worden gedaan door simpelweg de uitvoerrijen voor elke extra maand te herhalen totdat het de volgende bereikt At-end-of-month waarde.

Hier is de query met behulp van T-SQL op SQL Server (ik heb nu geen toegang tot Oracle). En hier is de SQLFiddle die ik gebruikte om tot een oplossing te komen: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

Deze zoekopdracht kan worden afgevlakt tot minder geneste zoekopdrachten door aggregatie- en vensterfuncties rechtstreeks te gebruiken in plaats van aliassen te gebruiken (zoals OtdYear , ActiveCount , enz.), maar dat zou de vraag veel moeilijker te begrijpen maken.



  1. Oracle-tekst bevat en technische inhoud

  2. PHP MySQL - Voor elke 6 rijen een div invoegen?

  3. Gebruikersgegevens versleutelen voor automatische aanmelding bij systeem van derden

  4. MySQL voegt zich bij veel tot veel enkele rijen