生成统计数据并导出Excel

2023-07-29,,

需求:看如下表格的统计需求

生产调度中心部门需要从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的相关教程结束。

《生成统计数据并导出Excel.doc》

下载本文的Word格式文档,以方便收藏与打印。