I have data in this format
student_id,month1,fees
A1,201612,22
A1,201611,33
A1,201610,44
A1,201609,55
A1,201608,66
A1,201607,77
A1,201606,88
A2,201612,12
A2,201610,24
A2,201609,36
A2,201607,48
I want fees of every student considering average of last three month fees means for student A1, for month 201612, fees will be sum(22,33,44)/3 so I used this query
(select student_id,month1,fees,(sum(fees) over(partition by
student_id
order by
student_id
,
month1
asc rows between 2 preceding and current row ))/3 as avg1 from table where
month1
>(select trim(Add_Months(cast(trim(maxrepmonth) as DATE Format 'YYYYMM'),-5) (format 'YYYYMM')) from (select max(
month1
) as maxrepmonth from table) z2) group by 1,2,3)
and this works fine for student A1 as it is having all months data but in case of student A2, for month 201612, It is taking fees from these months 201612,201610,201609 which is wrong, instead it should take only from 201612,201610 as 201611 is missing.
Please help.
Thanks
OLAP functions from the ANSI-99 standard are your friend here - especially the RANGE window clause.
Try this - I'm doing it with Vertica, but Teradata should be just as ANSI compliant and do it for you, too:
WITH foo(student_id,month1,fees) AS (
SELECT 'A1',DATE '2016-12-01',22
UNION ALL SELECT 'A1',DATE '2016-11-01',33
UNION ALL SELECT 'A1',DATE '2016-10-01',44
UNION ALL SELECT 'A1',DATE '2016-09-01',55
UNION ALL SELECT 'A1',DATE '2016-08-01',66
UNION ALL SELECT 'A1',DATE '2016-07-01',77
UNION ALL SELECT 'A1',DATE '2016-06-01',88
UNION ALL SELECT 'A2',DATE '2016-12-01',12
UNION ALL SELECT 'A2',DATE '2016-10-01',24
UNION ALL SELECT 'A2',DATE '2016-09-01',36
UNION ALL SELECT 'A2',DATE '2016-07-01',48
)
SELECT
*
, AVG(fees) OVER (
PARTITION BY student_id ORDER BY month1
RANGE BETWEEN INTERVAL '3 MONTHS' PRECEDING AND CURRENT ROW
) AS rolling_avg_3_months
FROM foo;
student_id|month1 |fees|rolling_avg_3_months
A1 |2016-06-01| 88| 88
A1 |2016-07-01| 77| 82.5
A1 |2016-08-01| 66| 77
A1 |2016-09-01| 55| 66
A1 |2016-10-01| 44| 55
A1 |2016-11-01| 33| 44
A1 |2016-12-01| 22| 33
A2 |2016-07-01| 48| 48
A2 |2016-09-01| 36| 42
A2 |2016-10-01| 24| 30
A2 |2016-12-01| 12| 18
Thanks Dudu, thanks Amit
So Teradata does not support RANGE ...
Now it becomes trickier.
Found a working solution, but it needs some explanation.
Hope the comments are enough.
WITH
-- the input data
foo(student_id,month1,fees) AS (
SELECT 'A1',DATE '2016-12-01',22
UNION ALL SELECT 'A1',DATE '2016-11-01',33
UNION ALL SELECT 'A1',DATE '2016-10-01',44
UNION ALL SELECT 'A1',DATE '2016-09-01',55
UNION ALL SELECT 'A1',DATE '2016-08-01',66
UNION ALL SELECT 'A1',DATE '2016-07-01',77
UNION ALL SELECT 'A1',DATE '2016-06-01',88
UNION ALL SELECT 'A2',DATE '2016-12-01',12
UNION ALL SELECT 'A2',DATE '2016-10-01',24
UNION ALL SELECT 'A2',DATE '2016-09-01',36
UNION ALL SELECT 'A2',DATE '2016-07-01',48
)
-- add two columns fees1before and fees2before, that can be null,
-- containing the fees of the two previous rows if the 'month1'
-- value of those rows is less than 3 months back
, foo_3_months AS (
SELECT
student_id
, month1
, fees AS fees_now
, CASE
WHEN
MONTHS_BETWEEN(month1,LAG(month1) OVER (PARTITION BY student_id ORDER BY month1))
< 3
THEN LAG(fees) OVER (PARTITION BY student_id ORDER BY month1)
END AS fees_1before
, CASE
WHEN MONTHS_BETWEEN(month1,LAG(month1,2) OVER (PARTITION BY student_id ORDER BY month1))
< 3
THEN LAG(fees,2) OVER (PARTITION BY student_id ORDER BY month1)
END AS fees_2before
FROM foo
)
-- finally, build a hard-wired average formula that takes care of
-- the fact that two of the three values can be NULL
-- I'm keeping the two additional columns for debugging purposes.
-- They can be removed in the end.
SELECT
*
, (fees_now+NVL(fees_1before,0)+NVL(fees_2before,0) )
/ (
1
+ (CASE WHEN fees_1before IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN fees_2before IS NOT NULL THEN 1 ELSE 0 END)
)
AS rolling_avg_3months
FROM foo_3_months
;
Here's the result:
student_id|month1 |fees_now|fees_1before|fees_2before|rolling_avg_3months
A1 |2016-06-01| 88|- |- |88.000000000000000000
A1 |2016-07-01| 77| 88|- |82.500000000000000000
A1 |2016-08-01| 66| 77| 88|77.000000000000000000
A1 |2016-09-01| 55| 66| 77|66.000000000000000000
A1 |2016-10-01| 44| 55| 66|55.000000000000000000
A1 |2016-11-01| 33| 44| 55|44.000000000000000000
A1 |2016-12-01| 22| 33| 44|33.000000000000000000
A2 |2016-07-01| 48|- |- |48.000000000000000000
A2 |2016-09-01| 36| 48|- |42.000000000000000000
A2 |2016-10-01| 24| 36|- |30.000000000000000000
A2 |2016-12-01| 12| 24|- |18.000000000000000000
Not an easy task - and maybe a Request for Enhancement to Teradata?
Happy playing -
Marco the Sane
I just finished a second way of dealing with your need. Took me two hours, though, and I had to find the time to do that. But I was curious myself, so it's not wasted time.
The advantage of this approach is that it is more flexible. It's easier to change if you need 4, 5 or 6 months instead of 3, and you don't have to cater for the possibility of components to be NULL, because you can use the normal AVG() OVER().
The downside is a much more complex data preparation phase: You have to fill gaps, containing NULLs for the measure, and create a list of all possible firsts of month between the smallest month1 and the greatest month1 values in the base table. For this purpose, I mimick the TIMESERIES clause from Vertica.
The solution contains a lot that I think will be handy in the survival kit of anybody digging deeper into SQL, like creating in-line tables of consecutive integers, and time series out of these. That's also why I create a series of 100 integers when 7 would be enough. It also shows that a CROSS JOIN is not always a disaster.
I tried to sufficiently comment what I'm doing here, I hope it's enough.
-- WITHOUT RANGE BETWEEN - vertical version
WITH
-- the input data
foo(student_id,month1,fees) AS (
SELECT 'A1',DATE '2016-12-01',22
UNION ALL SELECT 'A1',DATE '2016-11-01',33
UNION ALL SELECT 'A1',DATE '2016-10-01',44
UNION ALL SELECT 'A1',DATE '2016-09-01',55
UNION ALL SELECT 'A1',DATE '2016-08-01',66
UNION ALL SELECT 'A1',DATE '2016-07-01',77
UNION ALL SELECT 'A1',DATE '2016-06-01',88
UNION ALL SELECT 'A2',DATE '2016-12-01',12
UNION ALL SELECT 'A2',DATE '2016-10-01',24
UNION ALL SELECT 'A2',DATE '2016-09-01',36
UNION ALL SELECT 'A2',DATE '2016-07-01',48
)
-- 1. Mimick Vertica's TIMESERIES clause:
-- Prepare a series of month-start dates
-- from the first month to the last month
-- of the time series. Assuming it's more than
-- 10 months:
-- 1.a A series of 100 ints starting from 0
-- 1.a.1 start with 10 ints
, ten_ints(idx) AS (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
)
-- 1.a.2 make 100 out of 10
, idx_series AS (
SELECT
tens.idx * 10 + units.idx AS idx
FROM ten_ints units
CROSS JOIN ten_ints tens
)
-- 1.b get limit dates and total month count
, month_limits AS (
SELECT
MIN(month1) AS start_month
, MAX(month1) AS end_month
, MONTHS_BETWEEN(MAX(month1), MIN(month1)) AS monthcount
FROM foo
)
-- 1.c create an artificial list of all student_id
-- and all possible months to fill gaps
-- This is the end of the TIMESERIES mimick.
, student_month_list AS (
SELECT
student_id
, ADD_MONTHS(start_month,idx) AS month1
FROM month_limits
JOIN idx_series
ON idx <= monthcount
CROSS
JOIN (
SELECT DISTINCT student_id FROM foo
) bar
)
-- This returns:
-- student_id|month1
-- A1 |2016-06-01
-- A1 |2016-07-01
-- A1 |2016-08-01
-- A1 |2016-09-01
-- A1 |2016-10-01
-- A1 |2016-11-01
-- A1 |2016-12-01
-- A2 |2016-06-01
-- A2 |2016-07-01
-- A2 |2016-08-01
-- A2 |2016-09-01
-- A2 |2016-10-01
-- A2 |2016-11-01
-- A2 |2016-12-01
-- Main query:
-- left join student_month_list to the base table
-- and filter out the rows whose measure is NULL
SELECT
mth.student_id
, mth.month1
, AVG(foo.fees) OVER (
PARTITION BY mth.student_id ORDER BY mth.month1
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS running_avg_3months
FROM student_month_list mth
LEFT JOIN foo USING(student_id, month1)
WHERE foo.fees IS NOT NULL
ORDER BY 1,2
;
Related
I have a table1 that I wanted to transform into the expected table.
Expected table logic for columns:
cal: comes from cal of table1. ID comes from the ID of table1.
code: this is populated with lp or fp depending upon if we have a value in f_a then we create a new record with fp as code. corresponding to it we check if f_a is populated if yes then we take that date from f_a and put in in the Al column for the same ID. also we check if f_pl is populated if yes then we take the date from that and put it in the pl column.
If the code was lp then we check if l_a is populated then we take that date and place in the date in Al for that code and Id. also, we check if lpl is populated if yes then we take that date and put it in pl.
I am just a starter with SQL so it is a bit overwhelming for me on how to get it started. Please post some solutions.
table1:
ID f_a l_a f_pl lpl cal
CNT 6/20/2018 6/28/2018 6/28/2018 1/31/2020
expected output:
ID Cal code pl Al
CNT 1/31/2020 lp 6/28/2018 6/28/2018
CNT 1/31/2020 fp 6/20/2018
Update:
I have more IDs in the table, so it is not that CNT is the only Id. If I use unpivot then it should follow the same logic for all IDs.
This is a question about how to unpivot columns to rows. In Oracle, I would recommend a lateral join:
select t.id, t.cal, x.*
from mytable t
cross apply (
select 'lp' as code, t.lpl as pl, l_a as al from dual
union all
select 'fp', t.f_pl, t.f_a from dual
) x
This syntax is available in Oracle 12.1 onwards. In earlier versions, you would use union all:
select id, cal, 'lp' as code, lpl as pl, l_a as al from mytable
union all
select id, cal, 'lp' as code, 'fp', f_pl, f_a from mytable
You can use UNPIVOT for multiple columns then do the checks you need on dates:
with a as (
select
'CNT' as ID,
date '2018-06-20' as f_a,
date '2018-06-28' as l_a,
cast(null as date) as f_pl,
date '2018-06-28' as l_pl,
date '2020-01-31' as cal
from dual
)
select *
from a
unpivot(
(pl, al) for code in ((l_pl, l_a) as 'lp', (f_pl, f_a) as 'fp')
) up
ID | CAL | CODE | PL | AL
CNT | 31-JAN-07 | lp | 28-JUN-18 | 28-JUN-18
CNT | 31-JAN-07 | fp | | 20-JUN-18
Working example here.
Please try this script which is not version dependend:
-- Here we select columns foom source table. Please change the names if they are different
with r as (
select
ID,
f_a,
l_a,
f_pl,
lpl, -- Here not sure if example is wrong or no underscore in column deffinition
cal
from table_1 -- Please put real table name here
)
select * from (
select r.id, r.cal, 'lp' as code, r.l_pl as pl, l_a as ai
from r
where r.l_a is not null
union all
select r1.id, r1.cal, 'pl', r1.f_pl, r1.f_a
from r r1
where r1.f_a is not null
)
order by id, cal, code;
For example, I have sales data for 1 year, and some of the products not available on a specific date range.
I currently have for 1 date range, but what is the best practice if have multiple exclusions?
SELECT * FROM XXX
WHERE
IF(Date BETWEEN '2018-11-22' AND '2019-03-28',
ID IN (8467,8468,8469,8470),
ID IN (8467,8468,8469,8470,9551,9552,9553)
)
Especially how to solve the issue if dates are overlapping?
If you are trying to exclude values, I am thinking:
SELECT *
FROM XXX
WHERE ID IN (8467, 8468, 8469, 8470, 9551, 9552, 9553) AND
(Date BETWEEN '2018-11-22' AND '2019-03-28' AND
ID NOT IN (9551, 9552, 9553) OR
Date NOT BETWEEN '2018-11-22' AND '2019-03-28'
);
You can add multiple pairs for other dates.
For a full solution, you might want to create a table with olumns such as:
product_id
start_exclusion_date
end_exclusion_date
And then phrase the query as:
select xxx.*
from xxx left join
exclusions e
on xxx.id = e.product_id and
xxx.date >= e.start_exclusion_date and
xxx.date <= e.end_exclusion_date
where xxx.id in ( . . . );
This is likely to be easier to maintain in the long term.
Try this,
select * from xxx
where not(date between '2018-11-22' and '2019-03-28' and id in(9551,9552,9553))
order by id, date
Below is an example for BigQuery Standard SQL and shows direction for building "complete picture" with whitelist and blacklist rules (all with quite simplified dummy data just to demonstrate it in action)
#standardSQL
WITH `project.dataset.xxx` AS (
SELECT 1 id, DATE '2018-11-22' `date` UNION ALL
SELECT 2, '2018-11-23' UNION ALL
SELECT 3, '2018-11-24' UNION ALL
SELECT 4, '2018-11-25' UNION ALL
SELECT 1, '2018-11-26' UNION ALL
SELECT 2, '2018-11-27' UNION ALL
SELECT 3, '2018-11-28' UNION ALL
SELECT 8, '2018-11-29'
), `project.dataset.whitelist` AS (
SELECT DATE '2018-11-22' start, DATE '2018-11-29' finish, [2,3] ids UNION ALL
SELECT '2018-11-22', '2018-11-22', [1]
), `project.dataset.blacklist` AS (
SELECT DATE '2018-11-26' start, DATE '2018-11-28' finish, [1,3] ids UNION ALL
SELECT '2018-11-22', '2018-11-22', [10]
)
SELECT DISTINCT t.*
FROM `project.dataset.xxx` t
JOIN `project.dataset.whitelist` w
ON (`date` BETWEEN w.start AND w.finish AND id IN UNNEST(w.ids))
JOIN `project.dataset.blacklist` b
ON NOT(`date` BETWEEN b.start AND b.finish AND id IN UNNEST(b.ids))
with result
Row id date
1 1 2018-11-22
2 2 2018-11-27
3 2 2018-11-23
4 3 2018-11-28
5 3 2018-11-24
Obviously, in real case all involved tables are real tables and query will look just like below
#standardSQL
SELECT DISTINCT t.*
FROM `project.dataset.xxx` t
JOIN `project.dataset.whitelist` w
ON (`date` BETWEEN w.start AND w.finish AND id IN UNNEST(w.ids))
JOIN `project.dataset.blacklist` b
ON NOT(`date` BETWEEN b.start AND b.finish AND id IN UNNEST(b.ids))
I have a table with some IDs that correspond to duplicate data that i would like to get rid of. They are linked by a groupid number. Currently my data looks like this:
|GroupID|NID1 |NID2 |
|S1 |644763|643257|
|T2 |4759 |84689 |
|W3 |96676 |585876|
In order for the software to run, I need the data in the following format:
|GroupID|NID |
|S1 |644763|
|S1 |643257|
|T2 |4759 |
|T2 |84689 |
|W3 |96676 |
|W3 |585876|
Thank you for your time.
You want union all :
select groupid, nid1 as nid
from table t
union all -- use "union" instead if you don't want duplicate rows
select groupid, nid2
from table t;
In Oracle 12C+, you can use lateral joins:
select t.groupid, v.nid
from t cross apply
(select t.nid1 as nid from dual union all
select t.nid2 as nid from dual
) v;
This is more efficient than union all because it only scans the table once.
You can also express this as:
select t.groupid,
(case when n.n = 1 then t.nid1 when n.n = 2 then t.nid2 end) as nid
from t cross join
(select 1 as n from dual union all select 2 from dual) n;
A little more complicated, but still only one scan of the table.
I am trying to join three table and get the results, however, one of the tables has multiple event_code for the same CSO_Item_key which is resulting in duplicate records.
Please note my source is Vertica and Target is SQL server.
I tried stuff and for XML approach but is not working with vertica; it says incorrect syntax XML.
Is there any other solution
Table 1
Entry Date Cso Item Key Fail Code
8/1/2018 4:28 BLXB796201 CSL120
8/1/2018 4:40 BLXB799101 CLL250
8/1/2018 4:55 BLXB803001 CMS130
8/1/2018 5:08 BLXB806201 CNE100
Table 2
Cso Item Key Event Code
BLXB796201 GTS
BLXB796201 LC28
BLXB796201 SDR4
BLXB799101 GTS
BLXB799101 LC28
BLXB799101 SDR4
BLXB803001 GTS
BLXB803001 LC28
BLXB803001 SDR4
BLXB806201 GTS
BLXB806201 LC28
BLXB806201 SDR4
Table 3
Fail Code Desc
CSL120 Bad Part
CLL250 Unit Scrapped
CNE100 OS Reinstall
CBN101 NTF
Expected Result:
Entry_Date Cso_Item_Key Fail_Code Desc Event_Code
8/1/2018 4:28 BLXB796201 CSL120 Bad Part GTS,LC28,SDR4
8/1/2018 4:40 BLXB799101 CLL250 Unit Scrapped GTS,LC28,SDR4
8/1/2018 4:55 BLXB803001 CMS130 Null GTS,LC28,SDR4
8/1/2018 5:08 BLXB806201 CNE100 OS Reinstall GTS,LC28,SDR4
Screenshot of data:
One of the only solutions I've seen for this is the strings_package extension which can be found here on github. With it, you can use the group_concat function like so:
-- get a list of nodes
select group_concat(node_name) over () from nodes;
-- nodes with storage for a projection
select schema_name,projection_name,
group_concat(node_name) over (partition by schema_name,projection_name)
from (select distinct node_name,schema_name,projection_name from storage_containers) sc order by schema_name, projection_name;
This is trying to do it all in SQL - a bit cheating as I am relying on the fact that Table_2 always has 3 different event codes for each CSO Item Key.
If that is not the case, you would have to add a few rows - up to the maximum number of Event Codes per CSO Item Key, to the i index table I'm creating as a Common Table expression, and you would have to LEFT JOIN that i table to tb2, and add some NULL processing logic to the expression, for example: ||','||MAX(CASE i.i WHEN 2 THEN event_code END), so that an empty string is concatenated when the event_code in the expression is NULL.
But otherwise - with your input (which you should take out of the query when you really use it), it could look like this:
WITH
-- your input, don't use in real query ...
tb1(Entry_Date,Cso_Item_Key,Fail_Code) AS (
SELECT TIMESTAMP '8/1/2018 4:28','BLXB796201','CSL120'
UNION ALL SELECT TIMESTAMP '8/1/2018 4:40','BLXB799101','CLL250'
UNION ALL SELECT TIMESTAMP '8/1/2018 4:55','BLXB803001','CMS130'
UNION ALL SELECT TIMESTAMP '8/1/2018 5:08','BLXB806201','CNE100'
)
,
tb2(Cso_Item_Key,Event_Code) AS (
SELECT 'BLXB796201','GTS'
UNION ALL SELECT 'BLXB796201','LC28'
UNION ALL SELECT 'BLXB796201','SDR4'
UNION ALL SELECT 'BLXB799101','GTS'
UNION ALL SELECT 'BLXB799101','LC28'
UNION ALL SELECT 'BLXB799101','SDR4'
UNION ALL SELECT 'BLXB803001','GTS'
UNION ALL SELECT 'BLXB803001','LC28'
UNION ALL SELECT 'BLXB803001','SDR4'
UNION ALL SELECT 'BLXB806201','GTS'
UNION ALL SELECT 'BLXB806201','LC28'
UNION ALL SELECT 'BLXB806201','SDR4'
)
,
tb3(Fail_Code,Descr) AS (
SELECT 'CSL120','Bad Part'
UNION ALL SELECT 'CLL250','Unit Scrapped'
UNION ALL SELECT 'CNE100','OS Reinstall'
UNION ALL SELECT 'CBN101','NTF'
)
-- real WITH clause starts here - and table "i" can contain more than 3 rows..
,
i(i) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
)
,
tb2_w_i AS (
SELECT
*
, ROW_NUMBER() OVER (PARTITION BY cso_item_key ORDER BY event_code) AS i
FROM tb2
)
,
tb2_pivot AS (
SELECT
cso_item_key
, MAX(CASE i.i WHEN 1 THEN event_code END)
||','||MAX(CASE i.i WHEN 2 THEN event_code END)
||','||MAX(CASE i.i WHEN 3 THEN event_code END)
AS event_codes
FROM tb2_w_i JOIN i USING(i)
GROUP BY 1
)
SELECT
entry_date
, tb1.cso_item_key
, tb1.fail_code
, descr
, event_codes
FROM tb1
JOIN tb2_pivot USING(cso_item_key)
LEFT JOIN tb3 USING(fail_code)
;
The result (my NULLSTRING is the dash..)
entry_date |cso_item_key|fail_code|descr |event_codes
2018-08-01 04:28:00|BLXB796201 |CSL120 |Bad Part |GTS,LC28,SDR4
2018-08-01 04:40:00|BLXB799101 |CLL250 |Unit Scrapped|GTS,LC28,SDR4
2018-08-01 04:55:00|BLXB803001 |CMS130 |- |GTS,LC28,SDR4
2018-08-01 05:08:00|BLXB806201 |CNE100 |OS Reinstall |GTS,LC28,SDR4
l want to get the gap between dates range via SQL query lets see the situation:
l have table employees like : Every month the employee deserve payment
ID Name From_date To_date Paid_Amount`
1 ali 01/01/2002 31/01/2002 300
2 ali 01/02/2002 28/02/2002 300
3 ali 01/04/2002 30/04/2002 300
4 ali 01/05/2002 31/05/2002 300
5 ali 01/07/2002 31/07/2002 300
Now, we notice there are no payments in March and June
so, how by SQL query I can't get these months ??
Try this,
with mine(ID,Name,From_date,To_date,Paid_Amount) as
(
select 1,'ali','01/01/2002','31/01/2002',300 from dual union all
select 2,'ali','01/02/2002','28/02/2002',300 from dual union all
select 3,'ali','01/04/2002','30/04/2002',300 from dual union all
select 4,'ali','01/05/2002','31/05/2002',300 from dual union all
select 5,'ali','01/07/2002','31/07/2002',300 from dual
),
gtfirst (fromdt,todt) as (
select min(to_Date(from_Date,'dd/mm/yyyy')) fromdt,max(to_Date(to_Date,'dd/mm/yyyy')) todt from mine
),
dualtbl(first,last,fromdt,todt) as
(
select * from(select TRUNC(ADD_MONTHS(fromdt, rownum-1), 'MM') AS first,TRUNC(LAST_DAY(ADD_MONTHS(fromdt, rownum-1))) AS last,fromdt,todt from gtfirst connect by level <=12)
where first between fromdt and todt and last between fromdt and todt
)
select to_char(first,'month') no_payment_date from dualtbl where first not in (select to_Date(from_Date,'dd/mm/yyyy') from mine)
and first not in (select to_Date(to_date,'dd/mm/yyyy') from mine)
If you want to get the date difference between one payment date and the previous payment date and the ID field is sequential, then you may simply join back to the table and select the previous row.
SELECT X.From_date, Y.From_date, Y.From_date - X.From_date Difference
FROM Employees X
LEFT OUTER JOIN Employees Y ON Y.ID = X.ID - 1
If the ID field is not sequential, then you can use a similar method, but build a temporary table with a row index that you can use to join back to the previous payment.