Creating Monthly Data From Seasonal Factors and Annual Data - sql-server

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;

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

How to differentiate if difference between dates in weekly on monthly?

I have a table in which employee's loan details are being saved. Some employee get loan on weekly basis and wish to return each installment weekly and some wish to return installments monthly.
I want to get the total duration on the loan period if employee wishes to return installments weekly then it should give output like 5 Weeks or 10 Weeks but if employee wishes to return installments monthly it should give result like 4 Months or 15 Months.
Here is the sample data
LoanID DueDate AmountToBePaid
2 2019-01-01 500
2 2019-01-07 500
2 2019-01-14 500
2 2019-01-21 500
2 2019-01-28 500
3 2019-01-01 1500
3 2019-02-01 1500
3 2019-03-01 1500
3 2019-04-01 1500
Here is what I've tried but it gives output in weeks no matter if the installments are to be paid monthly as for loan id 3.
SELECT
LoanId,
DATEDIFF(WEEK, inst_start, inst_end) weeks
FROM LoanMaster LM
INNER JOIN (
SELECT
dateadd(month, -1,min(duedate)) inst_start,
max(duedate) inst_end
FROM LoanDetail
) LD
ON LM.ID = LD.LoanID
Out should be like:
LoanId Duration
2 4 Weeks
3 3 Months
If your data strictly follow the rule that for the same LoanID the DueDates will be either only consecuctive weeks or only consecuctive months, then we can deduce the period by dividing the range with the row count:
SELECT
LoanID,
case
when datediff(month,min(DueDate),max(dueDate))=count(*)-1
then str(datediff(month,min(DueDate),max(dueDate)))+' Months'
else
str(datediff(week,min(DueDate),max(dueDate)))+' Weeks'
end as Duration
FROM LoanMaster LM
GROUP BY LOANID

Calculating number of con-consecutive total years donated in SQL

I have some sample donation history:
ID TransactionDate Amount
10 2001-12-19 00:00:00.000 75.00
10 2001-07-11 00:00:00.000 760.00
10 2010-10-15 00:00:00.000 2200.00
10 2012-08-15 00:00:00.000 1220.00
10 2013-09-16 00:00:00.000 610.00
100 2000-09-26 00:00:00.000 3000.00
100 1999-01-01 00:00:00.000 5000.00
I am trying to get a summary of giving by year by donor. The total does not have to be for consecutive years, but will just total overall # of years given. The total does not need to equal the number of transactions per year, just that the ID donated in that particular year.
For instance, ID 10 above would equal 4 indicating giving in 4 calendar years (2 donations in 2001, 1 in 2010, 1 in 2012, and 1 in 2013). ID 100 would equal 2.
The donations date back many years, so hard-coding dates is not very feasible.
Any ideas are appreciated.
Try this:
SELECT ID, DATEPART(yy,TransactionDate) Year, COUNT(*)
FROM tableName
GROUP BY ID, DATEPART(yy,TransactionDate)
You just need to return and then group on the relevant parts of your data, which is the ID and the year of the TransactionDate:
select d.ID
,count(1) as DonationYears
from(select distinct ID -- Sub query returns one row per ID and Year, which is counted up by ID in the outer query
,year(TransactionDate) as TranYear
from Donations
) d
group by d.ID

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

Distribute values over value-range tiers

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

Resources