How to make existing values to null from the existing plsql logic - oracle

CREATE TABLE main_tab (
seq_id NUMBER(10),
e_id NUMBER(10),
code NUMBER(10),
CONSTRAINT pk_main_tab PRIMARY KEY(seq_id)
);
INSERT INTO main_tab VALUES(1,11,3);
CREATE TABLE transact_tab (
seq_id,
e_id,
code NUMBER(10),
start_date DATE,
end_date DATE
);
I have two tables main_tab and transact_tab wherein I need to insert the records into the transact_tab table based on the column code of table main_tab.
Ex. If code = 3 then in the table transact_tab it should load 3 records like 1, 2, and 3 with the start_date and end_date column as SYSDATE just for the current code value end_date would be null.
Code for the above requirement: -
DECLARE
l_transact_row transact_tab%ROWTYPE;
BEGIN
FOR r IN (SELECT * FROM main_tab) LOOP
FOR i IN 1 .. r.code LOOP
BEGIN
SELECT * INTO l_transact_row
FROM transact_tab
WHERE e_id = r.e_id AND code = i;
IF l_transact_row.end_date IS NULL AND i <> r.code THEN
UPDATE transact_tab SET end_date = SYSDATE WHERE e_id = r.e_id AND code = i;
END IF;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO transact_tab (e_id, code,start_date, end_date)
VALUES (r.e_id, i, SYSDATE, CASE WHEN i = r.code THEN NULL ELSE SYSDATE END);
END ;
END LOOP;
END LOOP;
END;
Database Version: Oracle 18c.
This code is perfectly working fine for top down approach wherein I need to load the data into the transact_tab but if my code gets decreased then the logic should handle this scenario as well. Example..if code gets changed from 3 to 2 then in this case it should update the already existing values of transact_tab to NULL if code is decreased.
Expected output of transact_tab if the code is decreased from 3 to 2 in main_tab should be:
+------+------+------------+----------+
| e_id | code | start_date | end_date |
+------+------+------------+----------+
| 11 | 1 | 19-06-22 | 19-06-22 |
| 11 | 2 | 19-06-22 | NULL |
| 11 | 3 | NULL | NULL |
+------+------+------------+----------+

Cant you use something like following -
insert into transact_tab (e_id,code, start_date,end_date)
select e_id,level,
case when code<level then null else sysdate end,
case when code<=level then null else sysdate end
from main_tab
connect by level<=3;
Refer fiddle here.
DECLARE
l_transact_row transact_tab%ROWTYPE;
BEGIN
FOR r IN (SELECT * FROM main_tab) LOOP
FOR i IN 1 .. r.code LOOP
BEGIN
SELECT * INTO l_transact_row
FROM transact_tab
WHERE e_id = r.e_id AND code = i;
UPDATE transact_tab SET (start_date,end_date) = (select sysdate,sysdate from dual) WHERE code < r.code;
UPDATE transact_tab SET (start_date,end_date) = (select null,null from dual) WHERE code > r.code;
UPDATE transact_tab SET (start_date,end_date) = (select sysdate,null from dual) WHERE code = r.code;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO transact_tab (e_id, code,start_date, end_date)
VALUES (r.e_id, i, SYSDATE, CASE WHEN i = r.code THEN NULL ELSE SYSDATE END);
END ;
END LOOP;
END LOOP;
END;
Refer to fiddle here.
Using the merge approach, it handles both increase and decrease in code values for main_tab, along with "if code is 3 then there will be 4 entries i.e 0, 1, 2, and 3"
merge into transact_tab t1
using
(select * from
(select e_id,level-1 as lvl,
case when code<level-1 then null else sysdate end start_date,
case when code<=level-1 then null else sysdate end end_date
from main_tab
connect by level <=
(select max(code) + 1 from (
(select max(code) code from transact_tab) union all
(select code from main_tab) )
)
) ) t2
on (t1.e_id = t2.e_id
and t1.code = t2.lvl)
when matched then
update set t1.start_date=t2.start_date, t1.end_date=t2.end_date
when NOT matched then
insert (e_id,code,start_date,end_date)
VALUES
(t2.e_id,t2.lvl,t2.start_date,t2.end_date);
Refer to the fiddle here, to see the merge approach in action.

