Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Oracle SQL — Wybierz użytkowników między dwiema datami po miesiącu

To zapytanie pokazuje liczbę aktywnych użytkowników obowiązującą na koniec miesiąca.

Jak to działa:

  1. Konwertuj każdy wiersz wejściowy (z StartDate i EndDate wartość) na dwa wiersze reprezentujące punkt w czasie, w którym wzrosła liczba aktywnych użytkowników (w dniu StartDate ) i zmniejszony (w dniu EndDate ). Musimy przekonwertować NULL na odległą wartość daty, ponieważ NULL wartości są sortowane przed zamiast po NULL wartości:

    Dzięki temu Twoje dane wyglądają tak:

    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. Następnie po prostu SUM OVER Change wartości (po sortowaniu), aby uzyskać liczbę aktywnych użytkowników na ten konkretny dzień:

    Więc najpierw posortuj według 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
    

    Następnie 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. Następnie PARTITION (nie grupuj!) wierszy według miesięcy i posortuj je według daty, abyśmy mogli zidentyfikować ostatni ActiveCount wiersz dla tego miesiąca (w rzeczywistości dzieje się to w WHERE najbardziej zewnętrznego zapytania, używając ROW_NUMBER() i COUNT() za każdy miesiąc 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. Następnie filtruj według tego, gdzie IsLastInMonth = 1 (właściwie, gdzie ROW_COUNT() = COUNT(*) wewnątrz każdej PARTITION ) aby dać nam ostateczne dane wyjściowe:

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

Powoduje to „luki” w zestawie wyników, ponieważ At-end-of-month kolumna pokazuje tylko wiersze, w których Active-count wartość faktycznie się zmieniła, zamiast uwzględniać wszystkie możliwe miesiące kalendarzowe – ale jest to idealne (jeśli o mnie chodzi), ponieważ wyklucza zbędne dane. Wypełnianie luk można wykonać w kodzie aplikacji, po prostu powtarzając wiersze wyjściowe dla każdego dodatkowego miesiąca, aż do osiągnięcia następnego At-end-of-month wartość.

Oto zapytanie używające T-SQL na SQL Server (nie mam w tej chwili dostępu do Oracle). A oto SQLFiddle, z którym kiedyś doszedłem do rozwiązania: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

To zapytanie może być spłaszczone do mniejszej liczby zagnieżdżonych zapytań, używając bezpośrednio funkcji agregujących i funkcji okna zamiast aliasów (takich jak OtdYear , ActiveCount , itp.), ale to sprawiłoby, że zapytanie byłoby znacznie trudniejsze do zrozumienia.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wyodrębnij Oracle LONG do ciągu w VBA

  2. Wypełnij tablicę asocjacyjną wewnątrz tabeli zagnieżdżonej

  3. Jak zmienić priorytet dla programu równoległego?

  4. Jak napisać sparametryzowaną kwerendę wstawiania Oracle?

  5. Oracle MIN jako funkcja analityczna — dziwne zachowanie z ORDER BY?