Find out the date of the current debt in SQL - sql

Please, help me figure out how to find out the date of the currently debt and the number of days since its inception I have this table:
Date
Customer
Deal
Sum
20.11.2009
220000
222221
25000
27.11.2009
220001
222221
-30000
20.12.2009
220000
222221
20000
31.12.2009
220001
222221
-10000
12.12.2009
111110
111111
12000
25.12.2009
111110
111111
5000
12.01.2010
111110
111111
-10100
12.12.2009
111110
122222
10000
29.12.2009
111110
122222
-10000
On the loan, payments can be made by co-borrowers. If a client with a loan misses the next payment on schedule, he has a debt. In this case, a corresponding record appears in the table, where Sum is the unpaid amount (with a positive sign). If, then, the client makes a payment (the full amount or part of it), a new record appears, where Sum is the amount paid (with a “-” sign). It should be noted that the client's payment does not necessarily completely extinguish the accumulated debt, it can only be part of the debt.
DROP TABLE IF EXISTS #PDCL
set dateformat dmy
CREATE TABLE #PDCL
(
Payment_dt date,
Customer int,
Deal int,
Currency varchar(5),
Sum_payment int
)
INSERT INTO #PDCL VALUES ('12.12.2009', 111110, 111111, 'RUR', 12000)
INSERT INTO #PDCL VALUES ('25.12.2009', 111110, 111111, 'RUR', 5000)
INSERT INTO #PDCL VALUES ('12.12.2009', 111110, 122222, 'RUR', 10000)
INSERT INTO #PDCL VALUES ('12.01.2010', 111110, 111111, 'RUR', -10100)
INSERT INTO #PDCL VALUES ('20.11.2009', 220000, 222221, 'RUR', 25000)
INSERT INTO #PDCL VALUES ('20.12.2009', 220000, 222221, 'RUR', 20000)
INSERT INTO #PDCL VALUES ('31.12.2009', 220001, 222221, 'RUR', -10000)
INSERT INTO #PDCL VALUES ('29.12.2009', 111110, 122222, 'RUR', -10000)
INSERT INTO #PDCL VALUES ('27.11.2009', 220001, 222221, 'RUR', -30000)
--Start date of the current debt
SELECT Deal
, MIN(Payment_dt) AS Start_date_current_debt
FROM #PDCL
WHERE Sum_payment > 0
GROUP BY Deal
--Number of days of current debt
SELECT Deal
, DATEDIFF(d, MIN(Payment_dt), MAX(Payment_dt)) AS Num_days_current_debt
FROM #PDCL
GROUP BY Deal
The dataset has many different Customers and Deal. I gave an illustrative example, because of which the question arose. In it, the client was twice in debt.
My desired answer:
Deal
Start_date_current_debt
111111
2009-12-12
122222
2009-12-12
222221
2009-12-20
Deal
Num_days_current_debt
111111
todate - 2009-12-12
122222
17
222221
todate - 2009-12-20

