Turn days into weeks, months, years.ORACLE - sql

I have this query and result:
SQL> select AVG(SYSDATE - DOB) AS AVERAGE_AGE_IN_DAYS
2 FROM MORTAL;
AVERAGE_AGE_IN_DAYS
-------------------
17877.44
Is there a way to convert this to years, months, and days, within the same query?

To get the number of months you could use months_between
SELECT select AVG(months_between(SYSDATE,DOB)) AS AVERAGE_AGE_IN_MONTHS
For years, just do months_between/12
SELECT select AVG(months_between(SYSDATE,DOB)/12) AS AVERAGE_AGE_IN_YEARS
For weeks, i would just take your result for days and divide it by 7.
You might have to handle the results to fit your needs, but i think this should cover it.

How about:
select
floor(months_between(SYSDATE,DOB)/12) AS years,
trunc( months_between(SYSDATE,DOB) ) AS months,
SYSDATE - add_months( SYSDATE, trunc(months_between(SYSDATE,DOB)) ) AS days
This is years,months,days as the detail suggests. The title said weeks too but I'm not if you actually want them.

Perhaps your query would be:
with dates as (select AVG(SYSDATE - DOB) as d --average_in_days
from MORTAL
)
select trunc(d/365) as years
, trunc((d/365 - trunc(d/365)) * 12 ) as months
, trunc((((d/365 - trunc(d/365)) * 12) - trunc((d/365 - trunc(d/365)) * 12 )) * 4.348214) as weeks
, trunc(((((d/365 - trunc(d/365)) * 12) - trunc((d/365 - trunc(d/365)) * 12 )) * 4.348214
- trunc((((d/365 - trunc(d/365)) * 12) - trunc((d/365 - trunc(d/365)) * 12 )) * 4.348214 )
) * 7
) as days
from dates;

CREATE FUNCTION time_spell(p_days VARCHAR2) RETURN VARCHAR2 IS
v_y NUMBER;
v_yd NUMBER;
v_m NUMBER;
v_md NUMBER;
v_d NUMBER;
v_w NUMBER;
v_wd NUMBER;
v_days NUMBER;
v_yi NUMBER;
v_mi NUMBER;
v_wi NUMBER;
BEGIN
SELECT p_days/365 INTO v_y FROM dual;
if v_y not like '%.%' then
SELECT trunc(v_y) INTO v_yi FROM dual;
select substr(v_y||'.0',(select instr(v_y||'.0','.') from dual)) INTO v_yd from dual;
SELECT v_yd * 12 INTO v_m FROM dual;
SELECT trunc(v_m) INTO v_mi FROM dual;
select substr(v_m,(select instr(v_m,'.') from dual)) INTO v_md from dual;
SELECT v_md * 30 INTO v_d FROM dual;
SELECT v_d / 7 INTO v_w FROM dual;
SELECT trunc(v_w) INTO v_wi FROM dual;
select substr(v_w,(select instr(v_w,'.') from dual)) INTO v_wd from dual;
SELECT round(v_wd * 7) INTO v_days FROM dual;
RETURN v_yi||' Year(s) '||v_mi||' Month(s) '||v_wi||' Week(s) '||v_days||' Day(s) ';
else
SELECT trunc(v_y) INTO v_yi FROM dual;
select substr(v_y,(select instr(v_y,'.') from dual)) INTO v_yd from dual;
SELECT v_yd * 12 INTO v_m FROM dual;
SELECT trunc(v_m) INTO v_mi FROM dual;
select substr(v_m,(select instr(v_m,'.') from dual)) INTO v_md from dual;
SELECT v_md * 30 INTO v_d FROM dual;
SELECT v_d / 7 INTO v_w FROM dual;
SELECT trunc(v_w) INTO v_wi FROM dual;
select substr(v_w,(select instr(v_w,'.') from dual)) INTO v_wd from dual;
SELECT round(v_wd * 7) INTO v_days FROM dual;
RETURN v_yi||' Year(s) '||v_mi||' Month(s) '||v_wi||' Week(s) '||v_days||' Day(s) ';
end if;
END;
SELECT time_spell(17877.44) FROM dual;

Related

Oracle calling a function within DML

