实践案例 | 如何使用多维数据库数据制做经营分析报表

在金蝶云星瀚产品中,合并报表及预算应用数据均存储在Kingdee多维数据库中,以解决EPM产品多维度、多层级数据处理的问题,有助于决策人员实时决策。 但客户在使用BI报表工具对合并报表和预算报表的数据做企业经营分析时,无法直接使用多维数据库作为数据源进行数据建模。如何高效解决该问题呢? 本期实践案例告诉你答案,通过多维数据转二维数据,并设置定时调度任务定期同步数据,快速实现经营分析报表的可视化~ 案例撰稿人:林文诗。 目前,X集团基于金蝶云·星瀚构建数字化运营管控平台,其中财务驾驶舱模块需要使用星瀚合并报表及预算报表作为数据源去做企业经营分析,可视化展现核心财务数据指标(如经营收款、营业收入、成本费用、利润总额等等),为管理层提供“一站式”决策分析支持的平台。在金蝶云星瀚产品中,合并报表及预算应用数据均存储在Kingdee多维数据库中,该多维数据库是金蝶自主研发的多维数据库产品,提供多维数据的存储、查询和计算能力,实现了毫秒级的数据快速检索,有效解决了EPM产品多维度、多层级数据处理的问题,为决策人员实时决策发挥了重要作用。 但该客户在使用轻分析对合并报表和预算报表的数据做企业经营分析时,遇到了多维数据库无法直接作为数据源做数据建模的问题(目前市面上尚未有BI报表工具支持)。因此,客户急需一个高效的解决方案,来实现使用多维数据库数据作为BI工具数据源制作可视化经营分析报表,为决策人员提供有效支撑。 针对以上业务场景,如何解决多维数据库无法作为BI报表数据源的问题,可以从以下3点切入: 1) 多维数据转二维数据:将多维数据库的数据抽取到一个二开中间表,然后再将该中间表作为企业经营分析报表的数据源使用。 2) 采集数据的时效:财务报表/预算报表通常都是定期上报(按月/季度/年编制报表),可通过设置定时调度任务定期将多维数据库数据同步至中间表。 3) 取数配置的可变性:经营分析指标的取数逻辑有变更的可能性,可以通过开发一个指标取数方案表,给用户提供一个可修改指标取数配置的入口。 方案业务流程图 步骤一:开发指标取数方案表(基础资料表单),该表单主要包含多维数据库取数体系及维度信息。如下图所示,“合并体系”为对应财务报表的取数体系,取数维度为财务报表模板内各个财务指标对应的取数维度,用户可以根据【企业绩效云】→【合并报表】→【系统配置】→【报表模板】对应财务报表的维度及成员信息在“合并指标维度信息”配置财务指标的取数维度。 预算指标可以根据【企业绩效云】→【预算体系】→【预算模板】→【预算模板】对应预算报表的维度及成员信息在“预算指标维度信息”配置。(注:因为每个项目的自定义维度字段有所差异,可根据项目进行微调) 财务报表取数方案明细图 预算取数方案明细图 步骤二:编写定时调度任务插件,插件代码主要分为三部分: 1) 设置调度任务参数syscDate(用于同步数据的会计期间,参数格式:yyyyMM,参数类型:文本),当syscDate为空时,查询上月的财务报表/预算数据,否则查询syscDate所属期间的数据。代码示例如下: 2) 查询指标取数方案表的所有经营分析指标的取数维度信息,代码示例如下: 3) 根据每个经营分析指标的取数维度编写多维数据库查询sql,调用多维数据库工具类查询数据: kd.fi.bcm.business.serviceHelper.OlapServiceHelper(合并报表) kd.epm.eb.common.shrek.controller.ShrekOlapServiceHelper(预算) 然后,将查询结果保存到企业经营数据基表。 财务数据查询代码示例如下:小编推荐
1 业务背景
2 解决方案
2.1 方案整体思路

2.2 方案实现步骤


@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();
}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);
}
}
}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.eq
实践案例 | 如何使用多维数据库数据制做经营分析报表
声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。如若本站内容侵犯了原著者的合法权益,可联系本站删除。



