如何在 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 掩盖重复数据。
备注:内容仅供参考。
