【问题标题】:How to implement many-to-many matching without cursor如何在没有游标的情况下实现多对多匹配
【发布时间】:2019-07-13 08:51:11
【问题描述】:

我有两张表 Orders 和 Inventory。

create table #orders
(orderId int,
demand decimal,
itemId int)

create table #inventory 
(invId int,
qtyOnHand decimal,
itemId int)

insert into #orders 
values(101, 2, 999)

insert into #orders 
values(102, 1, 999)

insert into #inventory
values(601, 1, 999)
insert into #inventory
values(602, 4, 999)
insert into #inventory
values(603, 1, 999)

我需要为已排序的订单分配相应的库存数量。所以预期的结果是:

orderID - inventoryID - reservedQty
101       601          1
101       602          1
102       602          1

主要问题是#inventory.qtyOnHand 在我将其分配给订单时正在发生变化

我已经尝试过的是汇总所有库存

 SELECT
itemId,
sum(qtyOnHand) as qtyOnHand,
STUFF((
SELECT ', ' + cast(invId as varchar)
FROM #inventory
WHERE (itemId = inv.itemId)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)'),1,2,'') AS inv_info
into #inv_aggr
FROM #inventory inv
GROUP BY itemId

select #orders.orderId, 
case  when ( #inv_aggr.qtyOnHand >= (select sum(o.demand) from #orders o where  o.itemId = #orders.itemId and o.orderId <= #orders.orderId))
then #orders.demand
else #inv_aggr.qtyOnHand - (select coalesce(sum(demand), 0) from #orders o where o.itemId = #orders.itemId and o.orderId < #orders.orderId)
end as qty_rsvd,
#inv_aggr.inv_info
from #orders
inner join #inv_aggr
on #inv_aggr.itemId =#orders.itemId and #inv_aggr.qtyOnHand > 0

但这无济于事,因为我可以按库存拆分和匹配订单

我将不胜感激有关如何实施此任务或如何将此任务拆分为多个方面的任何建议。

【问题讨论】:

  • this 回答有帮助吗?您可以通过搜索[tsql] deplete找到其他一些相关的帖子。
  • 伟大的第一篇文章!继续发布可用的示例数据和可读的期望结果的方法!

标签: tsql


【解决方案1】:

喜欢库存/订单管理问题,尝试完全基于集合(无循环)但没有像大多数人那样递归。递归增加了复杂性、潜在的性能挑战和无限循环的风险

/*Raw data setup*/
DROP TABLE IF EXISTS #Orders
DROP TABLE IF EXISTS #Inventory
DROP TABLE IF EXISTS #OrderRange
DROP TABLE IF EXISTS #InventoryRange

CREATE TABLE #Orders(OrderID INT,Demand INT,ItemID INT)
CREATE TABLE #Inventory (InvID INT,QtyOnHand INT,ItemID INT)

INSERT INTO #Orders 
VALUES(101, 2,999)
,(102, 1,999)
,(103,10,222)
,(104,1,222)
,(105,4,222)

INSERT INTO #Inventory
VALUES(601, 1,999)
,(602, 4,999)
,(603, 1,222)
,(604,2,222)
,(605,2,222)

/*Setup work tables
Essentially ranges number each inventory and reserves a range of ID's for each order*/
SELECT *
    ,LowerRange = SUM(Demand) OVER (PARTITION BY ItemID ORDER BY OrderID) - Demand + 1
    ,UpperRange = SUM(Demand) OVER (PARTITION BY ItemID ORDER BY OrderID)
INTO #OrderRange
FROM #Orders

SELECT *
    ,LowerRange = SUM(QtyOnHand) OVER (PARTITION BY ItemID ORDER BY InvID)- QtyOnHand + 1
    ,UpperRange = SUM(QtyOnHand) OVER (PARTITION BY ItemID ORDER BY InvID) 
INTO #InventoryRange
FROM #Inventory

/*Final query*/
SELECT 
     O.OrderID
    ,I.InvID
    ,O.ItemID
    ,C.ReservedQty
    ,O.Demand
    ,I.QtyOnHand
    ,QtyFulfilled = SUM(C.ReservedQty) OVER (PARTITION BY O.OrderID,O.ItemID ORDER BY I.InvID) /*When this less than Demand OR NULL, then order not enough stock to fulfill order*/
    ,QtyRemaining = O.Demand - SUM(ISNULL(C.ReservedQty,0)) OVER (PARTITION BY O.OrderID,O.ItemID ORDER BY I.InvID) /*Inverse basically of QtyFulfilled*/
FROM #OrderRange AS O
LEFT JOIN #InventoryRange AS I
    ON O.ItemID = I.ItemID
    AND (
        /*Overlap between order range and inventory ranges*/
        O.LowerRange BETWEEN I.LowerRange and I.UpperRange
        OR O.UpperRange BETWEEN I.LowerRange and I.UpperRange
        /*For when entire inventory range between order range*/
        OR I.UpperRange BETWEEN O.LowerRange AND O.UpperRange
        )
CROSS APPLY (
    /*Finds the count of overlap between order range and inventory range
    Essentially orders all range values and finds the two middle numbers, then calculates the delta of them
    Ex. If you have a range of 2 to 5 and 3 to 6
        It sorts the numbers as 2,3,5,6
        It grabs the second value 3 (OFFSET 1 FETCH 1), then because LEAD runs before OFFSET it grabs the next row so 5
        The delta of 3 and 5 = 2 so there's a 2 number overlap between the two ranges
    */
    SELECT ReservedQty = LEAD(DTA.Points,1) OVER (ORDER BY DTA.Points) - DTA.Points + 1
    FROM (VALUES(O.LowerRange),(O.UpperRange),(I.LowerRange),(I.UpperRange)) AS DTA(Points)
    ORDER BY Points
    OFFSET 1 ROW FETCH NEXT 1 ROW ONLY
            ) AS C
ORDER BY O.Orderid,I.InvID

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-30
    • 2023-03-08
    • 1970-01-01
    相关资源
    最近更新 更多