在SQL查询中实现借记信用解决系统

时间:2013-05-22 12:46:27

标签: sql sql-server algorithm

我正在尝试实施借记 - 贷方解决系统,但我很难根据集合来表达逻辑。

假设我有一个Orders表:

Id    OrderId     Amount     AdjustmentFlag
 1       1           10.00        0
 2       1           10.00        1
 3       1           10.00        2
 4       2           20.00        1
 5       2           20.00        2
 6       2           20.00        2
 7       3           30.00        1
 8       4           40.00        0
 9       4           40.00        0
10       4           40.00        1
11       5           50.00        0
12       5           50.00        1
13       5           60.00        2
14       5           60.00        1
15       5           60.00        2
16       5           70.00        1

我需要根据他们是否有匹配的“已取消”标记来挑选仍然有效的Id

0 - Original Order
1 - Cancelled Order
2 - Adjusted Order
  1. 102匹配,优先于0
  2. 如果1标志不匹配,则会被忽略。
  3. 鉴于以上例子:

    • Id 2将匹配Id 1,即Id 3。
    • Id 4将匹配Id 5或Id 6,但不能同时匹配。
    • Id 7将被忽略。
    • Id 10将匹配Id 8或Id 9,但不能同时匹配。
    • Id 12将与Id 11匹配。
    • Id 14将匹配Id 13或Id 15,但不能同时匹配。
    • Id 16将被忽略。

    可能的结果是[1,2,4,5,7,8,10,11,12,13,14,16](较低的Id有偏好)或[1,2,4,6,7 ,9,10,11,12,14,15,16](更高的Id有偏好)。只要结果是确定性的,任何一个都可以工作。

    创建脚本:

    CREATE TABLE [Order]
    (
         Id INT IDENTITY NOT NULL PRIMARY KEY
        ,OrderId INT NOT NULL
        ,Amount MONEY NOT NULL
        ,AdjustmentFlag TINYINT NOT NULL
    );
    
    INSERT INTO [Order](OrderId, Amount, AdjustmentFlag)
    SELECT 1, 10.00, 0
    UNION ALL
    SELECT 1, 10.00, 1
    UNION ALL
    SELECT 1, 10.00, 2
    UNION ALL
    SELECT 2, 20.00, 1
    UNION ALL
    SELECT 2, 20.00, 2
    UNION ALL
    SELECT 2, 20.00, 2
    UNION ALL
    SELECT 3, 30.00, 1
    UNION ALL
    SELECT 4, 40.00, 0
    UNION ALL
    SELECT 4, 40.00, 0
    UNION ALL
    SELECT 4, 40.00, 1
    UNION ALL
    SELECT 5, 50.00, 0
    UNION ALL
    SELECT 5, 50.00, 1
    UNION ALL
    SELECT 5, 60.00, 2
    UNION ALL
    SELECT 5, 60.00, 1
    UNION ALL
    SELECT 5, 60.00, 2
    UNION ALL
    SELECT 5, 70.00, 1
    

    这是我目前的部分解决方案:

    WITH Orders AS
    (
        SELECT
            Id,
            OrderId,
            Amount,
            AdjustmentFlag,
            EffectiveOrder = ROW_NUMBER() OVER (PARTITION BY OrderId, Amount ORDER BY AdjustmentFlag DESC),
            UnmatchedOrder = CASE WHEN EXISTS(SELECT 1 FROM [Order] uo WHERE uo.OrderId = o.OrderId GROUP BY uo.OrderId HAVING(COUNT(uo.OrderId) = 1)) THEN 1 ELSE 0 END,
            OriginalWithoutAdjustment = CASE WHEN EXISTS(SELECT 1 FROM [Order] uo WHERE uo.OrderId = o.OrderId AND uo.Amount = o.Amount GROUP BY uo.OrderId, uo.Amount HAVING (MAX(uo.AdjustmentFlag) = 1)) THEN 1 ELSE 0 END,
            AdjustmentWithoutOriginal = CASE WHEN EXISTS(SELECT 1 FROM [Order] uo WHERE uo.OrderId = o.OrderId AND uo.Amount = o.Amount GROUP BY uo.OrderId, uo.Amount HAVING (MIN(uo.AdjustmentFlag) = 1)) THEN 1 ELSE 0 END
        FROM [Order] o
    )
    ,MatchedOrders AS
    (
        SELECT
            Id
        FROM Orders
        WHERE
        -- Assume AdjustmentFlag = 2 and take everything else
        EffectiveOrder <> 1
        OR
        (
            -- Assume AdjustmentFlag = 2 and there is no Order with AdjustmentFlag = 0
            -- Take everything since the MIN AdjustmentFlag = 1
            AdjustmentWithoutOriginal = 1
            AND EffectiveOrder > 1
        )
        OR
        (
            -- Assume AdjustmentFlag = 1 and there are no other Orders, so ignore it
            AdjustmentFlag = 1
            AND UnmatchedOrder = 1
        )
        OR
        (
            -- We don't care about the orders if they don't have any Amount
            Amount = 0
            AND EffectiveOrder = 1
        )
        AND NOT
        (
            -- We have an Original without any other Orders
            EffectiveOrder = 1
            AND UnmatchedOrder = 1
            AND AdjustmentFlag = 0
        )
    )
    SELECT
        o.OrderId,
        o.AdjustmentFlag,
        o.Amount,
        o.EffectiveOrder,
        o.UnmatchedOrder,
        Excluded = CASE WHEN mo.Id IS NULL THEN 0 ELSE 1 END
    FROM Orders o
    LEFT OUTER JOIN MatchedOrders mo
    ON o.Id = mo.Id
    ORDER BY OrderId, Amount, AdjustmentFlag
    

    结果:

    Result

1 个答案:

答案 0 :(得分:2)

尝试:

with cte as
(select o.*, 
        case AdjustmentFlag when 1 then -1 else 1 end DrCr,
        row_number() over (partition by OrderId, Amount, case AdjustmentFlag when 1 then 1 end
                           order by AdjustmentFlag, Id) Rn
 from [Order] o)
select OrderId,
       max(case DrCr when 1 then Id end) DrId,
       sum(case DrCr when 1 then Amount else 0 end) DrAmount,
       max(case DrCr when 1 then AdjustmentFlag end) DrAdjustmentFlag,
       max(case DrCr when -1 then Id end) CrId,
       sum(case DrCr when -1 then Amount else 0 end) CrAmount,
       max(case DrCr when -1 then AdjustmentFlag end) CrAdjustmentFlag,
       sum(DrCr * Amount) BalanceAmount
from cte
group by OrderId, Amount, Rn
having sum(DrCr * Amount) >= 0 /* excludes unmatched cancelled orders */

- 如果您只想查看不匹配的原始/修订订单,请将having子句条件更改为> 0

SQLFiddle here