PAYATHON 2026

如何在 DB2 中设计包含函数与字段的单个 SELECT 语句

数据小许

结论

这类报表可以通过一条包含 GROUP BY 和 HAVING 的 SELECT 语句完成。凡是出现在 SELECT 中、又没有参与聚合的字段,都要写入 GROUP BY。明细记录的筛选条件放在 WHERE 中,汇总后的差额条件则放在 HAVING 中。

SELECT
    P.OFFIV,
    S.FILEST,
    S.TOTDST,
    S.PDUEST,
    SUM(P.AMTPIV - P.DTDCIV) AS TOTAL_INVOICE_VALUE,
    S.TOTDST - SUM(P.AMTPIV - P.DTDCIV) AS DIFFERENCE_VALUE
FROM IMPSTATP AS S
JOIN ACCIHEDP AS H
    ON S.FILEST = H.FILEIV
   AND S.BKRST  = H.BKRIV
JOIN ACCIPBLP AS P
    ON H.INVIV = P.INVIV
   AND H.BKRIV = P.BKRIV
   AND H.OFFIV = P.OFFIV
WHERE H.VDPDIV <> 'V'
  AND P.CHGPIV IN ('011', '012', '093')
  AND P.VNNMIV <> 'FLAGGED'
GROUP BY
    P.OFFIV,
    S.FILEST,
    S.TOTDST,
    S.PDUEST
HAVING
    S.TOTDST - SUM(P.AMTPIV - P.DTDCIV) <> 0
ORDER BY
    S.FILEST;

为什么原查询不能正常工作

原查询的分组只有:

GROUP BY FILEST

但 SELECT 中还有这些非聚合字段:

P.OFFIV
TOTDST
PDUEST

在 DB2 中,这些字段既没有包含在 SUM() 等聚合函数内,也没有列入 GROUP BY,通常会引发分组错误。

建议为三个表分别指定别名,并用别名限定字段,例如 S.FILEST 和 H.FILEIV。这样可以避免同名字段产生歧义,也便于核对连接条件。

查询的执行逻辑

WHERE 首先筛选需要参与汇总的 invoice 明细:

WHERE H.VDPDIV <> 'V'
  AND P.CHGPIV IN ('011', '012', '093')
  AND P.VNNMIV <> 'FLAGGED'

筛选完成后,再按 file 相关字段分组:

GROUP BY
    P.OFFIV,
    S.FILEST,
    S.TOTDST,
    S.PDUEST

每组的 invoice value 使用以下表达式计算:

SUM(P.AMTPIV - P.DTDCIV)

汇总完成后,HAVING 会过滤掉差额为零的记录:

HAVING S.TOTDST - SUM(P.AMTPIV - P.DTDCIV) <> 0

汇总差额不能在 WHERE 中判断,因为执行 WHERE 时,SUM() 还没有完成计算。

如果需要严格按 file number 返回一行

当前查询返回了 P.OFFIV,所以分组时也必须包含该字段。如果同一个 file number 关联了多个 OFFIV,结果会按 office 分别返回,而不是每个 file 只返回一行。

如果报表要求每个 file number 严格对应一行,而且不需要显示 office,可以删除 P.OFFIV:

SELECT
    S.FILEST,
    S.TOTDST,
    S.PDUEST,
    SUM(P.AMTPIV - P.DTDCIV) AS TOTAL_INVOICE_VALUE,
    S.TOTDST - SUM(P.AMTPIV - P.DTDCIV) AS DIFFERENCE_VALUE
FROM IMPSTATP AS S
JOIN ACCIHEDP AS H
    ON S.FILEST = H.FILEIV
   AND S.BKRST  = H.BKRIV
JOIN ACCIPBLP AS P
    ON H.INVIV = P.INVIV
   AND H.BKRIV = P.BKRIV
   AND H.OFFIV = P.OFFIV
WHERE H.VDPDIV <> 'V'
  AND P.CHGPIV IN ('011', '012', '093')
  AND P.VNNMIV <> 'FLAGGED'
GROUP BY
    S.FILEST,
    S.TOTDST,
    S.PDUEST
HAVING
    S.TOTDST - SUM(P.AMTPIV - P.DTDCIV) <> 0
ORDER BY
    S.FILEST;

如果 FILEST 只在同一个 broker 内唯一,还需要把 S.BKRST 加入 SELECT 和 GROUP BY。否则,不同 broker 下编号相同的 file 可能会被合并。

处理 NULL 值

当 AMTPIV 或 DTDCIV 为 NULL 时,下面的表达式也会返回 NULL:

P.AMTPIV - P.DTDCIV

如果需要明确将空金额按零处理,可以使用 COALESCE:

SUM(
    COALESCE(P.AMTPIV, 0) - COALESCE(P.DTDCIV, 0)
)

完整的差额表达式则改为:

COALESCE(S.TOTDST, 0)
- SUM(COALESCE(P.AMTPIV, 0) - COALESCE(P.DTDCIV, 0))

部分旧版 IBM i DB2 环境也支持类似的 VALUE() 写法:

VALUE(P.AMTPIV, 0)

应根据当前 IBM i DB2 版本支持的语法选择具体写法。

字符字段同样要考虑 NULL。例如:

P.VNNMIV <> 'FLAGGED'

这个条件不会匹配 VNNMIV 为 NULL 的记录。如果要把 NULL 视为“未标记”,可以改成:

COALESCE(P.VNNMIV, '') <> 'FLAGGED'

旧版 DB2 的兼容写法

如果旧环境不适合在 HAVING 中重复维护较长的表达式,可以先在派生表中完成汇总,再由外层查询筛选。整个查询仍是一条 SELECT statement:

SELECT
    X.OFFIV,
    X.FILEST,
    X.TOTDST,
    X.PDUEST,
    X.TOTAL_INVOICE_VALUE,
    X.TOTDST - X.TOTAL_INVOICE_VALUE AS DIFFERENCE_VALUE
FROM
(
    SELECT
        P.OFFIV,
        S.FILEST,
        S.TOTDST,
        S.PDUEST,
        SUM(
            COALESCE(P.AMTPIV, 0) - COALESCE(P.DTDCIV, 0)
        ) AS TOTAL_INVOICE_VALUE
    FROM IMPSTATP AS S
    JOIN ACCIHEDP AS H
        ON S.FILEST = H.FILEIV
       AND S.BKRST  = H.BKRIV
    JOIN ACCIPBLP AS P
        ON H.INVIV = P.INVIV
       AND H.BKRIV = P.BKRIV
       AND H.OFFIV = P.OFFIV
    WHERE H.VDPDIV <> 'V'
      AND P.CHGPIV IN ('011', '012', '093')
      AND P.VNNMIV <> 'FLAGGED'
    GROUP BY
        P.OFFIV,
        S.FILEST,
        S.TOTDST,
        S.PDUEST
) AS X
WHERE X.TOTDST - X.TOTAL_INVOICE_VALUE <> 0
ORDER BY
    X.FILEST;

这种写法只定义一次聚合结果,外层查询可以直接引用 TOTAL_INVOICE_VALUE。

还需要确认三个表之间的连接键是否唯一。如果一个 invoice header 连接到多条原本不应重复计算的 payable 记录,SUM() 会把这些行全部计入。发现汇总金额异常时,应先检查连接后的明细行,不要直接用 DISTINCT 掩盖重复数据。

备注:内容仅供参考。