After reading the comments on this answer, here is an approach that solves the question asked. I have taken a slightly verbose approach so that you can follow the logic, but feel free to collapse some of the common table expressions to make it shorter.
We can compute the running SUM for each deal, and I will number the rows for each deal. We can then compare the SUM for a current row of a deal to the SUM of a previous row of a deal using LAG. When the SUM goes positive from negative, or the sum is positive and the previous SUM is NULL, we have found where there is a debt crossing. I will multiply the row number by -1 in these situations so that I can find the MIN row number for each deal and that will be the most recent date when there was money owed. As I mentioned, this can be shortened but I left it a bit verbose so you can follow the logic:
;WITH sums AS (
SELECT Deal,
Payment_Dt,
SUM(Sum_payment) OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [currentSum],
ROW_NUMBER() OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [num]
FROM #PDCL
), sumsWithLag AS (
SELECT Deal, Payment_dt,
currentSum,
LAG(currentSum) OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [prevSum],
num
FROM sums
), markedCrossings AS (
SELECT Deal, Payment_dt,
CASE WHEN currentSum > 0 AND (prevSum IS NULL OR prevSum < 0) THEN -1 ELSE 1 END * num AS num
FROM sumsWithLag
), debtCrossings AS (
SELECT Deal, MIN(num) AS num
FROM markedCrossings
GROUP BY Deal
)
SELECT s.Deal, s.Payment_dt AS Start_date_current_debt
FROM debtCrossings AS c
INNER JOIN sums AS s ON s.Deal = c.Deal and s.num = ABS(c.num)
And it gives this result:
Deal
Start_date_current_debt
111111
2009-12-12
122222
2009-12-12
222221
2009-12-20
Those are the expected values. At this point, we can use the same common table expressions to answer the number of days in debt. We know the start date, so we just have to see if the deal has a positive amount at the most recent sum.
;WITH sums AS (
SELECT Deal,
Payment_Dt,
SUM(Sum_payment) OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [currentSum],
ROW_NUMBER() OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [num]
FROM #PDCL
), sumsWithLag AS (
SELECT Deal, Payment_dt,
currentSum,
LAG(currentSum) OVER (PARTITION BY Deal ORDER BY Payment_dt) AS [prevSum],
num
FROM sums
), markedCrossings AS (
SELECT Deal, Payment_dt,
CASE WHEN currentSum > 0 AND (prevSum IS NULL OR prevSum < 0) THEN -1 ELSE 1 END * num AS num
FROM sumsWithLag
), debtCrossings AS (
SELECT Deal, MIN(num) AS num
FROM markedCrossings
GROUP BY Deal
), startDates AS (
SELECT s.Deal, s.Payment_dt AS Start_date_current_debt
FROM debtCrossings AS c
INNER JOIN sums AS s ON s.Deal = c.Deal and s.num = ABS(c.num)
), balances AS (
SELECT Deal, SUM(Sum_payment) AS balance, MAX(Payment_dt) AS Payment_dt
FROM #PDCL
GROUP BY Deal
)
SELECT s.Deal,
DATEDIFF(day, s.Start_date_current_debt, CASE WHEN b.balance > 0 THEN GETDATE() ELSE b.Payment_dt END) AS Num_days_current_debt
FROM startDates AS s
INNER JOIN balances AS b ON s.Deal = b.Deal;
And the result is:
Deal
Num_days_current_debt
111111
4274
122222
17
222221
4266

Related

Modifying an Aggregate

I am trying to get my surgeries to calculate at different rates and I am struggling with it. For example, patient 58903 has 4 total surgeries as shown below. However, I would like the first surgery to calculate at 100% of the PPO SURG rate (so $4232), the second one at 50%, and all remaining surgeries at 25% of the main PPO SURG rate. My current code returns $16,929 for patient 5903 which is just $4232*4. My desired output for the SURG Total below is $8,464 (4232+2116+1058+1058).
My Current Code:
SELECT
DISTINCT PATNO,
SUM( PPOSURG) AS 'Surg Total',
SUM( PPONONSURG) AS 'Non Surg Total',
SUM( PPODRUG) AS 'Drug Total',
INSNME,
IIF( SUM( PPOSURG) IS NOT NULL,
SUM( PPOSURG) + SUM(CASE WHEN REV=278 THEN (AMT1)*0.446 END),
ISNULL(SUM( PPODRUG),0)+ISNULL( SUM( PPONONSURG),0)) AS 'Claim Total'
FROM
[OPGRACE$] AS GR --Main Table
LEFT JOIN [BCSURGOP$] AS SRG ON GR.CPTCDA=SRG.[CPTCODESURG] --SURG joined on cpt
LEFT JOIN [BCBSDRUG$] AS DRUG ON GR.CPTCDA=DRUG.[CPT CODE] --DRUG Schedules joined on cpt
LEFT JOIN [BCBSNONSURGOP$] AS NSRG ON GR.CPTCDA=NSRG.[CPT CODE] --Non-SURG joined on cpt
WHERE QTY>0 AND PATNO>0
GROUP BY PATNO,INSNME
ORDER BY PATNO ASC
I tried doing something like this but you can't have an aggregate in a SUM
SUM(CASE WHEN COUNT(CPTCODESURG)=1 THEN PPOSURG ELSE PPOSURG*0.5 END) + SUM(CASE WHEN REV=278 THEN (AMT1)*0.446 END),
Here is my output for just patient 58903
SURG Total
Non Surg Total
Drug Total
CLAIM Total
16929.472
3103
179
22598.84
Here is a blurb of my joined table showing how patient 58903 shows up:
PName
Rev
CPT
PPOSURG
58903
360
29882
4232.368
58903
360
29882
4232.368
58903
360
29882
4232.368
58903
360
29882
4232.368
Would a PARTITION be the way to go here? A subquery? Can I somehow use a case statement? I don't expect anyone to write my code but literally any ideas would be extremely helpful, I have been really stuck on this.
If I understand you correctly you just need a row number partitioned by the patient and then a CASE expression to convert that into a multiplier. I've added an id column to the sample data to allow for an order by (which you need for a row number).
declare #Test table (id int identity(1,1), PName int, Rev int, CPT int, PPOSURG money);
insert into #Test (PName, Rev, CPT, PPOSURG)
values
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368);
with cte as (
select *
, row_number() over (partition by PName order by id) rn
from #Test
)
select PName, Rev, CPT
, cast(sum(PPOSURG * case rn when 1 then 1.00 when 2 then 0.50 else 0.25 end) as decimal(9,2)) Total
from cte
group by PName, Rev, CPT;