Edited version with less code. Basicaly - it works, but I didn't test all possible waays of changing the value of CODE on different days. Probably there is some more tuning to be done. Try it and check it.
Declare
maxRepeats Number(3) := 0;
subMaxCode Number(3) := 0;
Begin
FOR r IN(SELECT * FROM a_main_tab) LOOP
maxRepeats := r.CODE;
SELECT Nvl(Max(CODE), 0) Into subMaxCode FROM a_transact_tab WHERE SEQ_ID = r.SEQ_ID And E_ID = r.E_ID; -- And start_date Is Not Null;
FOR i IN 1..maxRepeats LOOP
If subMaxCode >= 0 And subMaxCode = maxRepeats THEN
EXIT;
ElsIf subMaxCode >= 0 And subMaxCode < maxRepeats And i > subMaxCode THEN
Begin
INSERT INTO a_transact_tab (seq_id, e_id, code, start_date, end_date)
VALUES(r.seq_id, r.e_id, i, SYSDATE, CASE WHEN i = maxRepeats THEN Null ELSE SYSDATE END);
commit;
End;
ElsIf subMaxCode >= 0 And subMaxCode < maxRepeats And i <= subMaxCode THEN
Begin
UPDATE A_TRANSACT_TAB
SET start_date = CASE WHEN start_date Is Null Then SYSDATE ELSE start_date END,
end_date = CASE WHEN end_date Is Null Then SYSDATE ELSE end_date END
WHERE seq_id = r.seq_id and e_id = r.e_id and code = i;
commit;
End;
ElsIf subMaxCode >= 0 And subMaxCode >= maxRepeats And i <= subMaxCode THEN --UPD_x_TO_y
Begin
UPDATE A_TRANSACT_TAB updt
SET updt.start_date = CASE WHEN CODE > maxRepeats THEN Null ELSE CASE WHEN updt.start_date Is Not Null THEN updt.start_date ELSE SYSDATE END END,
updt.end_date = CASE WHEN CODE >= maxRepeats THEN Null ELSE CASE WHEN updt.start_date Is Not Null THEN updt.start_date ELSE SYSDATE END END
WHERE updt.seq_id = r.seq_id and updt.e_id = r.e_id and updt.code Between 1 and subMaxCode;
commit;
End;
EXIT;
End If;
END LOOP;
END LOOP;
End;
After this (2nd) edit and simullations the results of testing are here:
-- 17-JUN-22 Code is 3 *transact table empty
-- SEQ_ID E_ID CODE START_DATE END_DATE
-- ------ ------ ----- ---------- ----------
-- 1 11 1 17-JUN-22 17-JUN-22
-- 1 11 2 17-JUN-22 17-JUN-22
-- 1 11 3 17-JUN-22
--
-- ----------------------------------------------
-- 18-JUN-22 Code changed from 3 to 2
-- SEQ_ID E_ID CODE START_DATE END_DATE
-- ------ ------ ----- ---------- ----------
-- 1 11 1 17-JUN-22 17-JUN-22
-- 1 11 2 17-JUN-22
-- 1 11 3
--
-- ----------------------------------------------
-- 19-JUN-22 Code changed from 2 to 5
-- SEQ_ID E_ID CODE START_DATE END_DATE
-- ------ ------ ----- ---------- ----------
-- 1 11 1 17-JUN-22 17-JUN-22
-- 1 11 2 17-JUN-22 19-JUN-22
-- 1 11 3 19-JUN-22 19-JUN-22
-- 1 11 4 19-JUN-22 19-JUN-22
-- 1 11 5 19-JUN-22
--
-- ----------------------------------------------
-- 20-JUN-22 (1st) Code changed from 5 to 4
-- SEQ_ID E_ID CODE START_DATE END_DATE
-- ------ ------ ----- ---------- ----------
-- 1 11 1 17-JUN-22 17-JUN-22
-- 1 11 2 17-JUN-22 19-JUN-22
-- 1 11 3 19-JUN-22 19-JUN-22
-- 1 11 4 19-JUN-22
-- 1 11 5
--
-- ----------------------------------------------
-- 20-JUN-22 (2nd) Code changed from 4 to 7
-- SEQ_ID E_ID CODE START_DATE END_DATE
-- ------ ------ ----- ---------- ----------
-- 1 11 1 17-JUN-22 17-JUN-22
-- 1 11 2 17-JUN-22 19-JUN-22
-- 1 11 3 19-JUN-22 19-JUN-22
-- 1 11 4 19-JUN-22 20-JUN-22
-- 1 11 5 20-JUN-22 20-JUN-22
-- 1 11 6 20-JUN-22 20-JUN-22
-- 1 11 7 20-JUN-22

