PL SQL iterate loop through date range - oracle

I have a date range, I am trying to take one date every week through a loop
DECLARE
start_date DATE := TO_DATE('06.01.2021', 'dd.MM.yyyy');
end_date DATE := TO_DATE('26.05.2021', 'dd.mm.yyyy');
active_date DATE;
start_number NUMBER;
end_number NUMBER;
BEGIN
start_number := TO_NUMBER(TO_CHAR(start_date, 'j'));
end_number := TO_NUMBER(TO_CHAR(end_date, 'j'));
active_date := start_date;
FOR cur_r IN start_number..end_number
LOOP
INSERT INTO test_tbl
SELECT snap_date FROM s_act
WHERE
snap_date = active_date;
active_date := TRUNC(active_date) + 7;
COMMIT;
END LOOP;
END;
When I execute this script, only one date 06.01.2021 is written to the table through all iterations.
Where am I making a mistake? How can this be fixed?

You do not need PL/SQL for this and can just use a recursive sub-query:
INSERT INTO test_tbl
WITH date_range ( start_date, end_date ) AS (
SELECT DATE '2021-01-06', DATE '2021-05-26' FROM DUAL
UNION ALL
SELECT start_date + INTERVAL '7' DAY,
end_date
FROM date_range
WHERE start_date + INTERVAL '7' DAY <= end_date
)
SELECT snap_date
FROM s_act s
WHERE EXISTS(
SELECT 1
FROM date_range r
WHERE r.start_date = s.snap_date
);
or a hierarchical query:
INSERT INTO test_tbl
SELECT snap_date
FROM s_act s
WHERE EXISTS(
WITH date_range ( start_date, end_date ) AS (
SELECT DATE '2021-01-06', DATE '2021-05-26' FROM DUAL
)
SELECT 1
FROM date_range r
WHERE r.start_date + ( LEVEL - 1 ) * INTERVAL '7' DAY = s.snap_date
CONNECT BY r.start_date + ( LEVEL - 1 ) * INTERVAL '7' DAY <= r.end_date
);
If you really want to use PL/SQL then you can make it much simpler and iterate by weeks rather than days (however, this will be much less efficient as you will have one INSERT per week and the associated context switch from PL/SQL to SQL compared to the SQL solution which is only a single INSERT for the entire operation and no context switches):
DECLARE
start_date DATE := DATE '2021-01-06';
end_date DATE := DATE '2021-05-26';
active_date DATE := start_date;
BEGIN
LOOP
EXIT WHEN active_date > end_date;
INSERT INTO test_tbl
SELECT snap_date FROM s_act
WHERE snap_date = active_date;
active_date := active_date + INTERVAL '7' DAY;
END LOOP;
END;
/
db<>fiddle here

To me, it looks as if everything is, actually, OK with code you wrote, because active_date gets its new value:
SQL> set serveroutput on;
SQL> declare
2 start_date date := to_date('06.01.2021', 'dd.MM.yyyy');
3 end_date date := to_date('26.05.2021', 'dd.mm.yyyy');
4 active_date date;
5 start_number number;
6 end_number number;
7 begin
8 start_number := to_number(to_char(start_date, 'j'));
9 end_number := to_number(to_char(end_date, 'j'));
10 active_date := start_date;
11
12 for cur_r in start_number..end_number
13 loop
14 dbms_output.put_line('Active_date = ' || to_char(active_date, 'dd.mm.yyyy'));
15 /* Commented, as I don't have your tables nor data
16 INSERT INTO test_tbl
17 SELECT snap_date
18 FROM s_act
19 WHERE snap_date = active_date;
20 */
21 active_date := trunc(active_date) + 7;
22 end loop;
23 -- move COMMIT out of the loop!
24 commit;
25 end;
26 /
Active_date = 06.01.2021
Active_date = 13.01.2021
Active_date = 20.01.2021
<snip>
Active_date = 06.09.2023
Active_date = 13.09.2023
PL/SQL procedure successfully completed.
SQL>
You said
When I execute this script, only one date 06.01.2021 is written to the table through all iterations.
This is piece of code responsible for that:
INSERT INTO test_tbl
SELECT snap_date
FROM s_act
WHERE snap_date = active_date;
I interpret it as:
s_act table contains rows only with snap_date equal to 06.01.2021, or
if it contains rows with other dates, maybe they contain a time component (hours, minutes, seconds) and where condition prevents them to be inserted. If that's so, try with
where trunc(snap_date) = active_date
and see what happens.

