perf(FiveSql2): fix O(N²) window-function regression for default frame
Q2 Running total regressed 100ms→6.7s from the frame-aware rewrite. Default frame (UNBOUNDED PRECEDING to CURRENT ROW) now uses O(N) incremental path; general per-row-frame loop only for custom frames. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2955,7 +2955,7 @@ METHOD ApplyWindowFunctions( aRows, aFN, aCols ) CLASS TSqlExecutor
|
||||
LOCAL nLagLead, nArgCol, xDefault
|
||||
LOCAL nRunSum, nRunCount
|
||||
LOCAL aWinCols, nWC
|
||||
LOCAL hFrame, nFS, nFE, m, xVal, xMin, xMax
|
||||
LOCAL hFrame, nFS, nFE, m, xVal, xMin, xMax, lDefaultFrame
|
||||
|
||||
/* Scan for window function columns */
|
||||
aWinCols := {}
|
||||
@@ -3114,19 +3114,73 @@ METHOD ApplyWindowFunctions( aRows, aFN, aCols ) CLASS TSqlExecutor
|
||||
ENDIF
|
||||
ENDIF
|
||||
|
||||
/* Detect default frame (UNBOUNDED PRECEDING to CURRENT ROW)
|
||||
* which can use the O(N) incremental running-sum path instead
|
||||
* of the O(N²) general per-row-frame-aggregate. */
|
||||
/* Default frame = UNBOUNDED PRECEDING to CURRENT ROW.
|
||||
* This covers: no frame spec, or explicit ROWS UNBOUNDED
|
||||
* PRECEDING (without BETWEEN or with implied CURRENT ROW end).
|
||||
* The incremental O(N) path handles this; the general frame
|
||||
* loop is only needed for custom boundaries like
|
||||
* ROWS BETWEEN 6 PRECEDING AND CURRENT ROW. */
|
||||
lDefaultFrame := .T.
|
||||
IF hFrame != NIL .AND. ValType( hFrame ) == "H"
|
||||
IF hb_HHasKey( hFrame, "end" ) .AND. ;
|
||||
! ( "CURRENT ROW" $ hFrame[ "end" ] )
|
||||
lDefaultFrame := .F.
|
||||
ENDIF
|
||||
IF hb_HHasKey( hFrame, "start" ) .AND. ;
|
||||
! ( "UNBOUNDED PRECEDING" $ hFrame[ "start" ] )
|
||||
lDefaultFrame := .F.
|
||||
ENDIF
|
||||
ENDIF
|
||||
IF lDefaultFrame
|
||||
/* O(N) incremental path — original fast code */
|
||||
nRunSum := 0
|
||||
nRunCount := 0
|
||||
xMin := NIL
|
||||
xMax := NIL
|
||||
FOR k := 1 TO Len( aPartIdx )
|
||||
IF cFunc == "COUNT" .AND. nArgCol == 0
|
||||
nRunCount++
|
||||
ELSEIF nArgCol > 0 .AND. nArgCol <= Len( aRows[ aPartIdx[ k ] ] )
|
||||
xVal := aRows[ aPartIdx[ k ] ][ nArgCol ]
|
||||
IF xVal != NIL
|
||||
nRunCount++
|
||||
nRunSum += SqlCoerceNum( xVal )
|
||||
IF xMin == NIL .OR. SqlCmpLt( xVal, xMin )
|
||||
xMin := xVal
|
||||
ENDIF
|
||||
IF xMax == NIL .OR. SqlCmpLt( xMax, xVal )
|
||||
xMax := xVal
|
||||
ENDIF
|
||||
ENDIF
|
||||
ENDIF
|
||||
IF nColIdx <= Len( aRows[ aPartIdx[ k ] ] )
|
||||
DO CASE
|
||||
CASE cFunc == "SUM"
|
||||
aRows[ aPartIdx[ k ] ][ nColIdx ] := nRunSum
|
||||
CASE cFunc == "AVG"
|
||||
aRows[ aPartIdx[ k ] ][ nColIdx ] := iif( nRunCount > 0, nRunSum / nRunCount, NIL )
|
||||
CASE cFunc == "COUNT"
|
||||
aRows[ aPartIdx[ k ] ][ nColIdx ] := nRunCount
|
||||
CASE cFunc == "MIN"
|
||||
aRows[ aPartIdx[ k ] ][ nColIdx ] := xMin
|
||||
CASE cFunc == "MAX"
|
||||
aRows[ aPartIdx[ k ] ][ nColIdx ] := xMax
|
||||
ENDCASE
|
||||
ENDIF
|
||||
NEXT
|
||||
ELSE
|
||||
/* General frame path — O(N*W) where W = frame width */
|
||||
FOR k := 1 TO Len( aPartIdx )
|
||||
/* Compute frame boundaries for this row */
|
||||
nFS := 1 /* default: UNBOUNDED PRECEDING */
|
||||
nFE := k /* default: CURRENT ROW */
|
||||
IF hFrame != NIL .AND. ValType( hFrame ) == "H"
|
||||
IF hb_HHasKey( hFrame, "start" )
|
||||
nFS := SqlFrameOffset( hFrame[ "start" ], k, Len( aPartIdx ) )
|
||||
ENDIF
|
||||
IF hb_HHasKey( hFrame, "end" )
|
||||
nFE := SqlFrameOffset( hFrame[ "end" ], k, Len( aPartIdx ) )
|
||||
ELSE
|
||||
nFE := k
|
||||
ENDIF
|
||||
nFS := 1
|
||||
nFE := k
|
||||
IF hb_HHasKey( hFrame, "start" )
|
||||
nFS := SqlFrameOffset( hFrame[ "start" ], k, Len( aPartIdx ) )
|
||||
ENDIF
|
||||
IF hb_HHasKey( hFrame, "end" )
|
||||
nFE := SqlFrameOffset( hFrame[ "end" ], k, Len( aPartIdx ) )
|
||||
ENDIF
|
||||
IF nFS < 1
|
||||
nFS := 1
|
||||
@@ -3135,7 +3189,6 @@ METHOD ApplyWindowFunctions( aRows, aFN, aCols ) CLASS TSqlExecutor
|
||||
nFE := Len( aPartIdx )
|
||||
ENDIF
|
||||
|
||||
/* Aggregate over the frame window */
|
||||
nRunSum := 0
|
||||
nRunCount := 0
|
||||
xMin := NIL
|
||||
@@ -3174,6 +3227,7 @@ METHOD ApplyWindowFunctions( aRows, aFN, aCols ) CLASS TSqlExecutor
|
||||
ENDCASE
|
||||
ENDIF
|
||||
NEXT
|
||||
ENDIF /* lDefaultFrame */
|
||||
|
||||
ENDCASE
|
||||
NEXT
|
||||
|
||||
Reference in New Issue
Block a user