I have a function that takes in a string 'HH:MM:SS' and converts it to the number of seconds. See below example
CREATE OR REPLACE FUNCTION CONVERT_TO_SECONDS(
i_date_string IN VARCHAR2
)
RETURN INTEGER
AS
l_hours NUMBER;
l_minutes NUMBER;
l_seconds NUMBER;
BEGIN
SELECT trim('"'
FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,1)) hours,
trim('"'
FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,2)) minutes,
trim('"'
FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,3)) seconds
INTO l_hours ,
l_minutes ,
l_seconds
FROM dual ;
return
l_hours*3600 +
l_minutes*60 +
l_seconds;
END;
/
SELECT CONVERT_TO_SECONDS('08:08:08') FROM DUAL;
CONVERT_TO_SECONDS('08:08:08')
29288
I have a procedure that works fine, which creates emp_attendance rows.
After the rows are created I am trying to update the end_date of each row with the number of seconds returned by the function. Is this possible? If so, how can I get past the syntax error on the update.
Thanks in advance to all that answer and for your help, patience and expertise.
My test CASE is below.
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
/
Create table employees(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
work_days VARCHAR2(7)
);
INSERT INTO employees (
employee_id,
first_name,
last_name,
card_num,
work_days
)
WITH names AS (
SELECT 1, 'John', 'Doe', 'D564311','YYYYYNN' FROM dual UNION ALL
SELECT 2, 'Justin', 'Case', 'C224311','YYYYYNN' FROM dual UNION ALL
SELECT 3, 'Mike', 'Jones', 'J288811','YYYYYNN' FROM dual UNION ALL
SELECT 4, 'Jane', 'Smith', 'S564661','YYYYYNN' FROM dual
) SELECT * FROM names;
CREATE TABLE emp_attendance(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
start_date DATE,
end_date DATE,
week_number NUMBER(2));
CREATE OR REPLACE PROCEDURE create_emp_attendance (
p_start_date IN DATE,
p_end_date IN DATE
)
IS
BEGIN
INSERT INTO emp_attendance ( employee_id, start_date, end_date, week_number)
SELECT
employee_id
, start_date
, start_date+NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(3600,43200)), 'SECOND') AS end_date
, to_char(start_date,'WW') AS week_number
FROM ( -- Need subquery to generate end_date based on start_date.
SELECT e.employee_id, d.COLUMN_VALUE + NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(0,86399)), 'SECOND') AS start_date
FROM employees e
CROSS JOIN TABLE( generate_dates_pipelined(p_start_date, p_end_date) ) d
) ed
;
END;
/
EXEC create_emp_attendance(SYSDATE, SYSDATE);
-- Having problem with this update
update emp_attendance
set end_date=start_date + NUMTODSINTERVAL
CONVERT_TO_SECONDS('08:08:08'),'SECOND';
-- Once the update is working the query below should show 8hrs 8mins 8sec for each row.
select e.employee_id,
e.first_name,
e.last_name,
trunc(sum(a.end_date - a.start_date) * 24) hours,
trunc(mod(sum(a.end_date - a.start_date) * 24 * 60,60)) minutes,
round(mod(sum(a.end_date - a.start_date) * 24 * 60 * 60,60)) seconds
from employees e,
emp_attendance a
where a.employee_id = e.employee_id
AND start_date BETWEEN TRUNC(SYSDATE)
AND
TRUNC(SYSDATE)+ (1-1/24/60/60)
group by e.employee_id, e.first_name, e.last_name
order by e.employee_id, e.first_name,
e.last_name;
You can simplify your function to:
CREATE OR REPLACE FUNCTION CONVERT_TO_SECONDS(
i_date_string IN VARCHAR2
)
RETURN INTEGER DETERMINISTIC
AS
BEGIN
RETURN ( TO_DATE(i_date_string, 'HH24:MI:SS')
- TO_DATE('00:00:00', 'HH24:MI:SS')
) * 86400;
END;
/
Then you need brackets around the NUMTODSINTERVAL function arguments:
UPDATE emp_attendance
SET end_date = start_date + NUMTODSINTERVAL( CONVERT_TO_SECONDS('08:08:08'),'SECOND' );
db<>fiddle here
NUMTODSINTERVAL is a function. So it needs to be used like this:
update emp_attendance
set end_date=start_date + NUMTODSINTERVAL(CONVERT_TO_SECONDS('08:08:08'), 'SECOND')
;
There's no need to create custom function, because Oracle already has it: to_dsinterval. But you need to provide also a number of days, so add a zero before your time:
select
sysdate,
sysdate + to_dsinterval('0 ' || '08:08:08') as sysdate_shifted
from dual
SYSDATE | SYSDATE_SHIFTED
:------------------ | :------------------
2021-08-15 18:31:49 | 2021-08-16 02:39:57
db<>fiddle here
Then your update will be:
update emp_attendance
set end_date = start_date + to_dsinterval('0 ' || '08:08:08')

