Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
| Author |
Topic |
|
joeyhoffer
Starting Member
3 Posts |
Posted - 2010-10-26 : 12:39:56
|
| Hi,I'm trying to write a sql query that takes a table of tennants and a table of utility bills and gives the bill split amounts for each tennant. Not sure if I need to utilize stored procedures, udf's, or what. The calculation seems a bit complex. I can calculate it in ASP code for the webpage it is being displayed on but I was trying to do it all at the database level. The hard part is calculating the split/prorate field. The calculation should factor in the splitting of the bill between tennants as well as any prorating of the bill if a tennant moves in during the middle of the bill periods.See example below. Any thoughts???Example:Tennant tabletennant_id name move_in move_out1 "Clark Kent" 10/20/2009 NULL2 "Bruce Wayne" 08/28/2010 NULLBill tablebill_id bill_type service_from service_to amt_due1 "Power Bill" 08/01/2010 09/01/2010 100.002 "Water Bill" 08/15/2010 09/15/2010 25.00.Trying to get the result:tennant_id bill_type amt_due split_prorate_amt1 "Power Bill" 100.00 93.751 "Water Bill" 25.00 17.742 "Power Bill" 100.00 6.252 "Water Bill" 25.00 7.26.(Hopefully my math is correct...did it manually).Thanks for any help!Joey |
|
|
joeyhoffer
Starting Member
3 Posts |
Posted - 2010-10-26 : 13:54:36
|
| And..."tennant" should have been spelled "tenant". I misspelled it every time.Joey |
 |
|
|
Michael Valentine Jones
Yak DBA Kernel (pronounced Colonel)
7020 Posts |
Posted - 2010-10-26 : 14:00:11
|
| It would be helpful if you told what your formula is for prorating the bills.CODO ERGO SUM |
 |
|
|
joeyhoffer
Starting Member
3 Posts |
Posted - 2010-10-27 : 11:51:11
|
Sorry for the lengthy response. The formula/algorithm that I have come up with is kinda complicated. There may be a much simpler solution to the calculation. Feel free to share if there is clear simple formula/algorithm.Let me walk through a few examples to show how I came up with the values. Let's use the following data for the tables (almost same as before).Tenant Tabletenant_id name move_in move_out1 "Clark Kent" 10/20/2009 09/05/20102 "Bruce Wayne" 08/28/2010 NULL3 "Peter Parker" 09/10/2010 NULLBill Tablebill_id bill_type service_from service_to amt_due1 "Power Bill" 08/01/2010 09/01/2010 100.002 "Water Bill" 08/15/2010 09/15/2010 25.003 "Gas Bill" 07/28/2010 08/27/2010 75.004 "Cable Bill" 09/11/2010 10/10/2010 45.00Result table/querytenant_id bill_type amt_due split_prorate_amt1 "Gas Bill" 75.00 75.002 "Cable Bill" 45.00 22.503 "Cable Bill" 45.00 22.501 "Water Bill" 25.00 13.712 "Water Bill" 25.00 9.283 "Water Bill" 25.00 2.021 "Power Bill" 100.00 93.552 "Power Bill" 100.00 6.45***Explanation***Each bill above presents a different scenario (I'm sure there are more examples). For the Gas Bill, there is only one person living on the property (Clark Kent) so he is responsible for all of the bill (75.00). For the Cable Bill, the bill does not need to be prorated but it does need to be split between the two people living on the property during the bill period, therefore the two people (Peter Parker & Bruce Wayne) are each responsible for half (45.00/2 = 22.50). The other two bills have to be split and prorated. For these I broke up the bill period into split portions and divided each split portion by the number of tenants on the property during the split. Then I summed those amounts for each tenant. For example:- The Water Bill was billed for dates 8/15/10 to 9/15/10 (31 days). From 8/15/10 to 8/28/10 (13 days), only Clark Kent lived on the property. This split portion would be (((13 days/31 days) * 25.00 amt_due)/ 1 tenant) = $10.48. This split portion only applies to Clark Kent.
- From 8/28/10 to 9/5/10 (8 days), Clark Kent and Bruce Wayne lived on the property. This split portion would be (((8 days/31 days) * 25.00 amt_due)/ 2 tenants) = $3.23. This split portion applies to Clark Kent and Bruce Wayne. Clark now is responsible for $13.71 (10.48 + 3.23) and Bruce is now responsible for $3.23.
- From 9/5/10 to 9/10/10 (5 days), only Bruce Wayne lived on the property. The split portion would be (((5 days/31 days) * 25.00 amt_due)/ 1 tenant) = $4.03. This split portion only applies to Bruce Wayne. Clark is still responsible for $13.71 and Bruce is now responsible for $7.26 (3.23 + 4.03).
- From 9/10/10 to 9/15/10 (5 days), Bruce Wayne and Peter Parker lived on the property. The split portion would be (((5 days/31 days) * 25.00 amt_due)/ 2 tenants) = $2.02. This split portion applies to Bruce Wayne and Peter Parker. That completes the bill. Clark is responsible for $13.71; Bruce Wayne is responsible for $9.28 (7.26 + 2.02); Peter Parker is responsible for $2.02. These add up to 1 penny over, but that is ok for what I am trying to do.
Again, there may be a much simpler formula. Any ideas? thoughts? Joey |
 |
