Wrong calculation for daily partitions - oracle

Oracle : 11.2.0.2
I'm trying to drop monthy and daily partitions using a script. This works fine for monthly partitions but not for daily partitions. Below is the error I see in the log. Day of the month is becoming zero when calculating.
2013-08-0|SYS_P328538|2|YES
DECLARE
*
ERROR at line 1:
ORA-01847: day of month must be between 1 and last day of month
ORA-06512: at line 43
Here below is the script. I think highvalue date is miscalculated.
SQL> DECLARE
2 CURSOR tab_part_cur IS
3 select PARTITION_POSITION, PARTITION_NAME,HIGH_VALUE,INTERVAL from dba_tab_partitions where table_name = 'MO_USAGEDATA'
and table_owner = 'WSMUSER17'
order by PARTITION_POSITION;
4 tab_part_rec tab_part_cur%ROWTYPE;
5 lHighValue LONG;
6 strPartitionLessThanDate VARCHAR2(100);
7 dtTestDate DATE;
8 DaysInPast NUMBER;
9 SQLstr varchar2(100);
10 strIntervalType varchar2(1000);
11 strRunType varchar2(20);
12 BEGIN
13 strRunType := 'DRY_RUN';
14 select INTERVAL into strIntervalType from dba_part_tables where table_name ='MO_USAGEDATA' and owner = 'WSMUSER17';
15 strIntervalType := REGEXP_SUBSTR(strIntervalType, '''[^'']+''');
16 DBMS_OUTPUT.PUT_LINE(strIntervalType);
17 CASE
18 WHEN strIntervalType = '''DAY''' THEN
19 DBMS_OUTPUT.PUT_LINE('Interval type = '||strIntervalType);
20 -- dtTestDate := CURRENT_DATE - 7 - 1; Offset adjustment if necessary
21 dtTestDate := CURRENT_DATE - 7;
22 DBMS_OUTPUT.PUT_LINE('Test Date = '||dtTestDate);
23 WHEN strIntervalType = '''MONTH''' THEN
24 DBMS_OUTPUT.PUT_LINE('Interval type = '||strIntervalType);
25 -- dtTestDate := CURRENT_DATE - 90;
26 dtTestDate := ADD_MONTHS(current_date,- 7);
27 DBMS_OUTPUT.PUT_LINE('TestDate = '||dtTestDate);
28 ELSE
29 DBMS_OUTPUT.PUT_LINE('Unexpected interval, exiting.');
30 GOTO EXIT;
31 END CASE;
32 OPEN tab_part_cur;
33 LOOP
34 FETCH tab_part_cur INTO tab_part_rec;
35 EXIT WHEN tab_part_cur%NOTFOUND;
36 DBMS_OUTPUT.PUT_LINE(tab_part_cur%ROWCOUNT);
37 lHighValue := tab_part_rec.high_value;
38 /* This next line seems redundant but is needed for conversion quirk from LONG to VARCHAR2
39 */
40 strPartitionLessThanDate := lHighValue;
41 strPartitionLessThanDate := substr(strPartitionLessThanDate, 11, 10);
42 DBMS_OUTPUT.PUT_LINE(strPartitionLessThanDate ||'|'|| tab_part_rec.partition_name ||'|'|| tab_part_rec.partition_position ||'|'|| tab_part_rec.interval);
43 DBMS_OUTPUT.PUT_LINE(TO_DATE(strPartitionLessThanDate, 'YYYY-MM-DD') ||'******'||dtTestDate);
44 IF TO_DATE(strPartitionLessThanDate, 'YYYY-MM-DD') < dtTestDate AND tab_part_rec.partition_name <> 'PART_MINVALUE
' THEN
45 SQLstr := 'ALTER TABLE WSMUSER17.MO_USAGEDATA DROP PARTITION '||tab_part_rec.partition_name ||' update Global indexes';
46 DBMS_OUTPUT.PUT_LINE('Targeted Partition !!!!!!!!');
47 IF strRunType = 'LIVE_RUN' THEN
48 DBMS_OUTPUT.PUT_LINE('Dropping Partition !!!!!!!!');
49 execute immediate SQLstr;
50 END IF;
51 END IF;
52 END LOOP;
53 CLOSE tab_part_cur;
54 << EXIT >>
55 DBMS_OUTPUT.PUT_LINE('Partition purge complete');
56 END;
57 /
'DAY'
Interval type = 'DAY'
Test Date = 03-SEP-13
1
2012-06-1|PART_MINVALUE|1|NO
01-JUN-12******03-SEP-13
2
2013-08-0|SYS_P328538|2|YES
DECLARE
*
ERROR at line 1:
ORA-01847: day of month must be between 1 and last day of month
ORA-06512: at line 43
I'm trying to keep lat 7 partitions in the daily partitioned table and drop the rest of the partitions. But its not dropping them.

