Apache POI 和 SUBTOTAL 公式
Apache POI and SUBTOTAL formula
=SUMPRODUCT((K:K="yes")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))
和
=SUMPRODUCT((K:K="yes")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))+(NOW()*0)
这是使用 java poi api 在生成的 xls 工作簿中的一张工作表中使用的 excel 公式。
只有当我在 Excel 中的单元格上按 Enter 时,它才会正确计算。
公式计算器和 wb.setForceFormulaRecalculation(true)
似乎不起作用。
java 代码是:
cell.setCellFormula("SUMPRODUCT((K:K=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))");
公式的目标是计算 "yes" 在 K 列中的出现次数,但仅针对过滤后的可见行。 K10 是实际数据开始的单元格。此单元格上方的行包含 headers。
我可以使用 HSSF
确认这是一个问题。但它与公式重新计算无关。 OFFSET
本身和 NOW
一样易变。即使不设置 setForceFormulaRecalculation(true)
,这两个强制公式重新计算。但是在二进制 *.xls
文件中,OFFSET
中的 ROW(K:K)
最初并不作为数组求值。所以它只计算一次 1
(ROW(K1)
) 而不是数组 {1,2,3,4,5,...}
(ROW(K1), ROW(K2), ROW(K3), ...
)。使用 XSSF
(*.xlsx
) 它有效。
我发现问题在于 apache poi
如何为 SUMPRODUCT
创建 HSSF
公式。 SUMPRODUCT
总是一个数组函数。所以 SUMPRODUCT
中嵌入的所有函数也应该是数组函数。但是 apache poi
没有将 CLASS_ARRAY
设置为嵌入在 SUMPRODUCT
中的函数。相反,它将 CLASS_VALUE
设置为好像函数是独立的。
以下工作草案显示了这个问题。有一种方法 makeArrayFormula
可以将所有 FuncVarPtg
(函数变量解析对象)从 CLASS_VALUE
更改为 CLASS_ARRAY
。在 运行 该方法之后,SUMPRODUCT
公式在 HSSF
.
中也按预期工作
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.FuncVarPtg;
import java.lang.reflect.Field;
class CreateExcelFormula {
static void makeArrayFormula(HSSFCell formulaCell) throws Exception {
Field _record = HSSFCell.class.getDeclaredField("_record");
_record.setAccessible(true);
CellValueRecordInterface recordInterface = (CellValueRecordInterface)_record.get(formulaCell);
System.out.println(recordInterface);
if (recordInterface instanceof FormulaRecordAggregate) {
FormulaRecordAggregate formulaRecordAggregate = (FormulaRecordAggregate)recordInterface;
FormulaRecord formulaRecord = formulaRecordAggregate.getFormulaRecord();
Ptg[] ptgs = formulaRecord.getParsedExpression();
for (Ptg ptg : ptgs) {
if (ptg instanceof FuncVarPtg) {
if (ptg.getPtgClass() == Ptg.CLASS_VALUE) {
ptg.setClass(Ptg.CLASS_ARRAY);
}
}
}
formulaRecord.setParsedExpression(ptgs);
}
System.out.println(recordInterface);
}
public static void main(String[] args) throws Exception {
try (
Workbook workbook = new HSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xls") ) {
//Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx") ) {
Sheet sheet = workbook.createSheet();
Row row;
Cell cell;
row = sheet.createRow(0);
cell = row.createCell(0);
cell.setCellValue("F:");
cell = row.createCell(1);
//cell.setCellFormula("SUMPRODUCT((K:K=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))");
cell.setCellFormula("SUMPRODUCT((K10:K10000=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K10:K10000)-ROW(K10),0))))");
if (cell instanceof HSSFCell) {
makeArrayFormula((HSSFCell)cell);
}
for (int r = 9; r < 30; r++) {
row = sheet.createRow(r);
cell = row.createCell(10);
if (r % 2 == 0) cell.setCellValue("yes"); else cell.setCellValue("no");
}
for (int r = 14; r < 19; r++) {
sheet.getRow(r).setZeroHeight(true);
}
workbook.write(fileout);
}
}
}
顺便说一句:永远不要在数组公式中使用像 K:K
这样的完整列引用。这是一场性能噩梦。 ROW(K10:K10)
周围的 MIN
是多余的。
=SUMPRODUCT((K:K="yes")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))
和
=SUMPRODUCT((K:K="yes")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))+(NOW()*0)
这是使用 java poi api 在生成的 xls 工作簿中的一张工作表中使用的 excel 公式。
只有当我在 Excel 中的单元格上按 Enter 时,它才会正确计算。
公式计算器和 wb.setForceFormulaRecalculation(true)
似乎不起作用。
java 代码是:
cell.setCellFormula("SUMPRODUCT((K:K=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))");
公式的目标是计算 "yes" 在 K 列中的出现次数,但仅针对过滤后的可见行。 K10 是实际数据开始的单元格。此单元格上方的行包含 headers。
我可以使用 HSSF
确认这是一个问题。但它与公式重新计算无关。 OFFSET
本身和 NOW
一样易变。即使不设置 setForceFormulaRecalculation(true)
,这两个强制公式重新计算。但是在二进制 *.xls
文件中,OFFSET
中的 ROW(K:K)
最初并不作为数组求值。所以它只计算一次 1
(ROW(K1)
) 而不是数组 {1,2,3,4,5,...}
(ROW(K1), ROW(K2), ROW(K3), ...
)。使用 XSSF
(*.xlsx
) 它有效。
我发现问题在于 apache poi
如何为 SUMPRODUCT
创建 HSSF
公式。 SUMPRODUCT
总是一个数组函数。所以 SUMPRODUCT
中嵌入的所有函数也应该是数组函数。但是 apache poi
没有将 CLASS_ARRAY
设置为嵌入在 SUMPRODUCT
中的函数。相反,它将 CLASS_VALUE
设置为好像函数是独立的。
以下工作草案显示了这个问题。有一种方法 makeArrayFormula
可以将所有 FuncVarPtg
(函数变量解析对象)从 CLASS_VALUE
更改为 CLASS_ARRAY
。在 运行 该方法之后,SUMPRODUCT
公式在 HSSF
.
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.FuncVarPtg;
import java.lang.reflect.Field;
class CreateExcelFormula {
static void makeArrayFormula(HSSFCell formulaCell) throws Exception {
Field _record = HSSFCell.class.getDeclaredField("_record");
_record.setAccessible(true);
CellValueRecordInterface recordInterface = (CellValueRecordInterface)_record.get(formulaCell);
System.out.println(recordInterface);
if (recordInterface instanceof FormulaRecordAggregate) {
FormulaRecordAggregate formulaRecordAggregate = (FormulaRecordAggregate)recordInterface;
FormulaRecord formulaRecord = formulaRecordAggregate.getFormulaRecord();
Ptg[] ptgs = formulaRecord.getParsedExpression();
for (Ptg ptg : ptgs) {
if (ptg instanceof FuncVarPtg) {
if (ptg.getPtgClass() == Ptg.CLASS_VALUE) {
ptg.setClass(Ptg.CLASS_ARRAY);
}
}
}
formulaRecord.setParsedExpression(ptgs);
}
System.out.println(recordInterface);
}
public static void main(String[] args) throws Exception {
try (
Workbook workbook = new HSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xls") ) {
//Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx") ) {
Sheet sheet = workbook.createSheet();
Row row;
Cell cell;
row = sheet.createRow(0);
cell = row.createCell(0);
cell.setCellValue("F:");
cell = row.createCell(1);
//cell.setCellFormula("SUMPRODUCT((K:K=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K:K)-MIN(ROW(K10:K10)),0))))");
cell.setCellFormula("SUMPRODUCT((K10:K10000=\"yes\")*(SUBTOTAL(103,OFFSET(K10,ROW(K10:K10000)-ROW(K10),0))))");
if (cell instanceof HSSFCell) {
makeArrayFormula((HSSFCell)cell);
}
for (int r = 9; r < 30; r++) {
row = sheet.createRow(r);
cell = row.createCell(10);
if (r % 2 == 0) cell.setCellValue("yes"); else cell.setCellValue("no");
}
for (int r = 14; r < 19; r++) {
sheet.getRow(r).setZeroHeight(true);
}
workbook.write(fileout);
}
}
}
顺便说一句:永远不要在数组公式中使用像 K:K
这样的完整列引用。这是一场性能噩梦。 ROW(K10:K10)
周围的 MIN
是多余的。