|
|
robvolk
Most Valuable Yak
15732 Posts |
Posted - 2010-10-27 : 14:59:48
|
OK, this is a long one. One warning, I used to work for a utility billing company, so this math could be totally wrong. I was off by 1 cent on one of the results, but with rounding issues that's not too bad, and in fact the results you posted add up to 1 cent more than the total bill amount. Lawsuits have occurred for stuff like that. If you use this technique (or any technique) I strongly urge you to validate your resident bill totals against the total utility bill. (we called this a "reasonability" test)Here's the code, I'm going to let the comments do the explanation, brief as they are. I'm using common table expressions (CTEs) to generate data. If you have a specific question please post it and I'll try to answer as best I can. The first section is just to set up the data for the example:declare @tenant table(tenant_id int not null primary key, name varchar(30) not null, move_in smalldatetime not null, move_out smalldatetime null)declare @bill table(bill_id int not null primary key, bill_type varchar(20) not null, service_from smalldatetime not null, service_to smalldatetime not null, amt_due money not null)declare @result table(tenant_id int not null, bill_type varchar(20) not null, amt_due money not null,split_prorate_amt money not null)insert @tenant select 1, 'Clark Kent', '10/20/2009', '09/05/2010' union all select 2, 'Bruce Wayne', '08/28/2010', NULL union all select 3, 'Peter Parker', '09/10/2010', NULLinsert @bill select 1, 'Power Bill', '08/01/2010', '09/01/2010', 100.00 union all select 2, 'Water Bill', '08/15/2010', '09/15/2010', 25.00 union all select 3, 'Gas Bill', '07/28/2010', '08/27/2010', 75.00 union all select 4, 'Cable Bill', '09/11/2010', '10/10/2010', 45.00insert @result select 1, 'Gas Bill', 75.00, 75.00 union all select 2, 'Cable Bill', 45.00, 22.50 union all select 3, 'Cable Bill', 45.00, 22.50 union all select 1, 'Water Bill', 25.00, 13.71 union all select 2, 'Water Bill', 25.00, 9.28 union all select 3, 'Water Bill', 25.00, 2.02 union all select 1, 'Power Bill', 100.00, 93.55 union all select 2, 'Power Bill', 100.00, 6.45-- now the magic-- the following generates a sequence of numbers;with n(n) as (select 0 union all select n+1 from n where n<100),-- this next one generates all the billable days for each bill, plus the per diem amount b(bill_id, type, billable_date, per_diem, amt_due) as (select b.bill_id, b.bill_type, dateadd(day, n, b.service_from), amt_due/datediff(day,b.service_from,b.service_to), b.amt_due from @bill b cross join n where n<=datediff(day,b.service_from,b.service_to)-1),-- this one joins the tenants to the available billable days they occupied the property t(tenant_id, name, bill_id, type, billable_date, per_diem, amt_due) as (select t.tenant_id, t.name, b.bill_id, b.type, b.billable_date, b.per_diem, b.amt_due from b inner join @tenant t on b.billable_date between t.move_in and IsNull(t.move_out-1, b.billable_date)),-- this one summarizes each bill and counts the number of split occupants s(bill_id, billable_date, splits) as (select bill_id, billable_date, count(distinct tenant_id) from t group by bill_id, billable_date)-- the following sums up the bill amounts generated from the previous CTE'sselect t.tenant_id, t.name, t.type, sum(t.per_diem/s.splits) split_prorate_amt, round(sum(t.per_diem/s.splits),2) split_prorate_amt_rounded, count(*) days, max(t.amt_due) amt_due, max(t.per_diem) per_diemfrom tinner join s on t.bill_id=s.bill_id and t.billable_date=s.billable_dategroup by t.tenant_id,t.name, t.typeorder by 3,1 -- this lists the results you specifiedselect * from @result order by 2, 1 Some notes and advice:- No, we did not use this technique for billing at my former employer. Billing was done in a custom application using Visual Basic, then moved to C#.- This may work for RUBS (ratio) billing in some states but not others, depending on whether service dates are inclusive or exclusive.- Be EXTREMELY vigilant about data types and rounding. Money and smallmoney may not have enough decimal places. Float and real MUST be avoided, and watch out for expressions that yield a float/real data type, typically when dividing.- This could possibly be modified to handle metered or sub-metered billing too, but it will get very tricky.That being said, I can't fully recommend this technique. There's a lot of shortcomings with SQL data types and math, and anything more than the most basic calculations can cause problems. I showed this to a former co-worker (developer) who wrote our billing engine and he's...well...not convinced SQL is the right way to do it. (I can't print his true opinion) I don't entirely agree, but I've lost many arguments to him where he turned out to be right.Anyway, don't be discouraged, give it a try, and if you have questions please post them. |
 |
|
|
|
|
|
|
|