I've got 2 columns in a table, Financial_Year and Financial_Quarter. An example of the data is as follows:
Financial_Year Financial_Quarter
2018/2019 2
I want to produce a third column called 'Year_month' which is the last month of the quarter within each financial year, so in the above example I want the year_month column to read 201809.
Has anyone got any hints how to do this in Oracle SQL?
Assuming the FINANCIAL_YEAR column is a string (two numbers separated by slash) and the financial year 2018/2019 begins on 1 April 2018 and ends on 31 March 2019, you could do something like this:
with
test_data (financial_year, financial_quarter) as (
select '2018/2019', 1 from dual union all
select '2018/2019', 2 from dual union all
select '2018/2019', 3 from dual union all
select '2018/2019', 4 from dual
)
select financial_year, financial_quarter,
to_char(add_months(to_date(substr(financial_year, 1, 4) || '03', 'yyyymm'),
3 * financial_quarter), 'yyyymm') as year_month
from test_data
;
FINANCIAL_YEAR FINANCIAL_QUARTER YEAR_MONTH
-------------- ----------------- ----------
2018/2019 1 201806
2018/2019 2 201809
2018/2019 3 201812
2018/2019 4 201903
Also, just for fun, here is a different solution that doesn't use any date computations - it's all string based.
select financial_year, financial_quarter,
substr(financial_year, decode(financial_quarter, 4, 6, 1), 4) ||
decode(financial_quarter, 1, '06', 2, '09', 3, '12', 4, '03') as year_month
from test_data
;
I guess there are many ways to skin this cat, here is one
SELECT CASE WHEN financial_quarter = '4' THEN SUBSTR(financial_year, 6, 4) || '03'
ELSE SUBSTR(financial_year, 1, 4) || LPAD((financial_quarter + 1) * 3, 2, '0')
END
FROM some_table
Of course '4' needs to be replaced with 4 if financial_quarter is numeric
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 months ago.
Improve this question
With data like this
-- Sample data:
CREATE TABLE IN_OUT_TABLE (DATE_OF Date, INPUT_CASES Number(3), OUTPUT_CASES Number(3));
--
Insert Into IN_OUT_TABLE Values(To_Date('01-AUG-2019', 'dd-MON-yyyy'), 2, 1);
Insert Into IN_OUT_TABLE Values(To_Date('01-SEP-2019', 'dd-MON-yyyy'), 3, 1);
Insert Into IN_OUT_TABLE Values(To_Date('01-MAY-2020', 'dd-MON-yyyy'), 3, 3);
Insert Into IN_OUT_TABLE Values(To_Date('01-MAR-2020', 'dd-MON-yyyy'), 7, 2);
Insert Into IN_OUT_TABLE Values(To_Date('01-APR-2021', 'dd-MON-yyyy'), 3, 1);
Insert Into IN_OUT_TABLE Values(To_Date('01-JUL-2021', 'dd-MON-yyyy'), 4, 2);
Need to select the sum of the diferencies between input and output cases
(INPUT_CASES - OUTPUT_CASES) grouped by year of the DATE_OF.
The result should be shown as PERIOD for a particular year. Beside, there should be a CUMULATIVE column showing running sum of PERIOD values.
So,
PERIOD = INPUT_CASES - OUTPUT_CASES - this should be summed by year
CUMULATIVE = running sum of PERIOD - year by year (ordered by year)
Having that in mind is it posible to construct a select command something like this
SELECT DATE, PERIOD, SUM(PERIOD) OVER(ORDER BY DATE)
FROM ( SELECT EXTRACT(YEAR FROM DATE) as DATE, SUM(PERIOD) as PERIOD
FROM IN_OUT_TABLE
GROUP BY EXTRACT(YEAR FROM DATE))
... ... and get the expected result?
Expected result would be:
-- YEAR PERIOD CUMULATIVE
-- ---- ----------- -------------
-- 2019 3 3
-- 2020 5 8
-- 2021 4 12
Thanks, ...
Is this maybe the answer that you are looking for?...
WITH
tbl AS
(
Select To_Date('01-AUG-2019', 'dd-MON-yyyy') "DT", 2 "INP", 1 "OUTP" From DUAL UNION ALL
Select To_Date('01-SEP-2019', 'dd-MON-yyyy') "DT", 3 "INP", 1 "OUTP" From DUAL UNION ALL
Select To_Date('01-MAY-2020', 'dd-MON-yyyy') "DT", 3 "INP", 3 "OUTP" From DUAL UNION ALL
Select To_Date('01-MAR-2020', 'dd-MON-yyyy') "DT", 7 "INP", 2 "OUTP" From DUAL UNION ALL
Select To_Date('01-APR-2021', 'dd-MON-yyyy') "DT", 3 "INP", 1 "OUTP" From DUAL UNION ALL
Select To_Date('01-JUL-2021', 'dd-MON-yyyy') "DT", 4 "INP", 2 "OUTP" From DUAL
)
SELECT
YR,
PERIOD,
Sum(PERIOD) OVER(Order By YR ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "CUMUL"
FROM
(
SELECT DISTINCT
EXTRACT(YEAR FROM DT) "YR",
Sum(INP- OUTP) OVER(Partition By EXTRACT(YEAR FROM DT) Order By EXTRACT(YEAR FROM DT)) "PERIOD"
FROM
tbl
ORDER BY
EXTRACT(YEAR FROM DT)
)
--
-- R e s u l t
--
-- YR PERIOD CUMUL
-- ---------- ---------- ----------
-- 2019 3 3
-- 2020 5 8
-- 2021 4 12
Something like this:
with
tbl (date_, input, output) as (
select date '2019-08-01', 2, 1 from dual union all
select date '2019-09-01', 3, 1 from dual union all
select date '2020-05-01', 3, 3 from dual union all
select date '2020-03-01', 7, 2 from dual union all
select date '2021-04-01', 3, 1 from dual union all
select date '2021-07-01', 4, 2 from dual
)
select extract(year from trunc(date_, 'year')) as year_,
nvl(sum(input), 0) - nvl(sum(output), 0) as period,
sum(nvl(sum(input), 0) - nvl(sum(output), 0))
over (order by trunc(date_, 'year')) as cumulative
from tbl
group by trunc(date_, 'year')
order by year_
;
YEAR_ PERIOD CUMULATIVE
---------- ---------- ----------
2019 3 3
2020 5 8
2021 4 12
The with clause is for testing purposes only, it is not part of the query. To test the query on your actual data, remove the with clause, and use your actual table and column names (I used tbl for the table, and date_, input and output for the columns - note that date is a reserved keyword, which should not be used as column name).
In the query I truncate each date to the beginning of the year, and I group by the result. If there is an index on the date_ column, this may be more efficient than extracting the year with the extract function. (Worth trying both ways on your actual data.) In any case, you should need only one pass over the base data.
I wrapped the sums of input and output within nvl(..., 0) - that way if in a year all inputs (or all outputs) are null, the sum will be treated as 0, instead of messing up the difference (the period result).
I'm trying to return the total number of sales for every month, every quarter, for the year 2016. I want to display annual sales on the first month row, and not on the other rows. Plus, I want to display the quarter sales on the first month of each quarter, and not on the others.
To further explain this, here's what I want to achieve:
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
1 2183 5917 12505
2 1712 - -
3 1972 - -
4 2230 6588 -
5 2250 - -
6 2108 - -
Here's my SQL query so far:
SELECT
Time.month,
SUM(Sales.sales) AS MONTH_SALES, -- display monthly sales.
CASE
WHEN MOD(Time.month, 3) = 1 THEN ( -- first month of quarter
SELECT
SUM(Sales.sales)
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.quarter
FETCH FIRST 1 ROW ONLY
)
END AS QUARTER_SALES,
CASE
WHEN Time.month = 1 THEN ( -- display annual sales.
SELECT
SUM(Sales.sales)
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.year
)
END AS YEAR_SALES
FROM
Sales,
Time
WHERE
Sales.Time_id = Time.Time_id
AND Time.year = 2016
GROUP BY
Time.month
ORDER BY
Time.month
I'm almost getting the desired output, but I'm getting the same duplicated 6588 value in quarter sales for the first and fourth month (because I'm fetching the first row that comes from first quarter).
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
1 2183 6588 12505
2 1712 - -
3 1972 - -
4 2230 6588 -
5 2250 - -
6 2108 - -
I even tried to put WHERE Time.quarter = ((Time.month * 4) / 12) but the month value from the outer query doesn't get passed in the subquery.
Unfortunately I don't have enough experience with CASE WHEN expressions to know how to pass the month row. Any tips would be awesome.
How about this?
Sample data:
SQL> with
2 time (time_id, month, quarter, year) as
3 (select 1, 1, 1, 2016 from dual union all
4 select 2, 2, 1, 2016 from dual union all
5 select 3, 3, 1, 2016 from dual union all
6 select 4, 5, 2, 2016 from dual union all
7 select 5, 7, 3, 2016 from dual union all
8 select 6, 8, 3, 2016 from dual union all
9 select 7, 9, 3, 2016 from dual union all
10 select 8, 10, 4, 2016 from dual union all
11 select 9, 11, 4, 2016 from dual
12 ),
13 sales (time_id, sales) as
14 (select 1, 100 from dual union all
15 select 1, 100 from dual union all
16 select 2, 200 from dual union all
17 select 3, 300 from dual union all
18 select 4, 400 from dual union all
19 select 5, 500 from dual union all
20 select 6, 600 from dual union all
21 select 7, 700 from dual union all
22 select 8, 800 from dual union all
23 select 9, 900 from dual
24 ),
Query begins here; it uses sum aggregate in its analytic form; partition by clause says what to compute. row_number, similarly, sorts rows in each quarter/year - it is later used in CASE expression to decide whether to show quarterly/yearly total or not.
25 temp as
26 (select t.month, t.quarter, t.year, sum(s.sales) month_sales
27 from time t join sales s on s.time_id = t.time_id
28 where t.year = 2016
29 group by t.month, t.quarter, t.year
30 ),
31 temp2 as
32 (select month, quarter, month_sales,
33 sum(month_sales) over (partition by quarter) quarter_sales,
34 sum(month_sales) over (partition by year ) year_sales,
35 row_number() over (partition by quarter order by quarter) rnq,
36 row_number() over (partition by year order by null) rny
37 from temp
38 )
39 select month,
40 month_sales
41 case when rnq = 1 then quarter_sales end month_sales,
42 case when rny = 1 then year_sales end year_sales
43 from temp2
44 order by month;
MONTH MONTH_SALES QUARTER_SALES YEAR_SALES
---------- ---------- ----------- ----------
1 200 700 4600
2 200
3 300
4 400 1500
5 500
6 600
7 700 2400
8 800
9 900
9 rows selected.
SQL>
Need to find record having gap between months in a table if the data is present in two different year.
I have column like id, value,month, year.
Id, value, month,year
1, 123, oct, 2020
1, 128, nov, 2020
1, 127, jan ,2021
2, 121, Dec, 2020
2, 154, jan, 2021
Output I need:
Id 1 as there is a gap in month (Dec is Missing for id=1)
Here's one option. Read comments within code.
SQL> with test (id, value, month, year) as
2 -- sample data; you have that, don't type it
3 (select 1, 123, 'oct', 2020 from dual union all
4 select 1, 128, 'nov', 2020 from dual union all
5 select 1, 127, 'jan', 2021 from dual union all
6 select 2, 121, 'dec', 2020 from dual union all
7 select 2, 154, 'jan', 2021 from dual
8 ),
9 temp as
10 -- "convert" month and year to real date value
11 (select id,
12 value,
13 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
14 from test
15 ),
16 temp2 as
17 -- select difference in months between DATUM and next month (LEAD!)
18 (select id,
19 months_between
20 (datum,
21 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
22 ) diff
23 from temp
24 )
25 select distinct id
26 from temp2
27 where abs(diff) > 1;
ID
----------
1
SQL>
It can probably be compressed, but step-by-step CTEs show what's going on.
I would construct a date and use lag():
select t.*
from (select t.*,
lag(dte) over (partition by id order by dte) as prev_dte
from (select t.*,
to_date(year || '-' || month || '-01', 'YYYY-MON-DD') as dte
from t
) t
) t
where prev_dte <> dte - interval '1' month;
Here is a db<>fiddle.
Here is an example using the LAG function and finding rows where where the prior month is not one month behind (or non existent)
WITH
sample_data (Id,
VALUE,
month,
year)
AS
(SELECT 1, 123, 'oct', 2020 FROM DUAL
UNION ALL
SELECT 1, 128, 'nov', 2020 FROM DUAL
UNION ALL
SELECT 1, 127, 'jan', 2021 FROM DUAL
UNION ALL
SELECT 2, 121, 'Dec', 2020 FROM DUAL
UNION ALL
SELECT 2, 154, 'jan', 2021 FROM DUAL)
SELECT DISTINCT id
FROM (SELECT sd.id,
CASE
WHEN ADD_MONTHS (TO_DATE (sd.year || sd.month, 'YYYYMON'), -1) =
TO_DATE (
LAG (sd.year || sd.month)
OVER (
PARTITION BY id
ORDER BY
sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON'))),
'YYYYMON')
OR LAG (sd.id)
OVER (
PARTITION BY id
ORDER BY sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON')))
IS NULL
THEN
'Y'
ELSE
'N'
END AS valid_prev_month
FROM sample_data sd)
WHERE valid_prev_month = 'N';
I have a table having data for January to March (till current month) and I am able to take the month wise count.But user required is to display zero for rest of the month.Kindly suggest.
For example:
select count(a.emp_id) as cnt ,to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a
where a.due_date is not null
group by to_char(a.due_date,'MONTH')
Output:
cnt Process_month
20 JANUARY
35 FEBUARY
26 March
Desired output:
cnt Process_month
20 JANUARY
35 FEBUARY
26 March
0 APRIL
0 MAY
…….
….
….
0 DECEMBER
Please assist.
use WWV_FLOW_MONTHS_MONTH to get all the month and left join with your query to get the month name from the date column and join with it
with cte
(
SELECT month_display as month FROM WWV_FLOW_MONTHS_MONTH
) , cnt as
(
select count(a.emp_id) as cnt ,
to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a
where a.due_date is not null
group by to_char(a.due_date,'MONTH')
) select coalesce(Process_Month,month), cnt from cte left join cnt on cte.month=cnt.to_char(to_date(Process_Month, 'DD-MM-YYYY'), 'Month')
Right join months generator with your query:
select to_char(to_date(mth_num, 'MM'), 'MONTH') month, nvl(cnt, 0) cnt
from (
select count(emp_id) as cnt, to_char(due_date, 'mm') mth_num
from emp_request where due_date is not null
group by to_char(due_date, 'mm')) e
right join (
select to_char(level, 'fm00') mth_num
from dual connect by level <= 12) m using (mth_num)
order by mth_num
dbfiddle demo
Months generator is a simple hierarchical query which gives us 12 values 01, 02... 12:
select to_char(level, 'fm00') mth_num from dual connect by level <= 12
You can also use system views to get these numbers:
select to_char(rownum, 'fm00') mth_num from all_objects where rownum <= 12
or this syntax:
select to_char(column_value, 'fm00') mth_num
from table(sys.odcivarchar2list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
It's better to work on numbers which you can sort properly and convert to month names in the last step. This way you have natural months order.
If you want to be sure that month names are always in english, not dependent from local settings then use to_date with third parameter, like here:
select to_char(sysdate, 'month', 'nls_date_language=english') from dual
This is a general problem which is not really a sql problem. SQL doesn't really know about what months you are interested in. So the solution is to tell it in a sub query.
Here is a solution that doesn't use external tables. You simply select all months of the year and outer join your data.
select TO_CHAR(TO_DATE(available_months.m,'MM'),'MONTH') , NVL(sum(data.cnt),0) from
(select to_number(to_char(sysdate,'MM')) m, 7 cnt from dual) data,
(select 1 m from dual union select 2 from dual union select 3 from dual union select 4 from dual
union select 5 from dual union select 6 from dual union select 7 from dual
union select 8 from dual union select 9 from dual union select 10 from dual
union select 11 from dual union select 12 from dual) available_months
where
data.m (+) = available_months.m
group by available_months.m
order by available_months.m;
Or with your data query included is should look like (not tested):
select TO_CHAR(TO_DATE(available_months.m,'MM'),'MONTH') , NVL(sum(data.cnt),0) from
(select count(a.emp_id) as cnt ,to_char(a.due_date,'MONTH') as Process_Month from EMP_Request a where a.due_date is not null) data
(select 1 m from dual union select 2 from dual union select 3 from dual union select 4 from dual
union select 5 from dual union select 6 from dual union select 7 from dual
union select 8 from dual union select 9 from dual union select 10 from dual
union select 11 from dual union select 12 from dual) available_months
where
data.due_date (+) = available_months.m
group by available_months.m
order by available_months.m;
I am currently working on oracle sql and have some issues managing dates and quarters.
The first thing I try to do is given a quarter and a year I would like to know the number of days of the quarter. For example, quarter 1 in 2013 had 90 days but in 2012 had 91 days.
The second thing I would like to do is convert a date dd/qq/yyyy to a date
dd/mm/yyyy.
For instance, 60/2/2013 gives 30/5/2013. I am a beginner in Oracle so any help or function names will be highly appreciated.
Thanks
this gives the number of days in the current quarter, playing around with the add_months should allow you to find the length of other quarters
select (add_months(trunc(sysdate,'q'),3) - 1) - trunc(sysdate,'q')
from dual;
For your first question, hopefully this will give you some idea of what to do:
with sample_data as (select 2012 yr, 1 quarter from dual union all
select 2012 yr, 2 quarter from dual union all
select 2012 yr, 3 quarter from dual union all
select 2012 yr, 4 quarter from dual union all
select 2013 yr, 1 quarter from dual union all
select 2013 yr, 2 quarter from dual)
---- end of mimicking a table called "sample_data"; see query below:
select yr,
quarter,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), (quarter - 1)*3) qtr_st,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), quarter * 3) - 1 qtr_end,
add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), quarter * 3) - add_months(to_date('01/01/'||yr, 'dd/mm/yyyy'), (quarter - 1)*3) diff
from sample_data;
YR QUARTER QTR_ST QTR_END DIFF
---------- ---------- ---------- ---------- ----------
2012 1 01/01/2012 31/03/2012 91
2012 2 01/04/2012 30/06/2012 91
2012 3 01/07/2012 30/09/2012 92
2012 4 01/10/2012 31/12/2012 92
2013 1 01/01/2013 31/03/2013 90
2013 2 01/04/2013 30/06/2013 91
N.B. Because you're including the day of the start_date in the count, the difference is effectively how many days between the 1st of the quarter and the 1st of the next quarter, or qtr_end - qtr_st + 1 from my query above.
For your second question, here's one way:
with sample_data as (select '60/2/2013' dy_qtr_fmt from dual union all
select '60/02/2013' dy_qtr_fmt from dual union all
select '01/1/2013' dy_qtr_fmt from dual union all
select '1/1/2013' dy_qtr_fmt from dual)
---- end of mimicking a table called "sample_data"; see query below:
select dy_qtr_fmt,
add_months(year_st, (qtr-1)*3) + num_days_in_qtr - 1 dt
from (select dy_qtr_fmt,
to_date('01/01/'||substr(dy_qtr_fmt, -4), 'dd/mm/yyyy') year_st,
to_number(substr(dy_qtr_fmt, instr(dy_qtr_fmt, '/', 1, 1) + 1, instr(dy_qtr_fmt, '/', 1, 2) - instr(dy_qtr_fmt, '/', 1, 1) -1)) qtr,
to_number(substr(dy_qtr_fmt, 1, instr(dy_qtr_fmt, '/', 1, 1) - 1)) num_days_in_qtr
from sample_data);
DY_QTR_FMT DT
---------- ----------
60/2/2013 30/05/2013
60/02/2013 30/05/2013
01/1/2013 01/01/2013
1/1/2013 01/01/2013