Related

Is this possible to apply a function to every fields of table (groupy by type)

I would like to replace every string are null by 'n' and every number by 0.
Is there a way to do that?
With a polymorphic table function I can select all the columns of a certain type but I can't modify the value of the columns.
Yes, it is possible with PTF also. You may modify columns in case you've set pass_through to false for the column in the describe method (to drop it) and copy it into new_columns parameter of the describe.
Below is the code:
create package pkg_nvl as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_nvl;
/
create package body pkg_nvl as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
modif_cols dbms_tf.columns_new_t;
new_col_cnt pls_integer := 0;
begin
/*Mark input columns as used and as modifiable for subsequent row processing*/
for i in 1..tab.column.count loop
if tab.column(i).description.type in (
dbms_tf.type_number,
dbms_tf.type_varchar2
) then
/*Modifiable*/
tab.column(i).pass_through := FALSE;
/*Used in the PTF context*/
tab.column(i).for_read := TRUE;
/* Propagate column to the modified*/
modif_cols(new_col_cnt) := tab.column(i).description;
new_col_cnt := new_col_cnt + 1;
end if;
end loop;
/*Return the list of modified cols*/
return dbms_tf.describe_t(
new_columns => modif_cols
);
end;
procedure fetch_rows
/*Process rowset and replace nulls*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
in_col_vc2 dbms_tf.tab_varchar2_t;
in_col_num dbms_tf.tab_number_t;
new_col_vc2 dbms_tf.tab_varchar2_t;
new_col_num dbms_tf.tab_number_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for col_num in 1..rowset.count() loop
/*Loop through the columns*/
for rn in 1..num_rows loop
/*Calculate new values in the same row*/
/*Get column by index and nvl the value for return column*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_num
);
new_col_num(rn) := nvl(in_col_num(rn), 0);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_vc2
);
new_col_vc2(rn) := nvl(in_col_vc2(rn), 'n');
end if;
end loop;
/*Put the modified column to the result*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_num
);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_vc2
);
end if;
end loop;
end;
end pkg_nvl;
/
create function f_replace_nulls(tab in table)
/*Function to replace nulls using PTF*/
return table pipelined
row polymorphic using pkg_nvl;
/
with a as (
select
1 as id, 'q' as val_vc2, 1 as val_num
from dual
union all
select
2 as id, '' as val_vc2, null as val_num
from dual
union all
select
3 as id, ' ' as val_vc2, 0 as val_num
from dual
)
select
id
, a.val_num
, a.val_vc2
, n.val_num as val_num_repl
, n.val_vc2 as val_vc2_repl
from a
join f_replace_nulls(a) n
using(id)
ID | VAL_NUM | VAL_VC2 | VAL_NUM_REPL | VAL_VC2_REPL
-: | ------: | :------ | -----------: | :-----------
3 | 0 | | 0 |
2 | null | null | 0 | n
1 | 1 | q | 1 | q
db<>fiddle here
Use COALESCE or NVL and list the columns you want to apply them to:
SELECT COALESCE(col1, 'n') AS col1,
COALESCE(col2, 0) AS col2,
COALESCE(col3, 'n') AS col3
FROM table_name;
The way I understood the question, you actually want to modify table's contents. If that's so, you'll need dynamic SQL.
Here's an example; sample data first, with some numeric and character columns having NULL values:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SALARY NUMBER
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
Procedure reads USER_TAB_COLUMNS, checking desired data types (you can add some more, if you want), composes the update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select column_name, data_type
5 from user_tab_columns
6 where table_name = 'TEST'
7 and data_type in ('CHAR', 'VARCHAR2')
8 )
9 loop
10 l_str := 'update test set ' ||
11 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', ''n'')';
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select column_name, data_type
18 from user_tab_columns
19 where table_name = 'TEST'
20 and data_type in ('NUMBER')
21 )
22 loop
23 l_str := 'update test set ' ||
24 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', 0)';
25 execute immediate l_str;
26 end loop;
27 end;
28 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 n 200
3 Foot 0
4 n 0
SQL>
You can create a table macro to intercept the columns and replace them with nvl if they're a number or varchar2:
create or replace function replace_nulls (
tab dbms_tf.table_t
)
return clob sql_macro as
stmt clob := 'select ';
begin
for i in 1..tab.column.count loop
if tab.column(i).description.type = dbms_tf.type_number
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', 0 ) ' ||
tab.column(i).description.name || ',';
elsif tab.column(i).description.type = dbms_tf.type_varchar2
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', ''n'' ) ' ||
tab.column(i).description.name || ',';
else
stmt := stmt || tab.column(i).description.name || ',';
end if;
end loop;
stmt := rtrim ( stmt, ',' ) || ' from tab';
return stmt;
end;
/
with a (
aa1,aa2,aa3
) as (
select 1, '2hhhh', sysdate from dual
union all
select null, null, null from dual
)
select * from replace_nulls(a);
AA1 AA2 AA3
---------- ----- -----------------
1 2hhhh 27-JUN-2022 13:17
0 n <null>

