生成统计数据并导出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> ...
随机推荐
- centos7 开机启动服务链接说明
环境:centos7 创建的开机启动的链接地址: /etc/systemd/system/multi-user.target.wants/ 如: [root@tiaobanji system]# ll ...
- Python:Day45 Javascript的String字符串
typeof只能判断普通数据类型, 对于复杂的只是判断出来是一个Object: instanceof 可以判断数据是否是某一类型: alert(s instanceof String); String ...
- docker 8 docker的镜像命令
先回顾一下容器.存储.镜像三者之间的关系. 我们知道docker的logo是一条大鲸鱼背上驮着集装箱.那我们对应到docker如下: 1)蓝色的大海里面------->宿主机系统比如我笔记本wi ...
- Spring Security(三):1、Getting Started
The later parts of this guide provide an in-depth discussion of the framework architecture and imple ...
- Luogu P1967 货车运输
qwq 这题是知道了正解做法才写的.. 求每两点间最小权值最大的路径,本来我以为要每个点都跑一遍dij(?),后来意识到生成树好像是用来找这个的( ´▽`) 然后我问dtxdalao对不对,他说“我记 ...
- 深蓝词库转换2.2发布,支持手心输入法和Win10微软拼音
距离上一次大版本的发布已经很久很久了,中间是不是会收到一些用户的来信,提出新的需求,于是只是做小版本的更新,终于积累了一些更新后,打算做个大版本的发布了. 深蓝词库转换是一个输入法的词库互转和生成软件 ...
- Android/Linux boot time分析优化
如果需要优化boot time,就需要一个量化的工具来分析每个阶段的时间消耗.这种类型的优化特别适合使用基于timeline的图表,有着明显的时间顺序.要求不但能给出整个流程消耗的时间,还要能对流程进 ...
- GFF高仿QQ客户端及服务器
一.GFF简介 GFF是仿QQ界面,通信基于SAEA.MessageSocket.SAEA.Http.SAEA.MVC实现包含客户端和服务器的程序,源码完全公开,项目源码地址:https://gith ...
- 两篇 Spring 总结(一)
Spring4 概述以及 HelloWorld 概述 Spring 是一个 IOC(DI) 和 AOP 容器框架. 轻量级,Spring 是非侵入的,即使用的时候不需要实现任何接口或继承任何父类 面向 ...
- Minesweeper
你玩过扫雷吗?这个可爱的小游戏带有一个我们记不住名字的操作系统.游戏的目标是找到所有地雷在M x N场中的位置.游戏在一个正方形中显示一个数字,它告诉你在这个正方形附近有 多少个地雷.每个方块最多有八 ...