上一篇的 sql 语句蛮简单的,接下来,来看一个相对复杂的的场景。假设我们要编写一个查询,计算期间物料进出存的查询。需要用到 5 个表,将创建表的 sql 语句贴在下面,示例数据也已经上传到 github。
物料主数据表 (material_numbers):
create table material_numbers ([MaterialNo] varchar(20) primary key,[Description_e] varchar(100) null,[Description_c] varchar(100) null
);
仓位 (storage_locations)
create table storage_locations([StorageLocation] varchar(10) PRIMARY KEY,[LocationType] varchar(10) NULL,[Description] varchar(50) NULL);
movement_types:
CREATE TABLE movement_types([MovementTypeID] varchar(10) PRIMARY KEY,[Description] varchar(50) NULL,[InOutSign] varchar(1) NULL
);
stock_movement_headers:
CREATE TABLE stock_movement_headers([DocNo] varchar(10) PRIMARY KEY,[MovementType] varchar(10) NULL,[RefDocNo] varchar(10) NULL,[VendorID] varchar(10) NULL,[CustomerID] varchar(10) NULL,[DocDate] datetime NULL,[PeriodID] varchar(10) NULL,[GCInvoiceNo] varchar(15) NULL,[LongText] text NULL,[PostedBy] varchar(10) NULL,[PostedDate] datetime NULL,[Remarks] varchar(255) NULL
);
stock_movement_details:
CREATE TABLE stock_movement_details([DocNo] varchar(10),[MaterialNo] varchar(20),[StorageLocation] varchar(10) NULL,[Qty] float NULL,[Remarks] varchar(255) NULL,PRIMARY KEY(DocNo, MaterialNo)
);
在 MS Access 中表主要字段及表之间的关系:
查询条件是计算出 2008 年 4 月份物料的进出存,筛选条件:
- MaterialNo: Like "B180*'
- StorageLocation: 1001
最终的数据显示格式如下:
第一步:构建物料清单
首先构建 material_numbers 和 storage_locations 的组合 (笛卡尔乘积)。考虑到有些物料没有交易数据,这种组合能得到交易项全部为 0 的数据。在查询设计界面中,加入 material_numbers 和 storage_locations 两个表,双击 MaterialNo 和 StorageLocation 字段,将其加入到查询的字段。两个表为独立的状态,这样查询的结果就是一种笛卡尔乘积。在【条件】中,MaterialNo 输入 “B180*”, StorageLocation 输入 “1001”:
选中 material_numbers 表,将右边【属性表】表的别名改为 M,同样的方法将 storage_locations 表的别名改为 L。设置别名的目的是让 SQL 语句变得清晰。如果【属性表】没有出现,选中表,右键菜单有【属性】项,点击调出。完成后的界面应该如下所示:
进入 SQL 视图,此时 SQL 语句如下。因为 Access 不支持代码格式化和高亮,代码借助 Visual Studio Code 进行格式化。
SELECTM.MaterialNo,L.StorageLocation
FROMmaterial_numbers AS M,storage_locations AS L
WHERE(((M.MaterialNo) Like "B180*")AND ((L.StorageLocation) = "1001"));
第二步:构建交易清单
新建一个查询,将查询命名为 txns。查询基于三个表:
- stock_movement_headers: 交易表头
- stock_movement_details : 交易行项目
- movement_types : 移动类型,表示出入库的类型
因为没有维护表之间的关系,需要在查询中维护 stock_movement_headers 表和 movement_types 表之间的关系(左连接):
双击或拖放的方式选中需要的字段到查询中:
为了方便后面的根据年月筛选,基于 DocDate 新增两个计算字段: TxYear 和 TxMonth,分别表示交易的年和月。在表达式生成器中维护:
增加一个计算列 ActualQty, 正数表示入库,负数表示出库:
ActualQty: IIf([InOutSign]="+",Nz([Qty]),-1 * Nz([Qty]))
为了 SQL 语句更加清晰,设置三个表的别名:
H: stock_movement_headers
D: stock_movement_details
MVT: movement_types
然后对 materialNo 字段和 storageLocation 字段设置上一步相同的条件。设置完成后,查询设计视图的界面如下:
进入 SQL 视图,得到本步骤的 SQL 语句:
SELECTH.DocDate,D.MaterialNo,D.StorageLocation,H.MovementType,D.Qty,MVT.InOutSign,Year([DocDate]) AS TxYear,Month([DocDate]) AS TxMonth,IIf([InOutSign] = "+", Nz([Qty]), -1 * Nz([Qty])) AS ActualQty
FROM(stock_movement_headers AS HLEFT JOIN movement_types AS MVT ON H.MovementType = MVT.MovementTypeID)INNER JOIN stock_movement_details AS D ON H.DocNo = D.DocNo
WHERE(((D.MaterialNo) Like "B180*")AND ((D.StorageLocation) = "1001"));
运行一下查询,此时的界面如下:
第三步:添加条件列
接下来基于查询 txns 创建一个查询,使用行转列的方法增加三个计算列:
- BeginQty: 期初余额
- StockIn: 期间入库数量
- StockOut: 期间出库数量
将查询命名为 txn_summary,首先选取相关的列:
添加条件列,在表达式生成器界面中设置 BeginQty
的公式如下:
BeginQty: IIf([TxYear]<2008 Or ([TxYear]=2008 And [TxMonth]<4),[ActualQty],0)
同样的方法,添加计算列,得到物料入库数量和出库数量:
StockIn: IIf([TxYear]=2008 And [TxMonth]=4 And [InOutSign]="+",[ActualQty],0)
StockOut: IIf([TxYear]=2008 And [TxMonth]=4 And [InOutSign]="-",[ActualQty],0)
删除查询中不相关的字段(TxYear, TxMonth, InoutSign, ActualQty
),然后点击功能区上的汇总:
将需要计算的字段改为合计:
完成本步骤,得到的 SQL 语句为:
SELECTtxns.MaterialNo,txns.StorageLocation,Sum(IIf([TxYear] < 2008Or ([TxYear] = 2008And [TxMonth] < 4),[ActualQty],0)) AS BeginQty,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "+",[ActualQty],0)) AS StockIn,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "-",[ActualQty],0)) AS StockOut
FROMtxns
GROUP BYtxns.MaterialNo,txns.StorageLocation;
因为本步骤基于查询 txns,所以可以将查询稍加修改,并且插入上一步骤 txns 查询的代码。From 子句原来是这样的:
将其修改为:
括号中插入上一步 txns SQL 语句的码, 将其作为子查询:
SELECTtxns.MaterialNo,txns.StorageLocation,Sum(IIf([TxYear] < 2008Or ([TxYear] = 2008And [TxMonth] < 4),[ActualQty],0)) AS BeginQty,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "+",[ActualQty],0)) AS StockIn,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "-",[ActualQty],0)) AS StockOut
FROM(SELECTH.DocDate,D.MaterialNo,D.StorageLocation,H.MovementType,D.Qty,MVT.InOutSign,Year([DocDate]) AS TxYear,Month([DocDate]) AS TxMonth,IIf([InOutSign] = "+", Nz([Qty]), -1 * Nz([Qty])) AS ActualQtyFROM(stock_movement_headers AS HLEFT JOIN movement_types AS MVT ON H.MovementType = MVT.MovementTypeID)INNER JOIN stock_movement_details AS D ON H.DocNo = D.DocNoWHERE(((D.MaterialNo) Like "B180*")AND ((D.StorageLocation) = "1001"))) AS txns
GROUP BYtxns.MaterialNo,txns.StorageLocation;
计算期末库存
联合查询 material (别名 MT) 和 txn_summary (别名 TX),创建一个新的查询:
此时的 SQL 语句为:
SELECTMT.MaterialNo,MT.StorageLocation,TX.BeginQty,TX.StockIn,TX.StockOut,[BeginQty] + [StockIn] + [StockOut] AS EndQty
FROMmaterials AS MTLEFT JOIN txn_summary AS TX ON (MT.StorageLocation = TX.StorageLocation)AND (MT.MaterialNo = TX.MaterialNo);
将 FROM 子句后 materials
和 txn_summary
替换为子查询,得到最后完整的 SQL 语句:
SELECTMT.MaterialNo,MT.StorageLocation,TX.BeginQty,TX.StockIn,TX.StockOut,[BeginQty] + [StockIn] + [StockOut] AS EndQty
FROM(SELECTM.MaterialNo,L.StorageLocationFROMmaterial_numbers AS M,storage_locations AS LWHERE(((M.MaterialNo) Like "B180*")AND ((L.StorageLocation) = "1001"))) AS MTLEFT JOIN (SELECTtxns.MaterialNo,txns.StorageLocation,Sum(IIf([TxYear] < 2008Or ([TxYear] = 2008And [TxMonth] < 4),[ActualQty],0)) AS BeginQty,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "+",[ActualQty],0)) AS StockIn,Sum(IIf([TxYear] = 2008And [TxMonth] = 4And [InOutSign] = "-",[ActualQty],0)) AS StockOutFROM(SELECTH.DocDate,D.MaterialNo,D.StorageLocation,H.MovementType,D.Qty,MVT.InOutSign,Year([DocDate]) AS TxYear,Month([DocDate]) AS TxMonth,IIf([InOutSign] = "+", Nz([Qty]), -1 * Nz([Qty])) AS ActualQtyFROM(stock_movement_headers AS HLEFT JOIN movement_types AS MVT ON H.MovementType = MVT.MovementTypeID)INNER JOIN stock_movement_details AS D ON H.DocNo = D.DocNoWHERE(((D.MaterialNo) Like "B180*")AND ((D.StorageLocation) = "1001"))) AS txnsGROUP BYtxns.MaterialNo,txns.StorageLocation) AS TX ON (MT.StorageLocation = TX.StorageLocation)AND (MT.MaterialNo = TX.MaterialNo);
示例数据
github: stocks.accdb