diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java index cc69a491906..3d8f8b14ee6 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java @@ -68,4 +68,8 @@ public Set getCTENames() { public String getColumnMapping(String columnId) { return columnMappings.getOrDefault(columnId, columnId); } + + public boolean containsCteFilter(String cteFilterName) { + return cteDefinitions.containsKey(cteFilterName); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEUtils.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEUtils.java new file mode 100644 index 00000000000..08eb967609d --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEUtils.java @@ -0,0 +1,19 @@ +package org.hisp.dhis.analytics.common; + +import org.hisp.dhis.common.QueryItem; + +public class CTEUtils { + + public static String createFilterName(QueryItem queryItem) { + return "filter_" + getIdentifier(queryItem).replace('.', '_').toLowerCase(); + } + + public static String createFilterNameByIdentifier(String identifier) { + return "filter_" + identifier.replace('.', '_').toLowerCase(); + } + + public static String getIdentifier(QueryItem queryItem) { + String stage = queryItem.hasProgramStage() ? queryItem.getProgramStage().getUid() : "default"; + return stage + "." + queryItem.getItemId(); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java index bd69e3e8094..9453ba02cbf 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java @@ -32,6 +32,7 @@ import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.hisp.dhis.analytics.AnalyticsConstants.ANALYTICS_TBL_ALIAS; import static org.hisp.dhis.analytics.DataType.BOOLEAN; +import static org.hisp.dhis.analytics.common.CTEUtils.createFilterNameByIdentifier; import static org.hisp.dhis.analytics.event.data.OrgUnitTableJoiner.joinOrgUnitTables; import static org.hisp.dhis.analytics.util.AnalyticsUtils.withExceptionHandling; import static org.hisp.dhis.common.DataDimensionType.ATTRIBUTE; @@ -57,6 +58,7 @@ import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.analytics.analyze.ExecutionPlanStore; import org.hisp.dhis.analytics.common.CTEContext; +import org.hisp.dhis.analytics.common.CTEUtils; import org.hisp.dhis.analytics.common.ProgramIndicatorSubqueryBuilder; import org.hisp.dhis.analytics.event.EnrollmentAnalyticsManager; import org.hisp.dhis.analytics.event.EventQueryParams; @@ -503,7 +505,7 @@ private String addFiltersToWhereClause(EventQueryParams params) { return getQueryItemsAndFiltersWhereClause(params, new SqlHelper()); } - private String addCteFiltersToWhereClause(EventQueryParams params) { + private String addCteFiltersToWhereClause(EventQueryParams params, CTEContext cteContext) { StringBuilder whereClause = new StringBuilder(); // Iterate over each filter and apply the correct condition @@ -512,21 +514,32 @@ private String addCteFiltersToWhereClause(EventQueryParams params) { .filter(QueryItem::hasFilter) .toList()) { - String cteName = "filter_" + getIdentifier(item).replace('.', '_').toLowerCase(); - - for (QueryFilter filter : item.getFilters()) { - if ("NV".equals(filter.getFilter())) { // Handle null filters explicitly - whereClause.append(" AND ").append(cteName).append(".value IS NULL"); - } else { - String operator = getSqlOperator(filter); - String value = getSqlFilterValue(filter, item); - whereClause - .append(" AND ") - .append(cteName) - .append(".value ") - .append(operator) - .append(" ") - .append(value); + String cteName = CTEUtils.createFilterName(item); + + if (cteContext.containsCteFilter(cteName)) { + for (QueryFilter filter : item.getFilters()) { + if ("NV".equals(filter.getFilter())) { // Handle null filters explicitly + whereClause.append(" AND ").append(cteName).append(".value IS NULL"); + } else { + String operator = getSqlOperator(filter); + String value = getSqlFilterValue(filter, item); + whereClause + .append(" AND ") + .append(cteName) + .append(".value ") + .append(operator) + .append(" ") + .append(value); + } + } + } else { + // If the filter is not part of the CTE, apply it directly to the enrollment table + // using the standard where clause method + String filters = getQueryItemsAndFiltersWhereClause(params, new SqlHelper()); + if (StringUtils.isNotBlank(filters) && filters.trim().startsWith("where")) { + // remove the 'where' keyword + filters = filters.trim().substring(5); + whereClause.append("and ").append(filters); } } } @@ -598,26 +611,6 @@ SELECT DISTINCT ON (enrollment) enrollment, %s AS value .collect(Collectors.joining("\nUNION ALL\n")); } - private Stream asSqlCollection( - List queryItems, EventQueryParams params) { - return Optional.ofNullable(queryItems).orElse(List.of()).stream() - .map( - queryItem -> { - String sql = - queryItem.getFilters().stream() - .map(filter -> toSql(queryItem, filter, params)) - .collect(Collectors.joining(" AND ")); - return IdentifiableSql.builder() - .identifier(getIdentifier(queryItem)) - .sql(sql) - .build(); - }); - } - - private String getIdentifier(QueryItem queryItem) { - String stage = queryItem.hasProgramStage() ? queryItem.getProgramStage().getUid() : "default"; - return stage + "." + queryItem.getItemId(); - } @Override protected String getSelectClause(EventQueryParams params) { @@ -1009,6 +1002,7 @@ private String createOrderType(int offset) { private CTEContext getCteDefinitions(EventQueryParams params) { CTEContext cteContext = new CTEContext(); + for (QueryItem queryItem : params.getItems()) { if (queryItem.isProgramIndicator()) { ProgramIndicator pi = (ProgramIndicator) queryItem.getItem(); @@ -1029,7 +1023,7 @@ private CTEContext getCteDefinitions(EventQueryParams params) { params.getLatestEndDate(), cteContext); } - } else if (queryItem.hasProgramStage()) { + } else if (queryItem.hasProgramStage() && queryItem.hasProgram()) { // Generate CTE for program stage items String cteName = String.format( @@ -1096,10 +1090,10 @@ private void generateFilterCTEs(EventQueryParams params, CTEContext cteContext) // Process repeatable stage filters itemsByRepeatableFlag.getOrDefault(true, List.of()).stream() - .collect(groupingBy(this::getIdentifier)) + .collect(groupingBy(CTEUtils::getIdentifier)) .forEach( (identifier, items) -> { - String cteName = "filter_" + identifier.replace('.', '_').toLowerCase(); + String cteName = createFilterNameByIdentifier(identifier); String cteSql = buildFilterCteSql(items, params); cteContext.addCTE(cteName, cteSql); }); @@ -1109,9 +1103,11 @@ private void generateFilterCTEs(EventQueryParams params, CTEContext cteContext) .getOrDefault(false, List.of()) .forEach( queryItem -> { - String cteName = "filter_" + getIdentifier(queryItem).replace('.', '_').toLowerCase(); - String cteSql = buildFilterCteSql(List.of(queryItem), params); - cteContext.addCTE(cteName, cteSql); + if (queryItem.hasProgram() && queryItem.hasProgramStage()) { + String cteName = CTEUtils.createFilterName(queryItem); + String cteSql = buildFilterCteSql(List.of(queryItem), params); + cteContext.addCTE(cteName, cteSql); + } }); } @@ -1154,7 +1150,7 @@ private String buildEnrollmentQueryWithCte2(EventQueryParams params) { sql.append(" ") .append(getWhereClause(params)) .append(" ") - .append(addCteFiltersToWhereClause(params)); + .append(addCteFiltersToWhereClause(params, cteContext)); // 7. Order by sql.append(" ").append(getSortClause(params));