SQL Server Interest calculations on transactions

I'm looking for advice on best way to build a compound interest module in SQL server. Basic set up:
Table: Transactions {TransID, MemberID, Trans_Date, Trans_Code, Trans_Value).
Table: Interest {IntID, Int_eff_Date, Int_Rate}
In the interest table, there may be different rates with an effective date - can never have an over lapping date though. For example:
Int_Eff_Date Int_Rate
01/01/2016 7%
01/10/2016 7.5%
10/01/2017 8%
I want to calculate the interest based on the transaction date and transaction value, where the correct interest rate is applied relative to transaction date.
So if Table transaction had:
TransID MemberID Trans_Date Trans_Value
1 1 15/04/2016 150
2 1 18/10/2016 200
3 1 24/11/2016 200
4 1 15/01/2017 250
For transID 1 it would use 7% from 15/04/2016 until 30/09/2016 (168 days) from 1/10/2016 to 09/01/2017 it would use 7.% and then from 10/01/2007 to calculation date (input parameter) it would use 8%.
It would apply similar methodology for all transactions, add them up and display the interest value.
I'm not sure if I should use cursors, UDF, etc.
This should provide an outline of what you're trying to do.
--Build Test Data
CREATE TABLE #Rates(Int_Eff_Date DATE
, Int_Rate FLOAT)
CREATE TABLE #Transactions(TransID INT
,MemberID INT
,Trans_Date DATE
,Trans_Value INT)
INSERT INTO #Rates
VALUES ('20160101',7)
,('20161001',7.5)
,('20170110',8)
INSERT INTO #Transactions
VALUES
(1,1,'20160415',150)
,(2,1,'20161018',200)
,(3,1,'20161124',200)
,(4,1,'20170115',250)
;WITH cte_Date_Rates
AS
(
SELECT
S.Int_Eff_Date
,ISNULL(E.Int_Eff_Date,'20490101') AS "Expire"
,S.Int_Rate
FROM
#Rates S
OUTER APPLY (SELECT TOP 1 Int_Eff_Date
FROM #Rates E
WHERE E.Int_Eff_Date > S.Int_Eff_Date
ORDER BY E.Int_Eff_Date) E
)
SELECT
T.*
,R.Int_Rate
FROM
#Transactions T
LEFT JOIN cte_Date_Rates R
ON
T.Trans_Date >= R.Int_Eff_Date
AND
T.Trans_Date < R.Expire

Query to show stock based on previous transactions

