Arithmetic operations on Date values in Oracle - oracle

I am struggling with some case...
there is table, in which I have employee attendance records, for example:
for empID=1;
empID time Type date
-------------------------------
1 9:22 in sameday
1 11:23 out sameday
1 14:35 in sameday
1 16:21 out sameday
particularly, I want some fn/procedure that will take an EmpID and DATE parameters, and then based on this data if I'll write: select proc(EmployeeID, Date) from dual(or maybe some other table?) it should do such a work:
take first couples in table (table is ordered be ascending as default order), then calculate FROM first OUT (11:23) to first IN(9:22) time, save that time somewhere (int tempResult) and then calculate second couple, and calculate second tempResult and in finalResult, it should count the total time, like finalResult+=finalResult+tempResult (if it has been an iterations in loop);
I think it would be done someway like, in foreach (or whatever it is in pl/sql oracle) take first select with top result, then, second.. and so forth... and on each iteration calculate desire goal.
so.. logics is ok with me I think :), but the problem is that I'm not that familiar with PL/SQL, if it had been written in Java it should have come easy to me.
I will pay lots of Thanks to some one who will help me...
its crucial for me to day.
thanks in advance.
I have Date and Time is separate columns, like:
date time
----------------------
11-09-2013 12:34
so, I made little change like this
FOR rec IN
( SELECT t.EID, to_char(t.devent_date, 'DD.MM.YY') ||' '|| t.RegTime, t.acttype from turnicate_ie t WHERE t.EID = i_emp_id ORDER BY t.EID
)
LOOP
but it states that package or function is in incorrect state...
(t.devent_date is 11.09.2013, t.RegTime is 16:23)

The below will give you some idea of using plsql:
you need to use many logic of calculating total working hours, like multiple inputs within same time, multiple empId etc.
create table my_test ( empId number, log_time date, type varchar2(3));
INSERT INTO my_test VALUES( 1, to_date('11/sep/2013 09:22:00 am', 'dd/mon/yyyy hh:mi:ss am'), 'in');
INSERT INTO my_test VALUES( 1, to_date('11/sep/2013 11:23:00 am', 'dd/mon/yyyy hh:mi:ss am'), 'out');
INSERT INTO my_test VALUES( 1, to_date('11/sep/2013 02:35:00 pm', 'dd/mon/yyyy hh:mi:ss pm'), 'in');
INSERT INTO my_test VALUES( 1, to_date('11/sep/2013 04:21:00 pm', 'dd/mon/yyyy hh:mi:ss pm'), 'out');
CREATE OR REPLACE
FUNCTION total_hours(
i_emp_id IN NUMBER)
RETURN VARCHAR2
IS
l_total_seconds NUMBER := 0;
in_time DATE;
l_total_time VARCHAR2(20);
BEGIN
FOR rec IN
( SELECT log_time, type FROM my_test WHERE empid = i_emp_id ORDER BY log_time
)
LOOP
IF rec.TYPE = 'in' AND in_time IS NULL THEN
in_time := rec.log_time;
END IF;
IF rec.TYPE = 'out' AND in_time IS NOT NULL THEN
l_total_seconds := (rec.log_time - in_time)*24*60*60 + l_total_seconds;
in_time := NULL;
END IF;
END LOOP;
SELECT TO_CHAR(TRUNC(l_total_seconds/3600), 'FM999999990')
|| 'hh '
|| TO_CHAR(TRUNC(mod(l_total_seconds,3600)/60), 'FM00')
|| 'mm '
|| TO_CHAR(mod(l_total_seconds,60), 'FM00')
||'ss'
INTO l_total_time
FROM dual;
RETURN l_total_time;
END;
/
SELECT total_hours(1) from dual;

Related

how to run stored procedure in oracle with loop

