实践案例 | 如何使用多维数据库数据制做经营分析报表
小编推荐
在金蝶云星瀚产品中,合并报表及预算应用数据均存储在Kingdee多维数据库中,以解决EPM产品多维度、多层级数据处理的问题,有助于决策人员实时决策。
但客户在使用BI报表工具对合并报表和预算报表的数据做企业经营分析时,无法直接使用多维数据库作为数据源进行数据建模。如何高效解决该问题呢?
本期实践案例告诉你答案,通过多维数据转二维数据,并设置定时调度任务定期同步数据,快速实现经营分析报表的可视化~
案例撰稿人:林文诗。
1 业务背景
目前,X集团基于金蝶云·星瀚构建数字化运营管控平台,其中财务驾驶舱模块需要使用星瀚合并报表及预算报表作为数据源去做企业经营分析,可视化展现核心财务数据指标(如经营收款、营业收入、成本费用、利润总额等等),为管理层提供“一站式”决策分析支持的平台。在金蝶云星瀚产品中,合并报表及预算应用数据均存储在Kingdee多维数据库中,该多维数据库是金蝶自主研发的多维数据库产品,提供多维数据的存储、查询和计算能力,实现了毫秒级的数据快速检索,有效解决了EPM产品多维度、多层级数据处理的问题,为决策人员实时决策发挥了重要作用。
但该客户在使用轻分析对合并报表和预算报表的数据做企业经营分析时,遇到了多维数据库无法直接作为数据源做数据建模的问题(目前市面上尚未有BI报表工具支持)。因此,客户急需一个高效的解决方案,来实现使用多维数据库数据作为BI工具数据源制作可视化经营分析报表,为决策人员提供有效支撑。
2 解决方案
2.1 方案整体思路
针对以上业务场景,如何解决多维数据库无法作为BI报表数据源的问题,可以从以下3点切入:
1) 多维数据转二维数据:将多维数据库的数据抽取到一个二开中间表,然后再将该中间表作为企业经营分析报表的数据源使用。
2) 采集数据的时效:财务报表/预算报表通常都是定期上报(按月/季度/年编制报表),可通过设置定时调度任务定期将多维数据库数据同步至中间表。
3) 取数配置的可变性:经营分析指标的取数逻辑有变更的可能性,可以通过开发一个指标取数方案表,给用户提供一个可修改指标取数配置的入口。
方案业务流程图
2.2 方案实现步骤
步骤一:开发指标取数方案表(基础资料表单),该表单主要包含多维数据库取数体系及维度信息。如下图所示,“合并体系”为对应财务报表的取数体系,取数维度为财务报表模板内各个财务指标对应的取数维度,用户可以根据【企业绩效云】→【合并报表】→【系统配置】→【报表模板】对应财务报表的维度及成员信息在“合并指标维度信息”配置财务指标的取数维度。
预算指标可以根据【企业绩效云】→【预算体系】→【预算模板】→【预算模板】对应预算报表的维度及成员信息在“预算指标维度信息”配置。(注:因为每个项目的自定义维度字段有所差异,可根据项目进行微调)
财务报表取数方案明细图
预算取数方案明细图
步骤二:编写定时调度任务插件,插件代码主要分为三部分:
1) 设置调度任务参数syscDate(用于同步数据的会计期间,参数格式:yyyyMM,参数类型:文本),当syscDate为空时,查询上月的财务报表/预算数据,否则查询syscDate所属期间的数据。代码示例如下:
@Override public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException { Calendar calendar = Calendar.getInstance(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM"); if (map.get("syncDate") != null) { try { Date syncDate = dateFormat.parse(map.get("syncDate").toString()); calendar.setTime(syncDate); } catch (ParseException e) { e.getMessage(); } } else { //默认取上月数据 calendar.setTime(new Date()); calendar.add(Calendar.MONTH, -1); } //取数财年 year = calendar.get(Calendar.YEAR); //取数月度 month = calendar.get(Calendar.MONTH) + 1; //拉取多维数据库数据并存于基表 this.dealAccessSolution(); }
2) 查询指标取数方案表的所有经营分析指标的取数维度信息,代码示例如下:
public void dealAccessSolution() { QFilter filter = new QFilter("status", QCP.equals, "C"); filter.and("enable", QCP.equals, "1"); //查询取数方案 String selectProperties = "name,zlkj_cm_model,zlkj_eb_model,zlkj_eb_dataset,zlkj_isbudget,zlkj_projecttype,zlkj_cm_detail.zlkj_cm_entity,zlkj_cm_detail.zlkj_cm_account,zlkj_cm_detail.zlkj_cm_scenario,zlkj_cm_detail.zlkj_cm_process,zlkj_cm_detail.zlkj_cm_audittrail ,zlkj_cm_detail.zlkj_cm_isicnone,zlkj_cm_detail.zlkj_cm_isc1none,zlkj_cm_detail.zlkj_cm_isc2none,zlkj_cm_detail.zlkj_cm_isc3none,zlkj_cm_detail.zlkj_cm_isc4none,zlkj_cm_detail.zlkj_cm_changetype,zlkj_eb_detail.zlkj_eb_entity,zlkj_eb_detail.zlkj_eb_account,zlkj_eb_detail.zlkj_eb_version,zlkj_eb_detail.zlkj_eb_datatype,zlkj_eb_detail.zlkj_eb_metric,zlkj_eb_detail.zlkj_eb_attribute,zlkj_eb_detail.zlkj_eb_subclass,zlkj_eb_detail.zlkj_eb_zmisc,zlkj_eb_detail.zlkj_eb_isicnone,zlkj_eb_detail.zlkj_eb_isprojectnone,zlkj_eb_detail.zlkj_eb_changetype"; DynamicObject[] accessSolutions = BusinessDataServiceHelper.load(ACCESSSOLUTION, selectProperties, new QFilter[]{filter}); initBaseDataName(); DynamicObject solution; DynamicObjectCollection solutionDetail; for (int i = 0; i < accessSolutions.length; i++) { solution = accessSolutions[i]; if (solution.getBoolean("zlkj_isbudget")) { //全面预算 solutionDetail = solution.getDynamicObjectCollection("zlkj_eb_detail"); queryBudgetData(solution.getLong("zlkj_eb_model.id"), solution.getLong("zlkj_eb_dataset.id"), solution.getString("name"), solutionDetail); } else { //合并报表 solutionDetail = solution.getDynamicObjectCollection("zlkj_cm_detail"); queryFinData(solution.getDynamicObject("zlkj_cm_model"), solution.getString("name"), solutionDetail); } } }
3) 根据每个经营分析指标的取数维度编写多维数据库查询sql,调用多维数据库工具类查询数据:
kd.fi.bcm.business.serviceHelper.OlapServiceHelper(合并报表)
kd.epm.eb.common.shrek.controller.ShrekOlapServiceHelper(预算)
然后,将查询结果保存到企业经营数据基表。
财务数据查询代码示例如下:
public void queryFinData(DynamicObject model, String indexName, DynamicObjectCollection solutionDetail) { //传入体系cube SQLBuilder sql = new SQLBuilder(model.getString("number")); //需查询的财务维度 String[] finDims = {"Entity", "ChangeType", "Account", "Year", "Period", "InternalCompany", "Custom2", "Scenario", "Process", "Currency", "AuditTrail"}; sql.addSelectField(finDims); DynamicObject solution; List<String> orgNumber; List<String> changeType; for (int j = 0; j < solutionDetail.size(); j++) { solution = solutionDetail.get(j); sql.getFilters().clear(); //组织维度 orgNumber = solution.getDynamicObjectCollection("zlkj_cm_entity").stream().map(a -> a.getString("fbasedataid.number")).collect(Collectors.toList()); sql.addFilter("Entity", orgNumber.toArray(new String[0])); //科目 String account = solution.getString("zlkj_cm_account"); sql.addFilter("Account", new String[]{account}); //情景 sql.addFilter("Scenario", new String[]{solution.getString("zlkj_cm_scenario")}); //过程 sql.addFilter("Process", new String[]{solution.getString("zlkj_cm_process")}); //审计线索 sql.addFilter("AuditTrail", new String[]{solution.getString("zlkj_cm_audittrail")}); //往来组织 boolean isIcNone = solution.getBoolean("zlkj_cm_isicnone"); if(isIcNone){ sql.addFilter("InternalCompany", new String[]{"ICNone"}); }else{ QFilter filter = new QFilter("model",QCP.equals,model.get("id")); filter.and("isleaf",QCP.equals,"1"); DynamicObjectCollection dynCol = QueryServiceHelper.query("bcm_icmembertree","number",new QFilter[]{filter}); List<String> icNumber = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); sql.addFilter("InternalCompany", icNumber.toArray(new String[0])); } //C1 boolean isC1None = solution.getBoolean("zlkj_cm_isc1none"); if(isC1None){ sql.addFilter("Custom1", new String[]{"C1None"}); }else{ QFilter filter = new QFilter("model",QCP.equals,model.get("id")); filter.and("isleaf",QCP.equals,"1"); filter.and("dimension.number",QCP.equals,"Custom1"); DynamicObjectCollection dynCol = QueryServiceHelper.query("bcm_userdefinedmembertree","number",new QFilter[]{filter}); List<String> c1Number = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); sql.addFilter("Custom1", c1Number.toArray(new String[0])); } //C2项目 boolean isC2None = solution.getBoolean("zlkj_cm_isc2none"); if(isC2None){ sql.addFilter("Custom2", "C2None"); }else{ QFilter filter = new QFilter("model",QCP.equals,model.get("id")); filter.and("isleaf",QCP.equals,"1"); filter.and("dimension.number",QCP.equals,"Custom2"); DynamicObjectCollection dynCol = QueryServiceHelper.query("bcm_userdefinedmembertree","number",new QFilter[]{filter}); List<String> c2Number = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); sql.addFilter("Custom2", c2Number.toArray(new String[0])); } //C3 boolean isC3None = solution.getBoolean("zlkj_cm_isc3none"); if(isC3None){ sql.addFilter("Custom3", new String[]{"C3None"}); }else{ QFilter filter = new QFilter("model",QCP.equals,model.get("id")); filter.and("isleaf",QCP.equals,"1"); filter.and("dimension.number",QCP.equals,"Custom3"); DynamicObjectCollection dynCol = QueryServiceHelper.query("bcm_userdefinedmembertree","number",new QFilter[]{filter}); List<String> c3Number = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); sql.addFilter("Custom3", c3Number.toArray(new String[0])); } //C4 boolean isC4None = solution.getBoolean("zlkj_cm_isc4none"); if(isC3None){ sql.addFilter("Custom4", new String[]{"C4None"}); }else{ QFilter filter = new QFilter("model",QCP.equals,model.get("id")); filter.and("isleaf",QCP.equals,"1"); filter.and("dimension.number",QCP.equals,"Custom4"); DynamicObjectCollection dynCol = QueryServiceHelper.query("bcm_userdefinedmembertree","number",new QFilter[]{filter}); List<String> c4Number = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); sql.addFilter("Custom4", c4Number.toArray(new String[0])); } //变动类型 changeType = solution.getDynamicObjectCollection("zlkj_cm_changetype").stream().map(a -> a.getString("fbasedataid.number")).collect(Collectors.toList()); sql.addFilter("ChangeType", changeType.toArray(new String[0])); //财年 sql.addFilter("Year", new String[]{"FY" + year}); //期间 sql.addFilter("Period", new String[]{"M_M" + month / 10 + month % 10}); MDResultSet rs = OlapServiceHelper.queryData(sql); //保存多维数据库查询结果 this.saveFinData(rs, indexName,account); } }
预算数据查询代码示例如下:
public void queryBudgetData(Long modelId, Long dataSetId, String indexName, DynamicObjectCollection solutionDetail) { //预算体系 IModelCacheHelper cacheHelper = ModelCacheContext.getOrCreate(modelId); //数据集 Dataset dataset = Dataset.of(BusinessDataServiceHelper.loadSingleFromCache(dataSetId, EB_DATASET)); DynamicObject solution; List<String> orgNumber; List<String> changeType; SelectCommandInfo queryInfo = new SelectCommandInfo(); //需查询的预算维度 String[] budgetDims = {"Entity", "ChangeType", "Account", "DataType", "BudgetPeriod", "Version", "Currency", "AuditTrail"}; queryInfo.addDims(budgetDims); queryInfo.addMeasures("FMONEY"); queryInfo.setExcludeDynamicCalcResult(false); for (int j = 0; j < solutionDetail.size(); j++) { solution = solutionDetail.get(j); queryInfo.getFilter().clear(); //组织维度 orgNumber = solution.getDynamicObjectCollection("zlkj_eb_entity").stream().map(a -> a.getString("fbasedataid.number")).collect(Collectors.toList()); queryInfo.addFilter("Entity", orgNumber.toArray(new String[0])); //科目 String account = solution.getString("zlkj_eb_account"); queryInfo.addFilter("Account", new String[]{account}); //数据类型 queryInfo.addFilter("DataType", new String[]{solution.getString("zlkj_eb_changetype")}); //度量 queryInfo.addFilter("Metric", new String[]{solution.getString("zlkj_eb_metric")}); //审计线索 queryInfo.addFilter("AuditTrail", new String[]{solution.getString("zlkj_eb_audittrail")}); //往来组织 boolean isIcNone = solution.getBoolean("zlkj_eb_isicnone"); if(isIcNone){ queryInfo.addFilter("InternalCompany", new String[]{"ICNone"}); }else{ QFilter filter = new QFilter("model",QCP.equals,modelId); filter.and("isleaf",QCP.equals,"1"); DynamicObjectCollection dynCol = QueryServiceHelper.query("epm_icmembertree","number",new QFilter[]{filter}); List<String> icNumber = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); queryInfo.addFilter("InternalCompany", icNumber.toArray(new String[0])); } //细分类 queryInfo.addFilter("Subclass", new String[]{solution.getString("zlkj_eb_subclass")}); //项目 boolean isC2None = solution.getBoolean("zlkj_eb_isprojectnone"); if(isC2None){ queryInfo.addFilter("project", new String[]{"PRJNone"}); }else{ QFilter filter = new QFilter("model",QCP.equals,modelId); filter.and("isleaf",QCP.equals,"1"); filter.and("dimension.number",QCP.equals,"project"); DynamicObjectCollection dynCol = QueryServiceHelper.query("epm_userdefinedmembertree","number",new QFilter[]{filter}); List<String> c2Number = dynCol.stream().map(a->a.getString("number")).collect(Collectors.toList()); queryInfo.addFilter("project", c2Number.toArray(new String[0])); } //属性 queryInfo.addFilter("Attribute", new String[]{solution.getString("zlkj_eb_attribute")}); //综合维 queryInfo.addFilter("ZMISC", new String[]{solution.getString("zlkj_eb_zmisc")}); //变动类型 changeType = solution.getDynamicObjectCollection("zlkj_eb_changetype").stream().map(a -> a.getString("fbasedataid.number")).collect(Collectors.toList()); queryInfo.addFilter("ChangeType", changeType.toArray(new String[0])); //预算期间 queryInfo.addFilter("BudgetPeriod", new String[]{"FY" + year}); List<MembersKey> budgetList = ShrekOlapServiceHelper.queryData(cacheHelper.getModelobj(), dataset, queryInfo); //保存多维数据库查询结果 saveBudgetData(budgetList, indexName,account); } }
步骤三:企业经营数据基表存在数据后,即可以把该表作为轻分析数据源去制作企业经营分析报表。
企业经营数据底表
步骤四:利用轻分析制作企业经营分析报表的主要步骤为【数据建模】→【数据斗方】→【仪表板】。首先,将多维数据提取到企业经营数据基表后,可使用业务实体或当前数据中心的方式做数据建模;其次,选择合适的图表类型及展示维度创建数据斗方卡片;最后由多个数据斗方卡片组合成一个经营分析仪表板。
数据建模图
数据斗方卡片
收入构成分析仪表板
企业收支与损益概况图
3 方案的复用价值
行业的普适程度
该方案不限制行业,所有需要使用多维数据库数据作为数据分析的数据源的场景,均可适用,只需要根据客户合并报表/全面预算的应用维度对指标取数方案表做适当的调整即可。而且该方案适用于所有BI报表平台,不局限于轻分析平台。
对客户的价值
自动化采集合并报表/全面预算数据的方式,提高数据的采集效率,增加源数据的准确性。通过调整指标取数方案表的配置,即可达到对企业经营分析指标的取数逻辑的增删改操作,增加方案的扩展性,一定程度上节省后期的运维成本。
4 注意事项
1.当客户环境为分应用部署时,二开代码所属容器应存在fi-bcm-business-1.0.jar、epm-eb-common-1.0.jar两个jar包。
2.未来轻分析将支持直连多维数据库,敬请期待~
5 相关资料
多维数据库开发指导文档:
https://vip.kingdee.com/article/96990049127725568
#往期推荐#
更多精彩内容,“码”上了解!↓
实践案例 | 如何使用多维数据库数据制做经营分析报表
本文2024-09-23 00:20:47发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-138877.html
- 鼎捷EAI整合規範文件V3.1.07 (集團).pdf
- 鼎捷OpenAPI應用場景說明_基礎資料.pdf
- 鼎捷OpenAPI應用場景說明_財務管理.pdf
- 鼎捷T100 API設計器使用手冊T100 APIDesigner(V1.0).docx
- 鼎新e-GoB2雲端ERP B2 線上課程E6-2應付票據整批郵寄 領取.pdf
- 鼎新e-GoB2雲端ERP B2 線上課程A4使用者建立權限設定.pdf
- 鼎新e-GoB2雲端ERP B2 線上課程C3會計開帳與會計傳票.pdf
- 鼎新e-GoB2雲端ERP B2 線上課程E6-1應付票據.pdf
- 鼎新e-GoB2雲端ERP B2 線上課程A5-1進銷存參數設定(初階篇).pdf
- 鼎新e-GoB2雲端ERP B2 線上課程D2帳款開帳與票據開帳.pdf