在值范围层上分配值

时间:2014-07-30 17:32:17

标签: sql-server

我有两个表:每个员工的总销售额列表和补偿层列表

Employee Sales  |   Comp Tiers
==============  |   ===================
EmpID  Sales    |   TierID MaxAmt  Rate
  1    12000    |     1     10000   20%  --  Up to $10k sales  compensated at 20%
  2    17000    |     2     15000   25%  --The next $5k sales  compensated at 25%
  3    23000    |     3     20000   30%  --The next $5k sales  compensated at 30%
  4    31000    |     4     25000   40%  --The next $5k sales  compensated at 40%
                |     5     99999   50%  --Any remaining sales compensated at 50%

根据这些输入,我需要根据每个层的MaxAmt值将每个员工的销售额分摊到每个层,以计算每个层的补偿率。此外,我不想对每个层进行硬编码,因为层数可能会随着时间的推移而变化。 (第二个想法,我不介意硬编码,只要它可以处理UP-TO 5层。听起来很公平吗?)

期望的输出:

EmpID  Sales  TierID  TierAmt  Rate   Net
=========================================
  1    12000    1      10000    20%  2000
  1    12000    2       2000    25%   500
  2    17000    1      10000    20%  2000
  2    17000    2       5000    25%  1250
  2    17000    3       2000    30%   600
  3    23000    1      10000    20%  2000
  3    23000    2       5000    25%  1250
  3    23000    3       5000    30%  1500
  3    23000    4       3000    40%  1200
  4    31000    1      10000    20%  2000
  4    31000    2       5000    25%  1250
  4    31000    3       5000    30%  1500
  4    31000    4       5000    40%  2000
  4    31000    5       6000    50%  3000

我不熟悉SQL,但我甚至无法理解合适的策略。有任何想法吗?如果有助于实现目标,则允许对表结构进行更改。

SQLFiddle

4 个答案:

答案 0 :(得分:3)

让我们制作一些测试数据:

DECLARE @EmpSales TABLE
(
    EmpID INT,
    Sales INT
)

INSERT INTO @EmpSales
VALUES
( 1, 12000 ),
( 2, 17000 ),
( 3, 23000 ),
( 4, 31000 );

DECLARE @CompTiers TABLE
(
    TierID INT,
    MaxAmount INT,
    Rate DECIMAL(10,2)
)

INSERT INTO @CompTiers
VALUES
( 1, 10000, .20 ),
( 2, 15000, .25 ),
( 3, 20000, .30 ),
( 4, 25000, .40 ),
( 5, 99999, .50 );

在这里,我创建一个CTE来查找所有层和前一层(以获得层的顶部和底部)

WITH Tiers AS
(
    SELECT 
        n.TierID,
        n.MaxAmount,
        n.Rate,
        ISNULL(p.MaxAmount, 0) PrevAmount
    FROM @CompTiers n
    LEFT JOIN @CompTiers p
        ON p.TierID = n.TierID - 1
), 

让我们采取等级CTE并将其与销售选择交叉加入,只选择销售额大于预计数(层级底部)的层。

SalesComp AS
(
    SELECT *
    FROM @EmpSales e
    CROSS JOIN Tiers c
    WHERE Sales > PrevAmount
)

现在我们已经将数据匹配起来,只需要清理一些情况:

SELECT 
    s.EmpID,
    s.Sales,
    s.TierID,
    CASE 
        WHEN s.Sales > s.MaxAmount THEN s.MaxAmount - s.PrevAmount 
        ELSE s.Sales - s.PrevAmount 
    END TierAmount,
    s.Rate,
    CASE 
        WHEN s.Sales > s.MaxAmount THEN (s.MaxAmount - s.PrevAmount) * s.Rate
        ELSE (s.Sales - s.PrevAmount) * s.Rate
    END Net
FROM SalesComp s
ORDER BY EmpID, TierID

这是输出:

EmpID   Sales   TierID  TierAmount  Rate    Net
1   12000   1   10000   0.20    2000.00
1   12000   2   2000    0.25    500.00
2   17000   1   10000   0.20    2000.00
2   17000   2   5000    0.25    1250.00
2   17000   3   2000    0.30    600.00
3   23000   1   10000   0.20    2000.00
3   23000   2   5000    0.25    1250.00
3   23000   3   5000    0.30    1500.00
3   23000   4   3000    0.40    1200.00
4   31000   1   10000   0.20    2000.00
4   31000   2   5000    0.25    1250.00
4   31000   3   5000    0.30    1500.00
4   31000   4   5000    0.40    2000.00
4   31000   5   6000    0.50    3000.00

