diff --git a/doc/sql.extensions/README.percentile_disc_cont.md b/doc/sql.extensions/README.percentile_disc_cont.md new file mode 100644 index 00000000000..47997023665 --- /dev/null +++ b/doc/sql.extensions/README.percentile_disc_cont.md @@ -0,0 +1,187 @@ +# PERCENTILE_DISC and PERCENTILE_CONT functions + +The `PERCENTILE_CONT` and `PERCENTILE_DISC` functions are known as inverse distribution functions. +These functions operate on an ordered set. Both functions can be used as aggregate or window functions. + +## PERCENTILE_DISC + +`PERCENTILE_DISC` is an inverse distribution function that assumes a discrete distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_DISC` function as an aggregate function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) +``` + +Syntax for the `PERCENTILE_DISC` function as an window function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression that can be of any type that can be sorted. + +The function `PERCENTILE_DISC` returns a value of the same type as the argument in `ORDER BY`. + +For a given percentile value `P`, `PERCENTILE_DISC` sorts the values of the expression in the `ORDER BY` clause and +returns the value with the smallest `CUME_DIST` value (with respect to the same sort specification) +that is greater than or equal to `P`. + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + CUME_DIST() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "CUME_DIST", + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_DISC +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY CUME_DIST MEDIAN_DISC +======= ===================== ======================= ===================== +000 53793.00 0.5000000000000000 53793.00 +000 212850.00 1.000000000000000 53793.00 +100 44000.00 0.5000000000000000 44000.00 +100 111262.50 1.000000000000000 44000.00 +110 61637.81 0.5000000000000000 61637.81 +110 68805.00 1.000000000000000 61637.81 +115 6000000.00 0.5000000000000000 6000000.00 +115 7480000.00 1.000000000000000 6000000.00 +120 22935.00 0.3333333333333333 33620.63 +120 33620.63 0.6666666666666666 33620.63 +120 39224.06 1.000000000000000 33620.63 +121 110000.00 1.000000000000000 110000.00 +123 38500.00 1.000000000000000 38500.00 +125 33000.00 1.000000000000000 33000.00 +130 86292.94 0.5000000000000000 86292.94 +130 102750.00 1.000000000000000 86292.94 +140 100914.00 1.000000000000000 100914.00 +180 42742.50 0.5000000000000000 42742.50 +180 64635.00 1.000000000000000 42742.50 +``` + +## PERCENTILE_CONT + +`PERCENTILE_CONT` is an inverse distribution function that assumes a continuous distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_CONT` function as an aggregate function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) +``` + +Syntax for the `PERCENTILE_CONT` function as an window function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression, which must be of numeric type to perform interpolation. + +The `PERCENTILE_CONT` function returns a value of type `DOUBLE PRECISION` or `DECFLOAT(34)` depending on the type +of the argument in the `ORDER BY` clause. A value of type `DECFLOAT(34)` is returned if `ORDER BY` contains +an expression of one of the types `INT128`, `NUMERIC(38, x)` or `DECFLOAT(16 | 34)`, otherwise - `DOUBLE PRECISION`. + +The result of `PERCENTILE_CONT` is computed by linear interpolation between values after ordering them. +Using the percentile value (`P`) and the number of rows (`N`) in the aggregation group, you can compute +the row number you are interested in after ordering the rows with respect to the sort specification. +This row number (`RN`) is computed according to the formula `RN = (1 + (P * (N - 1))`. +The final result of the aggregate function is computed by linear interpolation between the values from rows +at row numbers `CRN = CEILING(RN)` and `FRN = FLOOR(RN)`. + +``` +function f(N) ::= value of expression from row at N + +if (CRN = FRN = RN) then + return f(RN) +else + return (CRN - RN) * f(FRN) + (RN - FRN) * f(CRN) +``` + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + PERCENT_RANK() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "PERCENT_RANK", + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_CONT +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY PERCENT_RANK MEDIAN_CONT +======= ===================== ======================= ======================= +000 53793.00 0.000000000000000 133321.5000000000 +000 212850.00 1.000000000000000 133321.5000000000 +100 44000.00 0.000000000000000 77631.25000000000 +100 111262.50 1.000000000000000 77631.25000000000 +110 61637.81 0.000000000000000 65221.40500000000 +110 68805.00 1.000000000000000 65221.40500000000 +115 6000000.00 0.000000000000000 6740000.000000000 +115 7480000.00 1.000000000000000 6740000.000000000 +120 22935.00 0.000000000000000 33620.63000000000 +120 33620.63 0.5000000000000000 33620.63000000000 +120 39224.06 0.2500000000000000 33620.63000000000 +121 110000.00 0.000000000000000 110000.0000000000 +123 38500.00 0.000000000000000 38500.00000000000 +125 33000.00 0.000000000000000 33000.00000000000 +130 86292.94 0.000000000000000 94521.47000000000 +130 102750.00 1.000000000000000 94521.47000000000 +140 100914.00 0.000000000000000 100914.0000000000 +180 42742.50 0.000000000000000 53688.75000000000 +180 64635.00 1.000000000000000 53688.75000000000 +``` + +## An example of using both aggregate functions + +```sql +SELECT + DEPT_NO, + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_CONT, + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_DISC +FROM EMPLOYEE +GROUP BY DEPT_NO; +``` + +``` +DEPT_NO MEDIAN_CONT MEDIAN_DISC +======= ======================= ===================== +000 133321.5000000000 53793.00 +100 77631.25000000000 44000.00 +110 65221.40500000000 61637.81 +115 6740000.000000000 6000000.00 +120 33620.63000000000 33620.63 +121 110000.0000000000 110000.00 +123 38500.00000000000 38500.00 +125 33000.00000000000 33000.00 +130 94521.47000000000 86292.94 +140 100914.0000000000 100914.00 +180 53688.75000000000 42742.50 +600 66450.00000000000 27000.00 +621 71619.75000000000 62550.00 +622 53167.50000000000 53167.50 +623 60000.00000000000 60000.00 +670 71268.75000000000 31275.00 +671 81810.19000000000 81810.19 +672 45647.50000000000 35000.00 +900 92791.31500000000 69482.63 +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 588252b429c..4440ccf55ff 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -373,6 +373,8 @@ PARSER_TOKEN(TOK_PARAMETER, "PARAMETER", false) PARSER_TOKEN(TOK_PARTITION, "PARTITION", true) PARSER_TOKEN(TOK_PASSWORD, "PASSWORD", true) PARSER_TOKEN(TOK_PERCENT_RANK, "PERCENT_RANK", true) +PARSER_TOKEN(TOK_PERCENTILE_CONT, "PERCENTILE_CONT", true) +PARSER_TOKEN(TOK_PERCENTILE_DISC, "PERCENTILE_DISC", true) PARSER_TOKEN(TOK_PI, "PI", true) PARSER_TOKEN(TOK_PKCS_1_5, "PKCS_1_5", true) PARSER_TOKEN(TOK_PLACING, "PLACING", true) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 4d86f3c82d7..808e9c7e1b0 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -359,6 +359,11 @@ AggNode* AggNode::pass2(thread_db* tdbb, CompilerScratch* csb) return this; } +void AggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); +} + void AggNode::aggInit(thread_db* tdbb, Request* request) const { impure_value_ex* impure = request->getImpure(impureOffset); @@ -1094,6 +1099,350 @@ AggNode* ListAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ return node; } +//-------------------- + + +static AggNode::RegisterFactory1 percentileContAggInfo( + "PERCENTILE_CONT", PercentileAggNode::TYPE_PERCENTILE_CONT); +static AggNode::RegisterFactory1 percentileDiscAggInfo( + "PERCENTILE_DISC", PercentileAggNode::TYPE_PERCENTILE_DISC); + +PercentileAggNode::PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg, + ValueListNode* aOrderClause) + : AggNode(pool, + (aType == PercentileAggNode::TYPE_PERCENTILE_CONT ? percentileContAggInfo : percentileDiscAggInfo), + false, false, aArg), + type(aType), + valueArg(nullptr), + dsqlOrderClause(aOrderClause) +{ + if (dsqlOrderClause) + valueArg = nodeAs(dsqlOrderClause->items[0])->value; +} + +void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) +{ + arg = PAR_parse_value(tdbb, csb); + valueArg = PAR_parse_value(tdbb, csb); + if (csb->csb_blr_reader.peekByte() == blr_sort) + sort = PAR_sort(tdbb, csb, blr_sort, true); +} + +bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const +{ + if (!AggNode::dsqlMatch(dsqlScratch, other, ignoreMapCast)) + return false; + + const PercentileAggNode* o = nodeAs(other); + fb_assert(o); + return PASS1_node_match(dsqlScratch, dsqlOrderClause, o->dsqlOrderClause, ignoreMapCast); +} + +void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) +{ + fb_assert(dsqlOrderClause); + if (dsqlOrderClause->items.getCount() != 1) + { + ERR_post(Arg::Gds(isc_percetile_only_one_sort_item)); + } + + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + } + else + { + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } +} + +void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + AggNode::genBlr(dsqlScratch); + GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); +} + +void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + valueArg->getDesc(tdbb, csb, desc); +} + +void PercentileAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + valueArg->getDesc(tdbb, csb, desc); + } + else + { + valueArg->getDesc(tdbb, csb, desc); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } +} + +ValueExprNode* PercentileAggNode::copy(thread_db* tdbb, NodeCopier& copier) const +{ + PercentileAggNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) PercentileAggNode(*tdbb->getDefaultPool(), type); + + node->nodScale = nodScale; + node->arg = copier.copy(tdbb, arg); + node->valueArg = copier.copy(tdbb, valueArg); + node->sort = sort->copy(tdbb, copier); + + return node; +} + +AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) +{ + AggNode::pass2(tdbb, csb); + + // impure area for calculate border + percentileImpureOffset = csb->allocImpure(); + + return this; +} + +string PercentileAggNode::internalPrint(NodePrinter& printer) const +{ + AggNode::internalPrint(printer); + + NODE_PRINT(printer, type); + + return "PercentileAggNode"; +} + + +void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const +{ + AggNode::aggInit(tdbb, request); + + impure_value_ex* impure = request->getImpure(impureOffset); + impure->vlu_desc.dsc_dtype = 0; + impure->vlux_count = 0; + + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + percentileImpure->vlux_count = 0; + percentileImpure->rn = 0; + percentileImpure->crn = 0; + percentileImpure->frn = 0; +} + +bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const +{ + dsc* percenteDesc = nullptr; + percenteDesc = EVL_expr(tdbb, request, arg); + if (!percenteDesc) + return false; + + dsc* desc = nullptr; + desc = EVL_expr(tdbb, request, valueArg); + if (!desc) + return false; + + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + if (percentileImpure->vlux_count++ == 0) // first call to aggPass() + { + if ((type == TYPE_PERCENTILE_CONT) && !desc->isNumeric()) + ERRD_post(Arg::Gds(isc_argmustbe_numeric_function) << Arg::Str("PERCENTILE_CONT")); + + if (desc->isBlob()) + ERRD_post(Arg::Gds(isc_blobnotsup) << Arg::Str("ORDER BY")); + + const auto percentileValue = MOV_get_double(tdbb, percenteDesc); + if ((percentileValue < 0) || (percentileValue > 1)) + { + if (type == TYPE_PERCENTILE_DISC) + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_DISC")); + else + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_CONT")); + } + + percentileImpure->percentile = percentileValue; + } + + if (sort) + { + fb_assert(asb); + // "Put" the value to sort. + impure_agg_sort* asbImpure = request->getImpure(asb->impure); + UCHAR* data; + asbImpure->iasb_sort->put(tdbb, reinterpret_cast(&data)); + + MOVE_CLEAR(data, asb->length); + + auto descOrder = asb->descOrder.begin(); + auto keyItem = asb->keyItems.begin(); + + for (auto& nodeOrder : sort->expressions) + { + dsc toDesc = *(descOrder++); + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; + if (const auto fromDsc = EVL_expr(tdbb, request, nodeOrder)) + { + if (IS_INTL_DATA(fromDsc)) + { + INTL_string_to_key(tdbb, INTL_TEXT_TO_INDEX(fromDsc->getTextType()), + fromDsc, &toDesc, INTL_KEY_UNIQUE); + } + else + MOV_move(tdbb, fromDsc, &toDesc); + } + else + *(data + keyItem->getSkdOffset()) = TRUE; + + // The first key for NULLS FIRST/LAST, the second key for the sorter + keyItem += 2; + } + + dsc toDesc = asb->desc; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; + MOV_move(tdbb, desc, &toDesc); + + return true; + } + + return true; +} + +void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + + if (type == TYPE_PERCENTILE_DISC) + { + if (impure->vlux_count++ == 0) + { + // calculate only ones + percentileImpure->rn = percentileImpure->percentile * percentileImpure->vlux_count; + percentileImpure->crn = MAX(static_cast(ceil(percentileImpure->rn)), 1); + } + + if (impure->vlux_count == percentileImpure->crn) + EVL_make_value(tdbb, desc, impure); + + } + else + { + if (impure->vlux_count++ == 0) + { + // calculate only ones + percentileImpure->rn = 1 + percentileImpure->percentile * (percentileImpure->vlux_count - 1); + percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + percentileImpure->frn = static_cast(floor(percentileImpure->rn)); + + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + Firebird::Decimal128 d128; + d128.set(0, decSt, 0); + impure->make_decimal128(d128); + } + else + impure->make_double(0); + } + + if (percentileImpure->crn == percentileImpure->frn) + { + if (impure->vlux_count == percentileImpure->frn) + { + if (desc->isDecOrInt128()) + { + const auto value = MOV_get_dec128(tdbb, desc); + impure->make_decimal128(value); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure->make_double(value); + } + } + } + else + { + if (impure->vlux_count == percentileImpure->frn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(percentileImpure->crn - percentileImpure->rn, decSt); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); + } + } + + if (impure->vlux_count == percentileImpure->crn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(percentileImpure->rn - percentileImpure->frn, decSt); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); + } + } + } + } +} + +dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + + if (!impure->vlux_count || !impure->vlu_desc.dsc_dtype) + return nullptr; + + return &impure->vlu_desc; +} + +AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ +{ + AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) PercentileAggNode(dsqlScratch->getPool(), type, + doDsqlPass(dsqlScratch, arg), + doDsqlPass(dsqlScratch, dsqlOrderClause) ); + + PASS1_set_parameter_type(dsqlScratch, node->arg, + [&](dsc* desc) { desc->makeDouble(); }, + false); + + return node; +} + //-------------------- diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index b9a186b698e..929c08e04dc 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -134,6 +134,67 @@ class ListAggNode final : public AggNode NestConst dsqlOrderClause; }; +class PercentileAggNode final : public AggNode +{ +public: + enum PercentileType : UCHAR + { + TYPE_PERCENTILE_CONT, + TYPE_PERCENTILE_DISC + }; + + struct PercentileImpure + { + SINT64 vlux_count; + double percentile; + double rn; + SINT64 crn; + SINT64 frn; + }; + + explicit PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg = nullptr, + ValueListNode* aOrderClause = nullptr); + + void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count) override; + + unsigned getCapabilities() const override + { + return CAP_WANTS_AGG_CALLS; + } + + bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; + + void getChildren(NodeRefsHolder& holder, bool dsql) const override + { + AggNode::getChildren(holder, dsql); + holder.add(valueArg); + } + + Firebird::string internalPrint(NodePrinter& printer) const override; + void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + + void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + + void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const override; + AggNode* pass2(thread_db* tdbb, CompilerScratch* csb) override; + + void aggInit(thread_db* tdbb, Request* request) const override; + bool aggPass(thread_db* tdbb, Request* request) const override; + void aggPass(thread_db* tdbb, Request* request, dsc* desc) const override; + dsc* aggExecute(thread_db* tdbb, Request* request) const override; + +protected: + AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ override; + +private: + const PercentileType type; + NestConst valueArg; + NestConst dsqlOrderClause; + ULONG percentileImpureOffset = 0; +}; + class CountAggNode final : public AggNode { public: diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index f769b4b33ed..8c0baa6ebda 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1092,6 +1092,8 @@ class AggNode : public TypedNode return NULL; } + virtual void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); + virtual void aggInit(thread_db* tdbb, Request* request) const = 0; // pure, but defined virtual void aggFinish(thread_db* tdbb, Request* request) const; virtual bool aggPass(thread_db* tdbb, Request* request) const; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index d18086526cc..b03e9a7c4fa 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -134 shift/reduce conflicts, 13 reduce/reduce conflicts. +134 shift/reduce conflicts, 25 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 28fc1e12163..9965bbfee3c 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -715,6 +715,8 @@ using namespace Firebird; %token LEAST %token LTRIM %token NAMED_ARG_ASSIGN +%token PERCENTILE_CONT +%token PERCENTILE_DISC %token RTRIM %token SCHEMA %token SEARCH_PATH @@ -4726,6 +4728,8 @@ keyword_or_column | WITHIN | LISTAGG | TRUNCATE + | PERCENTILE_CONT + | PERCENTILE_DISC ; col_opt @@ -8618,6 +8622,10 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } | BIN_XOR_AGG '(' DISTINCT value ')' { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } + | PERCENTILE_CONT '(' value ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_CONT, $3, $5); } + | PERCENTILE_DISC '(' value ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_DISC, $3, $5); } ; %type listagg_set_function @@ -10064,6 +10072,8 @@ non_reserved_word | FORMAT | GENERATE_SERIES | OWNER + | PERCENTILE_CONT + | PERCENTILE_DISC | SEARCH_PATH | SCHEMA | UNLIST diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 7ba26ca0ac2..be4dd4cde4e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1000,3 +1000,6 @@ FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored") FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1") FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") +FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") +FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") +FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 4fdd5a676e3..61bbb789f60 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5848,6 +5848,9 @@ IProfilerStatsImpl = class(IProfilerStats) isc_no_user_att_while_restore = 335545318; isc_genseq_stepmustbe_nonzero = 335545319; isc_argmustbe_exact_function = 335545320; + isc_sysf_argmustbe_range_inc0_1 = 335545321; + isc_argmustbe_numeric_function = 335545322; + isc_percetile_only_one_sort_item = 335545323; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index c7ae78e32f0..c513a1adb51 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1525,7 +1525,7 @@ void Optimizer::generateAggregateSort(AggNode* aggNode) fb_assert(prevKey); ULONG length = prevKey ? ROUNDUP(prevKey->getSkdOffset() + prevKey->getSkdLength(), sizeof(SLONG)) : 0; - aggNode->arg->getDesc(tdbb, csb, desc); + aggNode->makeSortDesc(tdbb, csb, desc); if (desc->dsc_dtype >= dtype_aligned) length = FB_ALIGN(length, type_alignments[desc->dsc_dtype]);