I have written a query to extract hours between dates. This will basically retrieve the time entry for the employee. When I am executing the below query, It is working fine but when i add the extract function I am getting an error :
ORA-30076: invalid extract field for extract source
select emp_id, start_date , stop_date ,
(ROUND(SUM(
(EXTRACT (DAY FROM line_stop_time - line_start_time)*(3600*24))
+ (EXTRACT (HOUR FROM line_stop_time - line_start_time)*(3600))
+ (EXTRACT (MINUTE FROM line_stop_time - line_start_time)*(60))
+ (EXTRACT (SECOND FROM line_stop_time- line_start_time)*1)
) / (3600), 2) )recorded
from
(SELECT DISTINCT papf.person_number emp_id,
( To_char (sh21.start_time, 'DD-MM-YYYY') ) start_date,
( To_char (sh21.stop_time, 'DD-MM-YYYY') ) stop_date,
To_char(sh21.start_time, 'HH24:MI') line_start_time,
To_char(sh21.stop_time, 'HH24:MI') line_stop_time
FROM per_all_people_f papf,
per_all_assignments_m asg,
per_legal_employers ple,
hwm_tm_rec sh21,
hwm_tm_rec_grp_usages sh22,
hwm_tm_rec_grp sh23,
-- hwm_tm_rec_grp sh29,
hwm_grp_type sh24,
hwm_tm_rep_atrb_usages sh25,
hwm_tm_rep_atrbs sh26,
hwm_tm_statuses sh27,
hwm_tm_status_def_b sh28
WHERE papf.person_id = asg.person_id(+)
AND asg.legal_entity_id = ple.organization_id
AND asg.primary_flag = 'Y'
AND asg.assignment_status_type = 'ACTIVE'
AND ple.status = 'A'
AND Trunc (SYSDATE) BETWEEN papf.effective_start_date AND
papf.effective_end_date
AND Trunc (SYSDATE) BETWEEN asg.effective_start_date AND
asg.effective_end_date
AND Trunc (SYSDATE) BETWEEN ple.effective_start_date AND
ple.effective_end_date
AND papf.person_number = '55'
AND sh21.latest_version = 'Y'
AND sh21.resource_type = 'PERSON'
AND sh21.tm_rec_id = sh22.tm_rec_id
AND sh21.tm_rec_version = sh22.tm_rec_version
AND sh21.layer_code = 'TIME_RPTD'
AND sh22.layer_code = 'TIME_RPTD'
AND sh22.tm_rec_grp_id = sh23.tm_rec_grp_id
AND sh22.tm_rec_grp_version = sh23.tm_rec_grp_version
AND sh23.latest_version = 'Y'
AND sh21.resource_id = sh23.resource_id
AND sh23.grp_type_id = sh24.grp_type_id
--AND sh25.usages_source_id =sum.TM_REC_GRP_ID
AND sh21.tm_rec_id = sh25.usages_source_id
AND sh21.tm_rec_version = sh25.usages_source_version
AND sh25.usages_type = 'TIME_RECORD'
AND sh24.name = 'Processed TimecardDay'
AND sh25.tm_rep_atrb_id = sh26.tm_rep_atrb_id
AND sh26.attribute_category IN (SELECT base_element_name
FROM pay_element_types_f)
AND sh21.resource_id = papf.person_id
AND sh21.tm_rec_type IN ( 'RANGE', 'MEASURE' )
AND sh27.tm_status_def_id = sh28.tm_status_def_id
AND sh27.tm_bldg_blk_id = sh21.tm_rec_id
AND sh27.tm_bldg_blk_version = sh21.tm_rec_version
ORDER BY papf.person_number,
( To_char (sh21.start_time, 'DD-MM-YYYY') ) DESC)
group by emp_id, start_date , stop_date
How can i change the extract function to give me the same result. Is there an alternative for extract function to give the same results ?
Try converting your columns to date first to avoid error...:
EXTRACT (DAY FROM to_date(line_stop_time , 'dd-mm-yyyy'))
- EXTRACT (DAY FROM to_date(line_start_time, 'dd-mm-yyyy'))
Here is the DEMO.
And for the HOUR, MINUTE and SECOND use the format mask 'dd-mm-yyyy hh24:mi:ss'
Hope this will help!
You can easily find the hours between two date using:
select (end_date - start_date) * 24 from dual;
If your column is of type timestamp then use the following:
SELECT
EXTRACT(DAY FROM DIFF) * 24 +
EXTRACT(HOUR FROM DIFF) +
ROUND(EXTRACT(MINUTE FROM DIFF) / 60, 2) +
ROUND(EXTRACT(SECOND FROM DIFF) / 3600, 2) TOTAL_HOURS
FROM
(SELECT END_TIMESTAMP - START_TIMESTAMP AS DIFF
FROM DUAL)
-- or --
select (CAST(END_TIMESTAMP AS DATE)- CAST(START_TIMESTAMP AS DATE) ) * 24 from dual;
Cheers!!
Well,
Your LINE_STOP_TIME and LINE_START_TIME are character strings, not dates, so you cannot extract date info from them. Remove the TO_CHAR from the inline SELECT and it should be ok.
Related
I want to connect the following two queries with union all.
First query is following:
WITH
week_source
AS
(SELECT week_desc,
TO_CHAR (day_date, 'IYYY') || 'W' || TO_CHAR (day_date, 'IW')
week,
(SELECT DISTINCT day_number
FROM period_day
WHERE day_key = d.day_key AND day_number NOT IN ('H', 'W') AND WEEK_NUM !=to_char(sysdate, 'WW')
)
workdays_count
FROM period_day d
WHERE TO_CHAR (day_date, 'IYYYIW') BETWEEN TO_CHAR (
(SYSDATE - 1) - 28,
'IYYYIW')
AND TO_CHAR (
(SYSDATE - 1),
'IYYYIW')
)
SELECT DISTINCT week_desc, week, workdays_count
FROM week_source
WHERE workdays_count IS NOT NULL
ORDER BY week;
It gives following table
The second query is:
SELECT 'W' || TO_CHAR (SYSDATE - 1, 'IW') WEEK_DESC,
TO_CHAR (SYSDATE - 1, 'IYYYIW') WEEK,
COUNT (day_date) WORKDAYS_COUNT
FROM period_day
WHERE day_number NOT IN ('H', 'W')
AND TO_CHAR (day_date, 'IYYYIW') =
TO_CHAR ((SYSDATE - 1), 'IYYYIW')
AND day_date <= (SYSDATE - 1)
which gives the following table
Any help?
You have to put order by clauseat the end.
If the first query returns 3 columns with data types varchar, int, varchar then the second query has to return columns of same data types.
WITH week_source AS
(SELECT week_desc,
TO_CHAR (day_date, 'IYYY') || 'W' || TO_CHAR (day_date, 'IW') week,
(SELECT DISTINCT day_number
FROM period_day
WHERE day_key = d.day_key
AND day_number NOT IN ('H', 'W')
AND WEEK_NUM !=to_char(sysdate, 'WW')) workdays_count
FROM period_day d
WHERE TO_CHAR (day_date, 'IYYYIW') BETWEEN
TO_CHAR ((SYSDATE - 1) - 28, 'IYYYIW')
AND
TO_CHAR ((SYSDATE - 1),'IYYYIW'))
SELECT DISTINCT week_desc, week, workdays_count
FROM week_source
WHERE workdays_count IS NOT NULL
union all
SELECT 'W' || TO_CHAR (SYSDATE - 1, 'IW') WEEK_DESC,
TO_CHAR (SYSDATE - 1, 'IYYYIW') WEEK,
to_char(COUNT(day_date)) WORKDAYS_COUNT
FROM period_day
WHERE day_number NOT IN ('H', 'W')
AND TO_CHAR (day_date, 'IYYYIW') = TO_CHAR ((SYSDATE - 1), 'IYYYIW')
AND day_date <= (SYSDATE - 1)
ORDER BY week
In this DEMO you can see the errors returning when the order by is located after the first query. Also you can see what error you get if I just remove the order by clause and this second error is because data types that first query is returning is different that the data types second row is returning. That is why I have added to_char to your count function in your second query. You could of also add to_number in your first query like this: SELECT DISTINCT to_number(day_number).
I have a report which should display enrollment data only within 2 date ranges Jan-June or July-dec depending on current date.
Scenarios:
If the current date is 042020 then I should display enrollement data between this range: 072019-122019
If the current date is 072020 then I should display enrollement data between this range: 012020-062020
If the current date is 022021 then I should display enrollement data between this range: 072020-122020
Current query reports everything past 6 months with his query.
select * from enrollement where enrollement_dt > add_months(sysdate - 6);
Is there any function available in oracle to do the same or how do i get the logic in a single statement?
Any help with this is highly appreciated.
You may try below query -
select *
from enrollement
WHERE TO_CHAR(enrollement_dt, 'MMYYYY') >= CASE WHEN TO_CHAR(SYSDATE, 'mm') <= '06'
THEN TO_DATE('07' || EXTRACT(YEAR FROM SYSDATE) - 1, 'MMYYYY')
ELSE THEN TO_DATE('01' || EXTRACT(YEAR FROM SYSDATE), 'MMYYYY')
END
AND TO_CHAR(enrollement_dt, 'MMYYYY') <= CASE WHEN TO_CHAR(SYSDATE, 'mm') <= '06'
THEN TO_DATE('12' || EXTRACT(YEAR FROM SYSDATE) - 1, 'MMYYYY')
ELSE THEN TO_DATE('06' || EXTRACT(YEAR FROM SYSDATE), 'MMYYYY')
END
Basically you want to truncate to the half-year. But Oracle doesn't support this.
One method counts half-years and compares them. You want the previous half year from the current date. That would be:
select (extract(year from sysdate) * 2 + floor(extract(month from sysdate) - 1) / 6) - 1
from dual
You can use this same formula:
where (extract(year from enrollement_dt) * 2 + floor(extract(month from enrollement_dt) - 1) / 6) - 1 =
extract(year from sysdate) * 2 + floor(extract(month from sysdate) - 1) / 6) - 1
)
from dual;
Unfortunately that can't use an index on the column. So, we can revisit this. You can get the first day of the current half using some date arithmetic:
select trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month
from dual
That just needs to be plugged into a where clause:
where enrollement_dt >= trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month - interval '6' month and
enrollement_dt < trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month
Voila! An expression that can even use an index.
You can use the below to get the start date and end date for enrollment
WITH data
AS (SELECT TRUNC(SYSDATE) curr_date from dual
),
d2
AS (SELECT curr_date,
To_date('0107'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
start_first_half,
To_date('3112'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
end_first_half,
To_date('0101'
||Extract (year FROM curr_date), 'ddmmyyyy')
start_second_half,
To_date('3006'
||Extract (year FROM curr_date), 'ddmmyyyy')
end_second_half
FROM data)
SELECT curr_date,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
start_second_half
ELSE start_first_half
END start_date1,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
end_second_half
ELSE end_first_half
END end_date1
FROM d2
You can use it in your query like below
Select * from enrollment_table a, (WITH data
AS (SELECT TRUNC(SYSDATE) curr_date from dual
),
d2
AS (SELECT curr_date,
To_date('0107'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
start_first_half,
To_date('3112'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
end_first_half,
To_date('0101'
||Extract (year FROM curr_date), 'ddmmyyyy')
start_second_half,
To_date('3006'
||Extract (year FROM curr_date), 'ddmmyyyy')
end_second_half
FROM data)
SELECT curr_date,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
start_second_half
ELSE start_first_half
END start_date1,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
end_second_half
ELSE end_first_half
END end_date1
FROM d2 ) b
where a.enrollment_date >=b.start_date1
and a.enrollment_date <=b.end_date1
For the query below, I'm trying to pull a specific date range depending on the current day of the month. If it's the 20th or less (e.g. "2/7/2020") then I want the date range for January. Otherwise, I want the date range for February. Is it possible to be done with a case statement? Or there is a better way?
SELECT
account,
start_date,
amount
FROM
table1
WHERE
CASE
WHEN (
SELECT
CAST(EXTRACT(DAY FROM sysdate) AS NUMBER)
FROM
dual
) <= 20 THEN
start_date
BETWEEN '2020-01-01' AND '2020-01-31'
ELSE start_date BETWEEN '2020-02-01' AND '2020-02-29'
END
You can do this by avoiding the case statement and using truncate the date - 20 to the month, e.g.:
SELECT account,
start_date,
amount
FROM table1
WHERE start_date >= TRUNC(SYSDATE - 20, 'mm')
AND start_date < add_months(TRUNC(dt - 20, 'mm'), 1);
If you really had to use a CASE expression (you can't use a CASE statement in SQL), you would need to do something like:
SELECT account,
start_date,
amount
FROM table1
WHERE start_date >= CASE WHEN to_char(SYSDATE, 'dd') <= '20' THEN add_months(TRUNC(SYSDATE, 'mm'), -1) ELSE TRUNC(SYSDATE, 'mm') END
AND start_date < CASE WHEN to_char(SYSDATE, 'dd') <= '20' THEN TRUNC(SYSDATE, 'mm') ELSE add_months(TRUNC(SYSDATE, 'mm'), 1) END;
N.B. if you're using a function, you don't need to wrap it in a select .. from dual, you can use it directly in the SQL statement.
I've also assumed that you want a dynamic range, e.g. if the day of the month is 20 or less, the range is for the previous month, otherwise the current month.
ETA: You would use the above two queries if there is an index on the start_date column, otherwise you could simply do:
SELECT account,
start_date,
amount
FROM table1
WHERE TRUNC(start_date, 'mm') = TRUNC(SYSDATE - 20, 'mm');
Case statements return single values. As such you should pull out the start date and you'll need two case statements.
select account, start_date, amount
from table1 where
start_date between
(case
when (select cast(extract(day from sysdate) as number) from dual) <= 20 then '2020-01-01'
else '2020-02-01'
end) and
(case
when (select cast(extract(day from sysdate) as number) from dual) <= 20 then '2020-01-31'
else '2020-02-29'
end)
One method subtracts 20 days and then gets the month boundary:
where start_date >= trunc(sysdate - interval '20' day, 'MON') and
start_date < trunc(sysdate - interval '20' day, 'MON') + interval '1' month
This approach is index (and partition) friendly -- an appropriate index on start_date can be used. It is also safe if start_date has time components.
Note: You can use sysdate without having to use a subquery.
You can use or operator with last_day function as following:
Select * from your_table
Where (
start_date <= trunc(sysdate,'mm') + 20
and start_date between trunc(sysdate,'mm') - interval '1' month and trunc(sysdate,'mm') - 1
)
Or
(
start_date > trunc(sysdate,'mm') + 20
and start_date between trunc(sysdate, 'mm') and last_day(sysdate)
)
This approach will use index on start_date, if any.
Cheers!!
select account, amount, start_date
from table1
where ( ( (select cast (extract (day from sysdate) as number) from dual) <= 20
and start_date between date '2020-01-01' and date '2020-01-31')
or ( (select cast (extract (day from sysdate) as number) from dual) > 20
and start_date between date '2020-02-01' and date '2020-02-29')
);
Using CASE expressions as BETWEEN operands:
SELECT account
, start_date
, amount
FROM table1
WHERE start_date BETWEEN CASE
WHEN extract(day from sysdate) <= 20
THEN trunc(sysdate -interval '1' month, 'month')
ELSE trunc(sysdate, 'month')
END
AND CASE
WHEN extract(day from sysdate) <= 20
THEN last_day(sysdate -interval '1' month)
ELSE last_day(sysdate)
END
How to round up time interval to next day in Oracle SQL?
select apppackage
, numtodsinterval(
sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
), 'SECOND') as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
The output of this is
'com.Freesoul.Rotter' '+2969 04:32:47.000000' '3'
and desired output is
'com.Freesoul.Rotter' '2970' '3'
but if the output of query is
'com.Freesoul.Rotter' '+2969 00:00:00.000000' '3'
then desired output is
'com.Freesoul.Rotter' '2969' '3'
column period is of INTERVAL DAY(9) TO SECOND(6) type
and i won't mind if the retention_period is changed to number datatype.
I'll be grateful if anyone can suggest change in my query to attain the desired output.
The result of your sum is in seconds, so you don't really need to convert it to an interval at all. Just divide by 60*60*24 to get the answer in days, and round it up with ceil():
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
Demo with artificial data in a CTE just to mimic your expected results, for both scenarios:
-- CTE for sample data
with retentions (apppackage, periods) as (
select 'com.Freesoul.Rotter', interval '+2967 04:32:47.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '+2967 00:00:00.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
)
-- actual query
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
-- extra clause for dummy data
or apppackage = 'com.Freesoul.XYZ'
group by apppackage;
APPPACKAGE RETENTION_PERIOD USERS
------------------- ---------------- ----------
com.Freesoul.XYZ 2969 3
com.Freesoul.Rotter 2970 3
Your expected output shows a plain number. If you actually want it as an interval, but as the whole number of days, just pass ceil'd number into numtodsinterval or more simply (and usually faster for some reason) multiply by interval '1' day.
With the same dummy data:
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) * interval '1' day as retention_period
, count(apppackage) as users
...
APPPACKAGE RETENTION_PERIOD USERS
------------------- --------------------- ----------
com.Freesoul.XYZ +2969 00:00:00.000000 3
com.Freesoul.Rotter +2970 00:00:00.000000 3
As #mathguy pointed out, you probably don't need or want the trunc() call in there; that is removing the fractional seconds from each period before they are summed, which sounds insignificant but could easily affect the result you get.
How about this?
CASE WHEN numtodsinterval(extract (day from periods), 'DAY') = periods THEN
extract (day from periods)
ELSE
extract (day from periods) + 1
END
I got this query on order to get all the days from the first day of the year (01/01/2018) to the end of next year (31/12/2019).
SELECT MYDATE,
TO_CHAR(NR_OF_SUNDAYS + 1,'FM09') WEEK_NUM,
FROM
(
SELECT MYDATE,
( (TRUNC(MYDATE,'DAY') - TRUNC(TRUNC(MYDATE,'YYYY'),'DAY')) / 7 ) +
CASE WHEN TO_CHAR(TRUNC(MYDATE,'YYYY'),'DAY') = 'SUN' THEN 1 ELSE 0 END AS NR_OF_SUNDAYS
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -
TRUNC (SYSDATE, 'YY')
)
)
I need a column that specifies the following cases:
1) CASE WHEN MYDATE < TO_CHAR(SYSDATE, 'DD/MM/YYYY') THEN 'PAST DUE'
(this works its easy and no problem)
2) if my current =< mydate
week_num then 'CURRENT WEEK'(Excluding PAST DUE)
3) if my current week + one week then
'NEXT WEEK' (Excluding PAST DUE)
4) else FUTURE
Thanks a lot for your help.
So, in my answer I tried retain the logic behind your week number calculation.
However keep in mind that you could calculate week number using oracle to_char(date,'WW'), to_char(date,'IW'), to_char(date,'W') functions and then your life would be easier.
WW Week of year (1-53) where week 1 starts on the first day of the year and continues to the seventh day of the year.
W Week of month (1-5) where week 1 starts on the first day of the month and ends on the seventh.
IW Week of year (1-52 or 1-53) based on the ISO standard.
Having said all that here is my solution that uses only sql (note that defining and using a function would be a lot easier), based on your calculation method.
with date_table as (
SELECT MYDATE, to_number(TO_CHAR(NR_OF_SUNDAYS + 1,'FM09')) WEEK_NUM, to_number(to_char(MYDATE+1,'IW')) as nu
FROM
(
SELECT MYDATE,
( (TRUNC(MYDATE,'DAY') - TRUNC(TRUNC(MYDATE,'YYYY'),'DAY')) / 7 ) +
CASE WHEN TO_CHAR(TRUNC(MYDATE,'YYYY'),'DY', 'NLS_DATE_LANGUAGE = american') = 'SUN' THEN 1 ELSE 0 END AS NR_OF_SUNDAYS
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -TRUNC (SYSDATE,'YY')
)
)
),
todays_week as
(
select distinct WEEK_NUM from date_table
where trunc(sysdate)=trunc(mydate)
),
pre_final as (
select MYDATE,WEEK_NUM, (select WEEK_NUM from todays_week) as todaysweek from date_table)
select MYDATE,sysdate,WEEK_NUM,todaysweek,
case when trunc(MYDATE) < trunc(sysdate) then 'PAST DUE'
when todaysweek = WEEK_NUM and abs(MYDATE-sysdate)<=7 then 'CURRENT WEEK'
when todaysweek +1 = WEEK_NUM and abs(MYDATE-sysdate)<=14 then 'Next Week'
else 'Future' end as description
from pre_final;
The main idea is to find today's week number and then use case when.
Here is my fiddle link with the results.
http://sqlfiddle.com/#!4/3149e4/148
EDIT 1:
Now, similar results one could achive with something like this:
select res.*,
case when trunc(MYDATE) < trunc(sysdate) then 'PAST DUE'
when todaysweek = WEEK_NUM and abs(MYDATE-sysdate)<=7 then 'CURRENT WEEK'
when todaysweek +1 = WEEK_NUM and abs(MYDATE-sysdate)<=14 then 'Next Week'
else 'Future' end as description
from (
SELECT MYDATE, to_number(to_char(MYDATE,'IW')) as WEEK_NUM,to_number(to_char(sysdate,'IW')) as todaysweek
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -TRUNC (SYSDATE,'YY')
)) res