Fill in gaps in cumulative query - oracle

I have the following query to calculate a cumulative sum:
SELECT to_char(INVC_DT, 'MON-YYYY') AS MONTH,
SUM(INVC_AMT),
SUM(SUM(INVC_AMT)) OVER (ORDER BY MIN(INVC_DT))
FROM T_INVC_INFO I INNER JOIN T_TASK_INFO T ON I.TASK_ID = T.TASK_ID
WHERE T.CNTRCT_ID = #session.user.cntrct_id#
GROUP BY to_char(INVC_DT, 'MON-YYYY')
ORDER BY MONTH DESC;
The problem I have is that I need to fill in the gaps in time. For example, if I have data for JAN, FEB, and APR, I need another row for MAR with value 0.

You may introduce a calendar table into your query, which will represent every month/year which you want to appear in your output. Assuming you wanted to cover all of 2017 to 2019, you might try:
WITH years AS (
SELECT '2017' AS year FROM dual UNION ALL
SELECT '2018' FROM dual UNION ALL
SELECT '2019' FROM dual
),
months AS (
SELECT 'JAN' AS month FROM dual UNION ALL
SELECT 'FEB' FROM dual UNION ALL
...
SELECT 'DEC' FROM dual
)
SELECT
m.month || '-' || y.year AS MONTH,
SUM(INVC_AMT),
SUM(SUM(INVC_AMT)) OVER (ORDER BY MIN(INVC_DT))
FROM years y
CROSS JOIN months m
LEFT JOIN T_INVC_INFO i
ON m.month || '-' || y.year = TO_CHAR(i.INVC_DT, 'MON-YYYY')
LEFT JOIN T_TASK_INFO t
ON i.TASK_ID = t.TASK_ID AND
t.CNTRCT_ID = #session.user.cntrct_id#
WHERE
TO_DATE(m.month || '-' || y.year, 'MON-YYYY') BETWEEN
(SELECT MIN(INVC_DT) FROM T_INVC_INFO) AND
(SELECT MAX(INVC_DT) FROM T_INVC_INFO)
GROUP BY
m.month || '-' || y.year
ORDER BY
MONTH DESC;

Related

how to get last businessday of last month in oralce

I have data like this my table
2020-01-01 H
2020-01-02 B
2020-01-03 B
2020-01-04 B
.
2020-01-29 B
2020-01-30 H
2020-01-31 H
2020-01-02 H
2020-02-02 H
2020-02-03 B
2020-02-04 B
2020-02-05 B
.
now my problem is in the current month i need to check third business day i.e in this case 2020-02-05 i need to get last business day of last month. i.e.2020-01-29
By adding 2 columns:
row_number() over(partition by trunc(date_value,'MM'), day_type order by date_value) as rn_month_asc,
row_number() over(partition by trunc(date_value,'MM'), day_type order by date_value desc) as rn_month_desc
in a month the 3rd business day will have rn_month_asc=3 and day_type ='B' and the latest business day will have rn_month_desc=1 and day_type ='B', and easy to query other situations if you need to.
in the current month I need to check third business day
From Oracle 12, you can use:
SELECT date_value
FROM table_name
WHERE TRUNC(SYSDATE, 'MM') <= date_value
AND date_value < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1)
AND day_type = 'B'
ORDER BY date_value ASC
OFFSET 2 ROWS
FETCH NEXT ROW ONLY;
Which, for the sample data:
CREATE TABLE table_name (date_value, day_type) AS
SELECT DATE '2020-01-01', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-02', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-03', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-04', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-05', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-28', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-29', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-30', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-31', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-02', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-02-02', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-02-03', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-02-04', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-02-05', 'B' FROM DUAL;
If the current month was 2020-01 then the output is:
DATE_VALUE
04-JAN-20
I need to get last business day of last month
SELECT date_value
FROM table_name
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1) <= date_value
AND date_value < TRUNC(SYSDATE, 'MM')
AND day_type = 'B'
ORDER BY date_value DESC
FETCH FIRST ROW ONLY;
If the current month is 2020-02 then the output is:
DATE_VALUE
29-JAN-20
fiddle

Concatenated Select List value returns 'not a valid month' when applied TO_DATE

