Merge Date ranges between two tables in oracle or db2 - sql

I have two tables with tenure and rate data for an employee as below:
1) Tenure_T
E_Id Start_Dt End_Dt
1 1-Jan-2013 31-Dec-2013
1 1-Jan-2014 30-Jun-2014
1 1-Jul-2014 31-Jul-2014
1 1-Aug-2014 31-Dec-2014
2) Rate_T
E_Id Rt_Strt_Dt Rt_End_Dt Amt
1 1-Jun-2013 30-Nov-2013 100
1 1-Dec-2013 31-Jan-2014 200
1 1-Feb-2014 31-Mar-2014 300
1 1-Apr-2014 31-Jul-2014 400
1 1-Aug-2014 31-Jan-2015 500
I want to join these tables and need output like this as below:
3) Emp_T
E_Id Start_Dt End_Dt Amt
1 1-Jan-2013 31-May-2013 0
1 1-Jun-2013 30-Nov-2013 100
1 1-Dec-2013 31-Dec-2013 200
1 1-Jan-2014 31-Jan-2014 200
1 1-Feb-2014 31-Mar-2014 300
1 1-Apr-2014 31-Jul-2014 400
1 1-Aug-2014 31-Dec-2014 500
1 1-Jan-2014 31-Jan-2015 500
So wherever there is an overlap of dates start break it with appropriate Amt through sqls.Data volume is not much so multiple queries/multiple scans can be written/done but solution should be dynamic.

You can do this by first arranging the data based only on start dates and then re-aggregating using the lead() function. I think the following does what you want:
select t.e_id, t.start_dte,
lead(t.start_dte) over (partition by t.e_id order by t.start_dte) as end_dte,
max(amt)
from ((select e_id, start_dt, 0 as amt
from tenure_t
) union all
(select e_id, rt_start_dte, amt
from rate_t
)
) t
group by t.e_id, t.start_dte;

Related

Running assignment of values with break T-SQL

With the below table of data
Customer
Amount Billed
Amount Paid
Date
1
100
60
01/01/2000
1
100
40
01/02/2000
2
200
150
01/01/2000
2
200
30
01/02/2000
2
200
10
01/03/2000
2
200
15
01/04/2000
I would like to create the next two columns
Customer
Amount Billed
Amount Paid
Assigned
Remainder
Date
1
100
60
60
40
01/01/2000
1
100
40
40
0
01/02/2000
2
200
150
150
50
01/01/2000
2
200
30
30
20
01/02/2000
2
200
10
10
10
01/03/2000
2
200
15
10
-5
01/04/2000
The amount paid on each line should be removed from the amount billed and pushed onto the next line for the same customer. The process should continue until there are no more records or the remainder is < 0.
Is there a way of doing this without a cursor? Maybe a recursive CTE?
Thanks
As I mentioned in the comments, this is just a cumulative SUM:
WITH YourTable AS(
SELECT *
FROM (VALUES(1,100,60 ,CONVERT(date,'01/01/2000')),
(1,100,40 ,CONVERT(date,'01/02/2000')),
(2,200,150,CONVERT(date,' 01/01/2000')),
(2,200,30 ,CONVERT(date,'01/02/2000')),
(2,200,10 ,CONVERT(date,'01/03/2000')),
(2,200,15 ,CONVERT(date,'01/04/2000')))V(Customer,AmountBilled,AmountPaid,[Date]))
SELECT Customer,
AmountBilled,
AmountPaid,
AmountBilled - SUM(AmountPaid) OVER (PARTITION BY Customer ORDER BY [Date] ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Remainder,
[Date]
FROM YourTable
ORDER BY Customer,
[Date];
Note this returns -5 for the last row, not 5, as 200 - 205 = -5. If you want 5 wrap the whole expression in an absolute function.
You can achieve this using recursive CTE as well.
DECLARE #customer table (Customer int, AmountBilled int, AmountPaid int, PaidDate date)
insert into #customer
values
(1 ,100, 60 ,'01/01/2000')
,(1 ,100, 40 ,'01/02/2000')
,(2 ,200, 150 ,'01/01/2000')
,(2 ,200, 30 ,'01/02/2000')
,(2 ,200, 10 ,'01/03/2000')
,(2 ,200, 15 ,'01/04/2000');
;WITH CTE_CustomerRNK as
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY customer order by paiddate) AS RNK
from #customer),
CTE_Customer as
(
SELECT customer, AmountBilled, AmountPaid, (amountbilled-amountpaid) as remainder, paiddate ,RNK FROM CTE_CustomerRNK where rnk = 1
union all
SELECT r.customer, r.AmountBilled, r.AmountPaid, (c.remainder - r.AmountPaid) as remainder, r.PaidDate, r.rnk
FROM CTE_CustomerRNK as r
inner join CTE_Customer as c
on c.Customer = r.Customer
and r.rnk = c.rnk + 1
)
SELECT customer, AmountBilled, AmountPaid, remainder, paiddate
FROM CTE_Customer order by Customer
customer
AmountBilled
AmountPaid
remainder
paiddate
1
100
60
40
2000-01-01
1
100
40
0
2000-01-02
2
200
150
50
2000-01-01
2
200
30
20
2000-01-02
2
200
10
10
2000-01-03
2
200
15
-5
2000-01-04

