Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Suma minut między wieloma zakresami dat

Gordon Linoff ma odpowiedź opartą na CTE

Wykonałem analizę wydajności we wszystkich działających algorytmach Puste wartości oznaczają, że trwało to zbyt długo. Jest to testowane na pojedynczym układzie Core i7 X920 @2GHz, wspieranym przez kilka dysków SSD. Jedynym utworzonym indeksem był klaster na UserID, AvailStart. Jeśli uważasz, że możesz poprawić jakąkolwiek wydajność, daj mi znać.

Ta wersja CTE była gorsza niż liniowa, SQL Server nie może wykonać sprzężenia RN =RN + 1 w wydajny sposób. Poprawiłem to za pomocą podejścia hybrydowego poniżej, gdzie zapisuję i indeksuję pierwsze CTE w zmiennej tabeli. To nadal zajmuje dziesięć razy więcej IO niż podejście oparte na kursorze.

With OrderedRanges as (
  Select
    Row_Number() Over (Partition By UserID Order By AvailStart) AS RN,
    AvailStart,
    AvailEnd
  From
    dbo.Available
  Where
    UserID = 456
),
AccumulateMinutes (RN, Accum, CurStart, CurEnd) as (
  Select
    RN, 0, AvailStart, AvailEnd
  From
    OrderedRanges
  Where 
    RN = 1
  Union All
  Select
    o.RN, 
    a.Accum + Case When o.AvailStart <= a.CurEnd Then
        0
      Else 
        DateDiff(Minute, a.CurStart, a.CurEnd)
      End,
    Case When o.AvailStart <= a.CurEnd Then 
        a.CurStart
      Else
        o.AvailStart
      End,
    Case When o.AvailStart <= a.CurEnd Then
        Case When a.CurEnd > o.AvailEnd Then a.CurEnd Else o.AvailEnd End
      Else
        o.AvailEnd
      End
  From
    AccumulateMinutes a
        Inner Join 
    OrderedRanges o On 
        a.RN = o.RN - 1
)

Select Max(Accum + datediff(Minute, CurStart, CurEnd)) From AccumulateMinutes 

http://sqlfiddle.com/#!6/ac021/2

Po przeprowadzeniu analizy wydajności, oto hybrydowa wersja zmiennej CTE/tabeli, która działa lepiej niż cokolwiek innego z wyjątkiem podejścia opartego na kursorze

Create Function dbo.AvailMinutesHybrid(@UserID int) Returns Int As
Begin

Declare @UserRanges Table (
  RN int not null primary key, 
  AvailStart datetime, 
  AvailEnd datetime
)
Declare @Ret int = Null

;With OrderedRanges as (
  Select
    Row_Number() Over (Partition By UserID Order By AvailStart) AS RN,
    AvailStart,
    AvailEnd
  From
    dbo.Available
  Where
    UserID = @UserID
)
Insert Into @UserRanges Select * From OrderedRanges


;With AccumulateMinutes (RN,Accum, CurStart, CurEnd) as (
  Select
    RN, 0, AvailStart, AvailEnd
  From
    @UserRanges
  Where 
    RN = 1
  Union All
  Select
    o.RN, 
    a.Accum + Case When o.AvailStart <= a.CurEnd Then
        0
      Else 
        DateDiff(Minute, a.CurStart, a.CurEnd)
      End,
    Case When o.AvailStart <= a.CurEnd Then 
        a.CurStart
      Else
        o.AvailStart
      End,
    Case When o.AvailStart <= a.CurEnd Then
        Case When a.CurEnd > o.AvailEnd Then a.CurEnd Else o.AvailEnd End
      Else
        o.AvailEnd
      End
  From
    AccumulateMinutes a
        Inner Join 
    @UserRanges o On 
        a.RN + 1 = o.RN
)

Select 
  @Ret = Max(Accum + datediff(Minute, CurStart, CurEnd)) 
From 
  AccumulateMinutes 
Option
  (MaxRecursion 0)

Return @Ret

End

http://sqlfiddle.com/#!6/bfd94



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. znajdź numer otwartego połączenia w bazie danych

  2. Łatwe przywracanie bazy danych SQL — przewodnik krok po kroku

  3. Instalacja i konfiguracja programu SQL Server Log Shipping i odzyskiwanie po awarii -4

  4. TSQL Pivot bez funkcji agregującej

  5. SQL Server Intellisense nie działa na *niektórych* serwerach