I have month and year values from select list, concatenated them in a string. If TO_DATE is applied on the string, that returns 'not a valid month' error.
Below is my code snippet,
V_DATE_FROM_STR_TEST := TRIM('01-'||TRIM(TO_CHAR(:P3_FROM_MONTH))||'-'||TRIM(TO_CHAR(:P3_YEAR)));
V_DATE_TO_STR_TEST := TRIM('01-'||TRIM(TO_CHAR(:P3_TO_MONTH))||'-'||TRIM(TO_CHAR(:P3_YEAR)));
Error 'not a valid month' occurs on this line,
TRUNC(TO_DATE(V_DATE_FROM_STR_TEST, 'DD-MON-YYYY'))
I have tried printing by HTP.PRN(V_DATE_FROM_STR_TEST) that shows date 01-JAN-2020.
Adding LOVs
Month LOV
select to_char(add_months(trunc(sysdate, 'yyyy'), level - 1), 'MONTH') d,
to_char(add_months(trunc(sysdate, 'yyyy'), level - 1), 'MON') r
from dual
connect by level <= 12
Year LOV
select YEAR d, YEAR r
from (select to_number(to_char(sysdate,'yyyy')) -8 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -7 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -6 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -5 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -4 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -3 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -2 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) -1 YEAR
from dual
UNION
select to_number(to_char(sysdate,'yyyy')) YEAR
from dual
) order by year desc
I am guessing there are some unwanted character is added in the date string from select list value, how can I remove that?
If you switch to digits, things might improve.
Month LoV: display words, but return numbers:
select to_char(add_months(trunc(sysdate, 'yyyy'), level - 1), 'MONTH') d,
level r
from dual
connect by level <= 12;
Then use this for the resulting string:
to_date(lpad(:P3_FROM_MONTH, 2, '0') || :P3_YEAR, 'mmyyyy')
Also, all those TRIMs look unnecessary, but OK - I understand - you tried to fix the problem.
While not directly referenced you can simplify your "Year LOV" query.
select yr d, yr r
from ( select extract(year from sysdate)-level +1 yr
from dual
connect by level <= 9
)
order by yr desc;

How to convert this code from oracle to redshift?

I am trying to implement the same in redshift and i am finding it little difficult to do that. Since redshift is in top of postgresql engine, if any one can do it in postgresql it would be really helpfull. Basically the code gets the count for previous two month at column level. If there is no count for exact previous month then it gives 0.
This is my code:
with abc(dateval,cnt) as(
select 201908, 100 from dual union
select 201907, 200 from dual union
select 201906, 300 from dual union
select 201904, 600 from dual)
select dateval, cnt,
last_value(cnt) over (order by dateval
range between interval '1' month preceding
and interval '1' month preceding ) m1,
last_value(cnt) over (order by dateval
range between interval '2' month preceding
and interval '2' month preceding ) m2
from (select to_date(dateval, 'yyyymm') dateval, cnt from abc)
I get error in over by clause. I tried to give cast('1 month' as interval) but still its failing. Can someone please help me with this windows function.
expected output:
Regards
This is how I would do it. In Redshift there's no easy way to generate sequences, do I select row_number() from an arbitrary table to create a sequence:
with abc(dateval,cnt) as(
select 201908, 100 union
select 201907, 200 union
select 201906, 300 union
select 201904, 600),
cal(date) as (
select
add_months(
'20190101'::date,
row_number() over () - 1
) as date
from <an arbitrary table to generate a sequence of rows> limit 10
),
with_lag as (
select
dateval,
cnt,
lag(cnt, 1) over (order by date) as m1,
lag(cnt, 2) over (order by date) as m2
from abc right join cal on to_date(dateval, 'YYYYMM') = date
)
select * from with_lag
where dateval is not null
order by dateval

Subqueries in 2 tables

