SQL版本的库龄分析报表,使用存储过程实现,同时支持分支是否启用。
CREATE PROCEDURE "IPS_Report_StockAge"
(
@BPLId NVARCHAR(50), -- 分支ID(筛选指定分支,为空时不限制)
@ItmsGrpCod NVARCHAR(20), -- 物料组编码(筛选指定物料组,为空时不限制)
@EndDate DATE -- 统计结束日期(所有数据的时间截止点)
)
AS
BEGIN
SET NOCOUNT ON; -- 关闭计数消息输出,提升执行效率
---01 临时表#StockList:筛选截止日期前库存数量不为0的物料清单
SELECT
T0."ItemCode", -- 物料编码
SUM(T0."InQty"-T0."OutQty") AS "Qty" -- 净库存数量(入库数量 - 出库数量)
INTO #StockList -- 存入临时表,用于后续筛选有效库存物料
FROM OIVL T0 -- OIVL:库存交易主表(存储所有库存进出记录)
INNER JOIN OITM T1 ON T0."ItemCode"= T1."ItemCode" -- 关联物料主表(OITM),获取物料组信息
INNER JOIN OWHS T2 ON T0."LocCode"= T2."WhsCode" -- 关联仓库表(OWHS),获取仓库所属分支信息
WHERE 1=1
AND T0."DocDate"<=@EndDate -- 交易日期不晚于统计截止日期
AND (T1."ItmsGrpCod"=@ItmsGrpCod OR ISNULL(@ItmsGrpCod,'')='') -- 物料组筛选(为空时不限制)
AND (CAST(T2."BPLid" AS NVARCHAR(50))=@BPLId OR ISNULL(@BPLId,'')='') -- 分支筛选(仓库BPLId转字符串匹配,为空时不限制)
GROUP BY T0."ItemCode" -- 按物料编码分组,计算每个物料的总库存
HAVING SUM(T0."InQty"-T0."OutQty")<>0; -- 仅保留净库存不为0的物料
---02 临时表#StockIn:获取有效库存物料的入库交易明细(含累计入库量和排序ID)
SELECT
T0."ItemCode", -- 物料编码
T0."DocDate", -- 入库交易日期
T0."TransType", -- 交易类型(区分不同入库业务,如采购入库、生产入库等)
T0."CreatedBy", -- 制单人
T0."BASE_REF", -- 源单据编号(关联原始业务单据)
T0."DocLineNum", -- 单据行号
T0."InQty", -- 本次入库数量
-- 按物料编码分区,交易日期倒序、交易序号倒序排序,生成入库记录序号(最新入库为ID=1)
ROW_NUMBER() OVER(PARTITION BY T0."ItemCode" ORDER BY T0."DocDate" DESC,T0."TransSeq" DESC) AS "ID",
-- 按物料编码分区,累计入库数量(从最新入库开始累加,用于匹配当前库存)
SUM(T0."InQty") OVER(PARTITION BY T0."ItemCode" ORDER BY T0."ItemCode",T0."DocDate" DESC,T0."TransSeq" DESC ) AS "SumQty",
T1."Qty" -- 关联#StockList获取该物料的当前总库存
INTO #StockIn -- 存入临时表,用于后续库龄计算
FROM OIVL T0 -- OIVL:库存交易主表
INNER JOIN #StockList T1 ON T0."ItemCode"= T1."ItemCode" -- 仅保留库存非零的物料
INNER JOIN OWHS T2 ON T0."LocCode"= T2."WhsCode"
AND (CAST(T2."BPLid" AS NVARCHAR(100))=@BPLId OR T2."BPLid" IS NULL) -- 分支筛选(含仓库BPLId为空的情况)
WHERE EXISTS(SELECT 1 FROM #StockList U0 WHERE T0."ItemCode"=U0."ItemCode" ) -- 二次确认库存非零(冗余筛选,提升稳定性)
AND T0."InQty">0 -- 仅保留入库记录(排除出库记录)
AND T0."TransType"<>'67' -- 排除交易类型为67的记录(通常为无效或冲销交易)
AND T0."DocDate"<@EndDate; -- 交易日期早于截止日期(截止日期当天入库不计入库龄计算)
-- 临时表#MinID:获取每个物料满足当前库存的最小入库记录ID(即覆盖库存所需的最早入库批次)
SELECT
T0."ItemCode", -- 物料编码
MIN(T0.ID) AS "ID", -- 满足库存的最小入库记录ID(分界点ID)
MAX(T0."DocDate") AS "MinDocDate" -- 该物料的最早入库日期(对应分界点及之前的入库记录)
INTO #MinID
FROM #StockIn T0
WHERE T0."SumQty">=T0."Qty" -- 累计入库量>=当前库存(找到覆盖库存的所有入库记录)
GROUP BY T0."ItemCode";
-- 临时表#DATA:计算每个入库批次对应的有效库龄库存(AgaQty)及关键日期
SELECT
T0."ItemCode", -- 物料编码
T0."DocDate", -- 入库日期
T0."TransType", -- 交易类型
T0."CreatedBy", -- 制单人
T0."BASE_REF", -- 源单据编号
T0."DocLineNum", -- 单据行号
T0."InQty", -- 本次入库数量
T0."Qty", -- 当前总库存
T0."SumQty", -- 累计入库量
T0."ID", -- 入库记录序号(倒序)
T1."ID" AS "MinID", -- 覆盖库存的最小分界点ID
-- 有效库龄库存计算:
-- 1. 序号<分界点ID:该批次入库量全额计入(已完全覆盖库存)
-- 2. 序号=分界点ID:用总库存 - 上一批次累计入库量(剩余库存计入当前批次)
CASE WHEN T0."ID"1096 THEN T0."AgaQty" ELSE 0 END) AS "三年以上" -- 库龄>3年的库存(1096=365*3+1)
FROM #DATA T0 -- 基于库龄计算中间表聚合
INNER JOIN OITM T1 ON T0."ItemCode"= T1."ItemCode" -- 关联物料主表,获取物料描述
INNER JOIN OITB T2 ON T1."ItmsGrpCod" = T2."ItmsGrpCod" -- 关联物料组表,获取物料组名称
GROUP BY T0."ItemCode",T1."ItemName",T2."ItmsGrpCod",T2."ItmsGrpNam" -- 按物料+物料组分组
ORDER BY 3,1; -- 按物料组编码、物料编码排序输出
-- 清理临时表(避免占用资源,存储过程结束后临时表会自动删除,此处为冗余处理)
DROP TABLE IF EXISTS #StockList;
DROP TABLE IF EXISTS #StockIn;
DROP TABLE IF EXISTS #MinID;
DROP TABLE IF EXISTS #DATA;
END
评论已关闭