While updating getting no data found error. Need to update the record to 0 if any one the rows for that ID is 0

CREATE TABLE test_tab (
s_id NUMBER(10),
e_id NUMBER(10),
active_flg NUMBER(1)
);
INSERT INTO test_tab VALUES(1,11,1);
INSERT INTO test_tab VALUES(2,11,1);
INSERT INTO test_tab VALUES(3,11,0);
INSERT INTO test_tab VALUES(4,12,1);
INSERT INTO test_tab VALUES(5,12,1);
COMMIT;
Tool Used: SQL Developer(18c)
I want to update the active_flg column by identifying the value 0 in it. Suppose, for e_id 11 we have 3 rows so first it should check if for e_id 11 is there any 0 active_flg if 0 exists for that particular e_id then it should update entire rows for e_id 11 to 0. If there are no entries for active_flg then it should not update anything.
My Attempt:
SET SERVEROUTPUT ON;
DECLARE
lv_row test_tab%ROWTYPE;
BEGIN
FOR i IN (SELECT * FROM test_tab)
LOOP
SELECT * INTO lv_row FROM test_tab WHERE e_id = i.e_id AND active_flg = 0;
UPDATE test_tab SET active_flg = 0 WHERE active_flg = 0;
END LOOP;
END;
But I am getting no data found error.
Expected output:
+------+------+------------+
| s_id | e_id | active_flg |
+------+------+------------+
| 1 | 11 | 0 |
| 2 | 11 | 0 |
| 3 | 11 | 0 |
| 4 | 12 | 1 |
| 5 | 12 | 1 |
+------+------+------------+
How about merge (instead of PL/SQL)?
Before:
SQL> select * From test_tab order by s_id;
S_ID E_ID ACTIVE_FLG
---------- ---------- ----------
1 11 1
2 11 1
3 11 0
4 12 1
5 12 1
Merge:
SQL> merge into test_tab a
2 using (select e_id from test_Tab
3 where active_flg = 0
4 ) b
5 on (a.e_id = b.e_id)
6 when matched then update set
7 a.active_flg = 0;
3 rows merged.
After:
SQL> select * From test_tab order by s_id;
S_ID E_ID ACTIVE_FLG
---------- ---------- ----------
1 11 0
2 11 0
3 11 0
4 12 1
5 12 1
SQL>
One (practical and fast) option is to use analytical function presuming active_flg has two values(0 and 1 only)
SQL> CREATE TABLE test_tab2 AS
SQL> SELECT t.s_id, t.e_id, MIN(t.active_flg) OVER (PARTITION BY t.e_id) AS active_flg
FROM test_tab t;
SQL> DROP TABLE test_tab;
SQL> ALTER TABLE test_tab2 RENAME TO test_tab;
but if the table has indexes, then they all should be recreated; and if there are privileges to be granted to the other users, they also should be regranted.
The reason the no data found is raised is that the SELECT INTO clause does not have data for each value of i.e_id. Some records have active_flg = 1 so it will raise a no data found for that record.
DECLARE
BEGIN
-- select DISTINCT so there is only 1 row per e_id.
-- add WHERE clause to only select e_id values that
-- have an active_flag = 0. since no action is to be take on the
-- others ignore those
FOR i IN (SELECT DISTINCT e_id FROM test_tab WHERE active_flag = 0)
LOOP
-- no need for the select into. The resultset of the cursor for loop
-- only contains the relevant records.
UPDATE test_tab SET active_flg = 0 WHERE e_id = i.e_id;
END LOOP;
END;

