使用来自企业应用程序的 PHP 从 MySQL 数据生成大型 Excel 文件

Posted

技术标签:

【中文标题】使用来自企业应用程序的 PHP 从 MySQL 数据生成大型 Excel 文件【英文标题】:Generating large Excel files from MySQL data with PHP from corporate applications 【发布时间】:2012-05-25 19:39:47 【问题描述】:

我们正在开发和维护几个系统,这些系统需要将 Excel 格式的报告导出给最终用户。这些报告是从 mysql 数据库中收集的,经过一些简单的处理,通常会产生约 40000 行 10-15 列的数据,我们预计数据量将稳步增长。

目前我们正在使用 phpExcel 生成 Excel,但它不再适合我们了。当我们超过 5000 行之后,内存消耗和加载时间变得无法忍受,并且无法通过无限增加 PHP 的内存使用和脚本执行时间的最大限制来解决。数据处理尽可能精简,整个问题在于 PHPExcel 是一个内存猪。 CSV 生成会更轻松,但不幸的是,由于用户需求,我们需要从我们的服务中导出 Excel(和单独的 Excel)。这是由于格式要求等原因,因此不能选择 CSV。

对于第三方应用程序/模块/服务/生成大型 excel 的任何想法/建议?不管它是否是商业许可证,只要它符合我们的需求,可以集成到现有的 PHP 应用程序中并完成它的工作。我们的服务通常在 linux/php/mysql 上运行,我们几乎可以对服务器做任何我们需要做的事情。

谢谢!

【问题讨论】:

我对实际生成 xls 文件的任何其他方法都太熟悉了。正如你所说,制作一个csv然后制作一个xls会更快:)。您是否研究过 phpExcel 的可能修复方法(就像这里正在进行的讨论:***.com/questions/4817651/… 您是否有不想使用 CSV(多张纸、格式等)的具体原因?还得导出成xls还是xlsx(或者没关系)? Nanne:据我所知,我们的一位程序员或多或少地尝试了一切以使 PHPExcel 尽可能轻量运行。我会将您的链接转发给他,但我想我们已经尝试过了。你能推荐任何可以更好地完成工作的 PHPExcel 的合理替代品吗? Bo:用户需求(即他们不知道如何正确导入 CSV 或不想打扰它)、格式和数据完整性、多张表格等等。 XLS 导出是最低限度的,因为我们无法控制最终用户拥有的 Excel 版本,XLSX 不是必需品,而是一个不错的奖励。 我过去唯一想到的,但由于几个原因拒绝了自己(第 3 方的东西,真的有点绕道,没有 API 经验等)正在推动它谷歌使用 API 进行传播。不确定将其作为 XLS 轻松下载是否可行,但您可以查看它:developers.google.com/google-apps/spreadsheets 【参考方案1】:

打印表格怎么样?

<?php
header("Content-Type:   application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename=abc.xls");  //File name extension was wrong
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);

echo "<table><tr><td>Test</td><td>Test2</td></table>";

【讨论】:

【参考方案2】:

对于如此大量的数据,我不推荐像 PHPExcel 或 ApachePOI(用于 Java)这样的工具,因为它们需要内存。我最近一直在为类似的任务而苦苦挣扎,我发现了将数据注入电子表格的方便(但可能有点繁琐)的方法。可以在服务器端生成或更新 Excel 电子表格,从而进行简单的 XML 编辑。我在服务器上有 XLSX 电子表格,每次从 dB 收集数据时,我都会使用 php 解压缩它。然后我访问包含需要手动注入和插入数据的工作表内容的特定 XML 文件。之后,我压缩电子表格文件夹,以便将其作为常规 XLSX 文件分发。整个过程非常快速和可靠。显然,与 XLSX/Open XML 文件的内部组织相关的问题和故障很少(例如,Excel 倾向于将所有字符串存储在单独的表中,并在工作表中使用对该表的引用)。但是当只注入像数字和字符串这样的数据时,这并不难。如果有人有兴趣,我可以提供一些代码。

好的,这里是示例代码。我试图评论它的作用,但请随时要求进一步解释。

<?php
/** 
 * Class for serverside spreadsheet data injecting
 * Reqs: unzip.php, zip.php (containing any utility functions able to unzip files & zip folders)
 *
 * Author: Poborak
 */