I have 2 tables: machine and work.
Table:machine
machine_no downtime location
A1-100-01 2 A1
A1-100 1.5 A1
A1-200 3 A1
CC3-100-01 0.5 CC3
CC3-100 1.5 CC3
Table:work
machine_no date
A1-100-01 2/4/14
A1-100 2/14/14
A1-200 2/6/14
CC3-100-01 3/15/14
CC3-100 3/2/14
I want the output to be like this:
machine_no total_downtime month
A1-100 3.5 (total of A1-100, A1-100-01) 02
A1-200 3 02
When location A1 is selected.
SELECT machine_no, SUM(downtime) as total_downtime
FROM (
SELECT
SUBSTR(machine_no, 1,
CASE WHEN INSTR(machine_no, '-', 1, 2) = 0
THEN LENGTH(machine_no)
ELSE INSTR(machine_no, '-', 1, 2)-1
END) as machine_no,
downtime
FROM machine
WHERE location='A1'
) InnerQuery
GROUP BY machine_no
How do I join table WORK and display the month? I'm using Oracle.
Thank you.
The month column's semantics in your expected query result is unclear. Assuming that it is another aggregation "key", then your query would be
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime) as total_downtime,
to_char(W.date, 'mm') as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+'),
to_char(W.date, 'mm')
;
Assuming it is a (somehow) aggregated value, let's say via min() function, then your query would be
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime) as total_downtime,
min(to_char(W.date, 'mm')) as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+')
;
Both of these, in addition, assume that the (total of A1-100, A1-100-01) in your expected result is just your note, not really a part of the result. But if not, then your query could be something along the lines of
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime)||
case when count(1) > 1 then
' (total of '||
listagg(M.machine_no)
within group (order by M.machine_no)||
')'
end
as total_downtime,
to_char(W.date, 'mm') as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+'),
to_char(W.date, 'mm')
;
And even this works because of a few more assumptions about the (unsaid) properties of your machine and work tables, so I'm going to stop my answer here. :-)
User regular expression to take sub string of machine_no and to_char to get the month
WITH machine(machine_no, downtime, location) as (
select 'A1-100-01', 2, 'A1' from dual union all
select 'A1-100', 1.5, 'A1' from dual union all
select 'A1-200', 3, 'A1' from dual union all
select 'CC3-100-01', 0.5, 'CC3' from dual union all
select 'CC3-100', 1.5, 'CC3' from dual),
work(machine_no, ddate) as (
select 'A1-100-01', to_date('2/4/14', 'mm/dd/yyyy') from dual union all
select 'A1-100', to_date('2/14/14', 'mm/dd/yyyy') from dual union all
select 'A1-200', to_date('2/6/14', 'mm/dd/yyyy') from dual union all
select 'CC3-100-01', to_date('3/15/14', 'mm/dd/yyyy') from dual union all
select 'CC3-100', to_date('3/2/14', 'mm/dd/yyyy') from dual)
--End of data preparation
SELECT regexp_substr(m.machine_no, '^\w+-\w+') AS machine_no,
sum(m.downtime) downtime_sum,
to_char(w.ddate , 'MM') MONTH
FROM WORK w
JOIN machine m ON m.machine_no = w.machine_no
WHERE m.location = 'A1'
GROUP BY regexp_substr(m.machine_no, '^\w+-\w+'),
to_char(w.ddate , 'MM');
Output:
| MACHINE_NO | DOWNTIME_SUM | MONTH |
|------------|--------------|-------|
| A1-200 | 3 | 02 |
| A1-100 | 3.5 | 02 |

self join with max value

I am have a table with 500k transactions. I want to fetch the last balance for a particular date. So I have have returned a query like below.
SELECT curr_balance
FROM transaction_details
WHERE acct_num = '10'
AND is_deleted = 'N'
AND ( value_date, srl_num ) IN(
SELECT MAX( value_date ), MAX( srl_num )
FROM transaction_details
WHERE TO_DATE( value_date, 'dd/mm/yyyy' )
<= TO_DATE( ADD_MONTHS( '05-APR-2012', 1 ), 'dd/mm/yyyy' )
AND acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y' )
AND ver_status = 'Y'
This has to be executed for incrementing of 12 months to find the last balance for each particular month. But this query is having more cpu cost, 12 times it is taking huge time. How to remodify the above query to get the results in faster way. Whether this can be broken into two part in PL/SQL to achieve the performance. ?
Try:
select * from(
SELECT value_date, srl_num, curr_balance
FROM transaction_details
WHERE acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y'
row_number() over (partition by trunc(value_date - interval '5' day,'MM')
order by srl_num desc
) as rnk
)
where rnk = 1;
You'll get a report with the ballance on last srl_num on each month in your table.
The benefit is that your approach scans the table 24 times for 12 months report and my approach scans the table once.
The analytic function gets the rank of record in current month(partition by clause) ordering the rows in the month after srl_num.
You don't have to query your table twice. Try using analytic functions
SELECT t.curr_balance
-- , any other column you want as long it is in the subselect.
FROM (
SELECT
trans.curr_balance
, trans.value_date
-- any other column you want
, trans.srl_num
, MAX(trans.srl_num) OVER(PARTITION BY trans.value_date, trans.srl_num) max_srl_num
, MAX(trans.value_date) OVER(PARTITION BY trans.value_date, trans.srl_num) max_date
FROM transaction_details trans
WHERE TO_DATE( value_date, 'dd/mm/yyyy' ) <= TO_DATE( ADD_MONTHS( '01-APR-2012', 1 ), 'dd/mm/yyyy' )
AND acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y'
) t
WHERE t.max_date = t.value_date
AND t.max_srl_num = t.srl_num
A couple of thoughts.
Why do you have TO_DATE( value_date...? Isn't your data type DATE? this might be breaking your index if you have one in that column.
Note that (this is a wild guess) if your srl_num is not the highest for the latest date, you will have incorrect results and might not return any rows.

Resources