生成统计数据并导出Excel
需求:看如下表格的统计需求
生产调度中心部门需要从IT技术部门得到这些统计数据

步骤:
(1)获取所有的子公司列表
(2)遍历所有的子公司,获取每个子公司的库存信息
(3)遍历所有的库存信息,并对库存信息进行扩充
(4)生成汇总库存信息(这里使用Redis进行生成)
(5)使用Excel工具类将汇总统计数据导出Excel
问题:
(1)为什么使用控制台后台生成统计数据
a. 因为在导出Excel的时候可能会出现120s的timeout问题,因为导出Excel是在浏览器操作完成的,web浏览器使用的是HTTP协议,HTTP协议的最大请求返回时间为120s,如果120s内,web服务器没有完成返回响应报文,则web服务器会返回一个120s超时的报文。
b. 是否是120s,是可以在web服务配置文件中设置的,如果设置成最大则封顶120s。
c. 所以,将生成统计数据功能放在控制台程序中完成,而降纯粹使用已经生成好的统计数据导出Excel在浏览器中完成。
当然也可以使用控制台程序导出Excel, 但是控制台程序只有IT部门才能运行操作;而web浏览器则所有人都可以操作
(2)为什么使用Redis生成汇总数据
a. 生成汇总数据是一个map-reduce的问题,比较耗费内存,为了防止内存溢出,则使用Redis内存,使用Redis的API进行map-reduce操作,而不直接使用web服务器机器的内存
b. 所以,所有比较占用内存的运算存储操作都应该使用专门的内存机器
(3)如何在浏览器导出Excel,如何在控制台导出Excel
a. 导出Excel当然使用php-excel进行操作Excel,最好引入php-excel并自己写一个导出Excel的工具类;
b. 如果使用浏览器导出Excel超时,则可以使用控制台程序导出Excel,即不适用HTTP协议,而是直接使用Socket创建连接,使用TCP协议
(4)生成使用Excel形成汇总数据
a. 首先,Excel的功能是非常的齐全的,可以使用Excel画单据的设计; 可以使用Excel的求和公式进行求和;可以使用Excel形成汇总透视表
b. 使用Excel形成汇总透视表的操作为: 插入->透视表; 当然,也可以生成透视图
(5)如何确认生成的统计数据是正确的数据
最好的方法是和其他一些统计功能进行对比核对,当然,有时需要更改一些统计条件才能得到和其他统计功能一模一样的数据
(6)有哪些好用的统计数据BI的相关工具
一般来说,有Superset和CBoard
代码如下:
在控制台中执行命令php yii wms-info/wms-export
/**
* 将库存及销售生成统计数据并导出EXCEL
*/
public function actionWmsExport(){
$data = [];
$wms_check_begin_at = 0;
# 统计数据的截止日期,使用当前日期则可以和其他统计功能进行核对数据是否正确
// $wms_check_end_at = time();
# 统计数据的截止日期
$wms_check_end_at = strtotime('2019-03-30 23:59:59');
# 调用API接口获取统计数据
$common_producer_info_list = \core\models\CommonProducerInfo::getCommonProducerInfoList();
foreach ($common_producer_info_list as $common_producer_info){
$common_producer_info_id = $common_producer_info['division_id'];
$dataProvider = \core\models\WmsInfo::_getWmsProductInfoListProvider($common_producer_info_id, $wms_check_begin_at, $wms_check_end_at);
# 不适用二层循环的方法,时间复杂度为n*n
/*
foreach ($dataProvider->getModels() as $model){
$data[] = $model;
}
*/
# 使用此方法,降低时间复杂度,时间复杂度为n
$data = array_merge($data, $dataProvider->getModels());
}
# 遍历统计数据,将统计数据进行扩充,并生成汇总统计数据
foreach ($data as &$item){
$in_sheet_number = $item['in_sheet_number'];
# 不推荐在循环结构中进行数据库查询,因为会进行n次数据库查询,占用数据库连接资源和内存,时间;
# 但是这里调用了现有的API则这样使用,不再编写新的API,增加开发速度
$in_sheet = \core\models\WmsProductInSheet::findOne(['wms_product_in_sheet_number'=>$in_sheet_number]);
# 是退货类型的入库单则为退货库存,无需查询销售退货表,这样降低了逻辑复杂度
if (strpos($in_sheet->stock_origin_type, '退货') !==false){
$item['refund_weight'] = \common\models\Base::weightBcdiv($item['weight']);
$item['not_refund_weight'] = 0;
}else{
$item['refund_weight'] = 0;
$item['not_refund_weight'] = \common\models\Base::weightBcdiv($item['weight']);
}
# 这里,本不推荐直接编写sql进行查询数据库,单为了开发速度,不得已而用之
$command = (new \yii\db\Query())->select([
'po.wms_product_out_sheet_type As type',
'
IFNULL(
podet.wms_product_out_detail_info_out_weight,
0
)
AS sum_out_weight',
])->from('{{%wms_product_out_detail_info}} podet')
->leftJoin('{{%wms_product_out_sheet}} po', 'po.wms_product_out_sheet_number = podet.wms_product_out_sheet_number')
->where('podet.is_del = 0
AND po.is_del = 0
AND po.wms_product_out_sheet_status = 1
AND podet.wms_product_in_sheet_number = :in_sheet_number
AND po.wms_product_out_sheet_product_out_date >= :wms_check_begin_at
AND po.wms_product_out_sheet_product_out_date <= :wms_check_end_at',
[
':in_sheet_number'=>$in_sheet_number,
':wms_check_begin_at'=>$wms_check_begin_at,
':wms_check_end_at'=>$wms_check_end_at
])->createCommand();
$ol = $command->queryAll();
# 调试用sql
// echo $command->getRawSql();die;
# 不推荐使用遍历求和,增加时间复杂度, 应该直接使用map-reduce进行求和,或直接使用统计求和的sql语句
$sales_out_weight = 0;
foreach ($ol as $o){
if (trim($o['type']) == '销售' || empty($o['type']) || trim($o['type']) == '现款现货订单'){
$sales_out_weight = bcadd($sales_out_weight, $o['sum_out_weight']);
}
}
// $sales_out_weight = \core\components\ArrayHelper::sumByColumn($ol, 'sum_out_weight');
# 为采购入库则为定向采购出库,不再查询销售订单相关数据表,降低逻辑复杂度
if (strpos($in_sheet->stock_origin_type, '采购') !==false){
$item['ding_weight'] = \common\models\Base::weightBcdiv(intval($sales_out_weight));
$item['not_ding_weight'] = 0;
}else{
$item['ding_weight'] = 0;
$item['not_ding_weight'] = \common\models\Base::weightBcdiv(intval($sales_out_weight));
}
if(!(empty(intval($item['refund_weight'])) && empty(intval($item['not_refund_weight'])) && empty(intval($item['ding_weight'])) && empty(intval($item['not_ding_weight'])))) {
$rk = $item['producer_id'] . "-" . $item['info_name'] . "-" . $item['grade_name'];
$producer_name = \core\models\WmsTransfer::getCommonProducerInfoName($item['producer_id']);
if (strpos($producer_name, '亳州')){
$producer_name = '亳州市汉广天工绿色中药材初加工有限公司';
}
# 使用Redis进行存储统计汇总数据,不占用系统内存;也可以直接使用map-reduce进行操作
if (\Yii::$app->redis->exists($rk)) {
$refund_weight = \Yii::$app->redis->hget($rk, 'refund_weight');
$not_refund_weight = \Yii::$app->redis->hget($rk, 'not_refund_weight');
$ding_weight = \Yii::$app->redis->hget($rk, 'ding_weight');
$not_ding_weight = \Yii::$app->redis->hget($rk, 'not_ding_weight');
\Yii::$app->redis->del($rk);
\Yii::$app->redis->hmset($rk,
'producer_name', $producer_name,
'info_name', $item['info_name'],
'grade_name', $item['grade_name'],
'refund_weight', bcadd($refund_weight, $item['refund_weight'], 2),
'not_refund_weight', bcadd($not_refund_weight, $item['not_refund_weight'], 2),
'ding_weight', bcadd($ding_weight, $item['ding_weight'], 2),
'not_ding_weight', bcadd($not_ding_weight, $item['not_ding_weight'], 2));
} else {
\Yii::$app->redis->hmset($rk,
'producer_name', $producer_name,
'info_name', $item['info_name'],
'grade_name', $item['grade_name'],
'refund_weight', $item['refund_weight'],
'not_refund_weight', $item['not_refund_weight'],
'ding_weight', $item['ding_weight'],
'not_ding_weight', $item['not_ding_weight']);
}
# 输出到控制台,方便调试; 也可以使用日志模块,将此信息输出到日志文件或者网络日志
$tip = Console::ansiFormat(implode('==', \Yii::$app->redis->hgetall($rk)), [Console::FG_GREEN]);
Console::output($tip);
unset($tip);
}
unset($item);
}
}
在浏览器中访问此链接导出Excel, http://erp.chinahanguang.com/wms-check/export
public function actionExport()
{
# 此为导出统计基本数据,方便核对数据使用
/*
$excel_name = '销售库存统计';
$headers = [
'producer_name' => '基地',
'info_name' => '品种',
'grade_name' => '等级',
'in_sheet_number' => '单号',
'refund_weight' => '库存退货',
'not_refund_weight' => '其他库存',
'ding_weight' => '销售出库定向采购',
'not_ding_weight' => '销售出库非定向采购',
];
$options = [
'creator'=>'中国汉广集团IT信息中心',
'last_modified_by'=>'中国汉广集团IT信息中心',
'title'=>$excel_name,
'subject'=>$excel_name,
'description'=>$excel_name,
'keywords'=>$excel_name,
'category'=>$excel_name,
'summary'=>[
'producer_name' => false,
'info_name' => false,
'grade_name' => false,
'in_sheet_number' => false,
'refund_weight' => false,
'not_refund_weight' =>false,
'ding_weight' => false,
'not_ding_weight' =>false,
]
];
$style_options = [
'h_align'=>[
'producer_name' => 'left',
'info_name' => 'left',
'grade_name' => 'left',
'in_sheet_number' => 'left',
'refund_weight' => 'right',
'not_refund_weight' =>'right',
'ding_weight' => 'right',
'not_ding_weight' =>'right',
]
];
\core\components\MyExcelHelper::array2excel($data, $excel_name, $headers, $options, $style_options);
die;*/
$ret_data = [];
# 从Redis获取统计汇总数据
$rks = \Yii::$app->redis->keys('*-*-*');
# 将Redis的键进行排序
sort($rks, SORT_STRING);
foreach ($rks as $rk){
$rk_st = explode('-', $rk);
if (count($rk_st) == 3){
$ret_data[] = [
'producer_name'=>\Yii::$app->redis->hget($rk, 'producer_name'),
'info_name'=>$rk_st[1],
'grade_name'=>$rk_st[2],
'refund_weight'=>\Yii::$app->redis->hget($rk, 'refund_weight'),
'not_refund_weight'=>\Yii::$app->redis->hget($rk, 'not_refund_weight'),
'ding_weight'=>\Yii::$app->redis->hget($rk, 'ding_weight'),
'not_ding_weight'=>\Yii::$app->redis->hget($rk, 'not_ding_weight'),
];
\Yii::$app->redis->del($rk);
}
}
# 过滤所有重量都为0的数据
$ret_data = array_filter($ret_data, [self::className(), '_filterZero']);
$excel_name = '销售库存统计';
$headers = [
'producer_name' => '基地',
'info_name' => '品种',
'grade_name' => '等级',
'refund_weight' => '库存退货',
'not_refund_weight' => '其他库存',
'ding_weight' => '销售出库定向采购',
'not_ding_weight' => '销售出库非定向采购',
];
$options = [
'creator'=>'中国汉广集团IT信息中心',
'last_modified_by'=>'中国汉广集团IT信息中心',
'title'=>$excel_name,
'subject'=>$excel_name,
'description'=>$excel_name,
'keywords'=>$excel_name,
'category'=>$excel_name,
'summary'=>[
'producer_name' => false,
'info_name' => false,
'grade_name' => false,
'refund_weight' => false,
'not_refund_weight' =>false,
'ding_weight' => false,
'not_ding_weight' =>false,
]
];
$style_options = [
'h_align'=>[
'producer_name' => 'left',
'info_name' => 'left',
'grade_name' => 'left',
'refund_weight' => 'right',
'not_refund_weight' =>'right',
'ding_weight' => 'right',
'not_ding_weight' =>'right',
]
];
# 调用Excel工具类API,使用HTTP协议导出Excel
\core\components\MyExcelHelper::array2excel($ret_data, $excel_name, $headers, $options, $style_options);
}
// 数据过滤器,过滤重量为0的数据
public static function _filterZero($item){
if (empty($item['refund_weight']) && empty($item['not_refund_weight']) && empty($item['ding_weight']) && empty($item['not_ding_weight'])){
return false;
}else{
return true;
}
}
导出的汇总统计Excel表如下:

生成的格式化汇总统计表如下:

生成的汇总统计图如下:

生成统计数据并导出Excel的更多相关文章
- 01 UIPath抓取网页数据并导出Excel(非Table表单)
上次转载了一篇<UIPath抓取网页数据并导出Excel>的文章,因为那个导出的是table标签中的数据,所以相对比较简单.现实的网页中,有许多不是通过table标签展示的,那又该如何处理 ...
- asp.net将内容导出到Excel,Table表格数据(html)导出EXCEL
代码: /// <summary> /// HTML Table表格数据(html)导出EXCEL /// </summary> /// <param name=&quo ...
- POI导出大量数据的简单解决方案(附源码)-Java-POI导出大量数据,导出Excel文件,压缩ZIP(转载自iteye.com)
说明:我的电脑 2.0CPU 2G内存 能够十秒钟导出 20W 条数据 ,12.8M的excel内容压缩后2.68M 我们知道在POI导出Excel时,数据量大了,很容易导致内存溢出.由于Excel ...
- piwik优化之定时任务生成统计数据
piwik的ui界面,使用起来是无比的慢,让苏南大叔不得不对比wordpress的使用体验.当然了,如果你的服务器足够强大,这些都是小事儿.官方对此给出了一系列的优化建议,大家可以读一下:https: ...
- .net实现一个简单的通用查询数据、导出Excel的网页
背景:临时提供一个简单的网页,供其他人浏览数据库(Oracel.MSSQL)的某些数据,并导出Excel.支持在配置文件中随时添加或修改sql. 实现:把sql语句等信息保存一个xml文件中,前端页面 ...
- OAF_文件系列10_实现OAF将数据资料导出Excel到本地JXL(案例)
20150729 Created By BaoXinjian
- AX 利用windows粘贴板功能实现批量数据快速导出EXCEL
static void test(Args _args) { int lineNum; int titleLines; SysExcelApplication excel; SysExcelWorkb ...
- 获取数据后导出Excel
List<PortResourceInfo> list = getList()//获取数据源 //导出excle Response.Clear(); Response.ContentTyp ...
- winform中DataGridView的数据实现导出excel
1,窗体设计 首先需要引入程序集:Microsoft.Office.Interop.Excel (如果没有引用过的需要右键添加引用再搜索就行了) 实现的方法: /// <summary> ...
随机推荐
- 【ES6】let 命令
let命令 为es6新增命令,用来声明变量,类似于var,但是let所声明的变量,只在let命令所在的块级作用域内有效 块级作用域写法(ES6块级作用域允许任意嵌套): // 块级作用域写法 { le ...
- Python框架学习之Flask中的常用扩展包
Flask框架是一个扩展性非常强的框架,所以导致它有非常多的扩展包.这些扩展包的功能都很强大.本节主要汇总一些常用的扩展包. 一. Flask-Script pip install flask-scr ...
- P2678 跳石头题解
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #i ...
- Linux内核入门到放弃-进程虚拟内存-《深入Linux内核架构》笔记
进程地址空间的布局 <mm_types.h> <mm_types.h> struct mm_struct { ... unsigned long (*get_unmapped_ ...
- Maven的porfile与SpringBoot的profile结合使用详解
使用maven的profile功能,我们可以实现多环境配置文件的动态切换,可参考我的上一篇博客.但随着SpringBoot项目越来越火,越来越多人喜欢用SpringBoot的profile功能 ...
- 单链表的python实现
首先说下线性表,线性表是一种最基本,最简单的数据结构,通俗点讲就是一维的存储数据的结构. 线性表分为顺序表和链接表: 顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存 ...
- RabbitMQ 高可用之镜像队列
如果RabbitMQ集群只有一个broker节点,那么该节点的失效将导致整个服务临时性的不可用,并且可能会导致message的丢失(尤其是在非持久化message存储于非持久化queue中的时候).可 ...
- java----牛客练习
1. 形式参数就是函数定义时设定的参数.例如函数头 int min(int x,int y,int z) 中 x,y,z 就是形参.实际参数是调用函数时所使用的实际的参数. 真正被传递的是实参 ...
- Python-random模块-59
random模块: 随机数模块 >>> import random #随机小数 >>> random.random() # 大于0且小于1之间的小数 0.76643 ...
- set和multiset的用法
set #include<iostream> #include<algorithm> #include<cstdio> #include<string.h&g ...