iT邦幫忙

1

PHPExcel或PhpSpreadsheet產出Excel格式

  • 分享至 

  • xImage

各位前輩你們好 想請教PhpSpreadsheet套件產出Excel格式問題
目前遇到程式技術上困難。

開發系統環境(自己家裡的環境)
OS Windows 10
php version 7.4.28
PhpSpreadsheet-1.20.0 version
Microsoft Excel 365 version

p.s. 開發系統環境(公司系統)
|OS|PHP version|PHPExcel|PHPMailer|Microsoft Excel version|
|---- |---|
|CentOS7|7.0.33 |1.8.0 |5.2.26 |2016


這是利用PhpSpreadsheet套件產出的格式
https://ithelp.ithome.com.tw/upload/images/20220521/20132230g4dvtgT7BU.png

附上我的程式碼

<?php

    // Load Composer's autoloader
    require_once 'D:\06.programming\php\vendor\autoload.php';

    // Import PhpSpreadsheet classes into the global namespace
    // These must be at the top of your script, not inside a function
    use PhpOffice\PhpSpreadsheet\Spreadsheet;
    use PhpOffice\PhpSpreadsheet\Writer\Xlsx;


    // Connecting to SQL Server
    require_once 'D:\06.programming\php\Config\connect-sqlserver.php';
    
    // ========== 建立工作表一 ==========
    
    $spreadsheet = new Spreadsheet();
    $sheet = $spreadsheet->getActiveSheetIndex(0);
    $sheet = $spreadsheet->getActiveSheet(0);

    // Excel 儲存格 A2~C2填入文字
    $TitleData = ['國家代碼', '國家地區', '人口數量'];
    $sheet->fromArray($TitleData, NULL, 'A2');
    
    // Excel 設定儲存格格式
    $styleArray = [
        'alignment' => [
            'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
        ],
        'fill' => [
            'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_GRADIENT_LINEAR,
            'startColor' => [
                'rgb' => 'ffff00',
            ],
            'endColor' => [
                'rgb' => 'ffff00',
            ],
        ],
    ];
    $sheet->getStyle('A2:C2')->applyFromArray($styleArray);

    // 設定儲存格,寬度
    $sheet->getColumnDimension('A')->setWidth(10);
    $sheet->getColumnDimension('B')->setWidth(15);
    $sheet->getColumnDimension('C')->setWidth(10);

    // ============== END ==============

    // ========== 連接資料庫取得資料 ==========

    $conn = OpenConnection();
    // SQL Server 撈取資料
    $tsql = "
        SELECT
            CountryCode
            ,District
            ,MAX(Population) AS Population
        FROM city
        WHERE District LIKE 'C%' OR District LIKE 'D%'
        GROUP BY CountryCode, District
        ORDER BY CountryCode, District
    ";

    $result = sqlsrv_query($conn, $tsql);
    if ($result == false) {
        die(sqlsrv_errors());
    } else {
        // $rowsheet從Excel儲存格第3列開始填入資料
        $rowsheet = 3;
        while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC))
        {
            $sheet->setCellValue('A'.$rowsheet, $row['CountryCode'])
                    ->setCellValue('B'.$rowsheet, $row['District'])
                    ->setCellValue('C'.$rowsheet, $row['Population']);
            $rowsheet++;
        }
    }

    // ============== END ==============

    // ========== Save Excel on Windows D disk ==========

    $writer = new Xlsx($spreadsheet);
    $writer->save('test.xlsx');

    // ========== END ==========

    sqlsrv_close($conn);

?>

假如想要呈現這樣的Excel格式呢?
https://ithelp.ithome.com.tw/upload/images/20220521/20132230czZu3UiMF6.png

問題?國家人口數量要怎麼呈現?卡住?
https://ithelp.ithome.com.tw/upload/images/20220521/20132230yVm1rxn8wB.png
我的想法
反推儲存格文字的第幾欄?第幾列?
利用陣列?取得Excel儲存格欄位在哪裡?填入國家人口數量?

更改我的程式碼,我直接擷取重點程式片段

// ========== 連接資料庫取得資料 ==========

    $conn = OpenConnection();
    
    $tsql = "
        SELECT
            CountryCode
            ,District
            ,MAX(Population) AS Population
        FROM city
        WHERE District LIKE 'C%' OR District LIKE 'D%'
        GROUP BY CountryCode, District
        ORDER BY CountryCode, District
    ";

    $tsql2 = "
        SELECT
            CountryCode
        FROM city
        WHERE District LIKE 'C%' OR District LIKE 'D%'
        ORDER BY CountryCode
    ";

    $tsql3 = "
        SELECT
            District
        FROM city
        WHERE District LIKE 'C%' OR District LIKE 'D%'
        ORDER BY District
    ";

    $result = sqlsrv_query($conn, $tsql);
    $result2 = sqlsrv_query($conn, $tsql2);
    $result3 = sqlsrv_query($conn, $tsql3);
    if ($result == false) {
        die(sqlsrv_errors());
    } else {

        // 開始顯示國家人口數量??
        /*
        $rowsheet = 3;
        while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC))
        {
            ??
        }
        */

        // 第3列開始以下顯示國家代碼
        $rowsheet = 3;
        while ($row = sqlsrv_fetch_array($result2, SQLSRV_FETCH_ASSOC))
        {
            $sheet->setCellValue('A'.$rowsheet, $row['CountryCode']);
            $rowsheet++;
        }

        // 第B欄開始往右顯示國家地區
        $colsheet = "B";
        while ($row = sqlsrv_fetch_array($result3, SQLSRV_FETCH_ASSOC))
        {
            $sheet->setCellValue($colsheet.'2', $row['District']);

            $colsheet++;
        }


    }

    // ============== END ==============

