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

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

commit 3e1018e0cbc6d28d59f2d0658c6a94b044484f67
Author: luoyuxia <[email protected]>
AuthorDate: Tue Jun 28 20:58:06 2022 +0800

    [FLINK-27619][sql] Support NTILE Function
---
 docs/data/sql_functions.yml                        |   5 +
 docs/data/sql_functions_zh.yml                     |   5 +
 .../planner/expressions/ExpressionBuilder.java     |   5 +
 .../functions/aggfunctions/NTILEAggFunction.java   | 112 +++++++++++++++++++++
 .../functions/sql/FlinkSqlOperatorTable.java       |   1 +
 .../exec/batch/BatchExecOverAggregateBase.java     |   5 +-
 .../planner/plan/utils/AggFunctionFactory.scala    |  11 ++
 .../runtime/batch/sql/OverAggregateITCase.scala    |  48 +++++++++
 .../operators/over/frame/InsensitiveOverFrame.java |   2 +-
 9 files changed, 192 insertions(+), 2 deletions(-)

diff --git a/docs/data/sql_functions.yml b/docs/data/sql_functions.yml
index 646beaae6f2..2a7ba09c844 100644
--- a/docs/data/sql_functions.yml
+++ b/docs/data/sql_functions.yml
@@ -1009,6 +1009,11 @@ aggregate:
     description: Return the cumulative distribution of a value in a group of 
values. The result is the number of rows preceding or equal to the current row 
in the ordering of the partition divided by the number of rows in the window 
partition.
   - sql: PERCENT_RANK()
     description: Return the percentage ranking of a value in a group of 
values. The result is the rank value minus one, divided by the number of rows 
in the parition minus one. If the partition only contains one row, the function 
will return 0.
+  - sql: NTILE(n)
+    description: |
+      Divides the rows for each window partition into `n` buckets ranging from 
1 to at most `n`.
+      If the number of rows in the window partition doesn't divide evenly into 
the number of buckets, then the remainder values are distributed one per 
bucket, starting with the first bucket.
+      For example, with 6 rows and 4 buckets, the bucket values would be as 
follows: 1 1 2 2 3 4
 
 catalog:
   - sql: CURRENT_DATABASE()
diff --git a/docs/data/sql_functions_zh.yml b/docs/data/sql_functions_zh.yml
index e43efd90282..ea47e7e0fe0 100644
--- a/docs/data/sql_functions_zh.yml
+++ b/docs/data/sql_functions_zh.yml
@@ -1100,6 +1100,11 @@ aggregate:
     description: 返回值在一组值的累积分布。结果是小于或等于当前行的值的行数除以窗口分区的总行数。
   - sql: PERCENT_RANK()
     description: 返回值在一组值的百分比排名。结果是当前行在窗口分区中的排名减 1,然后除以窗口分区的总行数减 1。如果窗口分区的总行数为 
1,则该函数返回 0。
+  - sql: NTILE(n)
+    description: |
+      将窗口分区中的所有数据按照顺序划分为 n 个分组,返回分配给各行数据的分组编号(从 1 开始)。
+      如果不能均匀划分为 n 个分组,则从第 1 个分组开始,为每一分组分配一个剩余值。
+      比如某个窗口分区有 6 行数据,划分为 4 个分组,则各行的分组编号为:1,1,2,2,3,4。
 
 catalog:
   - sql: CURRENT_DATABASE()
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/expressions/ExpressionBuilder.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/expressions/ExpressionBuilder.java
index 867ce097d66..213b8b51cef 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/expressions/ExpressionBuilder.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/expressions/ExpressionBuilder.java
@@ -40,6 +40,7 @@ import static 
org.apache.flink.table.functions.BuiltInFunctionDefinitions.GREATE
 import static org.apache.flink.table.functions.BuiltInFunctionDefinitions.IF;
 import static 
org.apache.flink.table.functions.BuiltInFunctionDefinitions.IS_NULL;
 import static 
org.apache.flink.table.functions.BuiltInFunctionDefinitions.LESS_THAN;
+import static 
org.apache.flink.table.functions.BuiltInFunctionDefinitions.LESS_THAN_OR_EQUAL;
 import static 
org.apache.flink.table.functions.BuiltInFunctionDefinitions.MINUS;
 import static org.apache.flink.table.functions.BuiltInFunctionDefinitions.MOD;
 import static org.apache.flink.table.functions.BuiltInFunctionDefinitions.NOT;
@@ -143,6 +144,10 @@ public class ExpressionBuilder {
         return call(EQUALS, input1, input2);
     }
 
