From 351ac5e6a71b3343b452aaf12e14590ef56e074a Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Wed, 30 Dec 2020 15:55:53 +0100 Subject: [PATCH] query designer - grop filter (having clause) --- packages/sqltree/src/dumpSqlCommand.ts | 5 + packages/sqltree/src/types.ts | 1 + .../web/src/designer/DesignerQueryDumper.ts | 97 +++++++++++++------ .../web/src/designer/QueryDesignColumns.js | 17 ++++ packages/web/src/designer/types.ts | 3 +- 5 files changed, 90 insertions(+), 33 deletions(-) diff --git a/packages/sqltree/src/dumpSqlCommand.ts b/packages/sqltree/src/dumpSqlCommand.ts index a9764097..564eb4fa 100644 --- a/packages/sqltree/src/dumpSqlCommand.ts +++ b/packages/sqltree/src/dumpSqlCommand.ts @@ -36,6 +36,11 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) { dmp.putCollection(', ', cmd.groupBy, (expr) => dumpSqlExpression(dmp, expr)); dmp.put('&n'); } + if (cmd.having) { + dmp.put('&n^having '); + dumpSqlCondition(dmp, cmd.having); + dmp.put('&n'); + } if (cmd.orderBy) { dmp.put('&n^order ^by '); dmp.putCollection(', ', cmd.orderBy, (expr) => { diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts index f778ebc6..5bcd0955 100644 --- a/packages/sqltree/src/types.ts +++ b/packages/sqltree/src/types.ts @@ -16,6 +16,7 @@ export interface Select { orderBy?: OrderByExpression[]; groupBy?: Expression[]; where?: Condition; + having?: Condition; } export type UpdateField = Expression & { targetColumn: string }; diff --git a/packages/web/src/designer/DesignerQueryDumper.ts b/packages/web/src/designer/DesignerQueryDumper.ts index cc589383..5b8e319c 100644 --- a/packages/web/src/designer/DesignerQueryDumper.ts +++ b/packages/web/src/designer/DesignerQueryDumper.ts @@ -1,5 +1,14 @@ import _ from 'lodash'; -import { dumpSqlSelect, Select, JoinType, Condition, Relation, mergeConditions, Source } from 'dbgate-sqltree'; +import { + dumpSqlSelect, + Select, + JoinType, + Condition, + Relation, + mergeConditions, + Source, + ResultField, +} from 'dbgate-sqltree'; import { EngineDriver } from 'dbgate-types'; import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types'; import { DesignerComponent } from './DesignerComponentCreator'; @@ -93,6 +102,59 @@ export class DesignerQueryDumper { } } + addGroupConditions(select: Select, tables: DesignerTableInfo[], selectIsGrouped: boolean) { + for (const column of this.designer.columns || []) { + if (!column.groupFilter) continue; + const table = (this.designer.tables || []).find((x) => x.designerId == column.designerId); + if (!table) continue; + if (!tables.find((x) => x.designerId == table.designerId)) continue; + + const condition = parseFilter(column.groupFilter, findDesignerFilterType(column, this.designer)); + if (condition) { + select.having = mergeConditions( + select.having, + _.cloneDeepWith(condition, (expr) => { + if (expr.exprType == 'placeholder') { + return this.getColumnOutputExpression(column, selectIsGrouped); + } + }) + ); + } + } + } + + getColumnOutputExpression(col, selectIsGrouped): ResultField { + const source = findQuerySource(this.designer, col.designerId); + const { columnName } = col; + let { alias } = col; + if (selectIsGrouped && !col.isGrouped) { + // use aggregate + const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate; + if (!alias) alias = `${aggregate}(${columnName})`; + + return { + exprType: 'call', + func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate, + argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null, + alias, + args: [ + { + exprType: 'column', + columnName, + source, + }, + ], + }; + } else { + return { + exprType: 'column', + columnName, + alias, + source, + }; + } + } + run() { let res: Select = null; for (const component of this.components) { @@ -120,37 +182,7 @@ export class DesignerQueryDumper { if (outputColumns.length == 0) { res.selectAll = true; } else { - res.columns = outputColumns.map((col) => { - const source = findQuerySource(this.designer, col.designerId); - const { columnName } = col; - let { alias } = col; - if (selectIsGrouped && !col.isGrouped) { - // use aggregate - const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate; - if (!alias) alias = `${aggregate}(${columnName})`; - - return { - exprType: 'call', - func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate, - argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null, - alias, - args: [ - { - exprType: 'column', - columnName, - source, - }, - ], - }; - } else { - return { - exprType: 'column', - columnName, - alias, - source, - }; - } - }); + res.columns = outputColumns.map((col) => this.getColumnOutputExpression(col, selectIsGrouped)); } const groupedColumns = topLevelColumns.filter((x) => x.isGrouped); @@ -176,6 +208,7 @@ export class DesignerQueryDumper { } this.addConditions(res, topLevelTables); + this.addGroupConditions(res, topLevelTables, selectIsGrouped); return res; } diff --git a/packages/web/src/designer/QueryDesignColumns.js b/packages/web/src/designer/QueryDesignColumns.js index b58d1f08..acea7edd 100644 --- a/packages/web/src/designer/QueryDesignColumns.js +++ b/packages/web/src/designer/QueryDesignColumns.js @@ -38,6 +38,8 @@ export default function QueryDesignColumns({ value, onChange }) { [onChange] ); + const hasGroupedColumn = !!(columns || []).find((x) => x.isGrouped); + return ( @@ -136,6 +138,21 @@ export default function QueryDesignColumns({ value, onChange }) { /> )} /> + {hasGroupedColumn && ( + ( + { + changeColumn({ ...row, groupFilter }); + }} + /> + )} + /> + )}