為甚麼要搞這麼麻煩?
抱歉,因為公司上層和各單位使用者要求我必須呈現這種格式...
煩請各位了,謝謝...

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

0
淺水員
iT邦大師 6 級 ‧ 2022-05-22 00:12:25
最佳解答

可以用 PHP 處理一下,大概像這樣吧

<?php
require('vendor/autoload.php');

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$data = [
    ['ANT', 'Curacao', 2345],
    ['ARG', 'Catamarca', 134935],
    ['BLZ', 'Cayo', 7105],
    ['COL', 'Caldas', 337580],
    ['ARG', 'Chaco', 229212],
    ['COL', 'Caqueta', 108574],
];

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();

$rowOffset = 3;
$rowOffsetArr = [];
$nCol = 2;
foreach ($data as $row) {
    $key = $row[0];
    if (!isset($rowOffsetArr[$key])) {
        $sheet->setCellValueByColumnAndRow(1, $rowOffset, $key);
        $rowOffsetArr[$key] = $rowOffset++;
    }
    $nRow = $rowOffsetArr[$key];
    $sheet->setCellValueByColumnAndRow($nCol, 2, $row[1]);
    $sheet->setCellValueByColumnAndRow($nCol, $nRow, $row[2]);
    ++$nCol;
}

$writer = new Xlsx($spreadsheet);
$writer->save('test.xlsx');
johnyowoy iT邦新手 5 級 ‧ 2022-06-02 17:30:58 檢舉

謝謝淺水員前輩!
之前在忙其它專案和IT的雜事,比較沒時間處理程式問題。

我有研讀並且理解您的程試碼。
後續再修改我自己的程式,有成功且符合公司的需求。
不過還有其它小問題啦XD,我自己嘗試修改。

附上修改完的程式

$rowOffset = 4;
$rowOffsetArr = [];
$colOffsetArr = [];
$colOffset = 1;

while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC))
{
    //var_dump($row);
    
    $key1 = $row[0];
    if (!isset($rowOffsetArr[$key1])) {
        $sheet->setCellValue('A'.$rowOffset, $key1);
        $rowOffsetArr[$key1] = $rowOffset++;
    }
    $key2 = $row[1];
    if (!isset($colOffsetArr[$key2])) {
        $sheet->setCellValueByColumnAndRow($colOffset, 3, $key2);
        $colOffsetArr[$key2] = $colOffset++;
    }
        $nRow = $rowOffsetArr[$key1];
        $nCol = $colOffsetArr[$key2];
        $sheet->setCellValueByColumnAndRow($nCol, $nRow, $row[2]);
    }

原本
https://ithelp.ithome.com.tw/upload/images/20220602/20132230YF0oSiOmz8.png
PHPExcel產出來的格式
https://ithelp.ithome.com.tw/upload/images/20220602/20132230W7zuiAzWPA.png

1
海綿寶寶
iT邦大神 1 級 ‧ 2022-05-22 12:37:55

也可以在 SQL 階段就做出結果
https://ithelp.ithome.com.tw/upload/images/20220522/20001787JGbaw94w9G.png
dbfiddle 練習結果

DECLARE @colnameList varchar(200)

SET @colnameList = NULL
SELECT @colnameList = COALESCE(@colnameList + ',','') + DISTRICT
FROM CITY;

DECLARE @SQLQuery NVARCHAR(MAX)

SET @SQLQuery ='
SELECT *
FROM (
	SELECT l.COUNTRYCODE, l.DISTRICT, l.POPULATION
	FROM dbo.CITY l
) t 
PIVOT (
	MAX(POPULATION)
	FOR DISTRICT IN ('+@colnameList+') 
) p;
'

EXEC(@SQLQuery)
看更多先前的回應...收起先前的回應...
ckp6250 iT邦好手 1 級 ‧ 2022-05-22 14:28:28 檢舉

支持海寶大,
我個人的習慣是,在SQL階段就搞定一切,
php端則簡單無比,一行一行一列一列地秀出來就好,
反正EXCEL的格式也很像資料表格樣子,

淺水員 iT邦大師 6 級 ‧ 2022-05-22 15:25:03 檢舉

不管是直接程式處理或是 sql procedure
有時候也要注意 atomic 的問題
需要的話要加上 TRANSACTION

ckp6250 iT邦好手 1 級 ‧ 2022-05-22 21:05:39 檢舉

請教淺水教練:
【TRANSACTION】不是主要用在寫入或刪除階段嗎?
像這個只是 select 的用途,有必要動用到 TRANSACTION ?

又,是【淺水】還是【潛水】啊?

淺水員 iT邦大師 6 級 ‧ 2022-05-22 22:00:33 檢舉

我的想法是:
假設兩個 SELECT 中間有人刪除或是修改資料的話
要看對後續的取得結果影響大不大
不一定是寫入或刪除才需要加

另外因為 SQL 的語法比較固定
所以一些寫法雖然能得到正確結果
但演算法的時間複雜度卻多繞一圈的話
也未必都要用 SQL 處理

以上是我個人想法啦
不一定都對,就一起討論了
(我 Microsoft SQL Server 不熟)

PS. 「潛水」是光看文不發表意見,我之前想提醒自己多發文就改「淺水」了,別潛太深的意思。

所以是在「淺水」的地方「潛水」的意思嘛?
我是來亂的。

淺水員 iT邦大師 6 級 ‧ 2022-05-23 19:13:37 檢舉

在「淺水」的地方「潛水」會吃土…

johnyowoy iT邦新手 5 級 ‧ 2022-06-02 17:33:55 檢舉

SQL Server 原來可以產生這樣的格式!
第一次看到,看來我的SQL功力不足....

有時間我再試著修改SQL看能不能達成這樣的效果。

謝謝海綿寶寶前輩。

我要發表回答

立即登入回答