Please I need your help..
for an Objective
match SO (Sales Order) quantity to PO (Purchase Order) quantity based on FIFO (First In, First Out) where the first stock items purchased must be the first items sold.
I have a table Stock which use to track the movement of stock in and out of imaginary stock warehouse. The warehouse is initially empty, and stock then moves into the warehouse as a result of a stock purchase (‘IN’) and stock moves out of the warehouse when it is sold (‘OUT’). Each type of stock item is identified by an ItemID. Each movement of stock in or out of the warehouse, due to a purchase or sale of a given item, results in a row being added to the Stock table, uniquely identified by the value in the StockID identify column, and describing how many items were added or removed and the date of the transaction.
Table stock :
StockId DocumentID ItemID TranDate TranCode Quantity
------------------------------------------------------------
1 PO001 A021 2016.01.01 IN 3
4 SO010 A021 2016.01.02 OUT 2
2 PO002 A021 2016.01.10 IN 7
3 PO003 A021 2016.02.01 IN 9
5 SO011 A021 2016.02.11 OUT 8
6 SO012 A023 2016.02.12 OUT 6
How could I write a query to give output like the table below?
SOID POID Quantity
------------------------
SO010 PO001 2
SO011 PO001 1
SO011 PO002 7
SO012 PO003 6
So, seeing as no one else has given this a go, I figure I'll post something that resembles an answer (I believe).
Essentially, what you want to do is keep track of the number of things you have in stock and the number of things that have gone out, based on the date (I haven't accounted for multiple things coming in or going out on the same date, though).
DECLARE #Table TABLE
(
DocumentID VARCHAR(10) NOT NULL,
TranCode VARCHAR(3) NOT NULL,
TranDate DATE NOT NULL,
Quantity INT NOT NULL
); -- I'm ignoring the other columns here because they don't seem important to your overall needs.
INSERT #Table (DocumentID, TranCode, TranDate, Quantity)
VALUES
('PO001', 'IN', '2016-01-01', 3),
('SO010', 'OUT', '2016-01-02', 2),
('PO002', 'IN', '2016-01-10', 7),
('PO003', 'IN', '2016-02-01', 9),
('SO011', 'OUT', '2016-02-11', 8),
('SO012', 'OUT', '2016-02-12', 6);
WITH CTE AS
(
SELECT DocumentID,
TranCode,
TranDate,
Quantity,
RunningQuantity = -- Determine the current IN/OUT totals.
(
SELECT SUM(Quantity)
FROM #Table
WHERE TranCode = T.TranCode
AND TranDate <= T.TranDate
),
PrevQuantity = -- Keep track of the previous IN/OUT totals.
(
SELECT ISNULL(SUM(Quantity), 0)
FROM #Table
WHERE TranCode = T.TranCode
AND TranDate < T.TranDate
)
FROM #Table T
)
SELECT Outgoing.DocumentID,
Incoming.DocumentID,
Quantity =
CASE WHEN Outgoing.RunningQuantity <= Incoming.RunningQuantity AND Outgoing.PrevQuantity >= Incoming.PrevQuantity
THEN Outgoing.RunningQuantity - Outgoing.PrevQuantity
WHEN Outgoing.RunningQuantity <= Incoming.RunningQuantity AND Outgoing.PrevQuantity < Incoming.PrevQuantity
THEN Outgoing.RunningQuantity - Incoming.PrevQuantity
ELSE Incoming.RunningQuantity - Outgoing.PrevQuantity
END
FROM CTE Outgoing
JOIN CTE Incoming ON
Incoming.TranCode = 'IN'
AND Incoming.RunningQuantity > Outgoing.PrevQuantity
AND Incoming.PrevQuantity < Outgoing.RunningQuantity
WHERE Outgoing.TranCode = 'OUT'
ORDER BY Outgoing.TranDate;
Note: I would highly recommend you keep track of the information in a better way. For example, create a table that actually details which orders took what from which other orders (an order transaction table or something), because while it's not impossible to achieve what you want with the way your data is structured, it's much less complicated if you just store more helpful data.

Need to calculate the average of cost based on a per fiscal week basis

I hope someone can help with this issue I have, which is I am trying to work out a weekly average from the following data example:
Practice ID Cost FiscalWeek
1 10.00 1
1 33.00 2
1 55.00 3
1 18.00 4
1 36.00 5
1 24.00 6
13 56.00 1
13 10.00 2
13 24.00 3
13 30.00 4
13 20.00 5
13 18.00 6
What I want is to group by the Practice ID but work out the average for each practice (there are over 500 of these not just those above) and work this out for each week so for example at Week 1 there will be no average, but Week 2 will be the average of Weeks 1 and 2, then Week 3 will be the average of Weeks 1, 2 and 3 and then so on. I need to then show this by Practice ID and for each Fiscal Week.
At the moment I have some code that is not pretty and there has to be an easier way, this code is:
I pass all the data into a table variable then using a CTE I then use case statements to set each individual week like:
CASE WHEN fiscalweek = 1 THEN cost ELSE 0 END AS [1],
CASE WHEN fiscalweek = 2 THEN cost ELSE 0 END AS [2],
CASE WHEN fiscalweek = 3 THEN cost ELSE 0 END AS [3]
This would then bring back the week 1 cost and so on into it's own column e.g. 1, 2, 3 etc. , then I've used a second CTE to sum the columns for each week so for example to work out week 6 I would use this code:
sum([1]) as 'Average Wk 1',
sum([1]+[2])/2 as 'Average Wk 2',
sum([1]+[2]+[3])/3 as 'Average Wk 3',
sum([1]+[2]+[3]+[4])/4 as 'Average Wk 4',
sum([1]+[2]+[3]+[4]+[5])/5 as 'Average Wk 5'
sum([1]+[2]+[3]+[4]+[5]+[6])/6 as 'Average Wk 6'
I've thought about various different ways of working out this average accurately in T-SQL so I can then drop this into SSRS eventually. I've thought about using a While..Loop, Cursor but failing to see an easy way of doing this.
You are looking for the cumulative average of the averages. In databases that support window/analytic functions, you can do:
select fiscalweek, avg(cost) as avgcost,
avg(avg(cost)) over (order by fiscalweek) as cumavg
from practices p
group by fiscalweek
order by 1;
If you don't have window functions, then you need to use some form of correlated subquery or join:
select p1.fiscalweek, avg(p1.avgcost)
from (select fiscalweek avg(cost) as avgcost
from practices p
group by fiscalweek
) p1 join
(select fiscalweek avg(cost) as avgcost
from practices p
group by fiscalweek
) p2
on p12 <= p1
group by p1.fiscalweek
order by 1;
I do want to caution you that you are calculating the "average of averages". This is different from the cumulative average, which could be calculated as:
select fiscalweek,
(sum(sum(cost)) over (order by fiscalweek) /
sum(count(*)) over (order by fiscalweek)
) avgcost
from practices p
group by fiscalweek
order by 1;
One treats every week as one data point in the final average (what you seem to want). The other weights each week by the number of points during the week (the latter solution). These can produce very different results when weeks have different numbers of points.
I dont know If I fully understand the question:But Try Executing this: should help you:
create table #practice(PID int,cost decimal,Fweek int)
insert into #practice values (1,10,1)
insert into #practice values (1,33,2)
insert into #practice values (1,55,3)
insert into #practice values (1,18,4)
insert into #practice values (1,36,5)
insert into #practice values (1,24,6)
insert into #practice values (13,56,1)
insert into #practice values (13,10,2)
insert into #practice values (13,24,3)
insert into #practice values (13,30,4)
insert into #practice values (13,20,5)
insert into #practice values (13,18,6)
select * from #practice
select pid,Cost,
(select AVG(cost) from #practice p2 where p2.Fweek <= p1.Fweek and p1.pid = p2.pid) WeeklyAVG,
Fweek,AVG(COST) over (Partition by PID) as PIDAVG
from #practice p1;
I think this would work:
SELECT t1.pid,
t1.fiscalweek,
(
SELECT SUM(t.cost)/COUNT(t.cost)
FROM tablename AS t
WHERE t.pid = t1.pid
AND t.fiscalweek <= t1.fiscalweek
) AS average
FROM tablename AS t1
GROUP BY t1.pid, t1.fiscalweek
EDIT
To take into account for fiscal weeks without an entry you can simply exchange
SELECT SUM(t.cost)/COUNT(t.cost)
for
SELECT SUM(t.cost)/t1.fiscalweek
to calculate from week 1 or
SELECT SUM(t.cost)/(t1.fiscalweek - MIN(t.fiscalweek) + 1)
to calculate from the first week of this practice.
If all practice averages should start the same week (and not necessarily week no 1) then you'd have to find the minimum of all week numbers.
Also, this won't work if you're calculating across multiple years, but I assume that is not he case.

SQL query that selects effective costing rate based on charge date

SQL novice here. I'm trying to generate a costing query that outputs employee time card information and calculates cost based on an effective employee costing rate.
My question is similar to the one asked here: Retro-active effective date changes with overlapping dates but I'm not dealing with retro-activity or overlapping date ranges.
Table examples (null values in the rate table indicate current rate):
CREATE TABLE Emp_Rate
(
Emp int,
Rate money,
Rate_Start datetime,
Rate_Exp datetime
)
CREATE TABLE Emp_Time
(
Emp int,
Chrg_Date datetime,
Chrg_Code varchar(10),
Chrg_Hrs decimal(8, 2)
)
Insert into Emp_Rate (Emp,Rate,Rate_Start,Rate_Exp) Values ('1','20','5/1/09','4/30/10')
Insert into Emp_Rate (Emp,Rate,Rate_Start,Rate_Exp) Values ('1','21','5/1/10','4/30/11')
Insert into Emp_Rate (Emp,Rate,Rate_Start,Rate_Exp) Values ('1','22','5/1/11',NULL)
Insert into Emp_Time (Emp,Chrg_Date,Chrg_Code,Chrg_Hrs) Values ('1','5/10/09','B','8')
Insert into Emp_Time (Emp,Chrg_Date,Chrg_Code,Chrg_Hrs) Values ('1','5/10/10','B','8')
Insert into Emp_Time (Emp,Chrg_Date,Chrg_Code,Chrg_Hrs) Values ('1','5/10/11','B','8')
The query (returns dupes caused by multiple rate entries(obviously)):
Select Emp_Time.Emp,
Cast(Emp_Time.Chrg_Date as DATE) as 'Chrg_Date',
Emp_Time.Chrg_Code,
Emp_Time.Chrg_Hrs,
Emp_Rate.Rate,
Emp_Time.Chrg_Hrs * Emp_Rate.Rate as 'Cost'
From Emp_Time inner join
Emp_Rate on Emp_Rate.Emp = Emp_Time.Emp
Order By [Emp],[Chrg_Date]
Desired output:
Emp Chrg_Date Chrg_Code Chrg_Hrs Rate Cost
1 2009-05-10 B 8.00 20.00 160.00
1 2010-05-10 B 8.00 21.00 168.00
1 2011-05-10 B 8.00 22.00 176.00
I've gone around in circles using the Between operator in a sub query to isolate the correct rate based on the charge date, but have not had any luck.
I appreciate any help!
You didn't specify the DBMS type the answer below is for sql-server. I am sure there are other ways to do this but this way will replace the null Rate_Exp date with the current date.
Select et.Emp,
Cast(et.Chrg_Date as DATEtime) as 'Chrg_Date',
et.Chrg_Code,
et.Chrg_Hrs,
er.Rate,
et.Chrg_Hrs * er.Rate as 'Cost'
From Emp_Time et
inner join
(
SELECT Emp
, Rate
, Rate_Start
, CASE
WHEN Rate_Exp is Null
THEN Convert(varchar(10), getdate(), 101)
ELSE Rate_Exp
END as Rate_Exp
FROM Emp_Rate
)er
on er.Emp = et.Emp
WHERE (et.Chrg_Date BETWEEN er.Rate_Start AND er.Rate_Exp)
Order By et.Emp,et.Chrg_Date
OR use the CASE Statement in your WHERE Clause:
Select et.Emp,
Cast(et.Chrg_Date as DATEtime) as 'Chrg_Date',
et.Chrg_Code,
et.Chrg_Hrs,
er.Rate,
et.Chrg_Hrs * er.Rate as 'Cost'
From Emp_Time et
inner join Emp_Rate er
on er.Emp = et.Emp
WHERE (et.Chrg_Date
BETWEEN er.Rate_Start
AND CASE WHEN er.Rate_Exp Is Null
THEN Convert(varchar(10), getdate(), 101)
ELSE er.Rate_Exp END)