答案 1 :(得分:1)

the following query背后的想法恰好与Kevin Cook's answer中的想法相同,两种解决方案的主要区别在于语法:

WITH TierRanges AS (
  SELECT
    *,
    MinAmt = LAG(MaxAmt, 1, 0) OVER (ORDER BY MaxAmt)
  FROM
    CompTiers
)
SELECT
  s.EmpID,
  s.Sales,
  t.TierID,
  x.TierAmt,
  t.Rate,
  Net = x.TierAmt * t.Rate
FROM
  EmpSales AS s
INNER JOIN
  TierRanges AS t ON s.Sales > t.MinAmt
CROSS APPLY
(
  SELECT CASE WHEN s.Sales > t.MaxAmt THEN t.MaxAmt ELSE s.Sales END - t.MinAmt
) AS x (TierAmt)
ORDER BY
  s.EmpID,
  t.TierID
;

TierRanges CTE“使用CompTiers列增强”MinAmt行集,与MaxAmt一起形成一个等级范围,MinAmt为先前tier的MaxAmt值(或第一层的0)。这可以被认为是另一个答案的Tiers CTE的直接等价物,但是你可以看到这里使用的是LAG函数而不是自联接,而前者的工作速度可能比后者。

主要查询将EmpSales中的每一行与TierRangesEmpSales.Sales超过TierRanges.MinAmt的每一行连接起来。 (在另一个答案中,此部分与主查询分开实现为另一个CTE,SalesComp。)要获取层数,它会从MinAmt或{{}中减去Sales值1}},取决于哪一个较小。因为在查询中需要两次层级数量(一次用于输出,第二次用于获取MaxAmt),CROSS APPLY用于避免重复代码。

由于LAG,SQL Server 2012是运行查询所需的最低版本。

答案 2 :(得分:0)

尝试在表格之间使用完整的外部(交叉)联接,只要销售额小于或等于最高级别数量

答案 3 :(得分:0)

以Beth的建议作为起点,我通过其他方式努力提出一个可行的解决方案。它以CROSS JOIN开头,显示Sales和Tiers的所有可能组合,过滤掉Sales超出其匹配层的范围,并使用递归CTE使用先前的Tier结果计算每个Tier的计算。

;WITH 
  /* CROSS JOIN Sales and Tiers, and then        ***/
 /** filter to only relevant Tiers for each Emp. **/
/*** Also include Prior Tier [MaxAmt] value.     */
combos AS
(   SELECT      s.EmpID,s.Sales,t.TierID,
                [MaxAmt-1]  = t0.MaxAmt,
                [MaxAmt]    =  t.MaxAmt
    FROM        EmpSales s 
    CROSS JOIN  CompTiers t
    LEFT JOIN   CompTiers t0 ON t0.TierID = t.TierID-1 --Prior Tier
    WHERE       s.Sales > ISNULL(t0.MaxAmt,0) --Filter out irrelevant Tiers given Sales
),

cte AS
 /* Calculate Tier1 Sales, then use Recursive CTE       **/
/** to calculate other Tiers based on prior Tier values */
(   SELECT      c.*,
                TierAmt = CAST(CASE 
                    WHEN c.Sales > c.[MaxAmt] 
                    THEN c.[MaxAmt] 
                    ELSE c.Sales END AS MONEY)
    FROM        combos c
    WHERE       c.TierID = 1
    UNION ALL
    SELECT      c.*,
                TierAmt = CAST(CASE 
                    WHEN c.Sales > c.[MaxAmt] 
                    THEN c.[MaxAmt]-ISNULL(c.[MaxAmt-1],0.0)
                    ELSE c.Sales-ISNULL(c.[MaxAmt-1],0) END AS MONEY)
    FROM        cte
    JOIN        combos c ON c.EmpID = cte.EmpID 
    WHERE       c.TierID = cte.TierID+1
)

 /* Combine [TierAmt] and [Rate] at each Tier */
/** to calculate Net Pay amount.            **/
SELECT      cte.EmpID,cte.Sales,cte.TierID,cte.TierAmt,
            t.Rate,
            Net = t.Rate * cte.TierAmt
FROM        cte
LEFT JOIN   CompTiers t ON t.TierID = cte.TierID
ORDER BY    EmpID,TierID

我邀请反馈,改进和替代方案。

SQLFiddle