生成统计数据并导出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> ...
随机推荐
- JSoup抓取本地页面
File in = new File("C:/Users/li/Desktop/2.html"); Document doc01 = Jsoup.parse(in, "U ...
- Bean named '*' must be of type [*], but was actually of type []
本地Service 名字和调用别的maven项目Service重名
- python3 迭代器
''' 迭代:重复的过程,并且每次迭代的结果都是下次迭代的初始值 可迭代的 Iterable 迭代器 Iterator iter(数据) == 数据.__iter__() 将数据转换为迭代器 next ...
- Redis 实现安全队列
Redis的列表数据结构可以让我们方便的实现消息队列 例如用 LPUSH(BLPUSH)把消息入队,用 RPOP(BRPOP)获取消息 绝大部分的情况下,这些操作都是没问题的,但并不能保证绝对安全 当 ...
- zabbix API应用
1.模拟登录 curl -i -X POST -H 'Content-Type:application/json' -d '{"jsonrpc":"2.0",& ...
- Git创建项目基本命令
前提:先在coding.net上创建项目Paper,并勾选“启用README.md文件”初始化项目. 1.给项目Paper创建版本库(仓库) cd Paper git init 2.把项目文件放到仓库 ...
- CF434D Nanami's Power Plant 最小割
传送门 因为连距离限制的边的细节调了贼久QAQ 这个题和HNOI2013 切糕性质相同,都是有距离限制的最小割问题 对于每一个函数,用一条链记录变量\(x\)在不同取值下这个函数的贡献.对于一个\(x ...
- HTML+CSS之盒子模型
一.元素分类 CSS中html的标签元素大体分为三种类型 1.块状元素 @特点: #每个块级元素都从新的一行开始,并且其后的元素也另起一行(一个块级元素独占一行) #元素的高度.宽度.行高以及顶和底边 ...
- 比起Windows,怎样解读Linux的文件系统与目录结构?
比起Windows,怎样解读Linux的文件系统与目录结构? Linux 和Windows的文件系统有些不同,在学习使用 Linux 之前,若能够了解这些不同,会有助于后续学习. 本文先对Window ...
- win 2008 R2 或以上版本,只有C盘情况下,PHP上传文件,显示不了解决办法
主要问题:因为没权限 解决办法:给C:\Windows\Temp 加上IIS账户读写权限