Partition rows where dates are between the previous dates

I have the below table.
I want to identify overlapping intervals of start_date and end_date.
*edit I would like to remove the row that has the least amount of days between the start and end date where those rows overlap.
Example:
pgid 1 & pgid 2 have overlapping days. Remove the row that has the least amount of days between start_date and end_date.
Table A
id pgid Start_date End_date Days
1 1 8/4/2018 9/10/2018 37
1 2 9/8/2018 9/8/2018 0
1 3 10/29/2018 11/30/2018 32
1 4 12/1/2018 sysdate 123
Expected Results:
id Start_date End_date Days
1 8/4/2018 9/10/2018 37
1 10/29/2018 11/30/2018 32
1 12/1/2018 sysdate 123
I am thinking exists:
select t.*,
(case when exists (select 1
from t t2
where t2.start_date < t.start_date and
t2.end_date > t.end_date and
t2.id = t.id
)
then 2 else 1
end) as overlap_flag
from t;
Maybe lead and lag:
SELECT
CASE
WHEN END_DATE > LEAD (START_DATE) OVER (PARTITION BY id ORDER BY START_DATE) THEN 1
WHEN START_DATE < LAG (END_DATE) OVER (PARTITION BY id ORDER BY START_DATE) THEN 1
ELSE 0
END OVERLAP_FLAG
FROM A

3 or more consecutive entries in the last 15 days

I have the following data:
ID EMP_ID SALE_DATE
---------------------------------
1 777 5/28/2016
2 777 5/29/2016
3 777 5/30/2016
4 777 5/31/2016
5 888 5/26/2016
6 888 5/28/2016
7 888 5/29/2016
8 999 5/29/2016
9 999 5/30/2016
10 999 5/31/2016
i need to fetch data for emp_id having 3 or more days of consecutive sales in the last 15 days.
Output should be:
777
999
Following is the query:
SELECT TRUNC (sale_date), emp_id
FROM table1
WHERE sale_date >= SYSDATE - 14
GROUP BY TRUNC (sale_date), emp_id
HAVING COUNT (*) >= 3
But this returns consecutive transactions in the last three days only.
Note: This is oracle.
Assuming you have one row per day, you can use lead():
select distinct emp_id
from (select t1.*,
lead(sale_date, 1) over (partition by emp_id order by sale_date) as sd_1,
lead(sale_date, 2) over (partition by emp_id order by sale_date) as sd_2
from table1 t1
where sale_date >= trunc(sysdate) - 14
) t
where sd_1 = sale_date + 1 and
sd_2 = sale_date + 2;