Ok, I created the table, inserted some data and ran some of your queries and you've got something wrong with your substring:
SQL> CREATE TABLE "MO_USAGEDATA" (
2 "REQUESTDTS" TIMESTAMP (9) NOT NULL ENABLE
3 )
4 partition by range ("REQUESTDTS") INTERVAL(NUMTODSINTERVAL(1,'DAY'))
5 (partition PART_MINVALUE values less than(TIMESTAMP '2012-06-18 00:00:00'));
Table created
SQL> INSERT INTO MO_USAGEDATA
2 (SELECT SYSDATE + ROWNUM FROM dual CONNECT BY LEVEL <= 30);
30 rows inserted
SQL> SELECT high_value, INTERVAL
2 FROM all_tab_partitions
3 WHERE table_name = 'MO_USAGEDATA'
4 AND table_owner = USER
5 ORDER BY PARTITION_POSITION;
HIGH_VALUE INTERVAL
------------------------------------ ---------
[...]
TIMESTAMP' 2013-09-30 00:00:00' YES
TIMESTAMP' 2013-10-01 00:00:00' YES
TIMESTAMP' 2013-10-02 00:00:00' YES
[...]
SQL> SELECT substr('TIMESTAMP'' 2013-10-02 00:00:00''', 11, 10) FROM dual;
SUBSTR('TIMESTAMP''2013-10-020
------------------------------
2013-10-0
As you can see you're off by one character. It works with DATE columns, but for TIMESTAMP partitionning, you'll need to adjust the offset.

Related

How to normalize DATE using BEFORE INSERT Trigger / PLSQL Procedure [duplicate]

ACTUAL
expected
SEP-10-2017
10-SEP-2017
SEP 30 2018
30-SEP-2018
OFFICE OF SMALL
OCT-11-2018
11-OCT-2018
O9-SEP-2009
O9-SEP-2009
Not Applicable
NOV-20-2001
20-NOV-2001
BANIJYA BHIBAG
AUGUST 03 2017
03-AUG-2017
AUG-04-1991
04-AUG-1991
97/2015
09/09/2018
09-SEP-2018
how can we get the result as above and discard the date that cannot be converted?
From Oracle 12, you do not need PL/SQL and can use:
SELECT actual,
COALESCE(
TO_DATE(
actual DEFAULT NULL ON CONVERSION ERROR,
'MM-DD-YYYY',
'NLS_DATE_LANGUAGE=ENGLISH'
),
TO_DATE(
actual DEFAULT NULL ON CONVERSION ERROR,
'DD-MON-YYYY',
'NLS_DATE_LANGUAGE=ENGLISH'
)
) AS parsed
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (ACTUAL) AS
SELECT 'SEP-10-2017' FROM DUAL UNION ALL
SELECT 'SEP 30 2018' FROM DUAL UNION ALL
SELECT 'OFFICE OF SMALL' FROM DUAL UNION ALL
SELECT 'OCT-11-2018' FROM DUAL UNION ALL
SELECT 'O9-SEP-2009' FROM DUAL UNION ALL
SELECT 'Not Applicable' FROM DUAL UNION ALL
SELECT 'NOV-20-2001' FROM DUAL UNION ALL
SELECT 'BANIJYA BHIBAG' FROM DUAL UNION ALL
SELECT 'AUGUST 03 2017' FROM DUAL UNION ALL
SELECT 'AUG-04-1991' FROM DUAL UNION ALL
SELECT '97/2015' FROM DUAL UNION ALL
SELECT '09/09/2018' FROM DUAL;
Outputs:
ACTUAL
PARSED
SEP-10-2017
10-SEP-2017
SEP 30 2018
30-SEP-2018
OFFICE OF SMALL
OCT-11-2018
11-OCT-2018
O9-SEP-2009
Not Applicable
NOV-20-2001
20-NOV-2001
BANIJYA BHIBAG
AUGUST 03 2017
03-AUG-2017
AUG-04-1991
04-AUG-1991
97/2015
09/09/2018
09-SEP-2018
Note: O9-SEP-2009 has not parsed because you have the letter O rather than the digit 0 as the first character.
db<>fiddle here
One option is to create a function which will cover all possible combinations. The following example has some of them - you'd have to add new when you find them.
SQL> create or replace function f_date (par_str in varchar2)
2 return date
3 is
4 l_date date;
5 begin
6 begin
7 l_date := to_date(par_str, 'mon-dd-yyyy');
8 return l_date;
9 exception
10 when others then null;
11 end;
12
13 --
14
15 begin
16 l_date := to_date(par_str, 'mon dd yyyy');
17 return l_date;
18 exception
19 when others then null;
20 end;
21
22 --
23
24 begin
25 l_date := to_date(par_str, 'dd-mon-yyyy');
26 return l_date;
27 exception
28 when others then null;
29 end;
30
31 --
32
33 begin
34 l_date := to_date(par_str, 'month dd yyyy');
35 return l_date;
36 exception
37 when others then null;
38 end;
39
40 --
41
42 begin
43 l_date := to_date(par_str, 'dd/mm/yyyy');
44 return l_date;
45 exception
46 when others then null;
47 end;
48
49 return null;
50
51 exception
52 when others then
53 return null;
54 end f_date;
55 /
Function created.
Testing:
SQL> select actual, f_date(actual) as expected
2 from test;
ACTUAL EXPECTED
--------------- -----------
SEP-10-2017 10-SEP-2017
SEP 30 2018 30-SEP-2018
OFFICE OF SMALL
09-SEP-2009 09-SEP-2009
Not applicable
AUGUST 03 2017 03-AUG-2017
97/2015
09/09/2018 09-SEP-2018
8 rows selected.
SQL>
Possible problems: if actual = 09/10/12, what is what?
09 can be day, month or year
10 can also be day, month or year
12 can also be day, month or year
So, what is it?
09-OCT-2012
10-SEP-2012
12-OCT-2009
...
If you're sure about that you don't have any other type of the format for those values which can be convertible to a date value(eg.all of the data conform with this sample data set), then use the following code block in order to populate another table(t2) with the decent date values while displaying the rotten values such as
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
dt DATE;
BEGIN
FOR c IN (SELECT * FROM t) LOOP
BEGIN
dt := TO_DATE(c.col, 'MON-DD-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH');
INSERT INTO t2 VALUES(c.id,c.col);
EXCEPTION
WHEN OTHERS THEN
BEGIN
dt := TO_DATE(c.col, 'DD/MM/YYYY', 'NLS_DATE_LANGUAGE=ENGLISH');
INSERT INTO t2 VALUES(c.id,c.col);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(c.col || ' is not a valid date value');
END;
END;
END LOOP;
COMMIT;
END;
/
Demo

How can we extract date only from a column with various date and string formats in Oracle?

ACTUAL
expected
SEP-10-2017
10-SEP-2017
SEP 30 2018
30-SEP-2018
OFFICE OF SMALL
OCT-11-2018
11-OCT-2018
O9-SEP-2009
O9-SEP-2009
Not Applicable
NOV-20-2001
20-NOV-2001
BANIJYA BHIBAG
AUGUST 03 2017
03-AUG-2017
AUG-04-1991
04-AUG-1991
97/2015
09/09/2018
09-SEP-2018
how can we get the result as above and discard the date that cannot be converted?
From Oracle 12, you do not need PL/SQL and can use:
SELECT actual,
COALESCE(
TO_DATE(
actual DEFAULT NULL ON CONVERSION ERROR,
'MM-DD-YYYY',
'NLS_DATE_LANGUAGE=ENGLISH'
),
TO_DATE(
actual DEFAULT NULL ON CONVERSION ERROR,
'DD-MON-YYYY',
'NLS_DATE_LANGUAGE=ENGLISH'
)
) AS parsed
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (ACTUAL) AS
SELECT 'SEP-10-2017' FROM DUAL UNION ALL
SELECT 'SEP 30 2018' FROM DUAL UNION ALL
SELECT 'OFFICE OF SMALL' FROM DUAL UNION ALL
SELECT 'OCT-11-2018' FROM DUAL UNION ALL
SELECT 'O9-SEP-2009' FROM DUAL UNION ALL
SELECT 'Not Applicable' FROM DUAL UNION ALL
SELECT 'NOV-20-2001' FROM DUAL UNION ALL
SELECT 'BANIJYA BHIBAG' FROM DUAL UNION ALL
SELECT 'AUGUST 03 2017' FROM DUAL UNION ALL
SELECT 'AUG-04-1991' FROM DUAL UNION ALL
SELECT '97/2015' FROM DUAL UNION ALL
SELECT '09/09/2018' FROM DUAL;
Outputs:
ACTUAL
PARSED
SEP-10-2017
10-SEP-2017
SEP 30 2018
30-SEP-2018
OFFICE OF SMALL
OCT-11-2018
11-OCT-2018
O9-SEP-2009
Not Applicable
NOV-20-2001
20-NOV-2001
BANIJYA BHIBAG
AUGUST 03 2017
03-AUG-2017
AUG-04-1991
04-AUG-1991
97/2015
09/09/2018
09-SEP-2018
Note: O9-SEP-2009 has not parsed because you have the letter O rather than the digit 0 as the first character.
db<>fiddle here
One option is to create a function which will cover all possible combinations. The following example has some of them - you'd have to add new when you find them.
SQL> create or replace function f_date (par_str in varchar2)
2 return date
3 is
4 l_date date;
5 begin
6 begin
7 l_date := to_date(par_str, 'mon-dd-yyyy');
8 return l_date;
9 exception
10 when others then null;
11 end;
12
13 --
14
15 begin
16 l_date := to_date(par_str, 'mon dd yyyy');
17 return l_date;
18 exception
19 when others then null;
20 end;
21
22 --
23
24 begin
25 l_date := to_date(par_str, 'dd-mon-yyyy');
26 return l_date;
27 exception
28 when others then null;
29 end;
30
31 --
32
33 begin
34 l_date := to_date(par_str, 'month dd yyyy');
35 return l_date;
36 exception
37 when others then null;
38 end;
39
40 --
41
42 begin
43 l_date := to_date(par_str, 'dd/mm/yyyy');
44 return l_date;
45 exception
46 when others then null;
47 end;
48
49 return null;
50
51 exception
52 when others then
53 return null;
54 end f_date;
55 /
Function created.
Testing:
SQL> select actual, f_date(actual) as expected
2 from test;
ACTUAL EXPECTED
--------------- -----------
SEP-10-2017 10-SEP-2017
SEP 30 2018 30-SEP-2018
OFFICE OF SMALL
09-SEP-2009 09-SEP-2009
Not applicable
AUGUST 03 2017 03-AUG-2017
97/2015
09/09/2018 09-SEP-2018
8 rows selected.
SQL>
Possible problems: if actual = 09/10/12, what is what?
09 can be day, month or year
10 can also be day, month or year
12 can also be day, month or year
So, what is it?
09-OCT-2012
10-SEP-2012
12-OCT-2009
...
If you're sure about that you don't have any other type of the format for those values which can be convertible to a date value(eg.all of the data conform with this sample data set), then use the following code block in order to populate another table(t2) with the decent date values while displaying the rotten values such as
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
dt DATE;
BEGIN
FOR c IN (SELECT * FROM t) LOOP
BEGIN
dt := TO_DATE(c.col, 'MON-DD-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH');
INSERT INTO t2 VALUES(c.id,c.col);
EXCEPTION
WHEN OTHERS THEN
BEGIN
dt := TO_DATE(c.col, 'DD/MM/YYYY', 'NLS_DATE_LANGUAGE=ENGLISH');
INSERT INTO t2 VALUES(c.id,c.col);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(c.col || ' is not a valid date value');
END;
END;
END LOOP;
COMMIT;
END;
/
Demo

oracle delete data from table with rownum

I have a procedure that deletes the data from table1. Five instances of the procedure running the same time and all ran without any error/exception.
But still, there are many records in the table1 where EndTime <= v_purgedate. Please help.
declare
rowcount1 NUMBER;
begin
LOOP
delete table1 eh
where eh.EndTime <= v_purgedate
and rownum <= 30000 ;
rowcount1 := rowcount1 + sql%rowcount;
commit;
exit when rowcount1=0;
rowcount1=0;
END LOOP;
end;
/
You are trying to do what is called "do it yourself parallelism", as opposed to the parallel processing that Oracle can do by itself if you ask nicely.
There is a package called DBMS_PARALLEL_EXECUTE that does this for you. You split the data into chunks (based on ROWIDs or numbers), then call the RUN_TASK procedure to process each chunk, either serially or in parallel. There is one commit per chunk.
Here is a demonstration that deletes 30,000 rows per commit. First, test data:
SQL> create table table1(endtime) as
2 select trunc(sysdate) - level/24 from dual
3 connect by level <= 240000;
Table TABLE1 created.
SQL> insert into table1 select * from table1;
240,000 rows inserted.
SQL> insert into table1 select * from table1;
480,000 rows inserted.
SQL> insert into table1 select * from table1;
960,000 rows inserted.
SQL> select count(*) from table1;
COUNT(*)
----------
1920000
Now, here is the code:
SQL> declare
2 l_task_name varchar2(128) := 'Delete_TABLE1';
3 l_sql_chunk clob := q'§
4 select date '2100-01-01' - min(endtime) start_id,
5 date '2100-01-01' - max(endtime) end_id,
6 chunk
7 from (
8 select endtime,
9 ceil(row_number() over(order by endtime) / 30000) chunk
10 from table1 where endtime < sysdate - 8000
11 )
12 group by chunk
13 §';
14 l_sql_run clob := q'§
15 delete from table1
16 where endtime between
17 date '2100-01-01' - :start_id and
18 date '2100-01-01' - :end_id
19 §';
20 l_boolean boolean := false;
21 task_not_found EXCEPTION;
22 PRAGMA EXCEPTION_INIT(task_not_found, -29498);
23 begin
24 begin
25 DBMS_PARALLEL_EXECUTE.DROP_TASK (l_task_name);
26 exception when task_not_found then null;
27 end;
28 DBMS_PARALLEL_EXECUTE.CREATE_TASK (l_task_name);
29 DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_SQL (
30 task_name => l_task_name,
31 sql_stmt => l_sql_chunk,
32 by_rowid => l_boolean
33 );
34 DBMS_PARALLEL_EXECUTE.RUN_TASK (
35 task_name => l_task_name,
36 sql_stmt => l_sql_run,
37 language_flag => 1,
38 parallel_level => 0 -- 0 for serial, 2+ for parallel
39 );
40 end;
41 /
you have an endless Loop, because rowcount1 will never be 0 when your table contains data that will be deleted.
i think what you what is
declare
rowcount1 NUMBER;
begin
LOOP
delete table1 eh
where eh.EndTime <= v_purgedate -- you should also initialize a variable
and rownum <= 30000 ;
--rowcount1 := rowcount1 + sql%rowcount;
commit;
exit when sql%rowcount = 0;
rowcount1=0;
END LOOP;
end;
/
When you declare the variable rowcount1, you do not assign any number to it, so its value is NULL. Afterwards, when you add a number to NULL the result is always NULL. This is surely not what you want. To illustrate:
SQL> declare
2 rowcount1 NUMBER;
3 begin
4 dbms_output.put_line('<'||rowcount1||'>');
5 rowcount1 := rowcount1 + 1;
6 dbms_output.put_line('<'||rowcount1||'>');
7 end;
8 /
<>
<>
PL/SQL procedure successfully completed.
It seems your code will go in an endless loop. You say that is not the case, so clearly you are not showing us any real code.
Anyway, this is not a good way to do what you want. I'll post another answer saying why.
Regards, Stew Ashton
It's a easy code for delete data in a table .
Try it.
declare
cursor c is
select * from table1 where rownum<=30000;
begin
for i in c loop
delete from table1 where EndTime <= v_purgedate;
end loop;
commit;
end;

pl sql replace multiple values in sequence

I have 2 columns in a table named Query and Values.
Query :
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values (l_Name,:l_Line,:l_Type,:l_Command,:l_Rule,:l_Client_ID,:l_Site_ID,l_Name)
Values : #1(2):20 #2(1):H #3(2):IF #4(27):FA - RETAIN OLD MASTER DATA #5(0): #6(0):
CName and SName use same value Manu.
I want to replace binded vars with values in next columns to get executable query.
What i want:
I want to write a procedure which get values from both columns of table to make a query and execute that query.
insert into address_MP (CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values ('Manu','20','H','IF','FA - RETAIN OLD MASTER DATA','','Manu')
Here's one option, which converts both query VALUES clause, as well as values themselves into rows - each in its own cursor loop, pairing bind variable and its value via their row number. CHR(39) is a single quote.
As the table most probably doesn't contain a single row (is it identified by some ID? You never told us), you'll have to adjust it, otherwise it won't work properly.
Test table:
SQL> select * from test;
QUERY
--------------------------------------------------------------------------------
C_VALUES
--------------------------------------------------------------------------------
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values (l_Name,:l_Line,:l_Type,:l_Command,:l_Rule,:l_Client_ID,:l_Site_ID,l_Name
)
#1(2):20 #2(1):H #3(2):IF #4(27):FA - RETAIN OLD MASTER DATA #5(0): #6(0):
SQL> set serveroutput on
Code & the result:
SQL> DECLARE
2 l_query test.query%TYPE;
3 l_name VARCHAR2 (30) := 'Manu';
4 BEGIN
5 SELECT query INTO l_query FROM test;
6
7 -- bind variables in QUERY
8 FOR cur_l
9 IN (SELECT ROW_NUMBER () OVER (ORDER BY lvl) rn, res l_val
10 FROM ( SELECT LEVEL lvl,
11 REGEXP_SUBSTR (res,
12 '[^,]+',
13 1,
14 LEVEL)
15 res
16 FROM (SELECT SUBSTR (query, INSTR (query, 'values')) res
17 FROM test)
18 CONNECT BY LEVEL <= REGEXP_COUNT (res, ':') + 1)
19 WHERE SUBSTR (res, 1, 1) = ':')
20 LOOP
21 -- values in VALUES
22 FOR cur_v
23 IN (SELECT rn, TRIM (SUBSTR (res, INSTR (res, ':') + 1)) c_val
24 FROM ( SELECT LEVEL rn,
25 REGEXP_SUBSTR (t.c_values,
26 '[^#]+',
27 1,
28 LEVEL)
29 res
30 FROM test t
31 CONNECT BY LEVEL <= REGEXP_COUNT (t.c_values, '#'))
32 WHERE rn = cur_l.rn)
33 LOOP
34 l_query :=
35 REPLACE (l_query,
36 cur_l.l_val,
37 CHR (39) || cur_v.c_val || CHR (39));
38 END LOOP;
39 END LOOP;
40
41 -- Put Manu into l_Name
42 l_query := REPLACE (l_query, 'l_Name', CHR (39) || l_name || CHR (39));
43
44 DBMS_OUTPUT.put_line (l_query);
45 END;
46 /
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values ('Manu','20','H','IF','FA - RETAIN OLD MASTER DATA','','','Manu')
PL/SQL procedure successfully completed.
SQL>

Create Procedure with no Parameters

Its my second time writing procedures and this one is the first time I have to use a cursor. For this procedure I am not expected to use parameters but Im having problems with it. This is what I currently have:
Create or Replace Procedure Update_Balance as
Cursor C1 is
Select purchases.Cust_ID, Purchase_Amount, curr_balance,
credit_line,Pending_Flag
from Purchases
inner join customers on purchases.cust_id = customers.cust_id
where purchases.pending_flag = 1
for update of curr_balance;
PendingBalance purchases.purchase_amount%type;
PurchaseRow c1%RowType;
ProposedNewBalance purchases.purchase_amount%type;
Begin
Begin
Open C1;
Fetch c1 into PurchaseRow;
While c1% Found Loop
Select sum(Purchase_amount)
into PendingBalance
from purchases
where cust_id = c1.cust_id
and pending_flag = 1;
end;
ProposedNewBalance := PendingBalance + c1.curr_balance;
If ProposedNewBalance > C1.Credit_Line then
dbms_output.put_line('One or more purchases were not processed for Customer');
end if;
If ProposedNewBalance <= c1.Credit_Line then
update Customers
set curr_balance = ProposedNewBalance
where customer.cust_id = c1.cust_id;
end if;
If ProposedNewBalance <= c1.credit_line then
Update Purchases
set Pending_Flag = 1
where purchases.cust_id = c1.cust_id;
end if;
end;
It would be better if you posted CREATE TABLE statements within the question, not as comments. Besides, you didn't post all tables involved (those referenced by referential integrity constraints so I removed them):
SQL> CREATE TABLE customers(
2 cust_id CHAR(6)
3 CONSTRAINT customers_pk PRIMARY KEY,
4 first_name VARCHAR2(100),
5 last_name VARCHAR2(100),
6 credit_line NUMBER(10,2),
7 curr_balance NUMBER(10,2),
8 earned_points NUMBER(5),
9 tier_id CHAR(2)
10 -- CONSTRAINT tier_fk FOREIGN KEY(tier_id)
11 -- REFERENCES rewards_tier,
12 -- CONSTRAINT balance_chk CHECK(curr_balance <= credit_line)
13 );
Table created.
SQL>
SQL> CREATE TABLE purchases(
2 purchase_id CHAR(7)
3 CONSTRAINT purchase_pk PRIMARY KEY,
4 cust_id CHAR(6),
5 purchase_date DATE,
6 purchase_amount NUMBER(10,2),
7 pending_flag NUMBER(1)
8 -- a value of 1 means it is pending
9 -- CONSTRAINT cust_pur_fk FOREIGN KEY(cust_id)
10 -- REFERENCES customers
11 );
Table created.
SQL>
Now, the procedure:
it is missing END LOOP (so I added it)
it contains one BEGIN-END pair that isn't necessary (so I removed it)
cursor name is c1, but you don't reference columns it returns by cursor name - you have to use cursor variable name (purchaserow) instead
This compiles; you said that you don't know whether what you're doing is correct or not. How are we supposed to know it? You never explained what problem you're solving.
SQL> CREATE OR REPLACE PROCEDURE update_balance AS
2 CURSOR c1 IS
3 SELECT purchases.cust_id,
4 purchase_amount,
5 curr_balance,
6 credit_line,
7 pending_flag
8 FROM purchases
9 INNER JOIN customers ON purchases.cust_id = customers.cust_id
10 WHERE purchases.pending_flag = 1
11 FOR UPDATE OF curr_balance;
12
13 pendingbalance purchases.purchase_amount%TYPE;
14 purchaserow c1%rowtype;
15 proposednewbalance purchases.purchase_amount%TYPE;
16 BEGIN
17 OPEN c1;
18 FETCH c1 INTO purchaserow;
19 WHILE c1%found LOOP
20 SELECT SUM(purchase_amount)
21 INTO pendingbalance
22 FROM purchases
23 WHERE cust_id = purchaserow.cust_id -- this
24 AND pending_flag = 1;
25
26 proposednewbalance := pendingbalance + purchaserow.curr_balance;
27 IF proposednewbalance > purchaserow.credit_line THEN
28 dbms_output.put_line('One or more purchases were not processed for Customer');
29 END IF;
30 IF proposednewbalance <= purchaserow.credit_line THEN
31 UPDATE customers
32 SET
33 curr_balance = proposednewbalance
34 WHERE customers.cust_id = purchaserow.cust_id; -- this
35
36 END IF;
37
38 IF proposednewbalance <= purchaserow.credit_line THEN
39 UPDATE purchases
40 SET
41 pending_flag = 1
42 WHERE purchases.cust_id = purchaserow.cust_id;
43
44 END IF;
45 END LOOP; -- this
46 END;
47 /
Procedure created.
SQL>

Resources