diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/SqlFunctionProvider.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/SqlFunctionProvider.java index 8e96da4d58..e82b720d37 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/SqlFunctionProvider.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/SqlFunctionProvider.java @@ -143,6 +143,8 @@ default Condition isNotEmptyValidityDate(ValidityDate validityDate) { */ ColumnDateRange allRange(); + Field anyValue(Field field); + /** * Creates a {@link ColumnDateRange} for a tables {@link CQTable}s validity date. The validity dates bounds will be restricted by the given date * restriction. diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/clickhouse/ClickhouseFunctionProvider.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/clickhouse/ClickhouseFunctionProvider.java index 0123b29f4a..7fbfd8f4fe 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/clickhouse/ClickhouseFunctionProvider.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/clickhouse/ClickhouseFunctionProvider.java @@ -107,6 +107,11 @@ public ColumnDateRange allRange() { return ColumnDateRange.of(getMinDateExpression().as("all_range_start"), getMaxDateExpression().as("all_range_end")); } + @Override + public Field anyValue(Field field) { + return DSL.anyValue(field); + } + private ColumnDateRange toColumnDateRange(ValidityDate validityDate) { String tableName = validityDate.getConnector().resolveTableId().getTable(); diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/hana/HanaSqlFunctionProvider.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/hana/HanaSqlFunctionProvider.java index ae4a3cad16..837478431c 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/hana/HanaSqlFunctionProvider.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/dialect/hana/HanaSqlFunctionProvider.java @@ -120,6 +120,12 @@ public ColumnDateRange allRange() { return ColumnDateRange.of(getMinDateExpression(), getMaxDateExpression()); } + @Override + public Field anyValue(Field field) { + // Hana does not have any_value + return DSL.min(field); + } + private ColumnDateRange toColumnDateRange(ValidityDate validityDate) { String tableName = validityDate.getConnector().resolveTableId().getTable(); diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/ExistsSqlSelect.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/ExistsSqlSelect.java index 01b19a7cc9..b6a15eaab5 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/ExistsSqlSelect.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/ExistsSqlSelect.java @@ -1,10 +1,10 @@ package com.bakdata.conquery.sql.conversion.model.select; +import com.bakdata.conquery.sql.conversion.dialect.SqlFunctionProvider; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.jooq.Field; import org.jooq.Name; -import org.jooq.impl.DSL; import java.util.Collections; import java.util.List; @@ -60,7 +60,14 @@ public SqlSelect toFinalRepresentation() { return new ExistsSqlSelect(coalesceWithZero().as(alias), alias); } + @Override + public List> aggregateForFinalQuery(SqlFunctionProvider functionProvider) { + // We have to coalesce at the end of the query because full-outer-joins will create null values, which we want to avoid. + Field coalesced = coalesce(max(select()), inline(0)); + return List.of(coalesced.as(alias)); + } + private Field coalesceWithZero() { - return coalesce(select(), value(0)); + return coalesce(select(), inline(0)); } } diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/SqlSelect.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/SqlSelect.java index 882925284c..5ca54b46c4 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/SqlSelect.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/model/select/SqlSelect.java @@ -1,11 +1,12 @@ package com.bakdata.conquery.sql.conversion.model.select; import com.bakdata.conquery.models.datasets.concepts.select.concept.specific.ExistsSelect; -import java.util.List; - +import com.bakdata.conquery.sql.conversion.dialect.SqlFunctionProvider; import com.bakdata.conquery.sql.conversion.model.Qualifiable; import org.jooq.Field; +import java.util.List; + public interface SqlSelect extends Qualifiable { List> toFields(); @@ -37,4 +38,13 @@ default SqlSelect toFinalRepresentation() { return this; } + /** + * Aggregate this select to one value per ID group in the final concept query. + */ + default List> aggregateForFinalQuery(SqlFunctionProvider functionProvider) { + return toFinalRepresentation().toFields().stream() + .>map(field -> functionProvider.anyValue(field).as(field.getName())) + .toList(); + } + } diff --git a/backend/src/main/java/com/bakdata/conquery/sql/conversion/query/ConceptQueryConverter.java b/backend/src/main/java/com/bakdata/conquery/sql/conversion/query/ConceptQueryConverter.java index 0eedc29c21..d0bbc3503b 100644 --- a/backend/src/main/java/com/bakdata/conquery/sql/conversion/query/ConceptQueryConverter.java +++ b/backend/src/main/java/com/bakdata/conquery/sql/conversion/query/ConceptQueryConverter.java @@ -7,14 +7,12 @@ import com.bakdata.conquery.sql.conversion.cqelement.ConversionContext; import com.bakdata.conquery.sql.conversion.dialect.SqlFunctionProvider; import com.bakdata.conquery.sql.conversion.model.*; +import com.bakdata.conquery.sql.conversion.model.select.FieldWrapper; import com.bakdata.conquery.sql.conversion.model.select.SqlSelect; import lombok.RequiredArgsConstructor; -import org.jooq.Field; +import org.jooq.*; import org.jooq.Record; -import org.jooq.Select; -import org.jooq.TableLike; -import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -82,18 +80,32 @@ public ConversionContext convert(ConceptQuery conceptQuery, ConversionContext co } private Selects getFinalSelects(ConceptQuery conceptQuery, Selects preFinalSelects, SqlFunctionProvider functionProvider) { + Selects finalSelects = preFinalSelects; if (conceptQuery.getDateAggregationMode() == DateAggregationMode.NONE) { - return preFinalSelects.blockValidityDate(); + finalSelects = preFinalSelects.blockValidityDate(); } - return preFinalSelects; + + return Selects.builder() + .ids(finalSelects.getIds()) + .validityDate(finalSelects.getValidityDate()) + .stratificationDate(finalSelects.getStratificationDate()) + .sqlSelects(getFinalAggregatedSelects(finalSelects, functionProvider)) + .build(); + } + + private List> getFinalAggregatedSelects(Selects finalSelects, SqlFunctionProvider functionProvider) { + return finalSelects.getSqlSelects().stream() + .flatMap(sqlSelect -> sqlSelect.aggregateForFinalQuery(functionProvider).stream()) + .map(this::toFieldWrapper) + .toList(); + } + + private FieldWrapper toFieldWrapper(Field field) { + return new FieldWrapper<>(field); } private List> getFinalGroupBySelects(Selects preFinalSelects) { - List> groupBySelects = new ArrayList<>(); - groupBySelects.addAll(preFinalSelects.getIds().toFields()); - // TODO instead us any_value selects - groupBySelects.addAll(preFinalSelects.explicitSelects()); - return groupBySelects; + return preFinalSelects.getIds().toFields(); } }