Merge Missing Dates With The Actual Query - sql

I am working on a query where a database may have all dates with corresponding data or may not have. Data in the
table are as follows:
ID DATE
1 6/1/2021
1 6/2/2021
1 6/3/2021
1 6/4/2021
1 6/5/2021
1 6/8/2021
2 6/4/2021
2 6/5/2021
2 6/8/2021
Expected Output:
ID DATE
1 6/1/2021
1 6/2/2021
1 6/3/2021
1 6/4/2021
1 6/5/2021
1 6/6/2021
1 6/7/2021
1 6/8/2021
2 6/1/2021
2 6/2/2021
2 6/3/2021
2 6/4/2021
2 6/5/2021
2 6/6/2021
2 6/7/2021
2 6/8/2021
So I tried the following query with LEFT JOIN that'll return all required date:
WITH all_dates AS (SELECT TO_DATE('01-JUN-2021') + ROWNUM - 1 AS d FROM dual CONNECT BY ROWNUM <= ADD_MONTHS(TO_DATE('01-JUN-2021'), 12 ) - TO_DATE('01-JUN-2021'))
SELECT T.ID, T.DATE FROM all_dates LEFT JOIN TABLE_HERE t on T.DATE = all_dates.d WHERE all_dates.d <= '08-JUN-2021' AND T.ID ('1', '2') AND T.DATE >= '01-JUN-2021' AND T.DATE <= '08-JUN-2021' ORDER BY all_dates.d;
Unfortunately this only returns data with matching dates, not the missing one (Missing one will be merged with the actual). Is there anything that I require to do to make it work?

To me, it looks as the following query; read comments within code:
SQL> with
2 your (id, datum) as
3 -- your sample data
4 (select 1, date '2021-06-01' from dual union all
5 select 1, date '2021-06-02' from dual union all
6 select 1, date '2021-06-08' from dual union all
7 --
8 select 2, date '2021-06-08' from dual union all
9 select 2, date '2021-06-04' from dual union all
10 select 2, date '2021-06-08' from dual
11 ),
12 calendar as
13 -- you already know how to create a calendar; I'm using only 10 days for simplicity
14 (select date '2021-06-01' + level - 1 datum
15 from dual
16 connect by level <= 10
17 ),
18 ids (id) as
19 -- distinct ID values from your sample table (returns two rows; "1" and "2")
20 (select distinct id from your)
21 -- final query: cross join of calendar and distinct ID values
22 select c.datum, i.id
23 from calendar c cross join ids i
24 order by i.id, c.datum;
The result is
DATUM ID
-------- ----------
01.06.21 1
02.06.21 1
03.06.21 1
04.06.21 1
05.06.21 1
06.06.21 1
07.06.21 1
08.06.21 1
09.06.21 1
10.06.21 1
01.06.21 2
02.06.21 2
03.06.21 2
04.06.21 2
05.06.21 2
06.06.21 2
07.06.21 2
08.06.21 2
09.06.21 2
10.06.21 2
20 rows selected.
SQL>

Related

How to increase date by 1 month in Oracle sql