Oracle SQL invalid number on toad

I have made this query but when I execute it, I got error about "invalid number".
But in SQL Developer for Oracle, there is no error; I got the result that I want but in Toad I got 'Invalid Number' .
DECLARE v_rep number;
BEGIN
EXECUTE IMMEDIATE
'SELECT to_number(REPLACE(max(substr(to_char(r_timestamp_arr,''HH24:MI''),1,2) ||
ltrim(to_char(round(to_number(Substr(to_char(r_timestamp_arr, ''HH24:MI''),4,2)) /
60,2),''.00''))), ''.'', '','')) -
to_number(REPLACE(MIN(substr(to_char(r_timestamp_arr,''HH24:MI''),1,2) ||
ltrim(to_char(round(to_number(Substr(to_char(r_timestamp_arr, ''HH24:MI''),4,2)) /
60,2),''.00''))), ''.'', '',''))
FROM TV_MAX
WHERE TV_UID = ''7a87e8e4861a4d0aae65da1a7248b256'''
INTO v_rep;
END ;
You don't need EXECUTE IMMEDIATE and don't need to use strings:
Oracle Setup:
CREATE TABLE tv_max ( tv_uid, r_timestamp_arr ) AS
SELECT '7a87e8e4861a4d0aae65da1a7248b256', DATE '2019-12-27' + INTERVAL '00:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT '7a87e8e4861a4d0aae65da1a7248b256', DATE '2019-12-27' + INTERVAL '01:30' HOUR TO MINUTE FROM DUAL;
Query 1:
If you want to ignore the date component of the date & time:
DECLARE
v_rep NUMBER;
BEGIN
SELECT ( MAX( r_timestamp_arr - TRUNC( r_timestamp_arr ) )
- MIN( r_timestamp_arr - TRUNC( r_timestamp_arr ) )
) * 24
INTO v_rep
FROM tv_max
WHERE TV_UID = '7a87e8e4861a4d0aae65da1a7248b256';
DBMS_OUTPUT.PUT_LINE( v_rep );
END;
/
Query 2:
If you want the min/max respecting the date component then the query can be even simpler:
DECLARE
v_rep NUMBER;
BEGIN
SELECT ( MAX( r_timestamp_arr ) - MIN( r_timestamp_arr ) ) * 24
INTO v_rep
FROM tv_max
WHERE TV_UID = '7a87e8e4861a4d0aae65da1a7248b256';
DBMS_OUTPUT.PUT_LINE( v_rep );
END;
/
Output:
For the test data, both output:
1.5
db<>fiddle here
Looks like You want to know the difference between max and min hour (including minutes, excluding seconds), date part truncated. So take truncated times, subtract as dates, you will get result in days, multiply by 24, result will be in hours. Query does not depend on NLS settings:
select 24 * (to_date(max(to_char(r_timestamp_arr, 'hh24:mi')), 'hh24:mi')
- to_date(min(to_char(r_timestamp_arr, 'hh24:mi')), 'hh24:mi')) as diff
from tv_max
where tv_uid = '7a87e8e4861a4d0aae65da1a7248b256'
dbfiddle

Oracle PL SQL - Issue with EXECUTE IMMEDIATE

DECLARE
start_date VARCHAR2(12);
end_date VARCHAR2(12);
start_epochtime VARCHAR2(15);
end_epochtime VARCHAR2(15);
v_sql VARCHAR2(1024);
BEGIN
SELECT to_char(current_date,'YYYY-MM-DD') into start_date from dual;
SELECT to_char(current_date - 30,'YYYY-MM-DD') into end_date from dual;
dbms_output.put_line(start_date);
dbms_output.put_line(end_date);
/* Below section will convert date to epochtime with hard code date value */
SELECT CAST((TO_DATE('2016-01-01','YYYY-MM-DD') - TO_DATE('1970-01- 01','YYYY-MM-DD') ) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into start_epochtime FROM DUAL;
SELECT CAST((TO_DATE('2016-01-01','YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') - 30) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into end_epochtime FROM DUAL;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
/* Below section will convert date to epochtime with a variable */
SELECT CAST((TO_DATE(start_date,'YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') ) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into start_epochtime FROM DUAL;
SELECT CAST((TO_DATE(end_date,'YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') - 30) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into end_epochtime FROM DUAL;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
EXECUTE IMMEDIATE q'[select to_char((TO_DATE('1970-01-01','yyyy-mm-dd') + (m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD'),count(1) from jivemessage_us m where m.CREATIONDATE >= :start_epochtime and m.CREATIONDATE <= :end_epochtime group by to_char((TO_DATE('1970-01-01','yyyy-mm-dd') + (m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD') order by 1]';
END;
/
I got this error ORA-01008: not all variables bound when i am running this pl sql. And, All statements are running fine except EXECUTE IMMEDIATE q'';
There doesn't appear to be a reason to use EXECUTE IMMEDIATE here. You're not building a dynamic query, nor are you executing a DDL statement. I suggest replacing the EXECUTE IMMEDIATE with
select to_char((TO_DATE('1970-01-01','yyyy-mm-dd') +
(m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD'),
count(1)
from jivemessage_us m
where m.CREATIONDATE >= start_epochtime and
m.CREATIONDATE <= end_epochtime
group by to_char((TO_DATE('1970-01-01','yyyy-mm-dd') +
(m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD')
order by 1
Best of luck.
If you really want to do it all in PL/SQL then you can do:
VARIABLE cur REFCURSOR;
DECLARE
start_date VARCHAR2(12);
end_date VARCHAR2(12);
start_epochtime VARCHAR2(15);
end_epochtime VARCHAR2(15);
v_sql VARCHAR2(1024);
BEGIN
start_date := TO_CHAR(current_date, 'YYYY-MM-DD');
end_date := TO_CHAR(current_date - 30, 'YYYY-MM-DD');
dbms_output.put_line(start_date);
dbms_output.put_line(end_date);
/* Below section will convert date to epochtime with hard code date value */
start_epochtime := ( DATE '2016-01-01' - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
end_epochtime := ( DATE '2016-01-01' - DATE '1970-01-01' - 30 ) * 24 * 60 * 60 * 1000;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
/* Below section will convert date to epochtime with a variable */
start_epochtime := ( CURRENT_DATE - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
end_epochtime := ( CURRENT_DATE - 30 - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
OPEN :cur FOR
select to_char(DATE '1970-01-01' + CREATIONDATE/1000/24/60/60,'YYYY-MM-DD'),
count(1)
from jivemessage_us
where CREATIONDATE BETWEEN start_epochtime and end_epochtime
group by CREATIONDATE
order by 1;
END;
/
PRINT cur;
But it would be simpler to do it in SQL:
select to_char(DATE '1970-01-01' + CREATIONDATE/1000/24/60/60,'YYYY-MM-DD'),
count(1)
from jivemessage_us
where CREATIONDATE BETWEEN ( CURRENT_DATE - DATE '1970-01-01' )*24*60*60*1000
AND ( CURRENT_DATE - 30 - DATE '1970-01-01' )*24*60*60*1000
group by CREATIONDATE
order by 1;
(Note: I've left your logic as-is but moved it from continually context switching from PL/SQL to SQL to just use PL/SQL as much as possible and ANSI Date literals; however, I do think that you have the -30 in the wrong places as it ought to be for the start_epochtime and not the end_epochtime.)

How can I use columns value from nested select in main select?

I'm trying to execute this SQL in Oracle:
select z.*
from (select (CASE
WHEN trunc(to_date('01.02.2015', 'DD.MM.YY'), 'MM') =
to_date('01.02.2015', 'DD.MM.YY') THEN
trunc(ADD_MONTHS(sysdate, -1), 'MM')
ELSE
trunc(sysdate - 1)
END) as sd,
trunc(sysdate) ed
from dual) t,
(SELECT *
FROM table(SOMEOWNERUSER.SOMEPACKAGE.getPipelinedTable(to_char(t.sd,
'dd.mm.yyyy'),
to_char(t.ed,
'dd.mm.yyyy')))) z
But I get error ORA-00904: "T"."SD": invalid identifier. What am I doing wrong?
You just need to directly reference the table() in the same level join as the t subquery:
create type dt_type as object (dt date);
/
create type dt_tab as table of dt_type;
/
create or replace function getpipelinedtable (p_sd date, p_ed date)
return dt_tab
as
l_tab dt_tab := dt_tab();
begin
for i in 1 .. (p_ed - p_sd) + 1
loop
l_tab.extend;
l_tab(l_tab.last) := dt_type(p_sd -1 + i);
end loop;
return l_tab;
end getpipelinedtable;
/
SELECT z.*
FROM (SELECT (CASE WHEN TRUNC (TO_DATE ('01.02.2015', 'DD.MM.YY'), 'MM') = TO_DATE ('01.02.2015', 'DD.MM.YY')
THEN TRUNC (ADD_MONTHS (SYSDATE, -1), 'MM')
ELSE TRUNC (SYSDATE - 1)
END) AS sd,
TRUNC (SYSDATE) ed
FROM DUAL) t,
TABLE ( getpipelinedtable (t.sd, t.ed) ) z;
DT
----------
01/01/2015
02/01/2015
03/01/2015
04/01/2015
05/01/2015
<snip>
06/02/2015
07/02/2015
08/02/2015
09/02/2015
drop function getpipelinedtable;
drop type dt_tab;
drop type dt_type;

Oracle Duration Function

Why isn't my PL/SQL duration function working correctly? In the query below, I manually calculate the 'hh:mm' the same way as in the function below. However, I get different results.
Calling Query:
WITH durdays AS (SELECT SYSDATE - (SYSDATE - (95 / 1440)) AS durdays FROM DUAL)
SELECT TRUNC (24 * durdays) AS durhrs,
MOD (TRUNC (durdays * 1440), 60) AS durmin,
steven.duration (SYSDATE - (95 / 1400), SYSDATE) duration
FROM durdays
Output:
durhrs: 1
durmin: 35
duration: '1:38'
Function code:
CREATE OR REPLACE FUNCTION steven.duration (d1 IN DATE, d2 IN DATE)
RETURN VARCHAR2 IS
tmpvar VARCHAR2 (30);
durdays NUMBER (20,10); -- Days between two DATEs
durhrs BINARY_INTEGER; -- Completed hours
durmin BINARY_INTEGER; -- Completed minutes
BEGIN
durdays := d2-d1;
durhrs := TRUNC(24 * durdays);
durmin := MOD (TRUNC(durdays * 1440), 60);
tmpvar := durhrs || ':' || durmin;
RETURN tmpvar;
END duration;
/
I think you might have a small typo:
WITH durdays AS (SELECT SYSDATE - (SYSDATE - (95 / 1440)) AS durdays FROM DUAL)
^^^^
SELECT TRUNC (24 * durdays) AS durhrs,
MOD (TRUNC (durdays * 1440), 60) AS durmin,
lims_sys.duration (SYSDATE - (95 / 1400), SYSDATE) duration
^^^^
Once this is fixed it works OK:
SQL> WITH durdays AS (SELECT SYSDATE - (SYSDATE - (95 / 1440)) AS durdays FROM DUAL)
2 SELECT TRUNC (24 * durdays) AS durhrs,
3 MOD (TRUNC (durdays * 1440), 60) AS durmin,
4 duration (SYSDATE - (95 / 1440), SYSDATE) duration
5 FROM durdays
6 ;
DURHRS DURMIN DURATION
---------- ---------- ----------------
1 34 1:34
sql>set time on
Then you will get the prompt like:
20:24:35 sql> select count(*) from v$session;
count(*)
-----------
78
20:24:50 sql>
Now you can come to know the query duration is 15sec's