diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 8f9ceb91cc..1797c1dcb9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -17360,6 +17360,36 @@ public class DSL { return new org.jooq.impl.Function("sum", true, SQLDataType.NUMERIC, nullSafe(field)); } + /** + * Get the product over a numeric field: product(field). + *

+ * No database currently supports multiplicative aggregation natively. jOOQ + * emulates this using exp(sum(log(arg))) for strictly positive + * numbers, and does some additional handling for zero and negative numbers. + *

+ * More information here: https://blog.jooq.org/2018/09/21/how-to-write-a-multiplication-aggregate-function-in-sql. + */ + @Support + public static AggregateFunction product(Field field) { + return new org.jooq.impl.Function(Term.PRODUCT, SQLDataType.NUMERIC, nullSafe(field)); + } + + /** + * Get the sum over a numeric field: product(distinct field). + *

+ * No database currently supports multiplicative aggregation natively. jOOQ + * emulates this using exp(sum(log(arg))) for strictly positive + * numbers, and does some additional handling for zero and negative numbers. + *

+ * More information here: https://blog.jooq.org/2018/09/21/how-to-write-a-multiplication-aggregate-function-in-sql. + */ + @Support + public static AggregateFunction productDistinct(Field field) { + return new org.jooq.impl.Function(Term.PRODUCT, true, SQLDataType.NUMERIC, nullSafe(field)); + } + /** * Get the average over a numeric field: avg(field). */ diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index fa0155d66f..1b167e5123 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -53,10 +53,13 @@ import static org.jooq.SQLDialect.POSTGRES_9_4; import static org.jooq.SQLDialect.SQLITE; // ... import static org.jooq.impl.DSL.condition; +import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.mode; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.percentileCont; +import static org.jooq.impl.DSL.when; +import static org.jooq.impl.DSL.zero; import static org.jooq.impl.Keywords.K_AS; import static org.jooq.impl.Keywords.K_DENSE_RANK; import static org.jooq.impl.Keywords.K_DISTINCT; @@ -78,6 +81,7 @@ import static org.jooq.impl.Term.ARRAY_AGG; import static org.jooq.impl.Term.LIST_AGG; import static org.jooq.impl.Term.MEDIAN; import static org.jooq.impl.Term.MODE; +import static org.jooq.impl.Term.PRODUCT; import static org.jooq.impl.Term.ROW_NUMBER; import static org.jooq.impl.Tools.DataKey.DATA_WINDOW_DEFINITIONS; @@ -237,6 +241,40 @@ class Function extends AbstractField implements ctx.visit(percentileCont(new BigDecimal("0.5")).withinGroupOrderBy(fields)); } + else if (term == PRODUCT) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + Field f = (Field) DSL.field("{0}", arguments.get(0)); + Field negatives = DSL.when(f.lt(zero()), inline(-1)); + + @SuppressWarnings("serial") + Field negativesSum = new CustomField("sum", SQLDataType.NUMERIC) { + @Override + public void accept(Context c) { + c.visit(distinct + ? DSL.sumDistinct(negatives) + : DSL.sum(negatives)); + + toSQLFilterClause(c); + toSQLOverClause(c); + } + }; + + @SuppressWarnings("serial") + Field logarithmsSum = new CustomField("sum", SQLDataType.NUMERIC) { + @Override + public void accept(Context c) { + c.visit(DSL.sum(DSL.ln(DSL.abs(f)))); + + toSQLFilterClause(c); + toSQLOverClause(c); + } + }; + + ctx.visit( + when(negativesSum.mod(inline(2)).lt(inline(BigDecimal.ZERO)), inline(-1)) + .otherwise(one()).mul(DSL.exp(logarithmsSum)) + ); + } else { toSQLArguments(ctx); toSQLKeepDenseRankOrderByClause(ctx); diff --git a/jOOQ/src/main/java/org/jooq/impl/Term.java b/jOOQ/src/main/java/org/jooq/impl/Term.java index 1db6e268b0..000557e119 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Term.java +++ b/jOOQ/src/main/java/org/jooq/impl/Term.java @@ -214,6 +214,12 @@ enum Term { return "octet_length"; } }, + PRODUCT { + @Override + public String translate(SQLDialect dialect) { + return "product"; + } + }, ROW_NUMBER { @Override public String translate(SQLDialect dialect) {