I want to add 1 month for loop by subscribed month to get each customers monthly payment date.
I have a table like this:
ID
REGISTER DATE
SUBSCRIBED MONTH
1
2022.01.01
3
2
2022.07.01
6
I want to have result like this:
ID
REGISTER DATE
SUBSCRIBED MONTH
MUST PAY DATE
1
2022.01.01
3
2022.01.01
1
2022.01.01
3
2022.02.01
1
2022.01.01
3
2022.03.01
2
2022.07.01
6
2022.07.01
2
2022.07.01
6
2022.08.01
2
2022.07.01
6
2022.09.01
2
2022.07.01
6
2022.10.01
2
2022.07.01
6
2022.11.01
2
2022.07.01
6
2022.12.01
Tried this, but returning duplicated.
SELECT ID, ADDMONTHS(REGISTER_DATE,LEVEL) FROM SUBLIST CONNECT BY LEVEL<=SUB_MONTH
Any help will be appreciated. Many thanks.
Here's one option:
Setting date format (you don't have to do that):
SQL> alter session set nls_date_format = 'yyyy.mm.dd';
Session altered.
Sample data:
SQL> with test (id, register_date, subscribed_month) as
2 (select 1, date '2022-01-01', 3 from dual union all
3 select 2, date '2022-07-01', 6 from dual
4 )
Query begins here:
5 select id, register_date, subscribed_month,
6 add_months(register_date, column_value - 1) must_pay_date
7 from test cross join table(cast(multiset(select level from dual
8 connect by level <= subscribed_month
9 ) as sys.odcinumberlist))
10 order by id, register_date, must_pay_date;
ID REGISTER_D SUBSCRIBED_MONTH MUST_PAY_D
---------- ---------- ---------------- ----------
1 2022.01.01 3 2022.01.01
1 2022.01.01 3 2022.02.01
1 2022.01.01 3 2022.03.01
2 2022.07.01 6 2022.07.01
2 2022.07.01 6 2022.08.01
2 2022.07.01 6 2022.09.01
2 2022.07.01 6 2022.10.01
2 2022.07.01 6 2022.11.01
2 2022.07.01 6 2022.12.01
9 rows selected.
SQL>
You can inner join your data to a subquery containing rows with numbers of months to be added:
WITH
tbl (ID, REGISTER_DATE, SUBSCRIBED_MONTH) AS
(
Select 1, DATE '2022-01-01', 3 From Dual Union All
Select 2, DATE '2022-07-01', 6 From Dual
)
SELECT t0.ID, t0.REGISTER_DATE, t0.SUBSCRIBED_MONTH,
Add_Months(t0.REGISTER_DATE, t1.MNTHS - 1) "PAY_DATE",
t1.MNTHS "PAYMENT_NO"
FROM tbl t0
INNER JOIN ( Select DISTINCT ID, LEVEL "MNTHS" From tbl Connect By LEVEL <= SUBSCRIBED_MONTH ) t1 ON(t1.ID = t0.ID)
ORDER BY t0.ID, t0.REGISTER_DATE, t1.MNTHS
Which with your sample data
WITH
tbl (ID, REGISTER_DATE, SUBSCRIBED_MONTH) AS
(
Select 1, DATE '2022-01-01', 3 From Dual Union All
Select 2, DATE '2022-07-01', 6 From Dual
)
... results as ...
ID REGISTER_DATE SUBSCRIBED_MONTH PAY_DATE PAYMENT_NO
---------- ------------- ---------------- --------- ----------
1 01-JAN-22 3 01-JAN-22 1
1 01-JAN-22 3 01-FEB-22 2
1 01-JAN-22 3 01-MAR-22 3
2 01-JUL-22 6 01-JUL-22 1
2 01-JUL-22 6 01-AUG-22 2
2 01-JUL-22 6 01-SEP-22 3
2 01-JUL-22 6 01-OCT-22 4
2 01-JUL-22 6 01-NOV-22 5
2 01-JUL-22 6 01-DEC-22 6

oracle moving where clause from join to outer join - not returning records properly

I have a query which as follows:
select c.* from cases c
left outer join (select case_id, case_status_id from case_status where case_id not in (SELECT case_id
FROM case_status
where (case_status_id = 16 and case_status_date < sysdate - 365))) cs ON cs.case_id = c.case_id
left outer join lkp_case_status lkp_cs
on lkp_cs.id = cs.case_status_id
where c.case_type = 'P'
and c.delete_date is null
The 2nd line used to be a join earlier but now I had to convert it to left outer join. What it does is it checks for case status and if the case status is 16 and the date is a year back don't show the record. When I change it to left outer join it picks up the record even when the dates are in 2019.
case
id name
1 AAA
2 BBB
3 CCC
4 DDD
case_status
1 16 01-NOV-19 03.42.37.420000000 PM
1 5 01-NOV-19 03.42.37.420000000 PM
2 1 18-NOV-19 12.36.11.268000000 PM
2 3 18-NOV-19 12.36.11.268000000 PM
3 5 18-NOV-21 12.36.11.268000000 PM
3 16 18-NOV-21 12.36.11.268000000 PM
The output should show as follows:
Result:
id name status
2 BBB 1,3
3 CCC 5,16
4 DDD
case id 1 has status 16 and dates back to 2019, case id 3 though has status 16 is within a year so should be picked up.
The query is really big but I am missing only this case status scenario hence posting a part of it.
Any suggestions or inputs, please.
How about not exists?
Sample data:
SQL> with
2 tcase (id, name) as
3 (select 1, 'AAA' from dual union all
4 select 2, 'BBB' from dual union all
5 select 3, 'CCC' from dual union all
6 select 4, 'DDD' from dual
7 ),
8 tcase_status (case_id, case_status_id, case_status_date) as
9 (select 1, 16, date '2019-11-01' from dual union all
10 select 1, 5, date '2019-11-01' from dual union all
11 select 2, 1, date '2019-11-18' from dual union all
12 select 2, 3, date '2019-11-18' from dual union all
13 select 3, 5, date '2021-11-18' from dual union all
14 select 3, 16, date '2021-11-18' from dual
15 )
Query:
16 select c.id,
17 c.name,
18 listagg(cs.case_status_id, ', ') within group (order by cs.case_status_id) status
19 from tcase c left join tcase_status cs on c.id = cs.case_id
20 where not exists (select null
21 from tcase_status c
22 where c.case_id = cs.case_id
23 and c.case_status_id = 16
24 and c.case_status_date < add_months(trunc(sysdate), -12)
25 )
26 group by c.id, c.name
27 order by c.id;
ID NAME STATUS
---------- ---- ----------
2 BBB 1, 3
3 CCC 5, 16
4 DDD
SQL>

oracle sql - numbering group of rows

i have the following table with different prices in every week and need a numbering like in the last column. consecutive rows with same prices should have the same number like in weeks 11/12 or 18/19. but on the other side weeks 2 and 16 have the same prices but are not consecutive so they should get a different number.
w | price | r1 | need
===========================
1 167,93 1 1
2 180 1 2
3 164,72 1 3
4 147,42 1 4
5 133,46 1 5
6 145,43 1 6
7 147 1 7
8 147,57 1 8
9 150,95 1 9
10 158,14 1 10
11 170 1 11
12 170 2 11
13 166,59 1 12
14 161,06 1 13
15 162,88 1 14
16 180 2 15
17 183,15 1 16
18 195 1 17
19 195 2 17
i have already experimented with the analytics functions (row_number, rank, dens_rank), but didn't found a solution for this problem so far.
(oracle sql 10,11)
does anyone have a hint? thanks.
Simulating your table first:
SQL> create table mytable (w,price,r1)
2 as
3 select 1 , 167.93, 1 from dual union all
4 select 2 , 180 , 1 from dual union all
5 select 3 , 164.72, 1 from dual union all
6 select 4 , 147.42, 1 from dual union all
7 select 5 , 133.46, 1 from dual union all
8 select 6 , 145.43, 1 from dual union all
9 select 7 , 147 , 1 from dual union all
10 select 8 , 147.57, 1 from dual union all
11 select 9 , 150.95, 1 from dual union all
12 select 10, 158.14, 1 from dual union all
13 select 11, 170 , 1 from dual union all
14 select 12, 170 , 2 from dual union all
15 select 13, 166.59, 1 from dual union all
16 select 14, 161.06, 1 from dual union all
17 select 15, 162.88, 1 from dual union all
18 select 16, 180 , 2 from dual union all
19 select 17, 183.15, 1 from dual union all
20 select 18, 195 , 1 from dual union all
21 select 19, 195 , 2 from dual
22 /
Table created.
Your need column is calculated in two parts: first compute a delta column which denotes whether the previous price-column differs from the current rows price column. If you have that delta column, the second part is easy by computing the sum of those deltas.
SQL> with x as
2 ( select w
3 , price
4 , r1
5 , case lag(price,1,-1) over (order by w)
6 when price then 0
7 else 1
8 end delta
9 from mytable
10 )
11 select w
12 , price
13 , r1
14 , sum(delta) over (order by w) need
15 from x
16 /
W PRICE R1 NEED
---------- ---------- ---------- ----------
1 167.93 1 1
2 180 1 2
3 164.72 1 3
4 147.42 1 4
5 133.46 1 5
6 145.43 1 6
7 147 1 7
8 147.57 1 8
9 150.95 1 9
10 158.14 1 10
11 170 1 11
12 170 2 11
13 166.59 1 12
14 161.06 1 13
15 162.88 1 14
16 180 2 15
17 183.15 1 16
18 195 1 17
19 195 2 17
19 rows selected.
You can nest your analytic functions using inline views, so you first group the consecutive weeks with same prices and then dense_rank using those groups:
select w
, price
, r1
, dense_rank() over (
order by first_w_same_price
) drank
from (
select w
, price
, r1
, last_value(w_start_same_price) ignore nulls over (
order by w
rows between unbounded preceding and current row
) first_w_same_price
from (
select w
, price
, r1
, case lag(price) over (order by w)
when price then null
else w
end w_start_same_price
from your_table
)
)
order by w
The innermost inline view with LAG function lets the starting week of every consecutive group get it's own week number, but every consecutive week with same price gets null (weeks 12 and 19 in your data.)
The middle inline view with LAST_VALUE function then use the IGNORE NULLS feature to give the consecutive weeks the same value as the first week within each group. So week 11 and 12 both gets 11 in first_w_same_price and week 18 and 19 both gets 18 in first_w_same_price.
And finally the outer query use DENSE_RANK to give the desired result.
For each row you should count previous rows where (w-1) row price isn't the same as (w) price:
select T1.*,
(SELECT count(*)
FROM T T2
JOIN T T3 ON T2.w-1=T3.w
WHERE T2.Price<>T3.Price
AND T2.W<=T1.W)+1 rn
from t T1
SQLFiddle demo
Try this:
with tt as (
select t.*, decode(lag(price) over(order by w) - price, 0, 1, 0) diff
from t
)
select w
, price
, r1
, row_number() over (order by w) - sum(diff) over(order by w rows between UNBOUNDED PRECEDING and current row) need
from tt
SELECT w, price, r1,
ROW_NUMBER () OVER (PARTITION BY price ORDER BY price) row_column
FROM TABLE

How do I select records with max from id column if two of three other fields are identical

I have a table that stores costs for consumables.
consumable_cost_id consumable_type_id from_date cost
1 1 01/01/2000 £10.95
2 2 01/01/2000 £5.95
3 3 01/01/2000 £1.98
24 3 01/11/2013 £2.98
27 3 22/11/2013 £3.98
33 3 22/11/2013 £4.98
34 3 22/11/2013 £5.98
35 3 22/11/2013 £6.98
If the same consumable is updated more than once on the same day I would like to select only the row where the consumable_cost_id is biggest on that day. Desired output would be:
consumable_cost_id consumable_type_id from_date cost
1 1 01/01/2000 £10.95
2 2 01/01/2000 £5.95
3 3 01/01/2000 £1.98
24 3 01/11/2013 £2.98
35 3 22/11/2013 £6.98
Edit:
Here is my attempt (adapted from another post I found on here):
SELECT cc.*
FROM
consumable_costs cc
INNER JOIN
(
SELECT
from_date,
MAX(consumable_cost_id) AS MaxCcId
FROM consumable_costs
GROUP BY from_date
) groupedcc
ON cc.from_date = groupedcc.from_date
AND cc.consumable_cost_id = groupedcc.MaxCcId
You were very close. This seems to work for me:
SELECT cc.*
FROM
consumable_cost AS cc
INNER JOIN
(
SELECT
Max(consumable_cost_id) AS max_id,
consumable_type_id,
from_date
FROM consumable_cost
GROUP BY consumable_type_id, from_date
) AS m
ON cc.consumable_cost_id = m.max_id
SELECT * FROM consumable_cost
GROUP by consumable_type_id, from_date
ORDER BY cost DESC;
Assuming consumable_cost_id is unique.
SELECT * FROM T t1
WHERE EXISTS(
SELECT t2.consumable_type_id, t2.from_date FROM T t2
GROUP by t2.consumable_type_id, t2.from_date
HAVING MAX(t2.consumable_cost_id) = t1.consumable_cost_id);
Because of comment that this was returning an incorrect result, I created a test-query for Oracle that proves that this query works. As I said, it's for Oracle, but there is really no reason why this should not work in MS Access. The only Oracle specific I used here is the FROM DUAL to generate the virtual data.
WITH T AS
(
SELECT 1 AS consumable_cost_id,1 AS consumable_type_id, TO_DATE('01/01/2000','DD/MM/YYYY') AS FROM_DATE, '£10.95' AS COST FROM DUAL
UNION ALL
SELECT 2,2,TO_DATE('01/01/2000','DD/MM/YYYY'),'£5.95' FROM DUAL
UNION ALL
SELECT 3,3,TO_DATE('01/01/2000','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 24,3,TO_DATE('01/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 27,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 33,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 34,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 35,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
)
SELECT * FROM T t1
WHERE EXISTS(
SELECT t2.consumable_type_id, t2.from_date FROM T t2
GROUP by t2.consumable_type_id, t2.from_date
HAVING MAX(t2.consumable_cost_id) = t1.consumable_cost_id);
Result:
1 1 01-JAN-00 £10.95
2 2 01-JAN-00 £5.95
3 3 01-JAN-00 £1.98
24 3 01-NOV-13 £1.98
35 3 22-NOV-13 £1.98

SQL pad query result for missing groups

Assume the following table:
TableA:
ID GroupName SomeValue
1 C 1
2 C 1
2 B 1
2 A 1
I need to construct a query that selects the following result:
ID GroupName SomeValue
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
The GroupName is actually derived from TableA column's CASE expression and can take only 3 values: A, B, C.
Are the analytic functions the way to go?
EDIT
Sorry, for not mentioning it, but the ID could consist of multiple columns. Consider this example:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 2 C 1
2 2 C 1
2 2 B 1
2 2 A 1
I need to pad SomeValue with 0 for each unique combination ID1+ID2. So the result should be like this:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
EDIT2
Seems like solution, proposed by #Laurence should work even for multiple-column 'ID'. I couldn't rewrite the query proposed by #Nicholas Krasnov to conform to this requirement. But could somebody compare these solutions performance-wise? Will the analytic function work faster than 'cross join + left outer join'?
To fill in gaps, you could write a similar query using partition by clause of outer join:
SQL> with t1(ID,GroupName,SomeValue) as
2 (
3 select 1, 'C', 1 from dual union all
4 select 2, 'C', 1 from dual union all
5 select 2, 'B', 1 from dual union all
6 select 2, 'A', 1 from dual
7 ),
8 groups(group_name) as(
9 select 'A' from dual union all
10 select 'B' from dual union all
11 select 'C' from dual
12 )
13 select t1.ID
14 , g.group_name
15 , nvl(SomeValue, 0) SomeValue
16 from t1
17 partition by (t1.Id)
18 right outer join groups g
19 on (t1.GroupName = g.group_name)
20 order by t1.ID asc, g.group_name desc
21 ;
ID GROUP_NAME SOMEVALUE
---------- ---------- ----------
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
6 rows selected
UPDATE: Response to the comment.
Specify ID2 column in the partition by clause as well:
SQL> with t1(ID1, ID2, GroupName,SomeValue) as
2 (
3 select 1, 1, 'C', 1 from dual union all
4 select 1, 2, 'C', 1 from dual union all
5 select 2, 2, 'C', 1 from dual union all
6 select 2, 2, 'B', 1 from dual union all
7 select 2, 2, 'A', 1 from dual
8 ),
9 groups(group_name) as(
10 select 'A' from dual union all
11 select 'B' from dual union all
12 select 'C' from dual
13 )
14 select t1.ID1
15 , t1.ID2
16 , g.group_name
17 , nvl(SomeValue, 0) SomeValue
18 from t1
19 partition by (t1.Id1, t1.Id2)
20 right outer join groups g
21 on (t1.GroupName = g.group_name)
22 order by t1.ID1, t1.ID2 asc , g.group_name desc
23 ;
ID1 ID2 GROUP_NAME SOMEVALUE
---------- ---------- ---------- ----------
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
9 rows selected
Select
i.Id1,
i.Id2,
g.GroupName,
Coalesce(a.SomeValue, 0) As SomeValue
From
(select distinct ID1, ID2 from TableA) as i
cross join
(select distinct GroupName from TableA) as g
left outer join
tableA a
on i.ID = a.ID and g.GroupName = a.GroupName
Order By
1,
2,
3 Desc