Use Type as output parameter for procedure

I'm trying to return a type as output parameter, but i'm getting error :
[Error] Compilation (12: 27): PLS-00382: expression is of wrong type
Here is the code:
create or replace TYPE OBJ_DCP FORCE as OBJECT (
ID NUMBER,
xxx_PROFILES_ID NUMBER,
DCP_NAME VARCHAR2(500 BYTE),
L_R_NUMBER VARCHAR2(150 BYTE),
STREET VARCHAR2(150 BYTE)
};
and
create or replace TYPE T_DCP_TYPE as TABLE OF OBJ_DCP;
and
create or replace procedure get_dcp_profiles(p_id in number, dcp_array_var OUT T_DCP_TYPE) is
cursor c_dcp_profiles is
select *
from table1 ca -- table1 has same structure of OBJ_DCP
where ca.id = p_id;
i number := 1;
begin
for r in c_dcp_profiles loop
dcp_array_var(i) := r; -- this line is errored
i := i + 1;
end loop;
end get_dcp_profiles;
Types are OK.
Sample table:
SQL> SELECT * FROM table1 ORDER BY id;
ID XXX_PROFILES_ID D L S
---------- --------------- - - -
1 1 a b c
1 2 d e f
2 3 x y z
SQL>
Procedure, modified:
SQL> CREATE OR REPLACE PROCEDURE get_dcp_profiles (
2 p_id IN NUMBER,
3 dcp_array_var OUT T_DCP_TYPE)
4 IS
5 CURSOR c_dcp_profiles IS
6 SELECT *
7 FROM table1 ca -- table1 has same structure of OBJ_DCP
8 WHERE ca.id = p_id;
9
10 i NUMBER := 1;
11 l_arr T_DCP_TYPE := T_DCP_TYPE ();
12 BEGIN
13 FOR r IN c_dcp_profiles
14 LOOP
15 l_arr.EXTEND;
16 l_arr (i) :=
17 OBJ_DCP (id => r.id,
18 xxx_profiles_id => r.xxx_profiles_id,
19 dcp_name => r.dcp_name,
20 l_r_number => r.l_r_number,
21 street => r.street);
22 i := i + 1;
23 END LOOP;
24
25 dcp_array_var := l_arr;
26 END get_dcp_profiles;
27 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_tab t_dcp_type;
3 BEGIN
4 get_dcp_profiles (1, l_tab);
5
6 FOR i IN 1 .. l_tab.COUNT
7 LOOP
8 DBMS_OUTPUT.put_line (l_tab (i).id || ' ' || l_tab (i).dcp_name);
9 END LOOP;
10 END;
11 /
1 a
1 d
PL/SQL procedure successfully completed.
SQL>
You do not need to use cursors or to manually populate the collection.
If you declare the table as an object-derived table:
CREATE TABLE table1 OF OBJ_DCP;
Then you can simplify the procedure down to:
CREATE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT VALUE(ca)
BULK COLLECT INTO dcp_array_var
FROM table1 ca
WHERE ca.id = p_id;
END get_dcp_profiles;
/
For the sample data:
INSERT INTO table1 (id, xxx_Profiles_id, DCP_NAME, L_R_NUMBER, STREET)
SELECT 1, 42, 'ABC name', 'ABC number', 'ABC street' FROM DUAL UNION ALL
SELECT 1, 63, 'DEF name', 'DEF number', 'DEF street' FROM DUAL UNION ALL
SELECT 2, 12, 'GHI name', 'GHI number', 'GHI street' FROM DUAL;
Then:
DECLARE
dcps t_dcp_type;
BEGIN
get_dcp_profiles (1, dcps);
FOR i IN 1 .. dcps.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (dcps(i).id || ' ' || dcps(i).dcp_name);
END LOOP;
END;
/
Outputs:
1 ABC name
1 DEF name
If you do not want to use an object-derived table then you can use:
CREATE OR REPLACE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT OBJ_DCP(id, xxx_profiles_id, dcp_name, l_r_number, street)
BULK COLLECT INTO dcp_array_var
FROM table1
WHERE id = p_id;
END get_dcp_profiles;
/
db<>fiddle here