class DataInjector
    
    //spreadsheet file, we inject data into this one
    const SPREADSHEET_FILE="datafile.xlsx";   
    // specific worksheet into which data are being injected    
    const SPREADSHEET_WORKSHEET_FILE="/xl/worksheets/sheet7.xml"; 
    //working directory, spreadsheet is extracted here
    const WSPACE_DIR="Wspace";
    // query for obtaining data from DB
    const STORE_QUERY = "SELECT * FROM stores ORDER BY store_number ASC"; 

    private $dbConn;
    private $storesData;

    /**
     * @param   mysqli  $dbConn
     */
    function __construct(mysqli $dbConn)    
        $this->dbConn = $dbConn;
    

    /**
     * Main method for whole injection process
     * First data are gathered from DB and spreadsheet is decompressed to workspace.
     * Then injection takes place and spreadsheet is ready to be rebuilt again by zipping.
     *
     * @return   boolean    Informace o úspěchu
     */     
    public function injectData() 

        if (!$this->getStoresInfoFromDB()) return false;        
        if (!$this->explodeSpreadsheet(self::SPREADSHEET_FILE,self::WSPACE_DIR)) return false;                      
        if (!$this->injectDataToSpreadsheet(self::WSPACE_SUBDIR.self::SPREADSHEET_WORKSHEET_FILE)) return false;            
        if (!$this->implodeSpreadsheet(self::SPREADSHEET_FILE,self::WSPACE_DIR)) return false;
        return true;
    

    /**
     * Decompress spreadsheet file to folder
     *
     * @param   string  $spreadsheet
     * @param   string  $targetFolder
     *
     * @return   boolean    success/fail 
     */   
    private function explodeSpreadsheet($spreadsheet, $targetFolder) 
        return unzip($spreadsheet,$targetFolder);
    

    /**
     * Compress source folder to spreadsheet file
     *
     * @param   string  $spreadsheet    
     * @param   string  $sourceFolder
     *
     * @return   boolean    success/fail 
     */   
    private function implodeSpreadsheet($spreadsheet, $sourceFolder) 
        return zip($sourceFolder,$spreadsheet);
    

    /**
     * Loads data from DB to member variable $storesDetails (as array)
     *
     * @return   boolean    success/fail 
     */ 
    private function getStoresInfoFromDb() 
        unset($this->storesData);       

        if ($stmt = $this->dbConn->prepare(self::STORE_QUERY)) 
            $stmt->execute();
            $stmt->bind_result($store_number, $store_regional_manager, $store_manager, $store_city, $store_address);
            while ($stmt->fetch()) 
                $this->storesData[trim($store_number)] = array(trim($store_regional_manager),trim($store_manager),trim($store_address),trim($store_city));
                       
            $stmt->close();
           
        return true;        
    

    /**
     * Injects data from member variable $storesDetails to spreadsheet $ws
     *
     * @param   string  $ws target worksheet
     *
     * @return   boolean    success/fail
     */ 
    private function injectDataToSpreadsheet($ws) 
         $worksheet = file_get_contents($ws);    
         if ($worksheet === false or empty($this->storesData) return false;

         $xml = simplexml_load_string($worksheet);  
         if (!$xml) return false;

        // Loop through $storesDetails array containing rows of data
        foreach ($this->storesData as $std)

            // For each row of data create new row in excel worksheet
            $newRow = $xml->sheetData->addChild('row'); 

            // Loop through columns values in rowdata
            foreach ($std as $cbd)                      
                // Save each column value into next column in worksheets row 
                 foreach ($this->storesData as $cbd)
                    $newCell = $newRow->addChild('c'); 
                    $newCell->addAttribute('t', "inlineStr");
                    $newIs = $newCell->addChild('is');
                    // text has to be saved as utf-8 (otherwise the spreadsheet file become corrupted)
                    if (!mb_check_encoding($cbd, 'utf-8')) $cbd = iconv("cp1250","utf-8",$cbd); 
                    $newT = $newIs->addChild('t',$cbd);                     
                
             
         

         // Save xml data back to worksheet file
         if (file_put_contents($ws, $xml->asXML()) !== false) return true;           
    

?>   

【讨论】:

您好 Poborak,您的解决方案听起来很有希望,我有兴趣查看所涉及的代码示例。能否提供下载链接? 我已将代码插入到我的原始答案中。我已经简化了我在项目中使用的类来展示基本的想法和工作流程。您只需要包含此示例的解压缩和文件夹压缩功能。如果需要我也可以提供... @Poborak 您正在使用哪个 XLSX 电子表格库,它适用于 2 lac 行吗?【参考方案3】:

查看OfficeWriter。我们最近专门为一家财富 500 强金融公司改进了海量数据集的性能。它对文件格式的作用比您特别需要的要多(图表和您拥有的东西),但是 API 非常易于使用,并且通过评估,您可以快速获得 POC。免责声明 - 我支持构建最新版本的工程师。

对你们来说,另一个缺点是它是 .NET。

【讨论】:

嗨 Nick,OfficeWriter 看起来不错,但由于我们的大多数服务都在 linux/php 上运行,迁移到 .net 是我们必须计划和考虑更长时间的事情,而我没有确定我们目前是否可以为该任务投入资源。对于现有的 .net 环境,这似乎是一个不错的选择。【参考方案4】:

我尝试更新的 PHPExcel 替代品列表是 here

如果您追求的原始速度/内存性能超出 PHPExcel 所能提供的任何东西,那么我真正推荐的唯一一个是 Ilia's wrapper extension for libXL,因为该库仍然受到积极支持。

【讨论】:

嗨,马克,感谢您提供替代方案列表。目前,您对 Ilia 的 libXL 包装器的推荐似乎是最佳选择,但我们必须进一步研究。【参考方案5】:

您尝试过旧的 Pear Excel(又名 Spreadsheet_Excel_Writer:http://pear.php.net/package/Spreadsheet_Excel_Writer/redirected)吗?

关于 Pear 与 PHPExcel 的结帐讨论:http://phpexcel.codeplex.com/discussions/240688

【讨论】:

但他指出,由于(可能不合理,但仍然存在)要求,CSV 不是一个选项。那么这是一个答案吗? @Nanne:最初并不清楚 CSV 不是一个选项。这应该是一个暗示,如果两个人做出相似的答案(可能不正确),那么问题可能不像最初计划的那么清楚。 原始问题的字面意思是:CSV generation would be lighter, but unfortunately we're required to export Excel (and Excel alone)。对我来说似乎很清楚。即使不清楚,问题也有问题,好吧,但这并不能使它成为对(可能不清楚)问题的有效答案,因为它清楚的。 @Nanne:我已编辑答案以删除对 CSV 的引用。 :) 不清楚的是“......我们需要导出 Excel(和 Excel 单独)......”因为 excel 不是文件格式,而是应用程序加载文件格式(Excel 支持多种格式:xls、xlsx、等)。 但是前面部分CSV这个词被丢弃的地方还是很清楚的。无论如何,这将一事无成。不要挑剔你或任何东西,但不需要问候和签署帖子,你的名字已经在那里了:)【参考方案6】:

您可以以 CSV 格式导出,Excel 可以处理。如果您在写入文件时遇到问题,您可以随时循环结果(分页)并将它们附加到 CSV 文件中

之后尝试使用 PHPExcel 将其转换为 .xsl 或 .odf 格式,否则将其保留为 CSV。

【讨论】:

但他指出,由于(可能不合理,但仍然存在)要求,CSV 不是一个选项。那么这是一个答案呢? 我建议将 CSV 作为中间步骤,因为它很容易生成。然后尝试将其转换为 Excel 格式。 但是您没有包含有关转换的任何内容(更不用说“将其保留为 CSV”备注)。由于PHPExcel 存在大纸张和内存问题,您会遇到同样的问题,不是吗?如果没有,您需要添加如何将大的.csv 文件转换为.xls,就我而言这是一个有用的答案。 嗨,Boby,我们曾经将 CSV 格式的报告交付给最终用户,但问题是 Excel 无法正确导入某些单元格内容,除非用户使用单独的导入功能 (注意;CSV 的格式正确)。例如,“000123123”等字符串丢失了前导零,从而导致数据损坏。这是由于 Excel 决定将字符串解释为数字,而原始内容(即前导零)丢失了。 这是我们使用 PHPExcel 生成文件的主要原因之一,因为我们可以直接设置单元格内容类型。如果我们的最终用户接受他们必须使用导入功能,那么交付 CSV 将不是问题 - 但不幸的是他们不会,我们必须交付 Excel。

以上是关于使用来自企业应用程序的 PHP 从 MySQL 数据生成大型 Excel 文件的主要内容,如果未能解决你的问题,请参考以下文章

来自远程系统的 php 中的 XAMPP Mysql 连接错误

PHP显示来自MySQL的图像BLOB [重复]

来自 PHP 的 MDX 查询

php代码没有使用wamp服务器从mysql数据库中获取数据

从MySQL加载数据并使用jQuery Mobile,PHP选择填充下拉列表

来自 php mysql 的 Highcharts 钻取 json