Distribute values over value-range tiers - sql-server

I have two tables: a list of Total Sales Per Employee, and a list of Compensation Tiers
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%
Based on these inputs, I need to split each employee's Sales over each Tier based on each tier's MaxAmt value to calculate compensation rates at each tier. Further, I don't want to hard-code calculations for each tier as the number of tiers may change over time. (On second thought, I don't mind hard-coding, as long as it can handle UP-TO 5 tiers. Sounds fair?)
Desired Output:
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
I'm not unskilled with SQL, but I can't even fathom an appropriate strategy. Any ideas? Changes to table structure are permissible if it helps achieve the goal.
SQLFiddle

Lets make some test data:
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 );
Here I make a CTE to find all of the tiers and the previous (to get the top and bottom of the tier)
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
),
Lets take the tier CTE and cross join it against the sales picking only the tiers where the sales is greater than the prevamount (bottom of the tier).
SalesComp AS
(
SELECT *
FROM #EmpSales e
CROSS JOIN Tiers c
WHERE Sales > PrevAmount
)
Now that we have the data matched up, just clean it up with some cases:
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
Here is the output:
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

The idea behind the following query happens to be same as the one in Kevin Cook's answer and the two solutions differ mainly by syntax:
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
;
The TierRanges CTE "enhances" the CompTiers row set with a MinAmt column to form a tier range together with MaxAmt, MinAmt being the previous tier's MaxAmt value (or 0, for the first tier). This can be considered a direct equivalent of the other answer's Tiers CTE, but you can see that here the LAG function is used instead of a self-join, and the former is likely to work faster than the latter.
The main query joins every row in EmpSales with every row in TierRanges where EmpSales.Sales exceeds TierRanges.MinAmt. (In the other answer this part is implemented separately from the main query as another CTE, SalesComp.) To get the tier amount, it subtracts the MinAmt value from either Sales or MaxAmt, depending on which one is lesser. Because the tier amount is needed twice in the query (once for the output as is and the second time to get Net), CROSS APPLY is used to avoid repetition of code.
Because of LAG, SQL Server 2012 is the minimum version required to run the query.

try using a full outer (cross) join between the tables as long as the sales amount is less than or equal to the tier max amount

With Beth's suggestion as a jumping-off point, I worked my way through the rest to come up with a workable solution. It starts with a CROSS JOIN to show all possible combinations of Sales and Tiers, filters out those with Sales out of range of their matched Tier, and uses a Recursive CTE to work through each Tier's calculation using the prior Tier results.
;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
I invite feedback, enhancements, and alternatives.
SQLFiddle

Related

SQL Pivot Table - Either make dynamic column headers in pivot, or transpose result

I have a table populated with timesheet information that looks like the following:
Interval
Team
Count
9:30AM
Team1
0
9:00AM
Team1
2
11:00AM
Team3
2.5
12:30AM
Team2
4
.
.
.
This table represents how many employess (Count, a float) are working for which team (Team, as Str) during which time of day (Interval, datatype Time).
I have tried the below:
SELECT *
FROM
(SELECT
[Interval] AS 'TimeBlock'
,Team
,ROUND([Count]/2,1) AS 'Hours'
FROM MyTable
WHERE [Interval] >= CONVERT(TIME, DATEADD(MINUTE, ROUND(DATEDIFF(MINUTE, 0, GETDATE()) / 30.0, 0) * 30, 0))
) AS "SUB"
PIVOT
(SUM(Hours)
FOR Team IN
([Team1]
,[Team2]
,[Team3])
) AS "Pivot";
Which yields a pivot table that lists the team across the top (along the pivot, or has the teams as the columns). What I would like is to show the time intervals across the top, and the team listed down the left hand side.
I know that listing the time intervals in the pivot will give me my desired result. However, I do not want to list every 30 min time interval between 6:00 AM and Midnight
As a work around I have simply been using excel to unpivot the columns, and then pivot along the time interval.
BONUS: Have a total by team as the final column and total by time as the final row
Ex//
Team
9:30 AM
10:00 AM
10:30 AM
11:00 AM
TOTAL
Team1
0
1
0.5
1
2.5
Team2
2
3
1
3
9
Team3
2.5
1
2
2
7.5
TOTAL
4.5
5
3.5
6
19

Creating Monthly Data From Seasonal Factors and Annual Data

I have two tables:
table of multiplicative seasonal factors for each month
table of annual budget amounts
I would like a T-SQL solution to create a third table that explodes (multiplies) the annual budget amounts into a monthly time series.
Example of Table 1 (seasonal factors):
Month
Seasonal Factor
1
.08
2
.075
3
.065
4
.085
5
.09
6
.08
7
.08
8
.075
9
.065
10
.085
11
.09
12
.13
Example of Table 2 (annual budget amounts)
Year
Budget
2019
250,000
2020
275,000
Desired Solution
Month
Imputed Budget
2019-01-01
.08 * 250,000
2019-02-01
.075 * 250,000
...
...
2020-12-01
.13 * 275,000
You seem to be describing a cross join:
select datefromparts(t2.year, t1.month, 1) as month,
t1.factor * t2.budget
from table1 t1 cross join
table2 t2
order by month;