PL SQL iterate loop through date range

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.

Re-write table update as set instead of iterative method

I have a list of user email addresses, in a test Oracle database, which are currently all set to the same value. I want to replace these with unique entries, with a number of invalid addresses and null values mixed in. My table currently looks like this and is around 250k rows in total.(I've excluded the null and invalid entries to save some space)
+-------------+--------------------+
| employee_id | email |
+-------------+--------------------+
| 1 | test#testemail.com |
| 2 | test#testemail.com |
| 3 | test#testemail.com |
|... |... |
+-------------+--------------------+
And I'd like it to look like this
+-------------+---------------------+
| employee_id | email |
+-------------+---------------------+
| 1 | test1#testemail.com |
| 2 | test2#testemail.com |
| 3 | test3#testemail.com |
|... |... |
+-------------+---------------------+
I've wrote the following PL/SQL to do the change, it works, but it's seems very inefficient. Can I use another method to take advantage of set processing?
Thanks for any help with this.
DECLARE
i number(20);
l_employee_id hr.employees.employee_id%TYPE;
output_query varchar2(1000);
CURSOR c_cursor IS
SELECT employee_id
FROM hr.employees;
PROCEDURE update_sql(id_num IN hr.employees.employee_id%TYPE,
email IN VARCHAR2) IS
BEGIN
output_query := 'UPDATE hr.employees
SET email = '''|| email ||'''
WHERE employee_id = '|| id_num;
dbms_output.put_line(output_query); --for debug
EXECUTE IMMEDIATE output_query;
END;
BEGIN
OPEN c_cursor;
i := 1;
<<outer_loop>>
LOOP
For j IN 1..5 LOOP
FETCH c_cursor INTO l_employee_id;
EXIT outer_loop WHEN c_cursor%NOTFOUND;
IF j <= 3 THEN
update_sql(l_employee_id, ('test' || i || '#testemail.com'));
ELSIF j = 4 THEN
update_sql(l_employee_id, ('test' || i || 'testemail.com'));
ELSIF j = 5 THEN
update_sql(l_employee_id, ' ');
END IF;
i := i + 1;
END LOOP;
END LOOP outer_loop;
CLOSE c_cursor;
END;
/
EDIT - 26/09/2016 - to clarify size of table.
create table emp (emp_id number, email varchar2(32));
insert into emp select level as emp_id, 'test#testemail.com' as email
from dual connect by level<=2500000;
update emp set email = regexp_replace(email, '(\w+)(#\w+\.\w+)', '\1' || emp_id || '\2');
--250,000 rows updated ~16 sec.
EMP_ID, EMAIL
1 test1#testemail.com
2 test2#testemail.com
3 test3#testemail.com
...
drop table emp;
First off, even if you were going to code this iteratively, please don't use dynamic SQL where it is not necessary. And it is only necessary if you don't know the table or columns you are going to be querying at compile time.
That said, it sounds like you just want
UPDATE employees
SET email = (case when employee_id <= 3
then 'test' || employee_id || '#testemail.com'
when employee_id = 4
then 'test' || employee_id || 'testemail.com'
when employee_id = 5
then ' '
else null
end)
Oracle Setup:
create table employees (
id NUMBER,
email VARCHAR2(100)
);
INSERT INTO employees
SELECT 1, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 2, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 3, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 4, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 5, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 6, 'test#testemail.com' FROM DUAL;
Query:
update employees
set email = CASE MOD( ROWNUM, 5 )
WHEN 4 THEN 'test' || ROWNUM || 'testemail.com'
WHEN 0 THEN ''
ELSE 'test' || ROWNUM || '#testemail.com'
END;
Output:
SELECT * FROM employees;
ID EMAIL
---------- ------------------------
1 test1#testemail.com
2 test2#testemail.com
3 test3#testemail.com
4 test4testemail.com
5
6 test6#testemail.com

Resources