I have a field c_days in the table my_table that accepts numeric values from 1 to 31.
In this field, numbers from 1 to 9 are single digits.
I am writing a condition, if c_day is greater than today, then you need to display c_day in the to_date date format, if less, then display c_day in the date format, only the next month.
For example, c_day is 14, and today we have the 8th number, so you need to display the date 14.02.2023, If c_day is equal to 5, then you need to display the date of the next month 05.03.2023
I did something like this:
SELECT
C_DAY,
CASE
WHEN C_DAY >= TO_CHAR(SYSDATE, 'DD') THEN
TO_DATE(C_DAY || '.' || TO_CHAR(SYSDATE, 'MM') || '.' || TO_CHAR(SYSDATE, 'YYYY'), 'dd.mm.yyyy')
WHEN C_DAY < TO_CHAR(SYSDATE, 'DD') THEN
TO_DATE(C_DAY || '.' || TO_CHAR(SYSDATE, 'MM') || '.' || TO_CHAR(SYSDATE, 'YYYY'), 'dd.mm.yyyy')
WHEN C_DAY IS NULL THEN
null
END AS new_field
FROM my_table
The problem is that the end result is not converted to the date format, I thought it's cause of that the dates can be displayed as 1.03.2023, 7.03.2023, so i tried convert it into
TO_CHAR(C_DAY, 'fm00')
and did this:
SELECT
C_DAY,
CASE
WHEN TO_CHAR(C_DAY, 'fm00') >= TO_CHAR(SYSDATE, 'DD') THEN
TO_DATE(TO_CHAR(C_DAY, 'fm00') || '.' || TO_CHAR(SYSDATE, 'MM') || '.' || TO_CHAR(SYSDATE, 'YYYY'), 'dd.mm.yyyy')
WHEN TO_CHAR(C_DAY, 'fm00') < TO_CHAR(SYSDATE, 'DD') THEN
TO_DATE(TO_CHAR(C_DAY, 'fm00') || '.' || TO_CHAR(SYSDATE, 'MM') || '.' || TO_CHAR(SYSDATE, 'YYYY'), 'dd.mm.yyyy')
WHEN C_DAY IS NULL THEN
null
END AS new_field
FROM my_table
But its not even working, it shows ora error
You can do it without any string-to-date (or vice-versa) conversions using:
SELECT C_DAY,
LEAST(
ADD_MONTHS(
TRUNC(SYSDATE, 'MM'),
CASE WHEN c_day <= EXTRACT(DAY FROM SYSDATE) THEN 0 ELSE 1 END
) + c_day - 1,
LAST_DAY(
ADD_MONTHS(
TRUNC(SYSDATE, 'MM'),
CASE WHEN c_day <= EXTRACT(DAY FROM SYSDATE) THEN 0 ELSE 1 END
)
)
) AS new_field
FROM my_table
Which, for the sample data:
CREATE TABLE my_table(c_day) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 31;
Outputs:
C_DAY
NEW_FIELD
1
2023-02-01 00:00:00
2
2023-02-02 00:00:00
3
2023-02-03 00:00:00
4
2023-02-04 00:00:00
5
2023-02-05 00:00:00
6
2023-02-06 00:00:00
7
2023-02-07 00:00:00
8
2023-02-08 00:00:00
9
2023-03-09 00:00:00
10
2023-03-10 00:00:00
11
2023-03-11 00:00:00
12
2023-03-12 00:00:00
13
2023-03-13 00:00:00
14
2023-03-14 00:00:00
15
2023-03-15 00:00:00
16
2023-03-16 00:00:00
17
2023-03-17 00:00:00
18
2023-03-18 00:00:00
19
2023-03-19 00:00:00
20
2023-03-20 00:00:00
21
2023-03-21 00:00:00
22
2023-03-22 00:00:00
23
2023-03-23 00:00:00
24
2023-03-24 00:00:00
25
2023-03-25 00:00:00
26
2023-03-26 00:00:00
27
2023-03-27 00:00:00
28
2023-03-28 00:00:00
29
2023-03-29 00:00:00
30
2023-03-30 00:00:00
31
2023-03-31 00:00:00
fiddle
Here's one option:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> WITH
2 my_table (c_day)
3 AS
4 (SELECT 14 FROM DUAL
5 UNION ALL
6 SELECT 5 FROM DUAL
7 UNION ALL
8 SELECT 30 FROM DUAL)
9 SELECT c_day,
10 TO_DATE (
11 LPAD (LEAST (c_day, TO_CHAR (LAST_DAY (SYSDATE), 'dd')), 2, '0')
12 || '.'
13 || TO_CHAR (
14 CASE
15 WHEN c_day < TO_CHAR (SYSDATE, 'dd')
16 THEN
17 ADD_MONTHS (SYSDATE, 1)
18 ELSE
19 SYSDATE
20 END,
21 'mm.yyyy'),
22 'dd.mm.yyyy') AS result
23 FROM my_table;
C_DAY RESULT
---------- ----------
14 14.02.2023
5 05.03.2023
30 28.02.2023
SQL>
As far as I understand your question, one solution could be this one
SELECT
C_DAY,
CASE
WHEN C_DAY >= TO_CHAR(SYSDATE, 'DD') THEN
ADD_MONTHS(TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-')||C_DAY , 'YYYY-MM-DD'), 1)
WHEN C_DAY < TO_CHAR(SYSDATE, 'DD') THEN
TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-')||C_DAY , 'YYYY-MM-DD')
END AS new_field
FROM my_table
The ADD_MONTHS function works as this:
If date is the last day of the month or if the resulting month has fewer days than the day component of date, then the result is the last day of the resulting month. Otherwise, the result has the same day component as date.
Related
I have the following output:
from_date
until_date
17.03.2020
18.05.2020
18.05.2020
08.06.2020
21.12.2020
01.03.2021
01.03.2021
11.03.2021
19.10.2021
22.10.2021
10.01.2022
14.01.2022
14.01.2022
NULL
I need to count the days between these two dates, second date inclusively, with this logic:
(18.05.2020 - 17.03.2020)+1 = 63
The next row begins at the same day as it ends in the first row,
then the 18.05.2020 must not be counted in the days difference, so:
08.06.2020 - 18.05.2020
if the until_date is null then it will be:
sysdate-from_date
but what im struggling to do is to get next element and previous element values in a loop so I can compare them
You can use the LAG analytic function to find the previous until_date:
SELECT from_date,
until_date,
COALESCE(until_date, TRUNC(SYSDATE)) - from_date
+ CASE
WHEN LAG(until_date) OVER (ORDER BY FROM_DATE) = from_date
THEN 0
ELSE 1
END
AS difference
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (from_date, until_date) AS
SELECT DATE '2020-03-17', DATE '2020-05-18' FROM DUAL UNION ALL
SELECT DATE '2020-05-18', DATE '2020-06-08' FROM DUAL UNION ALL
SELECT DATE '2020-12-21', DATE '2021-03-01' FROM DUAL UNION ALL
SELECT DATE '2021-03-01', DATE '2021-03-11' FROM DUAL UNION ALL
SELECT DATE '2021-10-19', DATE '2021-10-22' FROM DUAL UNION ALL
SELECT DATE '2022-01-10', DATE '2022-01-14' FROM DUAL UNION ALL
SELECT DATE '2022-01-14', NULL FROM DUAL;
Outputs:
FROM_DATE
UNTIL_DATE
DIFFERENCE
2020-03-17 00:00:00
2020-05-18 00:00:00
63
2020-05-18 00:00:00
2020-06-08 00:00:00
21
2020-12-21 00:00:00
2021-03-01 00:00:00
71
2021-03-01 00:00:00
2021-03-11 00:00:00
10
2021-10-19 00:00:00
2021-10-22 00:00:00
4
2022-01-10 00:00:00
2022-01-14 00:00:00
5
2022-01-14 00:00:00
null
154
If you want it in PL/SQL then wrap the query in a cursor and loop through the cursor in PL/SQL.
BEGIN
FOR r IN (
SELECT from_date,
until_date,
COALESCE(until_date, TRUNC(SYSDATE)) - from_date
+ CASE
WHEN LAG(until_date) OVER (ORDER BY FROM_DATE) = from_date
THEN 0
ELSE 1
END
AS difference
FROM table_name
) LOOP
DBMS_OUTPUT.PUT_LINE( r.from_date || ', ' || r.until_date || ', ' || r.difference );
END LOOP;
END;
/
Or you can do exactly the same thing by storing the previous until_date in a PL/SQL variable:
DECLARE
v_until_date DATE;
v_diff NUMBER;
BEGIN
FOR r IN (
SELECT from_date,
until_date
FROM table_name
ORDER BY from_date
) LOOP
v_diff := COALESCE(r.until_date, TRUNC(SYSDATE)) - r.from_date
+ CASE WHEN v_until_date = r.from_date THEN 0 ELSE 1 END;
DBMS_OUTPUT.PUT_LINE(
r.from_date
|| ', ' || r.until_date
|| ', ' || v_diff
);
v_until_date := r.until_date;
END LOOP;
END;
/
db<>fiddle here
How can i get the date of the first business day of a given date .
For example:
01-AUG-21 is Sunday, so the first business day is 02-AUG-21 .
You can use a simple case statement in SQL or PL/SQL. In the example below, just replace SYSDATE + LEVEL with the date you would like to use.
SELECT SYSDATE + LEVEL AS some_date,
TO_CHAR (SYSDATE + LEVEL, 'DY') AS some_day_of_week,
SYSDATE
+ LEVEL
+ CASE TO_CHAR (SYSDATE + LEVEL, 'DY') WHEN 'SAT' THEN 2 WHEN 'SUN' THEN 1 ELSE 0 END AS business_day
FROM DUAL
CONNECT BY LEVEL <= 14;
SOME_DATE SOME_DAY_OF_WEEK BUSINESS_DAY
____________ ___________________ ________________
02-JUL-21 FRI 02-JUL-21
03-JUL-21 SAT 05-JUL-21
04-JUL-21 SUN 05-JUL-21
05-JUL-21 MON 05-JUL-21
06-JUL-21 TUE 06-JUL-21
07-JUL-21 WED 07-JUL-21
08-JUL-21 THU 08-JUL-21
09-JUL-21 FRI 09-JUL-21
10-JUL-21 SAT 12-JUL-21
11-JUL-21 SUN 12-JUL-21
12-JUL-21 MON 12-JUL-21
13-JUL-21 TUE 13-JUL-21
14-JUL-21 WED 14-JUL-21
15-JUL-21 THU 15-JUL-21
Here is a PL/SQL example of the same logic
DECLARE
FUNCTION get_business_day (p_date DATE)
RETURN DATE
IS
BEGIN
RETURN TRUNC (
p_date
+ CASE TO_CHAR (p_date, 'DY')
WHEN 'SAT' THEN 2
WHEN 'SUN' THEN 1
ELSE 0
END);
END;
BEGIN
DBMS_OUTPUT.put_line (get_business_day (p_date => SYSDATE));
END;
/
Here's one option: as it is only about 1 week, create a little one-week-calendar and fetch date which is larger than the one entered as a parameter, and which isn't a weekend.
with little_calendar as
(select to_date(:par_datum, 'dd.mm.yyyy') + level - 1 datum
from dual
connect by level <= 7
)
select min(datum)
from little_calendar
where datum > to_date(:par_datum, 'dd.mm.yyyy')
and to_char(datum, 'dy', 'nls_date_language = english')
not in ('sat', 'sun');
A few examples in SQL*Plus:
SQL> with little_calendar as
2 (select to_date('&&par_datum', 'dd.mm.yyyy') + level - 1 datum
3 from dual
4 connect by level <= 7
5 )
6 select min(datum)
7 from little_calendar
8 where datum > to_date('&&par_datum', 'dd.mm.yyyy')
9 and to_char(datum, 'dy', 'nls_date_language = english')
10 not in ('sat', 'sun');
Enter value for par_datum: 01.07.2021 --> the 1st working day after 01.07.2021 (Thursday) ...
MIN(DATUM)
---------------
02.07.2021, fri --> ... is 02.07.2021 (Friday)
SQL> undefine par_datum
SQL> /
Enter value for par_datum: 02.07.2021 --> working day that follows 02.07.2021 (Friday) ...
MIN(DATUM)
---------------
05.07.2021, mon --> ... is 05.07.2021 (Monday)
SQL>
How to find number of Sundays in a given year in Oracle SQL.
Input: 1996
Expected output:
<date-of-sunday-1>
<date-of-sunday-2>
.............
.............
<date-of-sunday-n>
<count-of-no-of-sundays-in-that-year>
This is how to fetch Sundays:
SQL> with the_whole_year as
2 (select trunc(to_date(&&par_year, 'yyyy'), 'yyyy') + level - 1 c_date,
3 to_char(trunc(to_date(&&par_year, 'yyyy'), 'yyyy') + level - 1, 'fmday') c_day
4 from dual
5 connect by level <= add_months(trunc(to_date(&&par_year, 'yyyy'), 'yyyy'), 12) -
6 trunc(to_date(&&par_year, 'yyyy'), 'yyyy')
7 )
8 select c_date
9 from the_whole_year
10 where c_day = 'sunday';
C_DATE
----------
07.01.1996
14.01.1996
21.01.1996
28.01.1996
04.02.1996
11.02.1996
<snip>
I suppose you'll be able to count them yourself.
[EDIT: a function that does the counting]
SQL> CREATE OR REPLACE FUNCTION f_count_of_sundays (par_year IN NUMBER)
2 RETURN NUMBER
3 IS
4 retval NUMBER;
5 BEGIN
6 WITH the_whole_year
7 AS ( SELECT TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy') + LEVEL - 1
8 c_date,
9 TO_CHAR (
10 TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy') + LEVEL - 1,
11 'fmday')
12 c_day
13 FROM DUAL
14 CONNECT BY LEVEL <=
15 ADD_MONTHS (
16 TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy'),
17 12)
18 - TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy'))
19 SELECT COUNT (*)
20 INTO retval
21 FROM the_whole_year
22 WHERE c_day = 'sunday';
23
24 RETURN retval;
25 END;
26 /
Function created.
SQL> select f_count_of_sundays(1996) from dual;
F_COUNT_OF_SUNDAYS(1996)
------------------------
52
SQL>
I have a requirement to store 24 hours in a day in a table "time_hours". Here is the structure of my table:
Create table time_hours
(HOURS number,
HOUR_RANGE VARCHAR (20),
HOUR_MIN VARCHAR (20),
HOUR_MAX VARCHAR(20));
Here is the script I have so far:
Insert into time_hours(HOURS,
HOUR_RANGE,
HOUR_MIN,
HOUR_MAX)
Select
to_number(to_char(t,'HH24'),'00'),
to_char(t,'HH24:MI:SS'),
to_char(t,'HH24:MI:SS'),
to_char(t,'HH24:MI:SS')
FROM
(
Select trunc(sysdate) + (level-1)/24 as t
FROM dual
Connect by level <=24
);
The results is:
Hours | HOUR_RANGE | HOUR_MIN | HOUR_MAX
0 00:00:00 00:00:00 00:00:00
1 01:00:00 01:00:00 01:00:00
..
23 23:00:00 23:00:00 23:00:00
However I need this output:
Hours | HOUR_RANGE | HOUR_MIN | HOUR_MAX
0 00h-00h59 00:00:00 00:59:59
1 01h00-01h59 01:00:00 01:59:59
2 02h00-02h59 02:00:00 02:59:59
.. .. .. ..
23 23h00-23h59 23:00:00 23.59.59
My Question:
How can I format the "HOUR_RANGE" and "HOUR_MAX" columns to give me the expected output?
Thank you all in advance for your help
You could use:
Select
to_number(to_char(t,'HH24'),'00') AS Hours,
to_char(t,'HH24"h"MI"-"HH24"h59"') AS HOUR_RANGE,
to_char(t,'HH24:MI:SS') AS HOUR_MIN,
to_char(t+1/24-1/(24*3600),'HH24:MI:SS') AS HOUR_MAX
FROM (Select trunc(sysdate) + (level-1)/24 as t
FROM dual
Connect by level <=24 );
Rextester Demo
After all, it's just strings containing the hour plus some other characters. No date conversion needed at all.
insert into time_hours(hours, hour_range, hour_min, hour_max)
select
level-1 as hours,
to_char(level-1, '00') || 'h00-' || to_char(level-1, '00') || 'h59' as hour_range,
to_char(level-1, '00') || ':00:00'as hour_min,
to_char(level-1, '00') || ':59:59'as hour_max
from dual connect by level <= 24;
I think you can use code below.
You can use pipelines to concatenate characters.
You can use some arithmetic operations to get max minutes and seconds of given hour; 1/24*60 = 1/1440 to add a minute to a date value. 1/24*60*60 = 1/86400 to add a second to a date value.
INSERT INTO time_hours (HOURS, HOUR_RANGE, HOUR_MIN, HOUR_MAX)
SELECT
to_number(to_char(t, 'HH24'), '00'),
to_char(t, 'HH24') || 'h' || to_char(t, 'MI') || '-' || to_char(t, 'HH24') || 'h59',
to_char(t, 'HH24:MI:SS'),
to_char(t + 1/86400*59 + 1/1440*59, 'HH24:MI:SS')
FROM (SELECT trunc(SYSDATE) + (LEVEL - 1) / 24 AS t
FROM dual
CONNECT BY LEVEL <= 24);
This is what I got so far:
select
to_char(sysdate, 'yyyy') Time
from
dual;
Which gives me:
TIME
2015
Its working until this point.
I would like to add
if the month is >= 7 I get as output 01.07.current year
if the month is <= 7 I get as output 01.07.(current year - 1 year)
Any ideas how to handle this? I thought about CASE WHEN but I dont get know how.
Thanks!
A simple CASE expression would do the job.
For example,
SQL> SELECT
2 '01.07.' ||
3 CASE
4 WHEN TO_CHAR(SYSDATE, 'MM') < '07'
5 THEN
6 TO_CHAR(SYSDATE, 'YYYY')
7 ELSE
8 TO_CHAR(add_months(SYSDATE,-12), 'YYYY')
9 END case_date
10 FROM dual;
CASE_DATE
----------
01.07.2015
SQL>
To keep it even more precise, you could keep the common value outside the CASE expression:
SQL> SELECT '01.07.'
2 ||
3 CASE
4 WHEN TO_CHAR(SYSDATE, 'MM') < '07'
5 THEN TO_CHAR(SYSDATE, 'YYYY')
6 ELSE TO_CHAR(add_months(SYSDATE,-12), 'YYYY')
7 END case_date
8 FROM dual;
CASE_DATE
----------
01.07.2015
SQL>
Using extract more readable
SELECT
to_date((CASE
WHEN extract(MONTH FROM SYSDATE) >= 7 THEN
0
ELSE
-1
END) + extract(YEAR FROM SYSDATE) || '07-01', 'yyyy-mm-dd') END
FROM dual