Related

Oracle PLSQL escaping a single quote

My apologies for the verbose post but the setup is necessary to show my problem and ask a question.
In the anonymous block below I'm trying to construct a string, which encapsulates the table in a single quote ie 'T1' but I've been struggling for the past hour and can use some help.
Secondly, I purposely left out a row in the table partition_rention for table name T2. I suspect a NULL will be returned into the variable when the statement is executed. Will this work?
if v_days is NULL
then
v_days := 30
END IF;
Thanks in advance to all who answer and your expertise
create table partition_rention
(
TABLE_NAME VARCHAR2(30) NOT NULL,
DAYS NUMBER(6),
CONSTRAINT Check_gt0
CHECK (DAYS> 0)
);
/
INSERT into partition_rention (TABLE_NAME, DAYS)
VALUES
('T1', 15);
/
INSERT into partition_rention (TABLE_NAME, DAYS)
VALUES
('T3', 15);
/
CREATE TABLE t1 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL (NUMTODSINTERVAL(7,'DAY'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2022-01-01','YYYY-MM-DD'))
);
/
INSERT /*+ APPEND */ into t1 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(30,'MINUTE') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-01-15')
select dt from dt;
/
create index ix_local on t1 (dt) local;
/
CREATE TABLE t2
(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL (NUMTODSINTERVAL(1,'DAY'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2022-01-01','YYYY-MM-DD'))
);
/
INSERT /*+ APPEND */ into t2 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(30,'MINUTE') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-01-15')
select dt from dt;
/
create index ix_global on t2 (dt);
/
CREATE TABLE t3 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt TIMESTAMP)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (1, 'DAY') ) (
PARTITION OLD_DATA VALUES LESS THAN (TIMESTAMP '2022-01-01 00:00:00.000000')
);
/
INSERT /*+ APPEND */ into t3 (dt)
SELECT TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '5' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND
FROM DUAL
CONNECT BY
TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '5' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND < DATE '2022-01-15';
/
DECLARE
v_str VARCHAR2 (500);
v_days NUMBER := 0;
BEGIN
FOR cur_r IN(
SELECT TABLE_NAME, PARTITIONING_TYPE, COLUMN_NAME, DATA_TYPE
FROM USER_PART_TABLES
JOIN USER_PART_KEY_COLUMNS ON NAME = TABLE_NAME
JOIN USER_TAB_COLS USING (TABLE_NAME, COLUMN_NAME)
where OBJECT_TYPE = 'TABLE' AND
PARTITIONING_TYPE='RANGE' AND
regexp_like(DATA_TYPE,'^DATE$|^TIMESTAMP*')
)
LOOP
--DBMS_OUTPUT.put_line('Table '|| cur_r.table_name);
v_str := 'select DAYS FROM partition_rention into v_days where TABLE_NAME = '||cur_r.table_name||'';
DBMS_OUTPUT.put_line(v_str);
-- execute immediate v_str;
END LOOP;
END;
Statement processed.
select DAYS FROM partition_rention into v_days where TABLE_NAME = T1
select DAYS FROM partition_rention into v_days where TABLE_NAME = T2
select DAYS FROM partition_rention into v_days where TABLE_NAME = T3
There is no reason for dynamic SQL. It would be this:
begin
select DAYS
into v_days
FROM partition_rention
where TABLE_NAME = cur_r.table_name;
exception
when NO_DATA_FOUND THEN
v_days := 30;
end;
If you really insist for dynamic SQL then it would be this one
begin
v_str := 'select DAYS FROM partition_rention where TABLE_NAME = :t';
execute immediate v_str into v_days using cur_r.table_name;
exception
when NO_DATA_FOUND THEN
v_days := 30;
end;
NB, I guess the next step might be to drop outdated partitions. For this have a look at How to drop multiple interval partitions based on date?
If the LOOP statement comes between a BEGIN statement and its matching EXCEPTION or END; then the END LOOP statement has to come between them as well. If i want an EXCEPTION handler that catches errors that may occur within the loop, and then continues the loop then the exception handler doesn't appear to work.
The code was restructured to remove the expectation handler.
I already have a query that finds the tables and columns I'm interested in. Now, for each table in that result set, I want to get the matching days value from the partition_retention table if there is one, and if there is no matching row in partition_retention, I want the table_name anyway with a default value (30) for days. To do this I implemented an outer JOIN like this:
BEGIN
FOR td IN
(
SELECT table_name
, NVL (pr.days, 30) AS days
FROM user_part_tables pt
JOIN user_part_key_columns pkc ON pkc.name = pt.table_name
JOIN user_tab_cols tc USING (table_name, column_name)
LEFT JOIN partition_retention pr USING (table_name)
WHERE pkc.object_type = 'TABLE'
AND pt.partitioning_type = 'RANGE'
AND REGEXP_LIKE (tc.data_type, '^DATE$|^TIMESTAMP*')
ORDER BY table_name -- not needed, but could be handy in debugging
)
LOOP
-- For debugging:
dbms_output.put_line ( td.table_name
|| ' = table_name, '
|| TO_CHAR (td.days)
|| ' = days'
);
-- call procedure to remove old PARTITIONs here.
END LOOP;
END;
/
Output from my sample data:
T1 = table_name, 15 = days
T2 = table_name, 30 = days
T3 = table_name, 5 = days

How to get the first and last date of previous month without using to_date, trunc and predefined add_months function

I wrote the below code but I did not get the expected output:
SQL> select TO_CHAR(TO_CHAR(sysdate,'MM')-1,'DD-MM-YYYY') AS PREV_MON_FIRST,
TO_CHAR(TO_CHAR(sysdate,'MM'),'DD-MM-YYYY')-1 AS PREV_MON_LAST from dual;
select TO_CHAR(TO_CHAR(sysdate,'MM')-1,'DD-MM-YYYY') AS PREV_MON from dual
*
ERROR at line 1:
ORA-01481: invalid number format model
It's correct that 'DD' and 'YYYY' values are missing but when I tried to retrieve only month also it shows the same error
You can use the following solution:
SELECT
TRUNC(LAST_DAY(ADD_MONTHS(sysdate, -2))) + 1 AS first_date,
TRUNC(LAST_DAY(ADD_MONTHS(sysdate, -1))) AS last_date
FROM dual
Selecting the first day of the previous month using TO_CHAR only is easy, if YYYYMMDD format suits your needs:
SQL> select (to_char(sysdate, 'yyyymm') - 1) || '01' first_day
2 from dual;
FIRST_DAY
------------------------------------------
20180701
SQL>
But, for the last day - I have no idea.
Usual way to do it works, but involves other functions:
SQL> select trunc(add_months(sysdate, -1), 'mm') first_day,
2 trunc(sysdate, 'mm') - 1 last_day
3 from dual;
FIRST_DAY LAST_DAY
---------- ----------
2018-07-01 2018-07-31
SQL>
For LAST_DAY here is the query with hard_code of values of month without using add_months,trunc,to_date
select to_char(sysdate,'yyyy')||
to_char(sysdate,'mm')-1||
case when to_char(sysdate,'mm')-1 in (1,3,5,7,8,10,12) THEN 31
when to_char(sysdate,'mm')-1 in (4,6,9,11) THEN 30
when mod(to_char(sysdate,'yyyy'),4)=0 and to_char(sysdate,'mm')-1=2 THEN 29
when mod(to_char(sysdate,'yyyy'),4)!=0 and to_char(sysdate,'mm')-1=2 THEN 28
end
from dual
If I take your question literally one solution is
CREATE OR REPLACE FUNCTION MY_LAST_DAY RETURN TIMESTAMP AS
next_run_date TIMESTAMP;
return_date_after TIMESTAMP := SYSTIMESTAMP - INTERVAL '100' DAY;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=DAILY;INTERVAL=1;BYMONTHDAY=-1', NULL, return_date_after, next_run_date);
EXIT WHEN EXTRACT(MONTH FROM next_run_date) = EXTRACT(MONTH FROM SYSTIMESTAMP);
return_date_after := next_run_date;
END LOOP;
RETURN return_date_after;
END;
/
CREATE OR REPLACE FUNCTION MY_FIRST_DAY RETURN TIMESTAMP AS
next_run_date TIMESTAMP;
return_date_after TIMESTAMP := SYSTIMESTAMP - INTERVAL '100' DAY;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=DAILY;INTERVAL=1;BYMONTHDAY=1', NULL, return_date_after, next_run_date);
EXIT WHEN EXTRACT(MONTH FROM next_run_date) = EXTRACT(MONTH FROM SYSTIMESTAMP);
return_date_after := next_run_date;
END LOOP;
RETURN return_date_after;
END;
/
It returns the first and last date of previous month without using TO_DATE (or TO_TIMESTAMP), TRUNC or ADD_MONTHS
But I hope you agree that your question is rather stupid.
For LAST_DAY here is the query with hard_code of values of month without using add_months,trunc,to_date
select to_char(sysdate,'yyyy')||
to_char(sysdate,'mm')-1||
case when to_char(sysdate,'mm')-1 in (1,3,5,7,8,10,12) THEN 31
when to_char(sysdate,'mm')-1 in (4,6,9,11) THEN 30
when mod(to_char(sysdate,'yyyy'),4)=0 and to_char(sysdate,'mm')-1=2 THEN 29
when mod(to_char(sysdate,'yyyy'),4)!=0 and to_char(sysdate,'mm')-1=2 THEN 28
end
from dual
New Query to handle Janurary and Leap year :
select decode(to_char(sysdate,'mm'),1,to_char(sysdate,'yyyy')-1 ,to_char(sysdate,'yyyy'))||
decode(to_char(sysdate,'mm'),1,12, to_char(sysdate,'mm')-1)||
CASE WHEN to_char(sysdate,'mm')-1 in (1,3,5,7,8,10,0) THEN 31
WHEN to_char(sysdate,'mm')-1 in (4,6,9,11) THEN 30
WHEN to_char(sysdate,'mm')-1=2 AND mod(to_char(sysdate,'yyyy'),4)!=0 AND mod(to_char(sysdate,'yyyy'),400)!=0 THEN 29
ELSE 28
END
FROM dual

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;

ORA-01027: bind variables not allowed for data definition when trying to use if elseif

I am getting 'ORA-01027: bind variables not allowed for data definition'
procedure create_dates_testing (dummy_variable varchar2 default
to_char(sysdate,'YYYYMMDD')) is
begin
DECLARE
day_of_month varchar2(255) := extract(day from sysdate);
today varchar2(255) := to_char(sysdate, 'DAY');
start_date date;
next_start_date date;
BEGIN
IF today='SUNDAY' THEN
-- Select yesterday
start_date := trunc(sysdate) - interval '1' day;
next_start_date := trunc(sysdate);
ELSE IF day_of_month=3 then
-- Select the whole of last month
start_date := runc(sysdate, 'MM') - interval '1' month;
next_start_date := runc(sysdate, 'MM') - interval '1' month
END IF;
END;
execute immediate 'drop table new_customers';
execute immediate 'create table new_customers as
select id, client_name, invoice_date
from clients table
where transactiondate >= :start_date
and transactiondate < :next_start_date;';
end;
How can I resolve this error? Where am I going wrong? I need to put this procedure in a pl/sql package.
As the error says, you can't use bind variables here, so you have to concatenate:
create or replace procedure create_dates_testing
( dummy_variable varchar2 default to_char(sysdate,'YYYYMMDD') )
as
day_of_month varchar2(255) := extract(day from sysdate);
today varchar2(255) := to_char(sysdate +1, 'fmDAY', 'nls_date_language = English');
start_date date;
next_start_date date;
begin
if today = 'SUNDAY' then
-- select yesterday
start_date := trunc(sysdate) - interval '1' day;
next_start_date := trunc(sysdate);
elsif day_of_month = 3 then
-- select the whole of last month
start_date := trunc(sysdate, 'MM') - interval '1' month;
next_start_date := trunc(sysdate, 'MM') - interval '1' month;
else
return;
end if;
execute immediate 'drop table new_customers';
execute immediate 'create table new_customers as
select id, client_name, invoice_date
from clients table
where transactiondate >= date ''' || to_char(start_date,'YYYY-MM-DD') ||
''' and transactiondate < date ''' || to_char(next_start_date,'YYYY-MM-DD') ||'''';
end create_dates_testing;
Presumably there will be some more code to handle the case where it is neither Sunday nor the third of the month, or the new_customers table does not exist.
Edit: added else condition to end processing if neither of the date conditions are met.

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