diff --git a/core/core-backend/src/main/java/io/dataease/chart/charts/impl/table/TablePivotHandler.java b/core/core-backend/src/main/java/io/dataease/chart/charts/impl/table/TablePivotHandler.java index 2980fda8f5..cd45393fa2 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/charts/impl/table/TablePivotHandler.java +++ b/core/core-backend/src/main/java/io/dataease/chart/charts/impl/table/TablePivotHandler.java @@ -1,11 +1,383 @@ package io.dataease.chart.charts.impl.table; import io.dataease.chart.charts.impl.GroupChartHandler; +import io.dataease.engine.constant.DeTypeConstants; +import io.dataease.engine.constant.ExtFieldConstant; +import io.dataease.engine.sql.SQLProvider; +import io.dataease.engine.trans.Dimension2SQLObj; +import io.dataease.engine.trans.Quota2SQLObj; +import io.dataease.engine.utils.Utils; +import io.dataease.extensions.datasource.dto.DatasourceRequest; +import io.dataease.extensions.datasource.dto.DatasourceSchemaDTO; +import io.dataease.extensions.datasource.model.SQLMeta; +import io.dataease.extensions.datasource.provider.Provider; +import io.dataease.extensions.view.dto.*; +import io.dataease.extensions.view.util.FieldUtil; +import io.dataease.utils.BeanUtils; +import io.dataease.utils.IDUtils; +import io.dataease.utils.JsonUtil; import lombok.Getter; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import java.util.*; + @Component public class TablePivotHandler extends GroupChartHandler { @Getter private String type = "table-pivot"; + + @Override + public T calcChartResult(ChartViewDTO view, AxisFormatResult formatResult, CustomFilterResult filterResult, Map sqlMap, SQLMeta sqlMeta, Provider provider) { + T result = super.calcChartResult(view, formatResult, filterResult, sqlMap, sqlMeta, provider); + Map customCalc = calcCustomExpr(view, filterResult, sqlMap, sqlMeta, provider); + result.getData().put("customCalc", customCalc); + return result; + } + + private Map calcCustomExpr(ChartViewDTO view, CustomFilterResult filterResult, Map sqlMap, SQLMeta sqlMeta, Provider provider) { + Object totalStr = JsonUtil.toJSONString(view.getCustomAttr().get("tableTotal")); + TableTotal tableTotal = JsonUtil.parseObject((String) totalStr, TableTotal.class); + var dsMap = (Map) sqlMap.get("dsMap"); + List dsList = new ArrayList<>(); + for (Map.Entry next : dsMap.entrySet()) { + dsList.add(next.getValue().getType()); + } + boolean needOrder = Utils.isNeedOrder(dsList); + boolean crossDs = Utils.isCrossDs(dsMap); + DatasourceRequest datasourceRequest = new DatasourceRequest(); + datasourceRequest.setDsList(dsMap); + var allFields = (List) filterResult.getContext().get("allFields"); + var rowAxis = view.getXAxis(); + var colAxis = view.getXAxisExt(); + var dataMap = new HashMap(); + + // 行总计,列维度聚合加上自定义字段 + var row = tableTotal.getRow(); + if (row.isShowGrandTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : row.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + field.setDeType(DeTypeConstants.DE_FLOAT); + BeanUtils.copyBean(field, totalCfg); + field.setId(IDUtils.snowID()); + field.setExtField(ExtFieldConstant.EXT_CALC); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + Dimension2SQLObj.dimension2sqlObj(sqlMeta, colAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + dataMap.put("rowTotal", tmp); + tmp.put("data", buildCustomCalcResult(data, colAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + } + } + // 行小计,列维度聚合,自定义指标数 * (行维度的数量 - 1) + if (row.isShowSubTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : row.getCalcSubTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + field.setDeType(DeTypeConstants.DE_FLOAT); + BeanUtils.copyBean(field, totalCfg); + field.setId(IDUtils.snowID()); + field.setExtField(ExtFieldConstant.EXT_CALC); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + var tmpData = new ArrayList>(); + dataMap.put("rowSubTotal", tmpData); + for (int i = 0; i < rowAxis.size(); i++) { + if ( i == rowAxis.size() - 1) { + break; + } + var xAxis = new ArrayList<>(colAxis); + var subRowAxis = rowAxis.subList(0, i + 1); + xAxis.addAll(subRowAxis); + if (!yAxis.isEmpty()) { + Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + tmp.put("data", buildCustomCalcResult(data, xAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + tmpData.add(tmp); + } + } + } + } + // 列总计,行维度聚合加上自定义字段 + var col = tableTotal.getCol(); + if (col.isShowGrandTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : col.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + field.setDeType(DeTypeConstants.DE_FLOAT); + BeanUtils.copyBean(field, totalCfg); + field.setId(IDUtils.snowID()); + field.setExtField(ExtFieldConstant.EXT_CALC); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + Dimension2SQLObj.dimension2sqlObj(sqlMeta, rowAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + dataMap.put("colTotal", tmp); + tmp.put("data", buildCustomCalcResult(data, rowAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + } + } + // 列小计,行维度聚合,自定义指标数 * (列维度的数量 - 1) + if (col.isShowSubTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : col.getCalcSubTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + field.setDeType(DeTypeConstants.DE_FLOAT); + BeanUtils.copyBean(field, totalCfg); + field.setId(IDUtils.snowID()); + field.setExtField(ExtFieldConstant.EXT_CALC); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + var tmpData = new ArrayList>(); + dataMap.put("colSubTotal", tmpData); + for (int i = 0; i < colAxis.size(); i++) { + if ( i == colAxis.size() - 1) { + break; + } + var xAxis = new ArrayList<>(rowAxis); + var subColAxis = colAxis.subList(0, i + 1); + xAxis.addAll(subColAxis); + if (!yAxis.isEmpty()) { + Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + tmp.put("data", buildCustomCalcResult(data, xAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + tmpData.add(tmp); + } + } + } + } + // 行列交叉部分总计,无聚合,直接算,用列总计公式 + if (row.isShowGrandTotals() && col.isShowGrandTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : col.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + BeanUtils.copyBean(field, totalCfg); + field.setExtField(ExtFieldConstant.EXT_CALC); + field.setDeType(DeTypeConstants.DE_FLOAT); + field.setId(IDUtils.snowID()); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + // 清掉聚合轴 + Dimension2SQLObj.dimension2sqlObj(sqlMeta, Collections.emptyList(), FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + dataMap.put("rowColTotal", tmp); + var tmpData = new HashMap(); + for (int i = 0; i < yAxis.size(); i++) { + var a = yAxis.get(i); + tmpData.put(a.getDataeaseName(), data.getFirst()[i]); + } + tmp.put("data", tmpData); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + } + } + // 行总计里面的列小计 + if (row.isShowGrandTotals() && col.isShowSubTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : col.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + BeanUtils.copyBean(field, totalCfg); + field.setExtField(ExtFieldConstant.EXT_CALC); + field.setDeType(DeTypeConstants.DE_FLOAT); + field.setId(IDUtils.snowID()); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + var tmpData = new ArrayList>(); + dataMap.put("colSubInRowTotal", tmpData); + for (int i = 0; i < colAxis.size(); i++) { + if ( i == colAxis.size() - 1) { + break; + } + var xAxis = colAxis.subList(0, i + 1); + Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + tmp.put("data", buildCustomCalcResult(data, xAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + tmpData.add(tmp); + } + } + } + // 列总计里面的行小计 + if (col.isShowGrandTotals() && row.isShowGrandTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : row.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + BeanUtils.copyBean(field, totalCfg); + field.setExtField(ExtFieldConstant.EXT_CALC); + field.setDeType(DeTypeConstants.DE_FLOAT); + field.setId(IDUtils.snowID()); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + var tmpData = new ArrayList>(); + dataMap.put("rowSubInColTotal", tmpData); + for (int i = 0; i < rowAxis.size(); i++) { + if ( i == rowAxis.size() - 1) { + break; + } + var xAxis = rowAxis.subList(0, i + 1); + Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + tmp.put("data", buildCustomCalcResult(data, xAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + tmpData.add(tmp); + } + } + } + // 行小计和列小计相交部分 + if (row.isShowSubTotals() && col.isShowSubTotals()) { + var yAxis = new ArrayList(); + for (TableCalcTotalCfg totalCfg : col.getCalcTotals().getCfg()) { + if (StringUtils.equalsIgnoreCase(totalCfg.getAggregation(), "CUSTOM")){ + var field = new ChartViewFieldDTO(); + BeanUtils.copyBean(field, totalCfg); + field.setExtField(ExtFieldConstant.EXT_CALC); + field.setDeType(DeTypeConstants.DE_FLOAT); + field.setId(IDUtils.snowID()); + yAxis.add(field); + } + } + if (!yAxis.isEmpty()) { + var tmpData = new ArrayList>>(); + dataMap.put("rowSubInColSub", tmpData); + for (int i = 0; i < rowAxis.size(); i++) { + if ( i == rowAxis.size() - 1) { + break; + } + var tmpList = new ArrayList>(); + tmpData.add(tmpList); + var subRow = rowAxis.subList(0, i + 1); + var xAxis = new ArrayList<>(subRow); + for (int j = 0; j < colAxis.size(); j++) { + if ( j == colAxis.size() - 1) { + break; + } + var subCol = colAxis.subList(0, j + 1); + xAxis.addAll(subCol); + Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, FieldUtil.transFields(allFields), crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage); + String querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view); + querySql = provider.rebuildSQL(querySql, sqlMeta, crossDs, dsMap); + datasourceRequest.setQuery(querySql); + logger.debug("calcite chart sql: " + querySql); + List data = (List) provider.fetchResultField(datasourceRequest).get("data"); + nullToBlank(data); + var tmp = new HashMap(); + tmp.put("data", buildCustomCalcResult(data, xAxis, yAxis)); + tmp.put("sql", Base64.getEncoder().encodeToString(querySql.getBytes())); + tmpList.add(tmp); + } + } + } + } + return dataMap; + } + + private Map buildCustomCalcResult(List data, List dimAxis, List quotaAxis) { + var rootResult = new HashMap(); + for (int i = 0; i < data.size(); i++) { + var rowData = data.get(i); + Map curSubMap = rootResult; + for (int j = 0; j < dimAxis.size(); j++) { + var tmpMap = curSubMap.get(rowData[j]); + if (tmpMap == null) { + tmpMap = new HashMap(); + curSubMap.put(rowData[j], tmpMap); + curSubMap = (Map) tmpMap; + } else { + curSubMap = (Map) tmpMap; + } + if (j == dimAxis.size() - 1) { + for (int k = 0; k < quotaAxis.size(); k++) { + var qAxis = quotaAxis.get(k); + curSubMap.put(qAxis.getDataeaseName(), rowData[j + k + 1]); + } + } + } + } + return rootResult; + } + + private void nullToBlank(List data) { + data.forEach(r -> { + for (int i = 0; i < r.length; i++) { + if (r[i] == null) { + r[i] = ""; + } + } + }); + } + } diff --git a/core/core-backend/src/main/java/io/dataease/engine/trans/Quota2SQLObj.java b/core/core-backend/src/main/java/io/dataease/engine/trans/Quota2SQLObj.java index 54232693af..12c04ebfba 100644 --- a/core/core-backend/src/main/java/io/dataease/engine/trans/Quota2SQLObj.java +++ b/core/core-backend/src/main/java/io/dataease/engine/trans/Quota2SQLObj.java @@ -100,6 +100,9 @@ public class Quota2SQLObj { String cast = String.format(SQLConstants.CAST, originField, Objects.equals(y.getDeType(), DeTypeConstants.DE_INT) ? SQLConstants.DEFAULT_INT_FORMAT : SQLConstants.DEFAULT_FLOAT_FORMAT); if (StringUtils.equalsIgnoreCase(y.getSummary(), "count_distinct")) { fieldName = String.format(SQLConstants.AGG_FIELD, "COUNT", "DISTINCT " + cast); + } else if (y.getSummary() == null){ + // 透视表自定义汇总不用聚合 + fieldName = cast; } else { fieldName = String.format(SQLConstants.AGG_FIELD, y.getSummary(), cast); } diff --git a/core/core-frontend/src/models/chart/chart-attr.d.ts b/core/core-frontend/src/models/chart/chart-attr.d.ts index 9392f02a53..7a0dfd036a 100644 --- a/core/core-frontend/src/models/chart/chart-attr.d.ts +++ b/core/core-frontend/src/models/chart/chart-attr.d.ts @@ -478,9 +478,13 @@ declare interface CalcTotals { /** * 汇总聚合配置 */ -declare interface CalcTotalCfg { +declare interface CalcTotalCfg extends Axis { dataeaseName: string - aggregation: 'MIN' | 'MAX' | 'AVG' | 'SUM' | '' + aggregation: 'MIN' | 'MAX' | 'AVG' | 'SUM' | 'CUSTOM' | '' + /** + * 自定义汇总表达式 + */ + originName: string } /** diff --git a/core/core-frontend/src/models/chart/chart.d.ts b/core/core-frontend/src/models/chart/chart.d.ts index 0365d5f5f0..2e4860e5c6 100644 --- a/core/core-frontend/src/models/chart/chart.d.ts +++ b/core/core-frontend/src/models/chart/chart.d.ts @@ -32,6 +32,7 @@ declare interface Chart { fields: ChartViewField[] tableRow: [] } + customCalc: any } xAxis?: Axis[] xAxisExt?: Axis[] diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/CustomAggrEdit.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/CustomAggrEdit.vue new file mode 100644 index 0000000000..63ad7bf161 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/CustomAggrEdit.vue @@ -0,0 +1,628 @@ + + + + + + + diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue index 0a25c923c9..f579918733 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue @@ -1,8 +1,9 @@