Oracle SQL How to break down income by month based on a date range?

Trying to find an efficient way of achieving the results in table B below based on data from table A. Is there an efficient way (i.e. not resource hungry) of doing so assuming one has millions of such records in table A? Please note ID 1 has an end date of 12/31/2199 (not a typo), and we only list the income for each ID during the months of 09/2016 to 12/2016. Also note that ID 3 has two incomes in the month of 11/2016, with 600 representing the November income (since that's the income the ID had at the end of November 2016 month). As for IDs that started in say November 2016, their rows would be missing for Sept 16 and Oct 16 since they did not exist pre-November.
Table A:
ID INCOME EFFECTIVE_DATE END_DATE
1 700 07/01/2016 12/31/2199
2 500 08/20/2016 12/31/2017
3 600 11/11/2016 02/28/2017
3 100 09/01/2016 11/10/2016
4 400 11/21/2016 12/31/2016
Table B (Intended results):
ID INCOME MONTH
1 700 12/2016
1 700 11/2016
1 700 10/2016
1 700 09/2016
2 500 12/2016
2 500 11/2016
2 500 10/2016
2 500 09/2016
3 600 12/2016
3 600 11/2016
3 100 10/2016
3 100 09/2016
4 400 12/2016
4 400 11/2016
RESOLVED I used the answer provided by #mathguy below and it worked like a charm -- learned something new in this process: pivot and unpivot. Also thanks to #MTO (and everyone else) for taking the time to help.
Here is a solution that uses each row from the base table just once, and does not require joins, group by, etc. It uses the unpivot operation, available since Oracle 11.1, which is not an expensive operation.
with
table_a ( id, income, effective_date, end_date ) as (
select 1, 700, date '2016-07-01', date '2199-12-31' from dual union all
select 2, 500, date '2016-08-20', date '2017-12-31' from dual union all
select 3, 600, date '2016-11-11', date '2017-02-28' from dual union all
select 3, 100, date '2016-09-01', date '2016-11-10' from dual union all
select 4, 400, date '2016-11-21', date '2016-12-31' from dual
)
-- end of test data (not part of the solution): SQL query begins BELOW THIS LINE
select id, income, mth
from (
select id,
case when date '2016-09-30' between effective_date and end_date
then income end as sep16,
case when date '2016-10-31' between effective_date and end_date
then income end as oct16,
case when date '2016-11-30' between effective_date and end_date
then income end as nov16,
case when date '2016-12-31' between effective_date and end_date
then income end as dec16
from table_a
)
unpivot ( income for mth in ( sep16 as '09/2016', oct16 as '10/2016', nov16 as '11/2016',
dec16 as '12/2016' )
)
order by id, mth desc
;
Output:
ID INCOME MTH
-- ------ -------
1 700 12/2016
1 700 11/2016
1 700 10/2016
1 700 09/2016
2 500 12/2016
2 500 11/2016
2 500 10/2016
2 500 09/2016
3 600 12/2016
3 600 11/2016
3 100 10/2016
3 100 09/2016
4 400 12/2016
4 400 11/2016
14 rows selected.
A solution using a recursive sub-query factoring clause. This does not rely on hard-coding the bounds into the query as they can be passed as the bind variable :lower_bound and :upper_bound; in the example below they are set to DATE '2016-09-01' and DATE '2016-12-31' respectively.
Query:
WITH months ( id, income, month, end_dt ) AS (
SELECT id,
income,
CAST( TRUNC( GREATEST( a.effective_date, :lower_bound ), 'MM' ) AS DATE ),
LEAST( a.end_date, :upper_bound )
FROM TableA a
WHERE :lower_bound <= a.end_date
AND a.effective_date <= :upper_bound
UNION ALL
SELECT id,
income,
CAST( ADD_MONTHS( month, 1 ) AS DATE ),
end_dt
FROM months
WHERE ADD_MONTHS( month, 1 ) <= end_dt
)
SELECT id,
income,
LAST_DAY( month ) AS month
FROM months
WHERE LAST_DAY( month ) <= end_dt
ORDER BY id, month;
Output:
ID INCOME MONTH
-- ------ ----------
1 700 2016-09-30
1 700 2016-10-31
1 700 2016-11-30
1 700 2016-12-31
2 500 2016-09-30
2 500 2016-10-31
2 500 2016-11-30
2 500 2016-12-31
3 100 2016-09-30
3 100 2016-10-31
3 600 2016-11-30
3 600 2016-12-31
4 400 2016-11-30
4 400 2016-12-31

How to group, find min and count count of grouped records

I have table consisting of these fields:
id | date_from | date_to | price | status
----------------------------------------------------------
CK1 22-12-2012 29-12-2012 800 1
CK1 22-12-2012 29-12-2012 1200 1
CK2 24-12-2012 30-12-2012 1400 0
CK2 24-12-2012 30-12-2012 1800 1
CK2 24-12-2012 30-12-2012 2200 1
How do I create SQL select that groups results by ID, DATE_FROM, DATE_TO and picks lowest value from price where status == 1 and also to count amount of how many records where grouped?
So result would be
id | date_from | date_to | price | count
CK1 22-12-2012 29-12-2012 800 2
CK2 24-12-2012 30-12-2012 1800 2
And maybe, is there a way to find out how many of records were not grouped because of status == 0? This is not very important, I am just wondering whether there is a way how to find out number of uncounted records for group of records.
Your description doesn't match what you want the result to be.
This will match your description, i.e. give you the lowest price where status is 1, and count the number of records in the group:
select id, date_from, date_to, min(case status when 1 then price end) as price, count(*) as count
from TheTable
group by id, date_from, date_to
Result:
id | date_from | date_to | price | count
CK1 22-12-2012 29-12-2012 800 2
CK2 24-12-2012 30-12-2012 1800 3
This will give you the result that you asked for, i.e. filter out the records where status is 1, get you the lowest price, and get the number of records in the groups after filtering:
select id, date_from, date_to, min(price) as price, count(*) as count
from TheTable
where status = 1
group by id, date_from, date_to
Result:
id | date_from | date_to | price | count
CK1 22-12-2012 29-12-2012 800 2
CK2 24-12-2012 30-12-2012 1800 2
To get the number of records where the status is 0, you need to use the first method, where you don't filter out those records. If the status only can be 0 or 1, you can simply use sum(status) to get the number of records where the status is 1, and count(case status when 0 then 1 end) or sum(1 - status) to get the number of records where the status is 0.
Something like should do the trick:
select ID, DATE_FROM, DATE_TO, MIN(price), COUNT(*) from my_table where satus = 1 group by ID, DATE_FROM, DATE_TO
One remark: is it normal that an ID field can have the same value?
MSDN article on "group by" and onother on devguru
Maybe something like this:
test data
DECLARE #tbl TABLE
(
id VARCHAR(100),
date_from VARCHAR(100),
date_to VARCHAR(100),
price INT,
status INT
)
INSERT INTO #tbl
VALUES
('CK1','22-12-2012','29-12-2012',800,1),
('CK1','22-12-2012','29-12-2012',1200,1),
('CK2','22-12-2012','29-12-2012',1400,0),
('CK2','22-12-2012','29-12-2012',1800,1),
('CK2','22-12-2012','29-12-2012',2200,1)
Query
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY tbl.id ORDER BY tbl.price ASC) AS RowNbr,
SUM(status) OVER(PARTITION BY tbl.id) AS [count],
tbl.*
FROM
#tbl AS tbl
WHERE
status=1
)
SELECT
CTE.id,
CTE.date_from,
CTE.date_to,
CTE.price,
CTE.[count]
FROM
CTE
WHERE
CTE.RowNbr=1