PAYATHON 2026

Shopware 6 命令导出大量 CSV 数据时,如何实现全局多字段排序?

数据阿明

结论

优先在数据库中对全部待导出记录执行一次多字段 ORDER BY,再逐行读取结果并写入 CSV。如果数据必须通过多个独立查询生成,可以先写入数据库暂存表,再统一排序导出;也可以对临时 CSV 文件进行外部归并排序。

每批 100 个产品分别排序后直接写入文件,无法保证整个文件有序。fgetcsv() 和 fputcsv() 只负责读写 CSV,不负责排序。

为什么每个批次添加 ORDER BY 不够?

假设两个批次按某个字段升序排列,结果分别是:

第一批:10, 30
第二批:20, 40

依次写入文件后得到 10, 30, 20, 40,并非全局升序。即使每次 processChunk() 都使用相同的多字段排序条件,也会出现这个问题。

多字段排序还要明确字段优先级。例如,先按分类升序,再在同一分类内按价格降序,最后用唯一序号区分排序字段相同的记录。

方案一:在数据库中统一排序后导出

如果导出字段都来自数据库,且排序条件能够用 SQL 表达,可以先把输入文件中的产品编号分批写入暂存表,再通过关联查询一次性取得全部待导出记录,并在查询最外层使用 ORDER BY。

如果 processChunk() 还包含 PHP 计算、数据拼接等逻辑,也可以把每批生成的最终导出记录写入暂存表,全部生成后再查询排序结果。

下面的 SQL 仅演示如何对暂存结果排序。export_rows、category_name、price 和 seq 都是示例名称,并非已知的 Shopware 表或字段。seq 表示为每条导出记录生成的唯一序号:

SELECT
    product_number,
    category_name,
    price
FROM export_rows
ORDER BY
    category_name ASC,
    price DESC,
    seq ASC;

查询先按分类排序,同一分类内按价格降序,分类和价格都相同时再按序号排序。暂存字段的数据类型需要与业务值匹配。例如,价格应使用合适的数值类型,否则按文本排序可能得到错误的顺序。

执行排序查询后,按结果顺序写入 CSV。以下示例假设使用 PDO,且 $statement 是已经执行上述查询的语句对象:

fputcsv($exportFile, $this->header, ';');

while (($row = $statement->fetch(PDO::FETCH_NUM)) !== false) {
    fputcsv($exportFile, $row, ';');
}

表头顺序需要与 SELECT 的列顺序一致。使用其他数据库访问层时,应采用对应的逐行迭代接口。还要检查驱动是否缓存了全部查询结果:逐行读取本身不能保证客户端内存占用恒定。

这个流程仍可分批读取输入、分批生成数据,但最终 CSV 必须在全局排序之后写入。

方案二:使用外部归并排序

如果无法在数据库中统一排序,或者排序依赖 PHP 中生成的最终字段,可以先将导出记录分批排序,写入多个临时文件,再进行归并。

具体步骤是:

  1. 按内存预算收集一批最终导出记录。
  2. 使用统一的比较规则对这一批记录排序,写入一个临时 CSV 文件。
  3. 重复处理,直到所有记录都已写入临时文件。
  4. 从每个临时文件读取一条记录,选出排序最靠前的一条写入最终文件,再从对应文件读取下一条。
  5. 重复归并,直到所有临时文件读取完毕。

归并过程可以表示为:

为每个临时文件读取第一条记录

当仍有候选记录时:
    按多字段比较规则选出最靠前的记录
    将该记录写入最终 CSV
    从该记录所属的临时文件读取下一条
    更新候选记录

分块排序和归并必须使用完全相同的比较规则,包括升降序、数值比较、空值处理和字符串比较方式。临时文件较多时,可以用最小堆维护候选记录;如果文件数量超过可同时打开的数量,就分多轮归并。

这种方式不必把全部记录装入内存,但需要额外的磁盘空间和读写操作。表头只在最终文件中写入一次。临时文件也应使用 CSV 读写函数处理,避免将带引号、分号或字段内换行的记录当成普通文本行拆分。

当前代码还需要处理最后不足 100 条的数据

展示的循环只在积累到 100 条时调用 processChunk()。循环结束后,还需要处理剩余编号:

if ($productNumbers !== []) {
    $this->processChunk($productNumbers, $exportFile);
}

这段补充可以解决尾批未处理的问题,但不会实现全局排序。若采用暂存表或外部归并排序,processChunk() 的输出目标也要相应调整为暂存表或临时文件。

当前场景中,如果排序字段都能在数据库中表达,建议先暂存数据,统一执行 ORDER BY,再顺序写出 CSV。只有排序必须在 PHP 中完成时,再实现外部归并排序。

备注:内容仅供参考。