+    public static UnresolvedCallExpression lessThanOrEqual(Expression input1, 
Expression input2) {
+        return call(LESS_THAN_OR_EQUAL, input1, input2);
+    }
+
     public static UnresolvedCallExpression lessThan(Expression input1, 
Expression input2) {
         return call(LESS_THAN, input1, input2);
     }
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/aggfunctions/NTILEAggFunction.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/aggfunctions/NTILEAggFunction.java
new file mode 100644
index 00000000000..e133c0fc675
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/aggfunctions/NTILEAggFunction.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+package org.apache.flink.table.planner.functions.aggfunctions;
+
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.expressions.Expression;
+import org.apache.flink.table.expressions.UnresolvedReferenceExpression;
+import org.apache.flink.table.types.DataType;
+
+import static 
org.apache.flink.table.expressions.ApiExpressionUtils.unresolvedRef;
+import static 
org.apache.flink.table.expressions.ApiExpressionUtils.valueLiteral;
+import static org.apache.flink.table.planner.expressions.ExpressionBuilder.div;
+import static 
org.apache.flink.table.planner.expressions.ExpressionBuilder.greaterThan;
+import static 
org.apache.flink.table.planner.expressions.ExpressionBuilder.ifThenElse;
+import static 
org.apache.flink.table.planner.expressions.ExpressionBuilder.lessThanOrEqual;
+import static 
org.apache.flink.table.planner.expressions.ExpressionBuilder.literal;
+import static org.apache.flink.table.planner.expressions.ExpressionBuilder.mod;
+import static 
org.apache.flink.table.planner.expressions.ExpressionBuilder.plus;
+
+/** built-in NTILE agg function. */
+public class NTILEAggFunction extends DeclarativeAggregateFunction
+        implements SizeBasedWindowFunction {
+
+    private final Expression bucketSize;
+    private final Expression bucketsWithPadding;
+    private final UnresolvedReferenceExpression rowNumber = 
unresolvedRef("rowNumber");
+    private final UnresolvedReferenceExpression bucket = 
unresolvedRef("bucket");
+    private final UnresolvedReferenceExpression bucketThreshold = 
unresolvedRef("bucketThreshold");
+
+    public NTILEAggFunction() {
+        this.bucketSize = div(windowSizeAttribute(), operand(0));
+        this.bucketsWithPadding = mod(windowSizeAttribute(), operand(0));
+    }
+
+    @Override
+    public int operandCount() {
+        return 1;
+    }
+
+    @Override
+    public UnresolvedReferenceExpression[] aggBufferAttributes() {
+        return new UnresolvedReferenceExpression[] {rowNumber, bucket, 
bucketThreshold};
+    }
+
+    @Override
+    public DataType[] getAggBufferTypes() {
+        return new DataType[] {DataTypes.BIGINT(), DataTypes.BIGINT(), 
DataTypes.BIGINT()};
+    }
+
+    @Override
+    public DataType getResultType() {
+        return DataTypes.BIGINT();
+    }
+
+    @Override
+    public Expression[] initialValuesExpressions() {
+        return new Expression[] {valueLiteral(0L), valueLiteral(0L), 
valueLiteral(0L)};
+    }
+
+    @Override
+    public Expression[] accumulateExpressions() {
+        return new Expression[] {
+            /* rowNumber = */ plus(rowNumber, literal(1L)),
+            /* bucket = */ plus(bucket, bucketOverflowThen(literal(1L))),
+            /* bucketThreshold = */ plus(
+                    bucketThreshold,
+                    bucketOverflowThen(
+                            plus(
+                                    bucketSize,
+                                    ifThenElse(
+                                            lessThanOrEqual(bucket, 
bucketsWithPadding),
+                                            valueLiteral(1),
+                                            valueLiteral(0)))))
+        };
+    }
+
+    private Expression bucketOverflowThen(Expression e) {
+        return ifThenElse(greaterThan(rowNumber, bucketThreshold), e, 
valueLiteral(0L));
+    }
+
+    @Override
+    public Expression[] retractExpressions() {
+        throw new TableException("This function does not support retraction.");
+    }
+
+    @Override
+    public Expression[] mergeExpressions() {
+        throw new TableException("This function does not support merge.");
+    }
+
+    @Override
+    public Expression getValueExpression() {
+        return bucket;
+    }
+}
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/sql/FlinkSqlOperatorTable.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/sql/FlinkSqlOperatorTable.java
index 0161b3c2140..cfdf34e2e79 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/sql/FlinkSqlOperatorTable.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/sql/FlinkSqlOperatorTable.java
@@ -1140,6 +1140,7 @@ public class FlinkSqlOperatorTable extends 
ReflectiveSqlOperatorTable {
     public static final SqlAggFunction ROW_NUMBER = 
SqlStdOperatorTable.ROW_NUMBER;
     public static final SqlAggFunction CUME_DIST = 
SqlStdOperatorTable.CUME_DIST;
     public static final SqlAggFunction PERCENT_RANK = 
SqlStdOperatorTable.PERCENT_RANK;
+    public static final SqlAggFunction NTILE = SqlStdOperatorTable.NTILE;
     public static final SqlAggFunction LEAD = SqlStdOperatorTable.LEAD;
     public static final SqlAggFunction LAG = SqlStdOperatorTable.LAG;
 
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/batch/BatchExecOverAggregateBase.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/batch/BatchExecOverAggregateBase.java
index 437ab6a80d9..b639980610f 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/batch/BatchExecOverAggregateBase.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/batch/BatchExecOverAggregateBase.java
@@ -115,6 +115,9 @@ public abstract class BatchExecOverAggregateBase extends 
ExecNodeBase<RowData>
     /** Infer the over window mode based on given group info. */
     protected OverWindowMode inferGroupMode(GroupSpec group) {
         AggregateCall aggCall = group.getAggCalls().get(0);
+        if (aggCall.getAggregation().getKind() == NTILE) {
+            return OverWindowMode.INSENSITIVE;
+        }
         if (aggCall.getAggregation().allowsFraming()) {
             if (group.isRows()) {
                 return OverWindowMode.ROW;
@@ -147,7 +150,7 @@ public abstract class BatchExecOverAggregateBase extends 
ExecNodeBase<RowData>
         OFFSET,
         /**
          * The INSENSITIVE mode does not care the window framing without 
LEAD/LAG agg function, for
-         * example RANK/DENSE_RANK/PERCENT_RANK/ROW_NUMBER.
+         * example RANK/DENSE_RANK/PERCENT_RANK/ROW_NUMBER/NTILE.
          */
         INSENSITIVE
     }
diff --git 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/AggFunctionFactory.scala
 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/AggFunctionFactory.scala
index 1def38cec15..a9599ce5d0b 100644
--- 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/AggFunctionFactory.scala
+++ 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/AggFunctionFactory.scala
@@ -112,6 +112,13 @@ class AggFunctionFactory(
           throw new TableException("PERCENT_RANK Function is not supported in 
stream mode.")
         }
 
+      case _: SqlNtileAggFunction =>
+        if (isBounded) {
+          createNTILEAggFUnction(argTypes)
+        } else {
+          throw new TableException("NTILE Function is not supported in stream 
mode.")
+        }
+
       case func: SqlLeadLagAggFunction =>
         if (isBounded) {
           createBatchLeadLagAggFunction(argTypes, index)
@@ -535,6 +542,10 @@ class AggFunctionFactory(
     new PercentRankAggFunction(argTypes)
   }
 
+  private def createNTILEAggFUnction(argTypes: Array[LogicalType]): 
UserDefinedFunction = {
+    new NTILEAggFunction
+  }
+
   private def createFirstValueAggFunction(
       argTypes: Array[LogicalType],
       index: Int): UserDefinedFunction = {
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/OverAggregateITCase.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/OverAggregateITCase.scala
index 748f0a82379..979fa83e8e3 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/OverAggregateITCase.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/OverAggregateITCase.scala
@@ -2855,6 +2855,54 @@ class OverAggregateITCase extends BatchTestBase {
       )
     )
   }
+
+  @Test
+  def testNTILE(): Unit = {
+    checkResult(
+      "SELECT f, NTILE(4) over (order by e)," +
+        " NTILE(3) over (partition by d order by e desc) FROM Table5",
+      Seq(
+        row(0, 1, 1),
+        row(1, 1, 2),
+        row(2, 1, 1),
+        row(3, 1, 3),
+        row(4, 2, 2),
+        row(5, 2, 1),
+        row(6, 2, 3),
+        row(7, 2, 2),
+        row(8, 3, 1),
+        row(9, 3, 1),
+        row(10, 3, 3),
+        row(11, 3, 2),
+        row(12, 4, 2),
+        row(13, 4, 1),
+        row(14, 4, 1)
+      )
+    )
+
+    // test values of order-key containing duplicates
+    checkResult(
+      "SELECT d,  NTILE(4) over (order by d)" +
+        " FROM Table5",
+      Seq(
+        row(5, 3),
+        row(5, 3),
+        row(5, 4),
+        row(5, 4),
+        row(5, 4),
+        row(4, 2),
+        row(4, 2),
+        row(4, 3),
+        row(4, 3),
+        row(3, 1),
+        row(3, 2),
+        row(3, 2),
+        row(2, 1),
+        row(2, 1),
+        row(1, 1)
+      )
+    )
+  }
 }
 
 /** The initial accumulator for count aggregate function */
diff --git 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/frame/InsensitiveOverFrame.java
 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/frame/InsensitiveOverFrame.java
index 0f4383e9b78..2c8ba39ac14 100644
--- 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/frame/InsensitiveOverFrame.java
+++ 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/frame/InsensitiveOverFrame.java
@@ -27,7 +27,7 @@ import 
org.apache.flink.table.runtime.util.ResettableExternalBuffer;
 
 /**
  * The insensitive window frame calculates the statements which shouldn't care 
the window frame, for
- * example RANK/DENSE_RANK/PERCENT_RANK/CUME_DIST/ROW_NUMBER.
+ * example RANK/DENSE_RANK/PERCENT_RANK/NTILE/ROW_NUMBER.
  */
 public class InsensitiveOverFrame implements OverWindowFrame {
 

Reply via email to