Query to Find the "Balance Amount after each Payment of corresponding Bill" in SQL Server

PaymentID SupplyInvoiceID Date TotalBill BillPaidAmount Remaining Bill
1 1 05-04-2018 2,10,000 20,000 1,90,000
2 1 10-05-2018 2,10,000 60,000 1,30,000
3 1 13-06-2018 2,10,000 1,30,000 0
4 2 10-05-2018 80,000 40,000 40,000
5 2 13-06-2018 80,000 20,000 20,000
6 2 13-06-2018 80,000 20,000 0
The payment of each Bill is paid in installments in different dates as shown above. How to find the remaining Bill amount each time when the partial payment of each bill is made?
I used the following Query:
SELECT siph.SupplyPaymentID,si.SupplyInvoiceID,
siph.DateOfPayment,si.TotalBill, siph.BillPaidAmount,
si.TotalBill - SUM(siph.BillPaidAmount) over(order by siph.SupplyPaymentID asc) as RemainingBillAmount,
siph.PaymentMode
from SupplyInvoicePaymentHistory siph inner join
SupplyInvoice si
on siph.SupplyInvoiceID = si.SupplyInvoiceID
But it works fine for only bill payments of 1st SupplyInvoiceID. As i enter the bill payments of 2nd and onward SupplyInvoiceID, i gets the wrong result as follows:
PaymentID SupplyInvoiceID Date TotalBill BillPaidAmount Remaining Bill
1 1 05-04-2018 2,10,000 20,000 1,90,000
2 1 10-05-2018 2,10,000 60,000 1,30,000
3 1 13-06-2018 2,10,000 1,30,000 0
4 2 10-05-2018 80,000 40,000 -1,70,000
5 2 13-06-2018 80,000 20,000 -1,90,000
6 2 15-06-2018 80,000 20,000 -2,10,000
..please help to find the correct result as tabulated at the first para of the above question.
You need to add PARTITION BY clause to your sum() over () to make it a cumulative sum for each Invoice ID.
Add this to your RemainingBillAmount column:
... - SUM(...) over (partition by si.SupplyInvoiceID ...)
Entire query:
SELECT siph.SupplyPaymentID,si.SupplyInvoiceID,
siph.DateOfPayment,si.TotalBill, siph.BillPaidAmount,
si.TotalBill - SUM(siph.BillPaidAmount) over(partition by si.SupplyInvoiceID order by siph.SupplyPaymentID asc) as RemainingBillAmount,
siph.PaymentMode
from SupplyInvoicePaymentHistory siph inner join
SupplyInvoice si
on siph.SupplyInvoiceID = si.SupplyInvoiceID

Multiply by largest row and append to list