i want to call the procedure through anon block like
begin t_maha_del_22_06_p('22.06.2020');end;
but i run it once and want call with loop to take a large date time
like from first 1-st to 15-th august. How can i do it ?
create table t_maha_delete_22_06
(dt date,text varchar2(100));
create or replace procedure my_sch.t_maha_del_22_06_p(p_dt in date default trunc(sysdate) -1) as
begin
delete from t_maha_delete_22_06
where trunc(dt) = p_dt;
commit;
insert into t_maha_delete_22_06
select
trunc(p_dt) dt,
'blablabla' text from dual
commit;
end;
You can do it in loop as follows:
begin
For dt in (select date '2020-08-01' + level - 1 as dates
From dual
Connect by level <= date '2020-08-15' - date '2020-08-01')
Loop
t_maha_del_22_06_p(dt.dates);
End loop;
end;
/

PL SQL 'IF or CASE' using variable date

I am stuck with this and could use advice/help:
Basically, trying to set the date as a variable and then run select statements, using that date variable in the 'WHERE' section of the query. Not sure if I should be using IF or CASE, or neither? If its monday, i want to run 1 set of dates (prev thur and fri) any other day (just sysdate-2 and sysdate-1) Any help is much appreciated!
Code is below:
DECLARE
today_date number;
start_date date;
end_date date;
BEGIN
today_date := to_char(sysdate, 'D');
start_date := case when today_date ='2' then 'sysdate-4' else 'sysdate-2'
end;
end_date := case when today_date ='2' then 'sysdate-3' else 'sysdate-1' end;
SELECT COLUMN A, COLUMN B, COLUMN C, COLUMN D
FROM /*csv*/REPORT_NAME
WHERE COLUMN B between trunc(start_date)+21/24 and trunc(end_date)+21/24 and
BOOK_NAME = 'xxxxxx' and SERVER = 'xxxxxx' and EX_ACTION = 'xxxxx';
end;
You're mixing variables/functions and strings. This should work.
DECLARE
today_date number;
start_date date;
end_date date;
BEGIN
today_date := to_char(sysdate, 'D');
start_date := case when today_date ='2' then sysdate-4 else sysdate-2
end;
end_date := case when today_date ='2' then sysdate-3 else sysdate-1 end;
/* this won't work without declaring a cursor, and returning it to the client
SELECT COLUMN A, COLUMN B, COLUMN C, COLUMN D
FROM REPORT_NAME
WHERE COLUMN B between trunc(start_date)+21/24 and trunc(end_date)+21/24 and
BOOK_NAME = 'xxxxxx' and SERVER = 'xxxxxx' and EX_ACTION = 'xxxxx';
*/
end;
Note you also have some implicit type conversion happening. today_date should probably be char(1) instead.
You don't really need variables for this, and you don't need PL/SQL; you can calculate the dates as part of the where clause using case expressions in that instead:
SELECT /*csv*/ COLUMN_A, COLUMN_B, COLUMN_C, COLUMN_D
FROM REPORT_NAME
WHERE COLUMN_B >= trunc(sysdate) - case to_char(sysdate, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH')
when 'Mon' then 4 else 2 end + 21/24
AND COLUMN_B < trunc(sysdate) - case to_char(sysdate, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH')
when 'Mon' then 3 else 1 end + 21/24
AND BOOK_NAME = 'xxxxxx'
AND SERVER = 'xxxxxx'
AND EX_ACTION = 'xxxxx';
I've taken the liberty of changing the logic from being based on the day number being 2 to it having a specific day abbreviation, because the value D returns is based on your NLS settings so it could vary (and give unexpected results) for someone else running your code. As day names and abbreviations are also NLS-dependent I've specified the language to use. (You can't specify how D is used in the same way, unfortunately).
I've changed the time window slightly, so it goes from 9pm on one day to up to but not including 9pm the next day. If you use between then it's inclusive at both ends, so runs on consecutive days you both pick up any data at exactly 21:00:00 from the overlapping day. That probably isn't what you want (but if it really is, just change < to <=, or revert to between if you prefer...).
If you want a PL/SQL wrapper you can do the same thing, but you either have to select the results into something like a collection, or use a ref cursor to return the result set to the caller. It isn't clear if you actually need or want to do that though.
have you tried selecting into your variables?
DECLARE
TODAY_DATE NUMBER;
START_DATE DATE;
END_DATE DATE;
BEGIN
TODAY_DATE := TO_CHAR (SYSDATE, 'D');
SELECT CASE WHEN TODAY_DATE = '2' THEN SYSDATE - 4 ELSE SYSDATE - 2 END,
CASE WHEN TODAY_DATE = '2' THEN SYSDATE - 3 ELSE SYSDATE - 1 END
INTO START_DATE, END_DATE
FROM DUAL;
SELECT COLUMN_A, COLUMN_B, COLUMN_C, COLUMN_D
FROM REPORT_NAME /*csv*/
WHERE COLUMN_B BETWEEN TRUNC (START_DATE) + 21 / 24
AND TRUNC (END_DATE) + 21 / 24
AND BOOK_NAME = 'xxxxxx'
AND SERVER = 'xxxxxx'
AND EX_ACTION = 'xxxxx';
END;

Variable/Literal replacement for PL/SQL Cursors?

I often have to debug cursors in Oracle PL/SQL. My problem is that I end up with a few hundered lines big cursors with like 50+ variables and constants. I'm searching for a way to get a version of the statement where constants and variables are replaced with their literals. If I want to find out why the cursor isn't showing the record/line it should I end up replacing those variables/literals for 30 minutes before I can run the select and comment out some of the statements to find out what's wrong.
So if I have something like
CURSOR cFunnyCursor (
v1 NUMBER,
v2 NUMBER
) IS
SELECT * FROM TABLE
WHERE col1 = v1
AND col2 != v2
AND col3 = CONSTANT;
I need the SELECT like this:
SELECT * FROM TABLE
WHERE col1 = 123
AND col2 != 5324
AND col3 = 'ValueXyz';
is there any way to get/log the SELECT in that way so I could just copy paste it in a new SQL window so I don't have to spend 30 minutes to replace that stuff? (should be something I can reuse that's not bind to that special cursor because I need that stuff quite often on a ton of different cursors).
The below function replaces bind variables with recent literals, using data from GV$SQL_BIND_CAPTURE. Oracle bind metadata is not always available, so the below function may not work with all queries.
Create the function:
create or replace function get_sql_with_literals(p_sql_id varchar2) return clob authid current_user is
/*
Purpose: Generate a SQL statement with literals, based on values in GV$SQL_BIND_CAPTURE.
This can be helpful for queries with hundreds of bind variables (or cursor sharing),
and you don't want to spend minutes manually typing each variable.
*/
v_sql_text clob;
v_names sys.odcivarchar2list;
v_values sys.odcivarchar2list;
begin
--Get the SQL_ID and text.
--(Use dynamic SQL to simplify privileges. Your user must have access to GV$ views,
-- but you don't need to have them directly granted to your user, role access is fine.)
execute immediate
q'[
select sql_fulltext
from gv$sql
--There may be multiple rows, for clusters or child cursors.
--Can't use distinct with CLOB SQL_FULLTEXT, but since the values will be the same
--we can pick any one of the rows.
where sql_id = :p_sql_id
and rownum = 1
]'
into v_sql_text
using p_sql_id;
--Try to find the binds from GV$SQL_MONITOR. If the values exist, this is the most accurate source.
execute immediate
q'[
--Get the binds for the latest run.
select
case
when name like ':SYS_%' then ':"' || substr(name, 2) || '"'
else name
end name,
case
when dtystr like 'NUMBER%' then nvl(the_value, 'NULL')
when dtystr like 'VARCHAR2%' then '''' || the_value || ''''
when dtystr like 'DATE%' then 'to_date('''||the_value||''', ''MM/DD/YYYY HH24:MI:SS'')'
--From: https://ardentperf.com/2013/11/19/convert-rawhex-to-timestamp/
when dtystr like 'TIMESTAMP%' then
'to_timestamp('''||
to_char( to_number( substr( the_value, 1, 2 ), 'xx' ) - 100, 'fm00' ) ||
to_char( to_number( substr( the_value, 3, 2 ), 'xx' ) - 100, 'fm00' ) ||
to_char( to_number( substr( the_value, 5, 2 ), 'xx' ), 'fm00' ) ||
to_char( to_number( substr( the_value, 7, 2 ), 'xx' ), 'fm00' ) ||
to_char( to_number( substr( the_value, 9, 2 ), 'xx' )-1, 'fm00' ) ||
to_char( to_number( substr( the_value,11, 2 ), 'xx' )-1, 'fm00' ) ||
to_char( to_number( substr( the_value,13, 2 ), 'xx' )-1, 'fm00' ) ||
''', ''yyyymmddhh24miss'')'
else 'Unknown type: '||dtystr
end the_value
from
(
select xmltype.createXML(binds_xml) binds_xml
from
(
select binds_xml, last_refresh_time, max(last_refresh_time) over () max_last_refresh_time
from gv$sql_monitor
where sql_id = :p_sql_id
and binds_xml is not null
)
where last_refresh_time = max_last_refresh_time
and rownum = 1
) binds
cross join
xmltable('/binds/bind' passing binds.binds_xml
columns
name varchar2(128) path '#name',
dtystr varchar2(128) path '#dtystr',
the_value varchar2(4000) path '/'
)
--Match longest names first to avoid matching substrings.
--For example, we don't want ":b1" to be matched to ":b10".
order by length(name) desc, the_value
]'
bulk collect into v_names, v_values
using p_sql_id;
--Use gv$sql_bind_capture if there was nothing from SQL Monitor.
if v_names is null or v_names.count = 0 then
--Get bind data.
execute immediate
q'[
select
name,
--Convert to literals that can be plugged in.
case
when datatype_string like 'NUMBER%' then nvl(value_string, 'NULL')
when datatype_string like 'VARCHAR%' then '''' || value_string || ''''
when datatype_string like 'DATE%' then 'to_date('''||value_string||''', ''MM/DD/YYYY HH24:MI:SS'')'
--TODO: Add more types here
end value
from
(
select
datatype_string,
--If CURSOR_SHARING=FORCE, literals are replaced with bind variables and use a different format.
--The name is stored as :SYS_B_01, but the actual string will be :"SYS_B_01".
case
when name like ':SYS_%' then ':"' || substr(name, 2) || '"'
else name
end name,
position,
value_string,
--If there are multiple bind values captured, only get the latest set.
row_number() over (partition by name order by last_captured desc nulls last, address) last_when_1
from gv$sql_bind_capture
where sql_id = :p_sql_id
)
where last_when_1 = 1
--Match longest names first to avoid matching substrings.
--For example, we don't want ":b1" to be matched to ":b10".
order by length(name) desc, position
]'
bulk collect into v_names, v_values
using p_sql_id;
end if;
--Loop through the binds and replace them.
for i in 1 .. v_names.count loop
v_sql_text := replace(v_sql_text, v_names(i), v_values(i));
end loop;
--Return the SQL.
return v_sql_text;
end;
/
Run the function:
Oracle only captures the first instance of bind variables. Run this statement before running the procedure to clear existing bind data. Be careful running this statement in production, it may temporarily slow down the system because it lost cached plans.
alter system flush shared_pool;
Now find the SQL_ID. This can be tricky, depending on how generic or unique the SQL is.
select *
from gv$sql
where lower(sql_fulltext) like lower('%unique_string%')
and sql_fulltext not like '%quine%';
Finally, plug the SQL into the procedure and it should return the code with literals. Unfortunately the SQL lost all formatting. There's no easy way around this. If it's a huge deal you could potentially build something using PL/Scope to replace the variables in the procedure instead but I have a feeling that would be ridiculously complicated. Hopefully your IDE has a code beautifier.
select get_sql_with_literals(p_sql_id => '65xzbdjubzdqz') sql
from dual;
Full example with a procedure:
I modified your source code and added unique identifiers so the queries can be easily found. I used a hint because parsed queries do not include regular comments. I also changed the data types to include strings and dates to make the example more realistic.
drop table test1 purge;
create table test1(col1 number, col2 varchar2(100), col3 date);
create or replace procedure test_procedure is
C_Constant constant date := date '2000-01-01';
v_output1 number;
v_output2 varchar2(100);
v_output3 date;
CURSOR cFunnyCursor (
v1 NUMBER,
v2 VARCHAR2
) IS
SELECT /*+ unique_string_1 */ * FROM TEST1
WHERE col1 = v1
AND col2 != v2
AND col3 = C_CONSTANT;
begin
open cFunnyCursor(3, 'asdf');
fetch cFunnyCursor into v_output1, v_output2, v_output3;
close cFunnyCursor;
end;
/
begin
test_procedure;
end;
/
select *
from gv$sql
where lower(sql_fulltext) like lower('%unique_string%')
and sql_fulltext not like '%quine%';
Results:
select get_sql_with_literals(p_sql_id => '65xzbdjubzdqz') sql
from dual;
SQL
---
SELECT /*+ unique_string_1 */ * FROM TEST1 WHERE COL1 = 3 AND COL2 != 'asdf' AND COL3 = to_date('01/01/2000 00:00:00', 'MM/DD/YYYY HH24:MI:SS')
The way I do this is to copy and paste the sql into an editor window, prepend all the variables with : and then run the query. As I use Toad, I get a window prompting me for values for all the bind variables in the query, so I fill those out and the query runs. Values are saved, so the query can be rerun without much hassle, or if you need to tweak a value, you can do.
e.g.:
SELECT * FROM TABLE
WHERE col1 = v1
AND col2 != v2
AND col3 = CONSTANT;
becomes
SELECT * FROM TABLE
WHERE col1 = :v1
AND col2 != :v2
AND col3 = :CONSTANT;
I think you have to use Dynamic SQL functionality to get those variable values. By using ref cursor variable you can even see the output.
Please take a look at the below query.
DECLARE
vString VARCHAR2 (32000);
vResult sys_refcursor;
BEGIN
vString :=
'SELECT * FROM table
WHERE col1 = '|| v1|| '
AND col2 != '|| v2|| '
AND col3 = '|| v;
OPEN vResult FOR vString;
DBMS_OUTPUT.put_line (vString);
END;
If you have a larger Cursor query it is not a efficient way. Because you may need to replace whole Cursor query into Dynamic SQL.
A possible approach would be assiging the cursor to a SYS_REFCURSOR variable, and then assign the SYS_REFCURSOR to a bind variable.
If you run this snippet in Toad, you'll be asked to define the :out variable in the pop-up window: just select Direction: OUT / Type: CURSOR and the dataset will be shown in the "Data Grid" tab.
declare
l_refcur sys_refcursor;
v1 varchar2(4) := 'v1';
v2 varchar2(4) := 'v2';
c_constant varchar2(4) := 'X';
begin
open l_refcur for
SELECT * FROM dual
WHERE dummy = c_CONSTANT;
:out := l_refcur;
end;
Other SQL IDEs should support this feature as well.

oracle select date from partition table

Following statement allows me to retrieve information about a table's partitions:
select table_name, partition_name, high_value from user_tab_partitions where table_name = 'T1';
problem is that, for some unknown reason, high_value column's values are expressed as:
TO_DATE(' 2015-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')
is it possible to retrieve them as date? Or at least do something like "eval" on that expression?
That field is defined as a long type, so depending on the partitioning scheme, it can hold various values (a string like 'ABC', a number 123, a string representation of a date, whatever).
Anyway, sounds like you want to get data from a particular partition, and you're trying to figure out the partition name from the metadata. If thats the case, you can simply use the partition for clause:
select * from my_partitioned_table partition for (to_date('20150801', 'YYYYMMDD'));
Which would select data from the partition housing that particular date (assuming your partitioning by date). This is particularly useful in interval partitioning, where Oracle assigns partition names like SYS_xxxx which seem arbitrary at best.
If you'd like to drop partitions older than a given date, its a bit more tricky. The above syntax is for selecting data, not DDL (alter table). To do that, you could do something like this (loosely tested):
Create a function to identify which partitions hold data with dates less than a given reference date:
create or replace function fn_partition_is_earlier(i_part_tab_name varchar2, i_partition_position number, i_ref_date in date)
return number
is
l_date_str varchar2(2000);
l_date date;
begin
execute immediate 'select high_value from all_tab_partitions where table_name = :tab and partition_position = :pos'
into l_date_str
using i_part_tab_name, i_partition_position;
execute immediate 'select ' || l_date_str || ' from dual' into l_date;
if (l_date < i_ref_date) then
return 1;
end if;
return 0;
end;
Use the function as follows:
with part_name as (
select partition_name
from (
select fn_partition_is_earlier(p.table_name, p.partition_position, to_date('20130501', 'YYYYMMDD')) should_drop_flag, p.*
from all_tab_partitions p
where table_name = 'MY_TAB'
)
where should_drop_flag = 1
)
select 'alter table MY_TAB drop partition ' || part_name.partition_name || ' update global indexes;'
from part_name;
The output would give you the script you'd have the DBAs run off hours.
Hope that helps.

finding working days in sql

I have a table 'country_holiday' which has two columns country_id and holiday_dt , this table doesnt have entries for weekends.
I need to write a procedure that takes 3 inputs start_dt , end_dt and country_id and then iterate over all working dates between the two given dates for given country_id
I tried writing something like this which doesnt work ( i get a blank cursor)
create or replace procedure check_data(p_start_date in date, p_end_date in date, p_country_id in number)
IS
curr_date date;
CURSOR v_buss_days is select p_start_date + rownum -1
from all_objects where rownum <= p_end_date - p_start_date +1
and to_char(p_start_date+rownum-1,'DY') not in ('SAT','SUN')
and p_start_date + rownum -1 not in (select holiday_dt from country_holiday where country_id = p_country_id)
BEGIN
for curr_date in v_buss_days
LOOP
dbms_output.put_line(curr_date)
END LOOP;
END
I tried running the query
select p_start_date + rownum -1
from all_objects where rownum <= p_end_date - p_start_date +1
and to_char(p_start_date+rownum-1,'DY') not in ('SAT','SUN')
this gives me 0 rows with p_start_date='01 dec 2013' and p_end_date='31 dec 2013' , seems like my query to populate cursor is incorrect.
After populating the cursor correctly i face issue
thanks for your help , indeed it works .... but facing issue when i try to use in procedure ....
create or replace procedure check_data(p_start_date in date, p_end_date in date, p_cntry_id in number)
IS
curr_num_of_empoyee number;
curr_date date;
CURSOR v_buss_days is select work_date from
( with dates as
( select p_start_date dt_start, p_end_date dt_end from dual )
select dt_start + (level-1) work_date from dates
connect by level <= (dt_end - dt_start + 1 )
) wdates
where work_date not in ( select HOLIDAY_DATE
from country_holiday
where country_id = p_cntry_id)
and to_char(work_date,'DY') not in ('SAT','SUN')
order by work_date;
BEGIN
for curr_date in v_buss_days
LOOP
select count(*) into curr_num_of_empoyee from employee_details where country_id = p_cntry_id and data_dt = curr_date;
END LOOP;
END;
Error is
19/101 PLS-00382: expression is of wrong type
seems like issue is in part "data_dt = curr_date"
Here is a query I put on SQLFiddle. Remove the WITH clause and replace with your procedure date parameters. You can use the combination of CONNECT BY and LEVEL to generate a set of rows with increasing numeric values. Then, add that to your start date, and filter out from your holiday table and weekends.
select work_date from
(
with dates as
( select to_date('01/01/2014','MM/DD/YYYY') dt_start,
to_date('01/10/2014','MM/DD/YYYY') dt_end
from dual
)
select dt_start + (level-1) work_date
from dates
connect by level <= (dt_end - dt_start + 1 )
) wdates
where work_date not in ( select holiday_dt
from country_holiday
where country_id = 1)
and to_char(work_date,'DY') not in ('SAT','SUN')
order by work_date

Resources