This is an automated email from the ASF dual-hosted git repository.

mrhhsg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 355a300ec37 [fix](window) Recompute sliding frames for floating-point 
sum/avg (#68113)
355a300ec37 is described below

commit 355a300ec37c6a0f36dbf0c1c9f0835dc74c68d8
Author: Jerry Hu <[email protected]>
AuthorDate: Fri Sep 18 16:33:35 2026 +0800

    [fix](window) Recompute sliding frames for floating-point sum/avg (#68113)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Problem Summary:
    
    Sliding `ROWS` window frames evaluate `sum`/`avg` incrementally: the
    outgoing
    row is subtracted from the accumulator and the incoming row is added.
    For
    floating-point accumulators this is not exact. Once `2^54 + 1` rounds to
    `2^54`, subtracting `2^54` again leaves `0` instead of `1`, so the
    rounding
    lost by a value that already left the frame keeps distorting later
    results.
    
    ```sql
    WITH t AS (
        SELECT 1 AS id, CAST(18014398509481984 AS DOUBLE) AS v
        UNION ALL SELECT 2, CAST(1 AS DOUBLE)
        UNION ALL SELECT 3, CAST(1 AS DOUBLE)
    )
    SELECT id, v,
           avg(v) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) 
AS got
    FROM t ORDER BY id;
    ```
    
    Before: the third row returned `0.5` (frame `[1, 1]`, sum `1`, count
    `2`).
    After: the third row returns `1`.
    
    Fix: `AggregateFunctionSum` and `AggregateFunctionAvg` now report
    `supported_incremental_mode() == false` when the accumulator is a
    floating-point type, so the analytic sink recomputes each sliding frame
    from
    its rows (the same approach PostgreSQL takes by not providing inverse
    transition functions for `float4`/`float8`). Integer and decimal
    accumulators
    are exact and keep the incremental path. Frames that only grow
    (`UNBOUNDED PRECEDING`) never removed rows and are unaffected.
    
    Cost: sliding `ROWS` frames over `FLOAT`/`DOUBLE` columns now cost
    O(frame size) per row instead of O(1). This is the trade-off for exact
    results; wide sliding frames over floating-point columns will be slower.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test:
    - Unit Test:
    
`AnalyticSinkOperatorTest.SlidingRowsDouble{Avg,Sum}IgnoresRoundingOfOutgoingRow`,
    
    `AggregateFunction{Avg,Sum}Test.test_incremental_mode_only_for_exact_sum`
    - Regression test:
    `query_p0/sql_functions/window_functions/test_window_float_sliding_frame`
    - Behavior changed: Yes (sliding window sum/avg over FLOAT/DOUBLE now
    return the exact per-frame result; wide frames are slower)
    - Does this need documentation: No
    
    https://claude.ai/code/session_01KSyhoeWWGWukpHP6MBTbEt
---
 be/src/exprs/aggregate/aggregate_function_avg.h    |   5 +-
 be/src/exprs/aggregate/aggregate_function_sum.h    |   8 +-
 .../exec/operator/analytic_sink_operator_test.cpp  |  79 +++++++++++
 be/test/exprs/aggregate/agg_avg_test.cpp           |  11 ++
 .../{agg_avg_test.cpp => agg_sum_test.cpp}         |  19 ++-
 .../test_window_float_sliding_frame.out            |  66 +++++++++
 .../test_window_float_sliding_frame.groovy         | 150 +++++++++++++++++++++
 7 files changed, 332 insertions(+), 6 deletions(-)

diff --git a/be/src/exprs/aggregate/aggregate_function_avg.h 
b/be/src/exprs/aggregate/aggregate_function_avg.h
index 795dce34ec3..2ea1ba8c020 100644
--- a/be/src/exprs/aggregate/aggregate_function_avg.h
+++ b/be/src/exprs/aggregate/aggregate_function_avg.h
@@ -305,7 +305,10 @@ public:
         return std::make_shared<DataTypeFixedLengthObject>();
     }
 
-    bool supported_incremental_mode() const override { return true; }
+    // Floating-point accumulation is not exactly invertible: subtracting an 
outgoing
+    // value cannot restore the rounding lost when it was added, so a value 
that has
+    // left the frame would still distort later results. Recompute such frames 
instead.
+    bool supported_incremental_mode() const override { return 
!std::is_floating_point_v<DataType>; }
 
     void execute_function_with_incremental(int64_t partition_start, int64_t 
partition_end,
                                            int64_t frame_start, int64_t 
frame_end,
diff --git a/be/src/exprs/aggregate/aggregate_function_sum.h 
b/be/src/exprs/aggregate/aggregate_function_sum.h
index 8eddb893ed9..8c00d1934ea 100644
--- a/be/src/exprs/aggregate/aggregate_function_sum.h
+++ b/be/src/exprs/aggregate/aggregate_function_sum.h
@@ -23,6 +23,7 @@
 #include <stddef.h>
 
 #include <memory>
+#include <type_traits>
 #include <vector>
 
 #include "common/compiler_util.h"
@@ -205,7 +206,12 @@ public:
         return std::make_shared<DataTypeFixedLengthObject>();
     }
 
-    bool supported_incremental_mode() const override { return true; }
+    // Floating-point accumulation is not exactly invertible: subtracting an 
outgoing
+    // value cannot restore the rounding lost when it was added, so a value 
that has
+    // left the frame would still distort later results. Recompute such frames 
instead.
+    bool supported_incremental_mode() const override {
+        return !std::is_floating_point_v<typename 
PrimitiveTypeTraits<TResult>::CppType>;
+    }
 
     NO_SANITIZE_UNDEFINED void execute_function_with_incremental(
             int64_t partition_start, int64_t partition_end, int64_t 
frame_start, int64_t frame_end,
diff --git a/be/test/exec/operator/analytic_sink_operator_test.cpp 
b/be/test/exec/operator/analytic_sink_operator_test.cpp
index 4641a42cd56..a8537f12f6a 100644
--- a/be/test/exec/operator/analytic_sink_operator_test.cpp
+++ b/be/test/exec/operator/analytic_sink_operator_test.cpp
@@ -572,6 +572,85 @@ TEST_F(AnalyticSinkOperatorTest, 
SlidingRowsSumRetainsOutgoingRowDuringEviction)
     std::cout << "######### sliding rows sum eviction test end #########" << 
std::endl;
 }
 
+// Floating-point sum/avg cannot be rolled back exactly: once 2^54 + 1 rounds 
to 2^54,
+// removing 2^54 leaves 0 instead of 1. Sliding frames must be recomputed so 
that a value
+// which already left the frame cannot distort the current result.
+TEST_F(AnalyticSinkOperatorTest, 
SlidingRowsDoubleAvgIgnoresRoundingOfOutgoingRow) {
+    const std::vector<double> data_vals {18014398509481984.0, 1.0, 1.0};
+    const std::vector<double> expect_vals {18014398509481984.0, 
9007199254740992.0, 1.0};
+    Initialize(data_vals.size());
+    create_operator(true, 1, "avg", {std::make_shared<DataTypeFloat64>()},
+                    std::make_shared<DataTypeFloat64>());
+    sink->_agg_expr_ctxs.resize(1);
+    sink->_agg_expr_ctxs[0] =
+            MockSlotRef::create_mock_contexts(0, 
std::make_shared<DataTypeFloat64>());
+    TAnalyticWindow temp_window;
+    temp_window.type = TAnalyticWindowType::ROWS;
+    TAnalyticWindowBoundary window_start;
+    window_start.type = TAnalyticWindowBoundaryType::PRECEDING;
+    window_start.__set_rows_offset_value(1);
+    temp_window.__set_window_start(window_start);
+    TAnalyticWindowBoundary window_end;
+    window_end.type = TAnalyticWindowBoundaryType::CURRENT_ROW;
+    temp_window.__set_window_end(window_end);
+    create_window_type(true, true, temp_window);
+    create_local_state();
+    EXPECT_FALSE(sink_local_state->_support_incremental_calculate);
+
+    {
+        Block block = ColumnHelper::create_block<DataTypeFloat64>(data_vals);
+        auto st = sink->sink(state.get(), &block, true);
+        EXPECT_TRUE(st.ok()) << st.msg();
+    }
+    {
+        Block block = ColumnHelper::create_block<DataTypeFloat64>({});
+        bool eos = false;
+        auto st = source->get_block(state.get(), &block, &eos);
+        EXPECT_TRUE(st.ok()) << st.msg();
+        EXPECT_TRUE(ColumnHelper::block_equal(
+                block, ColumnHelper::create_block<DataTypeFloat64>(data_vals, 
expect_vals)))
+                << block.dump_data();
+    }
+}
+
+TEST_F(AnalyticSinkOperatorTest, 
SlidingRowsDoubleSumIgnoresRoundingOfOutgoingRow) {
+    const std::vector<double> data_vals {18014398509481984.0, 1.0, 1.0};
+    const std::vector<double> expect_vals {18014398509481984.0, 
18014398509481984.0, 2.0};
+    Initialize(data_vals.size());
+    create_operator(true, 1, "sum", {std::make_shared<DataTypeFloat64>()},
+                    std::make_shared<DataTypeFloat64>());
+    sink->_agg_expr_ctxs.resize(1);
+    sink->_agg_expr_ctxs[0] =
+            MockSlotRef::create_mock_contexts(0, 
std::make_shared<DataTypeFloat64>());
+    TAnalyticWindow temp_window;
+    temp_window.type = TAnalyticWindowType::ROWS;
+    TAnalyticWindowBoundary window_start;
+    window_start.type = TAnalyticWindowBoundaryType::PRECEDING;
+    window_start.__set_rows_offset_value(1);
+    temp_window.__set_window_start(window_start);
+    TAnalyticWindowBoundary window_end;
+    window_end.type = TAnalyticWindowBoundaryType::CURRENT_ROW;
+    temp_window.__set_window_end(window_end);
+    create_window_type(true, true, temp_window);
+    create_local_state();
+    EXPECT_FALSE(sink_local_state->_support_incremental_calculate);
+
+    {
+        Block block = ColumnHelper::create_block<DataTypeFloat64>(data_vals);
+        auto st = sink->sink(state.get(), &block, true);
+        EXPECT_TRUE(st.ok()) << st.msg();
+    }
+    {
+        Block block = ColumnHelper::create_block<DataTypeFloat64>({});
+        bool eos = false;
+        auto st = source->get_block(state.get(), &block, &eos);
+        EXPECT_TRUE(st.ok()) << st.msg();
+        EXPECT_TRUE(ColumnHelper::block_equal(
+                block, ColumnHelper::create_block<DataTypeFloat64>(data_vals, 
expect_vals)))
+                << block.dump_data();
+    }
+}
+
 TEST_F(AnalyticSinkOperatorTest, AggFunction5) {
     int batch_size = 2;
     Initialize(batch_size);
diff --git a/be/test/exprs/aggregate/agg_avg_test.cpp 
b/be/test/exprs/aggregate/agg_avg_test.cpp
index 90847bda0aa..4c7858353ac 100644
--- a/be/test/exprs/aggregate/agg_avg_test.cpp
+++ b/be/test/exprs/aggregate/agg_avg_test.cpp
@@ -31,4 +31,15 @@ TEST_F(AggregateFunctionAvgTest, test_int64) {
     execute(Block({ColumnHelper::create_column_with_name<DataTypeInt64>({1, 2, 
3})}),
             ColumnHelper::create_column_with_name<DataTypeFloat64>({2}));
 }
+
+TEST_F(AggregateFunctionAvgTest, test_incremental_mode_only_for_exact_sum) {
+    create_agg("avg", false, {std::make_shared<DataTypeInt64>()},
+               std::make_shared<DataTypeFloat64>());
+    EXPECT_TRUE(agg_fn->supported_incremental_mode());
+
+    // Floating-point sums are not exactly invertible, so sliding frames must 
be recomputed.
+    create_agg("avg", false, {std::make_shared<DataTypeFloat64>()},
+               std::make_shared<DataTypeFloat64>());
+    EXPECT_FALSE(agg_fn->supported_incremental_mode());
+}
 } // namespace doris
diff --git a/be/test/exprs/aggregate/agg_avg_test.cpp 
b/be/test/exprs/aggregate/agg_sum_test.cpp
similarity index 58%
copy from be/test/exprs/aggregate/agg_avg_test.cpp
copy to be/test/exprs/aggregate/agg_sum_test.cpp
index 90847bda0aa..b8761a1ab69 100644
--- a/be/test/exprs/aggregate/agg_avg_test.cpp
+++ b/be/test/exprs/aggregate/agg_sum_test.cpp
@@ -22,13 +22,24 @@
 
 namespace doris {
 
-struct AggregateFunctionAvgTest : public AggregateFunctiontest {};
+struct AggregateFunctionSumTest : public AggregateFunctiontest {};
 
-TEST_F(AggregateFunctionAvgTest, test_int64) {
-    create_agg("avg", false, {std::make_shared<DataTypeInt64>()},
+TEST_F(AggregateFunctionSumTest, test_int64) {
+    create_agg("sum", false, {std::make_shared<DataTypeInt64>()},
                std::make_shared<DataTypeInt64>());
 
     execute(Block({ColumnHelper::create_column_with_name<DataTypeInt64>({1, 2, 
3})}),
-            ColumnHelper::create_column_with_name<DataTypeFloat64>({2}));
+            ColumnHelper::create_column_with_name<DataTypeInt64>({6}));
+}
+
+TEST_F(AggregateFunctionSumTest, test_incremental_mode_only_for_exact_sum) {
+    create_agg("sum", false, {std::make_shared<DataTypeInt64>()},
+               std::make_shared<DataTypeInt64>());
+    EXPECT_TRUE(agg_fn->supported_incremental_mode());
+
+    // Floating-point sums are not exactly invertible, so sliding frames must 
be recomputed.
+    create_agg("sum", false, {std::make_shared<DataTypeFloat64>()},
+               std::make_shared<DataTypeFloat64>());
+    EXPECT_FALSE(agg_fn->supported_incremental_mode());
 }
 } // namespace doris
diff --git 
a/regression-test/data/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.out
 
b/regression-test/data/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.out
new file mode 100644
index 00000000000..a1d913228bf
--- /dev/null
+++ 
b/regression-test/data/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.out
@@ -0,0 +1,66 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !avg_double_preceding_1 --
+1      1.8014398509481984e+16  1.8014398509481984e+16
+2      0.5     9007199254740992
+3      0.5     0.5
+4      0.5     0.5
+
+-- !sum_double_preceding_1 --
+1      1.8014398509481984e+16  1.8014398509481984e+16
+2      0.5     1.8014398509481984e+16
+3      0.5     1
+4      0.5     1
+
+-- !avg_sum_float_preceding_1 --
+1      1.80143985E16   1.8014398509481984e+16  1.8014398509481984e+16
+2      0.5     9007199254740992        1.8014398509481984e+16
+3      0.5     0.5     1
+4      0.5     0.5     1
+
+-- !avg_sum_double_sign_flip --
+5      1.8014398509481984e+16  1.8014398509481984e+16  1.8014398509481984e+16
+6      0.5     9007199254740992        1.8014398509481984e+16
+7      -1.8014398509481984e+16 -9007199254740992       -1.8014398509481984e+16
+8      0.5     -9007199254740992       -1.8014398509481984e+16
+9      0.5     0.5     1
+
+-- !avg_sum_double_wider_frames --
+10     1.8014398509481984e+16  1.8014398509481984e+16  1.8014398509481984e+16  
9007199254740992        1.8014398509481984e+16  6004799503160661
+11     0.5     9007199254740992        1.8014398509481984e+16  
6004799503160661        1.8014398509481984e+16  0.5
+12     0.5     6004799503160661        1.8014398509481984e+16  0.5     1.5     
0.5
+13     0.5     0.5     1.5     0.5     1.5     0.5
+14     0.5     0.5     1.5     0.5     1       0.5
+
+-- !avg_sum_nullable_partitioned --
+1      1       1.8014398509481984e+16  1.8014398509481984e+16  
1.8014398509481984e+16
+10     3       \N      \N      \N
+11     3       0.5     0.5     0.5
+12     3       0.5     0.5     1
+13     3       0.5     0.5     1
+14     3       0.5     0.5     1
+2      1       0.5     9007199254740992        1.8014398509481984e+16
+3      1       0.5     0.5     1
+4      1       \N      0.5     0.5
+5      2       1.8014398509481984e+16  1.8014398509481984e+16  
1.8014398509481984e+16
+6      2       0.5     9007199254740992        1.8014398509481984e+16
+7      2       -1.8014398509481984e+16 -9007199254740992       
-1.8014398509481984e+16
+8      2       0.5     -9007199254740992       -1.8014398509481984e+16
+9      2       0.5     0.5     1
+
+-- !avg_sum_double_unbounded --
+1      1.8014398509481984e+16  1.8014398509481984e+16  1.8014398509481984e+16
+2      0.5     9007199254740992        1.8014398509481984e+16
+3      0.5     6004799503160661        1.8014398509481984e+16
+4      0.5     4503599627370496        1.8014398509481984e+16
+
+-- !avg_sum_exact_types --
+1      1.8014398509481984e+16  3.602879701896397e+16   36028797018963968       
18014398509481984.0000  18014398509481984.000
+2      0.5     1.8014398509481984e+16  36028797018963969       
9007199254740992.2500   18014398509481984.500
+3      0.5     1       2       0.5000  1.000
+4      0.5     1       2       0.5000  1.000
+
+-- !avg_double_cte --
+1      1.8014398509481984e+16  1.8014398509481984e+16
+2      1       9007199254740992
+3      1       1
+
diff --git 
a/regression-test/suites/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.groovy
 
b/regression-test/suites/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.groovy
new file mode 100644
index 00000000000..15a630e0b1d
--- /dev/null
+++ 
b/regression-test/suites/query_p0/sql_functions/window_functions/test_window_float_sliding_frame.groovy
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Sliding ROWS frames over floating-point columns must not carry the rounding
+// loss of a value that already left the frame into the current result.
+//
+// Expected values must not depend on how the compiler groups the additions
+// (the accumulators allow floating-point reassociation). The data therefore
+// uses 2^54 as the large value and 0.5 as the small value: half an ulp of 2^54
+// is 2, so as long as the small values inside a frame sum to less than 2 in
+// magnitude, any grouping rounds the frame sum back to 2^54, while removing
+// 2^54 incrementally would still leave 0 instead of the exact small sum.
+// Frames that mix the large positive and negative value hold two operands
+// only, because a two-operand IEEE addition has a single correctly rounded
+// result.
+suite("test_window_float_sliding_frame") {
+    sql "DROP TABLE IF EXISTS test_window_float_sliding_frame"
+    sql """
+        CREATE TABLE test_window_float_sliding_frame (
+            id INT,
+            grp INT,
+            v_double DOUBLE,
+            v_float FLOAT,
+            v_nullable DOUBLE NULL
+        ) ENGINE = OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_allocation" = "tag.location.default: 1")
+    """
+
+    // grp 1: a large value (2^54) leaves the frame, then only small values 
remain.
+    // grp 2: sign flip of a large value around small values.
+    // grp 3: consecutive small values after a large one, with NULL in the 
nullable column.
+    sql """
+        INSERT INTO test_window_float_sliding_frame VALUES
+        (1, 1, 18014398509481984, 18014398509481984, 18014398509481984),
+        (2, 1, 0.5, 0.5, 0.5),
+        (3, 1, 0.5, 0.5, 0.5),
+        (4, 1, 0.5, 0.5, NULL),
+        (5, 2, 18014398509481984, 18014398509481984, 18014398509481984),
+        (6, 2, 0.5, 0.5, 0.5),
+        (7, 2, -18014398509481984, -18014398509481984, -18014398509481984),
+        (8, 2, 0.5, 0.5, 0.5),
+        (9, 2, 0.5, 0.5, 0.5),
+        (10, 3, 18014398509481984, 18014398509481984, NULL),
+        (11, 3, 0.5, 0.5, 0.5),
+        (12, 3, 0.5, 0.5, 0.5),
+        (13, 3, 0.5, 0.5, 0.5),
+        (14, 3, 0.5, 0.5, 0.5)
+    """
+
+    // The reported case: after row 1 leaves the frame, avg over [0.5, 0.5] 
must be 0.5.
+    order_qt_avg_double_preceding_1 """
+        SELECT id, v_double,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got
+        FROM test_window_float_sliding_frame
+        WHERE grp = 1
+    """
+
+    order_qt_sum_double_preceding_1 """
+        SELECT id, v_double,
+               sum(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got
+        FROM test_window_float_sliding_frame
+        WHERE grp = 1
+    """
+
+    order_qt_avg_sum_float_preceding_1 """
+        SELECT id, v_float,
+               avg(v_float) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got_avg,
+               sum(v_float) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got_sum
+        FROM test_window_float_sliding_frame
+        WHERE grp = 1
+    """
+
+    // Sign flip: a large positive value leaves, then a large negative value
+    // enters and leaves. Two-operand frames keep the expected sums exact.
+    order_qt_avg_sum_double_sign_flip """
+        SELECT id, v_double,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got_avg,
+               sum(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 
CURRENT ROW) AS got_sum
+        FROM test_window_float_sliding_frame
+        WHERE grp = 2
+    """
+
+    // Consecutive small values after the large one, wider frames and 
following bounds.
+    order_qt_avg_sum_double_wider_frames """
+        SELECT id, v_double,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND 
CURRENT ROW) AS avg_p2,
+               sum(v_double) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND 
CURRENT ROW) AS sum_p2,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 
FOLLOWING) AS avg_p1f1,
+               sum(v_double) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 
FOLLOWING) AS sum_p1f1,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN CURRENT ROW AND 2 
FOLLOWING) AS avg_f2
+        FROM test_window_float_sliding_frame
+        WHERE grp = 3
+    """
+
+    // Nullable input: NULL rows are skipped while the frame keeps sliding.
+    order_qt_avg_sum_nullable_partitioned """
+        SELECT id, grp, v_nullable,
+               avg(v_nullable) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN 
1 PRECEDING AND CURRENT ROW) AS got_avg,
+               sum(v_nullable) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN 
1 PRECEDING AND CURRENT ROW) AS got_sum
+        FROM test_window_float_sliding_frame
+    """
+
+    // Frames that only grow keep the incremental path and stay unchanged.
+    order_qt_avg_sum_double_unbounded """
+        SELECT id, v_double,
+               avg(v_double) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED 
PRECEDING AND CURRENT ROW) AS got_avg,
+               sum(v_double) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED 
PRECEDING AND CURRENT ROW) AS got_sum
+        FROM test_window_float_sliding_frame
+        WHERE grp = 1
+    """
+
+    // Exact accumulators (integer / decimal) still use the incremental path 
and stay exact.
+    order_qt_avg_sum_exact_types """
+        SELECT id, v_double,
+               avg(CAST(v_double * 2 AS BIGINT)) OVER (ORDER BY id ROWS 
BETWEEN 1 PRECEDING AND CURRENT ROW) AS avg_bigint,
+               sum(CAST(v_double * 2 AS BIGINT)) OVER (ORDER BY id ROWS 
BETWEEN 1 PRECEDING AND CURRENT ROW) AS sum_bigint,
+               avg(CAST(v_double AS DECIMAL(27, 3))) OVER (ORDER BY id ROWS 
BETWEEN 1 PRECEDING AND CURRENT ROW) AS avg_decimal,
+               sum(CAST(v_double AS DECIMAL(27, 3))) OVER (ORDER BY id ROWS 
BETWEEN 1 PRECEDING AND CURRENT ROW) AS sum_decimal
+        FROM test_window_float_sliding_frame
+        WHERE grp = 1
+    """
+
+    // The reported shape without a table.
+    order_qt_avg_double_cte """
+        WITH t AS (
+            SELECT 1 AS id, CAST(18014398509481984 AS DOUBLE) AS v
+            UNION ALL SELECT 2, CAST(1 AS DOUBLE)
+            UNION ALL SELECT 3, CAST(1 AS DOUBLE)
+        )
+        SELECT id, v,
+               avg(v) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT 
ROW) AS got
+        FROM t
+    """
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to