I have data that is imported daily into a table called TEMP. The value in the Amount field represents a running total which resets to zero on the first day of each month. I need to subtract the previous day's value from the current day's value before inserting a record for the adjusted value into the ACT_M table. The first day of the month is accounted for so I just need to figure out how to make the adjustment when the current day is not the first of the month. Below is an example the TEMP table and the desired results of the ACT_M table using Jan 20 as the current day and Jan 19 as the previous day. The Dept and Type columns must be treated as key fields as some Dept's can have multiple Type's and there will always be two records for each Dept and Type combination. There is also a possibility of negative values. Some Dept's, like 10000087, are used as transitionary Dept's and the amounts can later be adjusted and credited to a regular Dept resulting in negative values.
TABLE "TEMP"
DATE
DEPT
TYPE
AMOUNT
20230120
10000064
AC
525
20230119
10000064
AC
498
20230120
10000064
A
696
20230119
10000064
A
667
20230120
10000066
A
731
20230119
10000066
A
707
20230120
10000067
O
182
20230119
10000067
O
175
20230120
10000068
A
641
20230119
10000068
A
611
20230120
10000087
A
-5
20230119
10000087
A
-4
TABLE "ACT_M"
DATE
DEPT
TYPE
AMOUNT
20230120
10000064
AC
27
20230120
10000064
A
29
20230120
10000066
A
24
20230120
10000067
O
7
20230120
10000068
A
30
20230120
10000087
A
-1
I tried modifying code snippets I found online with no luck. I'm totally out of my element here and don't know if some type of join would make more sense. I last tried using examples I found for the LAG function but can't resolve the errors. Says I the function must have an OVER clause. Let me know if additional information is needed.
SELECT Date, Dept, Type, Amount,
LAG(Amount) AS PrvDay,
Amount - LAG(Amount)
OVER (ORDER BY Date) AS Diff_PrvDay
FROM {temp}
WITH CTE AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Dept, Type ORDER BY Date) AS RowNum
FROM TEMP
)
SELECT
T1.Date,
T1.Dept,
T1.Type,
T1.Amount - T2.Amount AS Amount
FROM
CTE T1
LEFT JOIN CTE T2 ON T1.Dept = T2.Dept
AND T1.Type = T2.Type
AND T1.RowNum = T2.RowNum + 1
WHERE
T1.RowNum > 1;
TEST
http://sqlfiddle.com/#!18/881df/1
Related
I have a PaymentSchedule table that looks like the below which contains information about contracts, and when we expect to get paid on them.
contractkey
payment
total
DueDate
385884
Upfront
95.356
2022-05-17 00:00:00.000
385884
First
1
2022-06-09 00:00:00.000
385884
Final
143.034
2024-07-17 00:00:00.000
I then have another table which contains payments received at ContractKey level structured like the below..
PaymentKey
ContractKey
Total
1
385884
47.68
These tables are joined using ContractKey. What I am trying to do is add a column to my PaymentSchedule table which shows the amount of each scheduled payment that has already been paid off in the Payments table. So the example below we can see that 47.68 has been received for ContractKey 385884, which should then show in my calculated column the below..
I have wrote the below SQL and it isn't giving me the correct output for the subsequent rows..
with debitdetails as(
select contractkey,sum(total)[totalpaid] from fact.Payments
group by contractkey
)
select s.contractkey, s.Payment, s.total, [DueDate],
sum(s.total) over (partition by s.contractkey order by [DueDate] asc) - totalpaid [TotalRemaining]
from [ref].[PaymentSchedule] s
left join debitdetails dd on s.contractkey=dd.ContractKey
where s.contractkey = 385884
order by s.contractkey
This is giving me the below.. which isn't what I want as I want it to show me of the amount due, how much is remaining after minusing the already paid amount. So the 2nd row should show as 1, and the third as 143.03
contractkey
Payment
total
DueDate
TotalRemaining
385884
Upfront
95.356
2022-05-17 00:00:00.000
47.676
385884
First
1
2022-06-09 00:00:00.000
47.676
385884
Final
143.034
2024-07-17 00:00:00.000
190.71
Can anyone help me identify where I am going wrong? I assume I am just missing something really simple..
use case expression to check the totalpaid against the cumulative sum and calculate the remaining amount accordingly
First condition is when totalpaid is more than the cumulative sum, so remaining = 0
Second condition is when totalpaid is only able to partially cover the cumulative sum
Final condition (else) is when totalpaid is totally not enough to cover amount, so Remaining = 0
TotalRemaining = case when isnull(dd.totalpaid, 0)
>= sum(s.Total) over (partition by s.contractkey
order by s.DueDate)
then 0
when isnull(dd.totalpaid, 0)
>= sum(s.Total) over (partition by s.contractkey
order by s.DueDate)
- s.Total
then sum(s.Total) over (partition by s.contractkey
order by s.DueDate)
- isnull(dd.totalpaid, 0)
else s.Total
end
My scenario is not really hard. Basically, I have two tables and I need to join them through a PK and a date, the thing is that salary table has a date per each monthly payment and the second table called bonus has just a date with the annual bonus that has to be linked with salary on the date after declare the year bonus but just until next year bonus.
Knowing that and just you give you an idea this is how you check the tables.
Salary table:
Sample data:
DateHist;NumSalarie;ValeurMontant;ChargePatronal;ChargesSalariales
2012-10-31 00:00:00.000;1;3519;1322;766,49
2012-11-30 00:00:00.000;1;3519;1322;766,49
2012-12-31 00:00:00.000;1;3519;1322;766,49
2013-01-31 00:00:00.000;1;3519;1395,15;867,84
2013-02-28 00:00:00.000;1;3592,33;1936,78;1157,09
2013-03-31 00:00:00.000;1;3592,33;1423,23;882,85
2013-04-30 00:00:00.000;1;3592,33;1423,23;882,85
2013-05-31 00:00:00.000;1;3592,33;1423,23;882,85
2013-06-30 00:00:00.000;1;3592,33;1423,23;882,85
2013-07-31 00:00:00.000;1;3592,33;1423,23;882,85
2013-08-31 00:00:00.000;1;3592,33;1202,4;765,41
2013-09-30 00:00:00.000;1;3592,33;1385,19;862,52
2013-10-31 00:00:00.000;1;3592,33;1423,23;882,85
2013-11-30 00:00:00.000;1;3592,33;1423,23;882,85
2013-12-31 00:00:00.000;1;3592,33;1423,23;882,85
2014-01-31 00:00:00.000;1;3592,33;1439,35;897,52
2014-02-28 00:00:00.000;1;3592,33;1825,8;1104,15
2014-03-31 00:00:00.000;1;3666,67;2858,27;1656,17
2014-04-30 00:00:00.000;1;3666,67;1468,1;912,89
2014-05-31 00:00:00.000;1;3666,67;1468,1;912,89
Bonus table:
Sample data:
CodeRubrique;NumSalarie;ValeurMontant;DateHist
1200;1;1267;2013-02-28 00:00:00.000
1200;1;3448,64;2014-03-31 00:00:00.000
1200;1;3633;2015-03-31 00:00:00.000
1200;1;2244;2015-09-30 00:00:00.000
1200;1;4042,84;2016-10-31 00:00:00.000
So, now when I join both tables I do in T-SQL:
SELECT
salpaid.DateHist,
salpaid.NumSalarie,
salpaid.ValeurMontant,
bonus.ValeurMontant AS bonus
FROM
(select CodeRubrique,NumSalarie,ValeurMontant,DateHist
FROM table ) salpaid
LEFT JOIN
(select CodeRubrique,NumSalarie,ValeurMontant,DateHist
FROM T_HBNS
WHERE CodeRubrique='1200') bonus
ON salpaid.NumSalarie=bonus.NumSalarie
AND salpaid.DateHist >= bonus.DateHist
So, here is my problem. The thing is that the date joins it's not right because the result when a complete the first year bonus and then I'm on a date after the first year bonus I link twice. the previous year bonus and the current one. just to show you guys my output:
As you can see the payments before line 73 have NULL in the bonus because the first registered bonus was after those dates then between the line 74 and 87 are ok. the nightmare came when I go through second-year bonus because I get a link on the right bonus category but I have another additional link with previous year bonus as you can see in lines after 88.
How should I improve my code to get the right JOIN?
thanks guys
If you build a CTE with the start and end dates for each bonus, you can outer join to the cte where the DateHist falls between the Begin and End dates of the bonuses
WITH Bonus_Ordered AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [NumSalarie] ORDER BY [DateHist]) Rn
FROM Bonus
WHERE CodeRubrique = '1200'
),
Bonus_Periods AS (
SELECT a.*,
b.DateHist - 1 AS EndDateHist
FROM Bonus_Ordered a LEFT JOIN Bonus_Ordered b ON a.Rn + 1 = b.Rn
)
SELECT *
FROM Salary s
LEFT JOIN Bonus_Periods bp ON s.NumSalarie = bp.NumSalarie
AND s.DateHist BETWEEN bp.DateHist AND bp.EndDateHist
ORDER BY s.DateHist
SQL Fiddle example
What if you change
salpaid.DateHist >= bonus.DateHist
to
Year(salpaid.DateHist) = Year(bonus.DateHist)
My query is as follows
SELECT
LEFT(TimePeriod,6) Period, -- string field with YYYYMMDD
SUM(Value) Value
FROM
f_Trans_GL
WHERE
Account = 228
GROUP BY
TimePeriod
And it returns
Period Value
---------------
201412 80
201501 20
201502 30
201506 50
201509 100
201509 100
I'd like to know the Value difference between rows where the period is 1 month apart. The calculation being [value period] - [value period-1].
The desired output being;
Period Value Calculated
-----------------------------------
201412 80 80 - null = 80
201501 20 20 - 80 = -60
201502 30 30 - 20 = 10
201506 50 50 - null = 50
201509 100 (100 + 100) - null = 200
This illustrates a second challenge, as the period needs to be evaluated if the year changes (the difference between 201501 and 201412 is one month).
And the third challenge being a duplicate Period (201509), in which case the sum of that period needs to be evaluated.
Any indicators on where to begin, if this is possible, would be great!
Thanks in advance
===============================
After I accepted the answer, I tailored this a little to suit my needs, the end result is:
WITH cte
AS (SELECT
ISNULL(CAST(TransactionID AS nvarchar), '_nullTransactionId_') + ISNULL(Description, '_nullDescription_') + CAST(Account AS nvarchar) + Category + Currency + Entity + Scenario AS UID,
LEFT(TimePeriod, 6) Period,
SUM(Value1) Value1,
CAST(LEFT(TimePeriod, 6) + '01' AS date) ord_date
FROM MyTestTable
GROUP BY LEFT(TimePeriod, 6),
TransactionID,
Description,
Account,
Category,
Currency,
Entity,
Scenario,
TimePeriod)
SELECT
a.UID,
a.Period,
--a.Value1,
ISNULL(a.Value1, 0) - ISNULL(b.Value1, 0) Periodic
FROM cte a
LEFT JOIN cte b
ON a.ord_date = DATEADD(MONTH, 1, b.ord_date)
ORDER BY a.UID
I have to get the new value (Periodic) for each UID. This UID must be determined as done here because the PK on the table won't work.
But the issue is that this will return many more rows than I actually have to begin with in my table. If I don't add a GROUP BY and ORDER by UID (as done above), I can tell that the first result for each combination of UID and Period is actually correct, the subsequent rows for that combination, are not.
I'm not sure where to look for a solution, my guess is that the UID is the issue here, and that it will somehow iterate over the field... any direction appreciated.
As pointed by other, first mistake is in Group by you need to Left(timeperiod, 6) instead of timeperiod.
For remaining calculation try something like this
;WITH cte
AS (SELECT LEFT(timeperiod, 6) Period,
Sum(value) Value,
Cast(LEFT(timeperiod, 6) + '01' AS DATE) ord_date
FROM f_trans_gl
WHERE account = 228
GROUP BY LEFT(timeperiod, 6))
SELECT a.period,
a.value,
a.value - Isnull(b.value, 0)
FROM cte a
LEFT JOIN cte b
ON a.ord_date = Dateadd(month, 1, b.ord_date)
If you are using SQL SERVER 2012 then this can be easily done using LAG analytic function
Using a derived table, you can join the data to itself to find rows that are in the preceding period. I have converted your Period to a Date value so you can use SQL Server's dateadd function to check for rows in the previous month:
;WITH cte AS
(
SELECT
LEFT(TimePeriod,6) Period, -- string field with YYYYMMDD
CAST(TimePeriod + '01' AS DATE) PeriodDate
SUM(Value) Value
FROM f_Trans_GL
WHERE Account = 228
GROUP BY LEFT(TimePeriod,6)
)
SELECT c1.Period,
c1.Value,
c1.Value - ISNULL(c2.Value,0) AS Calculation
FROM cte c1
LEFT JOIN cte c2
ON c1.PeriodDate = DATEADD(m,1,c2.PeriodDate)
Without cte, you can also try something like this
SELECT A.Period,A.Value,A.Value-ISNULL(B.Value) Calculated
FROM
(
SELECT LEFT(TimePeriod,6) Period
DATEADD(M,-1,(CONVERT(date,LEFT(TimePeriod,6)+'01'))) PeriodDatePrev,SUM(Value) Value
FROM f_Trans_GL
WHERE Account = 228
GROUP BY LEFT(TimePeriod,6)
) AS A
LEFT OUTER JOIN
(
SELECT LEFT(TimePeriod,6) Period
(CONVERT(date,LEFT(TimePeriod,6)+'01')) PeriodDate,SUM(Value) Value
FROM f_Trans_GL
WHERE Account = 228
GROUP BY LEFT(TimePeriod,6)
) AS B
ON (A.PeriodDatePrev = B.PeriodDate)
ORDER BY 1
I have a table where I add every month new a new record of data (key performance indicators) of that month. The table is made of 30 fields/columns. For the purpose of this question I include only three fields.
Table: tbl_KPIMonth, Primary Key: YearMonth
YearMonth OrderAmount OrderLines OrderLinesOnTime
201311 2500 350 330
201312 3000 400 390
201401 1000 100 95
201402 500 150 140
201403 2000 200 190
201404 1500 100 90
I have a query that aggregates the monthly data to a current year-to-date (YTD) total for every field/column.
SELECT Val(Left(m.YearMonth,4)) AS Year, Sum(m.OrderAmount) AS OrderAmountYTD,
Sum(m.OrderLines) AS OrderLinesYTD,
Sum(m.OrderLinesOnTime)/Sum(m.OrderLines) AS OnTimeDeliveryYTD
FROM tbl_KPIMonth AS m
WHERE Val(Left(m.YearMonth,4))=2014
GROUP BY Val(Left(m.YearMonth,4))
Every month I want to add the YTD aggregated data to a history table to display the progress of the measure. The expected result should look like this:
Table: tbl_HistKPIYear, Primary Key: YearMonth
YearMonth OrderAmountYTD OrderLinesYTD OnTimeDeliveryYTD
201401 1000 100 0.95
201402 1500 250 0.94
201403 3500 450 0.94
201404 5000 550 0.93
Using the query above as an INSERT query doesn't work, if I include the field YearMonth, because the GROUP BY m.YearMonth would prevent from aggregating the rest of the fields.
I must set the primary key on YearMonth to avoid duplicates.
INSERT INTO tbl_HistKPIYear (OrderAmountYTD, OrderLinesYTD, OnTimeDeliveryYTD)
SELECT Sum(m.OrderAmount) AS OrderAmountYTD,
Sum(m.OrderLines) AS OrderLinesYTD,
Sum(m.OrderLinesOnTime)/Sum(m.OrderLines) AS OnTimeDeliveryYTD
FROM tbl_KPIMonth AS m
WHERE Val(Left(m.YearMonth,4))=2014
GROUP BY Val(Left(m.YearMonth,4))
What can I do to have the field YearMonth in the table tbl_HistKPIYear populated with the current month (e.g. 201404)?
You actually need a cumulative query, requiring subqueries in order to achieve running sums of each month within the same year.
See my example below (I add the OrderLineYTD to show you how the last column is calculated):
SELECT t1.YearMonth,
(SELECT SUM(t2.OrderAmount) FROM tbl_KPIMonth t2
WHERE t2.YearMonth <= t1.YearMonth AND Left(t1.YearMonth, 4) = Left(t2.YearMonth, 4)) As OrderAmountYTD,
(SELECT SUM(t2.OrderLines) FROM tbl_KPIMonth t2
WHERE t2.YearMonth <= t1.YearMonth AND Left(t1.YearMonth, 4) = Left(t2.YearMonth, 4)) As OrderLinesYTD,
(SELECT SUM(t2.OrderLinesOnTime) FROM tbl_KPIMonth t2
WHERE t2.YearMonth <= t1.YearMonth AND Left(t1.YearMonth, 4) = Left(t2.YearMonth, 4)) As OrderLineOnTimeYTD,
([OrderLineOnTimeYTD] / [OrderLinesYTD]) As OnTimeDeliveryYTD
FROM tbl_KPIMonth t1;
Notice for the last column I reference the query's new column aliases instead of repeating the subqueries again.
For a development aid project I am helping a small town in Nicaragua improving their water-network-administration.
There are about 150 households and every month a person checks the meter and charges the houshold according to the consumed water (reading from this month minus reading from last month). Today all is done on paper and I would like to digitalize the administration to avoid calculation-errors.
I have an MS Access Table in mind - e.g.:
*HousholdID* *Date* *Meter*
0 1/1/2013 100
1 1/1/2013 130
0 1/2/2013 120
1 1/2/2013 140
...
From this data I would like to create a query that calculates the consumed water (the meter-difference of one household between two months)
*HouseholdID* *Date* *Consumption*
0 1/2/2013 20
1 1/2/2013 10
...
Please, how would I approach this problem?
This query returns every date with previous date, even if there are missing months:
SELECT TabPrev.*, Tab.Meter as PrevMeter, TabPrev.Meter-Tab.Meter as Diff
FROM (
SELECT
Tab.HousholdID,
Tab.Data,
Max(Tab_1.Data) AS PrevData,
Tab.Meter
FROM
Tab INNER JOIN Tab AS Tab_1 ON Tab.HousholdID = Tab_1.HousholdID
AND Tab.Data > Tab_1.Data
GROUP BY Tab.HousholdID, Tab.Data, Tab.Meter) As TabPrev
INNER JOIN Tab
ON TabPrev.HousholdID = Tab.HousholdID
AND TabPrev.PrevData=Tab.Data
Here's the result:
HousholdID Data PrevData Meter PrevMeter Diff
----------------------------------------------------------
0 01/02/2013 01/01/2013 120 100 20
1 01/02/2013 01/01/2012 140 130 10
The query above will return every delta, for every households, for every month (or for every interval). If you are just interested in the last delta, you could use this query:
SELECT
MaxTab.*,
TabCurr.Meter as CurrMeter,
TabPrev.Meter as PrevMeter,
TabCurr.Meter-TabPrev.Meter as Diff
FROM ((
SELECT
Tab.HousholdID,
Max(Tab.Data) AS CurrData,
Max(Tab_1.Data) AS PrevData
FROM
Tab INNER JOIN Tab AS Tab_1
ON Tab.HousholdID = Tab_1.HousholdID
AND Tab.Data > Tab_1.Data
GROUP BY Tab.HousholdID) As MaxTab
INNER JOIN Tab TabPrev
ON TabPrev.HousholdID = MaxTab.HousholdID
AND TabPrev.Data=MaxTab.PrevData)
INNER JOIN Tab TabCurr
ON TabCurr.HousholdID = MaxTab.HousholdID
AND TabCurr.Data=MaxTab.CurrData
and (depending on what you are after) you could only filter current month:
WHERE
DateSerial(Year(CurrData), Month(CurrData), 1)=
DateSerial(Year(DATE()), Month(DATE()), 1)
this way if you miss a check for a particular household, it won't show.
Or you might be interested in showing last month present in the table (which can be different than current month):
WHERE
DateSerial(Year(CurrData), Month(CurrData), 1)=
(SELECT MAX(DateSerial(Year(Data), Month(Data), 1))
FROM Tab)
(here I am taking in consideration the fact that checks might be on different days)
I think the best approach is to use a correlated subquery to get the previous date and join back to the original table. This ensures that you get the previous record, even if there is more or less than a 1 month lag.
So the right query looks like:
select t.*, tprev.date, tprev.meter
from (select t.*,
(select top 1 date from t t2 where t2.date < t.date order by date desc
) prevDate
from t
) join
t tprev
on tprev.date = t.prevdate
In an environment such as the one you describe, it is very important not to make assumptions about the frequency of reading the meter. Although they may be read on average once per month, there will always be exceptions.
Testing with the following data:
HousholdID Date Meter
0 01/12/2012 100
1 01/12/2012 130
0 01/01/2013 120
1 01/01/2013 140
0 01/02/2013 120
1 01/02/2013 140
The following query:
SELECT a.housholdid,
a.date,
b.date,
a.meter,
b.meter,
a.meter - b.meter AS Consumption
FROM (SELECT *
FROM water
WHERE Month([date]) = Month(Date())
AND Year([date])=year(Date())) a
LEFT JOIN (SELECT *
FROM water
WHERE DateSerial(Year([date]),Month([date]),Day([date]))
=DateSerial(Year(Date()),Month(Date())-1,Day([date])) ) b
ON a.housholdid = b.housholdid
The above query selects the records for this month Month([date]) = Month(Date()) and compares them to records for last month ([date]) = Month(Date()) - 1)
Please do not use Date as a field name.
Returns the following result.
housholdid a.date b.date a.meter b.meter Consumption
0 01/02/2013 01/01/2013 120 100 20
1 01/02/2013 01/01/2013 140 130 10
Try
select t.householdID
, max(s.theDate) as billingMonth
, max(s.meter)-max(t.meter) as waterUsed
from myTbl t join (
select householdID, max(theDate) as theDate, max(meter) as meter
from myTbl
group by householdID ) s
on t.householdID = s.householdID and t.theDate <> s.theDate
group by t.householdID
This works in SQL not sure about access
You can use the LAG() function in certain SQL dialects. I found this to be much faster and easier to read than joins.
Source: http://blog.jooq.org/2015/05/12/use-this-neat-window-function-trick-to-calculate-time-differences-in-a-time-series/