I have the following in two tables:
(SFR_MAIN)
SQ. FT. AREA 1 2
400 86.6600 86.7300
500 82.3300 82.4000
600 78.9600 79.0200
700 76.2100 76.2700
800 73.9100 73.9700
900 71.9400 71.9900
1000 70.2200 70.2700
1100 68.7000 68.7500
1200 67.3400 67.3900
1300 66.1200 66.1600
1400 65.0000 65.0400
1600 63.0400 63.0800
1800 61.3600 61.3900
2000 59.8900 59.9300
2200 58.6000 58.6300
2400 57.4400 57.4700
2600 56.3900 56.4200
2800 55.4400 55.4700
3000 54.5700 54.6000
And
(MULT)
SQ. FT. AREA MULTIPLIER
3100 0.992
3200 0.986
3300 0.978
3400 0.971
3500 0.964
3600 0.958
3700 0.952
3800 0.946
4000 0.934
The idea would be to create a table with both. The problem is that the second is a multiplier that should be multiplied to that last(Largest) squarfootage in the first.
So 3100 would be .992 * 54.57 for column 1 and .992 * 54.6 for column 2, yes those are the actual names of the columns.
So the desired output would be:
SQ. FT. AREA 1 2
400 86.6600 86.7300
500 82.3300 82.4000
600 78.9600 79.0200
700 76.2100 76.2700
800 73.9100 73.9700
900 71.9400 71.9900
1000 70.2200 70.2700
1100 68.7000 68.7500
1200 67.3400 67.3900
1300 66.1200 66.1600
1400 65.0000 65.0400
1600 63.0400 63.0800
1800 61.3600 61.3900
2000 59.8900 59.9300
2200 58.6000 58.6300
2400 57.4400 57.4700
2600 56.3900 56.4200
2800 55.4400 55.4700
3000 54.5700 54.6000
3100 54.1334 54.1632
3200 53.8060 53.8356
3300 53.3695 53.3988
3400 52.9875 53.0166
3500 52.6055 52.6344
3600 52.2781 52.3068
3700 51.9506 51.9792
3800 51.6232 51.6516
4000 50.9684 50.9964
I can do one column like this:
select
[SQ. FT. AREA],
[1]
from test.dbo.SFR_MAIN
union all
Select
[SQ. FT. AREA],
MULTIPLIER * (select
m.[1]
from test.dbo.SFR_MAIN m
inner join
(
select max([SQ. FT. AREA]) as mmm
from test.dbo.SFR_MAIN
) tt on tt.mmm = m.[SQ. FT. AREA]
) as [1]
from test.dbo.mult
And if I only had the two columns I would just do it twice, but I have 16 columns. Is there a way to iterate through the columns to get them all side by side in a table?
I am using SQL Server 2012
Example
Select * From SFR_MAIN
Union All
Select B.[SQ. FT. AREA]
,[1] = A.[1]*B.MULTIPLIER
,[2] = A.[2]*B.MULTIPLIER
From (Select Top 1 with ties * from SFR_MAIN Order by [SQ. FT. AREA] Desc) A
Join MULT B on B.[SQ. FT. AREA]>A.[SQ. FT. AREA]
Returns
Iteration in databases is generally a bad idea. Databases work better on set based approaches. So, union as you describe but then use cross join on the record from the first set that has the highest sq foot and then just do the math.
SELECT [SQ. Ft. Area], [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]
FROM sfr_Main
UNION ALL
SELECT A.[SQ. FT. Area]
, B.multiplier*[1] as 1
, B.multiplier*[2] as 2
, B.multiplier*[3] as 3
, B.multiplier*[4] as 4
, B.multiplier*[5] as 5
, B.multiplier*[6] as 6
, B.multiplier*[7] as 7
, B.multiplier*[8] as 8
, B.multiplier*[9] as 9
, B.multiplier*[10] as 10
, B.multiplier*[11] as 11
, B.multiplier*[12] as 12
, B.multiplier*[13] as 13
, B.multiplier*[14] as 14
, B.multiplier*[15] as 15
, B.multiplier*[16] as 16
FROM MULT A
CROSS JOIN (SELECT top 1 [SQ. Ft. Area], [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]
FROM sfr_main
ORDER BY [sq.ft.area] desc) B

Return value's percentage of sub category in SQL

I have a query: (using a very simple example)
SELECT
ItemCategory,
ItemID,
Sales
FROM
MyTable
WHERE
CustomerID = 1
Which returns
ItemCategory | ItemID | Sales
A 0 75.00
A 1 50.00
A 2 0.00
B 3 25.00
B 4 25.00
C 5 20.00
C 6 30.00
C 7 10.00
C 8 0.00
C 9 50.00
How can I modify this query so that I receive the percentage of sales for each item grouped by ItemCategory?
That is, I would like this returned:
ItemCategory | ItemID | Sales | PercentageOfCategory
A 0 75.00 60%
A 1 50.00 40%
A 2 0.00 0%
B 3 25.00 50%
B 4 25.00 50%
C 5 20.00 20%
C 6 30.00 30%
C 7 10.00 10%
C 8 0.00 0%
C 9 50.00 50%
I tried to keep the example as trivial as possible, the actual query is pretty complex but I imagine the same logic still applies.
EDIT: I believe the server is sql server 2008
You did not mention what version of SQL server but if you have 2005+ then you could use Common Table Expressions (CTE)
;WITH RawData As
(
SELECT
ItemCategory,
ItemID,
Sales
FROM
MyTable
WHERE
CustomerID = 1
),
GroupedData As
(
SELECT
ItemCategory,
Sum(Sales) As TotalSales
FROM
RawData
GROUP BY
ItemCategory
)
SELECT
R.ItemCategory,
R.ItemID,
R.Sales,
R.Sales / G.TotalSales * 100.0 As PercentageSales
FROM
RawData R
INNER JOIN
GroupedData G
ON
R.ItemCategory = G.ItemCategory
Assuming SQL Server 2005+ for the CTE:
WITH cteCategoryTotals AS (
SELECT ItemCategory, SUM(Sales) AS TotalSales
FROM MyTable
WHERE CustomerID = 1
GROUP BY ItemCategory)
SELECT m.ItemCategory, m.ItemId, m.Sales, (m.Sales/c.TotalSales)*100.0 AS PercentageOfCategory
FROM MyTable m
INNER JOIN cteCategoryTotals
ON m.ItemCategory= c.ItemCategory
WHERE m.CustomerID = 1
You can use SUM ... OVER
SELECT
ItemCategory,
ItemID,
Sales,
100.0 * Sales /
NULLIF(SUM(Sales) OVER (PARTITION BY ItemCategory),0) AS PercentageOfCategory
FROM
MyTable
WHERE
CustomerID = 1

Resources