I would like to track user activities for a certain table.
For this I have created a table in which I can insert the audit data:
CREATE TABLE "AUDIT_FINALPAPERS"
( "TBLID" NUMBER,
"NAME" VARCHAR2(10),
"REMARKS" VARCHAR2(255),
"AUDIT_USER" VARCHAR2(50),
"AUDIT_DATE" DATE,
"AUDIT_ACTION" VARCHAR2(30)
)
Then created a trigger to track user activities and insert data into the audit table:
CREATE OR REPLACE TRIGGER AUDIT_TBL1
2 -- starts on every update, insert or delete command
3 AFTER INSERT OR DELETE OR UPDATE
4 ON TBL1
5 FOR EACH ROW
6 DECLARE
7 -- variable which declares if update, delete or insert process
8 v_trg_action VARCHAR2 (30);
9 v_user VARCHAR2 (30);
10 BEGIN
11 v_user := SYS_CONTEXT ('APEX$SESSION', 'APP_USER');
12
13 IF UPDATING
14 THEN
15 -- when update
16 v_trg_action := 'UPDATED.';
17 ELSIF DELETING
18 THEN
19 -- when delete
20 v_trg_action := 'DELETED.';
21 ELSIF INSERTING
22 THEN
23 -- when insert
24 v_trg_action := 'SUBMITTED.'; -- aKtion
25 ELSE
26 -- if something else
27 v_trg_action := NULL;
28 END IF;
29
30 IF v_trg_action IN ('DELETED.', 'UPDATED.')
31 THEN
32 -- if v_trg_action is DELETE or UPDATE then insert old table values
33 INSERT INTO AUDIT_TBL1 (TBLID,
34 NAME,
35 REMARKS,
36 AUDIT_USER,
37 AUDIT_DATE,
38 AUDIT_ACTION)
39 VALUES (:OLD.TBLID,
40 :OLD.NAME,
41 :OLD.REMARKS,
42 v_user,
43 SYSDATE,
44 v_trg_action);
45 ELSE
46 INSERT INTO AUDIT_TBL1 (TBLID,
47 NAME,
48 REMARKS,
49 AUDIT_USER,
50 AUDIT_DATE,
51 AUDIT_ACTION)
52 VALUES (:NEW.TBLID,
53 :NEW.NAME,
54 :NEW.REMARKS,
55 v_user,
56 SYSDATE,
57 v_trg_action);
58 END IF;
59 -- about the insert command on the audit table
60 -- for current apex user:SYS_CONTEXT('APEX$SESSION','APP_USER');
61 -- for date: SYSDATE
62 -- for sql command: v_trg_action
63 END AUDIT_TBL1;
64 /
However I get error: PLS-00049: bad bind variable
What exactly does this mean and how can this be solved?
Looks like a wrongly reported error. When I ran your code, it went well except for this part as you named the variable v_trg_aKtion (instead of aCtion)
21 ELSIF INSERTING
22 THEN
23 -- when insert
24 v_trg_action := 'SUBMITTED.'; -- aKtion
Have a look; trigger created.
SQL> CREATE OR REPLACE TRIGGER AUDIT_TBL1
2 -- starts on every update, insert or delete command
3 AFTER INSERT OR DELETE OR UPDATE
4 ON TBL1
5 FOR EACH ROW
6 DECLARE
7 -- variable which declares if update, delete or insert process
8 v_trg_action VARCHAR2 (30);
9 v_user VARCHAR2 (30);
10 BEGIN
11 v_user := SYS_CONTEXT ('APEX$SESSION', 'APP_USER');
12
13 IF UPDATING
14 THEN
15 -- when update
16 v_trg_action := 'UPDATED.';
17 ELSIF DELETING
18 THEN
19 -- when delete
20 v_trg_action := 'DELETED.';
21 ELSIF INSERTING
22 THEN
23 -- when insert
24 v_trg_action := 'SUBMITTED.'; -- aKtion
25 ELSE
26 -- if something else
27 v_trg_action := NULL;
28 END IF;
29
30 IF v_trg_action IN ('DELETED.', 'UPDATED.')
31 THEN
32 -- if v_trg_action is DELETE or UPDATE then insert old table values
33 INSERT INTO AUDIT_TBL1 (TBLID,
34 NAME,
35 REMARKS,
36 AUDIT_USER,
37 AUDIT_DATE,
38 AUDIT_ACTION)
39 VALUES (:OLD.TBLID,
40 :OLD.NAME,
41 :OLD.REMARKS,
42 v_user,
43 SYSDATE,
44 v_trg_action);
45 ELSE
46 INSERT INTO AUDIT_TBL1 (TBLID,
47 NAME,
48 REMARKS,
49 AUDIT_USER,
50 AUDIT_DATE,
51 AUDIT_ACTION)
52 VALUES (:NEW.TBLID,
53 :NEW.NAME,
54 :NEW.REMARKS,
55 v_user,
56 SYSDATE,
57 v_trg_action);
58 END IF;
59 -- about the insert command on the audit table
60 -- for current apex user:SYS_CONTEXT('APEX$SESSION','APP_USER');
61 -- for date: SYSDATE
62 -- for sql command: v_trg_action
63 END AUDIT_TBL1;
64 /
Trigger created.
SQL>
Related
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
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>
I will publish the package body and call the function
The function is execute, but does not return data.
Maybe my calling function is bad.
Can you tell me where the error is?
Thanks in advance
My function in package body looks like this:
SQL> CREATE OR REPLACE PACKAGE BODY account_api AS
2 PROCEDURE add_new_account
3 ( p_acc_id accounts.acc_id%type
4 , p_acc_name accounts.acc_name%type
5 , p_acc_amount accounts.acc_amount%type
6 , p_acc_date accounts.acc_date%type)
7 IS
8 BEGIN
9 INSERT INTO accounts(acc_id, acc_name, acc_amount, acc_date)
10 VALUES (p_acc_id, p_acc_name, p_acc_amount, p_acc_date);
11 COMMIT;
12 EXCEPTION
13 WHEN OTHERS THEN
14 ROLLBACK;
15 RAISE;
16 END;
17 PROCEDURE upd_account
18 (p_acc_id accounts.acc_id%type
19 , p_acc_name accounts.acc_name%type
20 , p_acc_amount accounts.acc_amount%type
21 , p_acc_date accounts.acc_date%type
22 )
23 IS
24 BEGIN
25 UPDATE accounts
26 set acc_name = p_acc_name
27 , acc_amount = p_acc_amount
28 , acc_date = p_acc_date
29 WHERE acc_id = p_acc_id;
30 COMMIT;
31 END;
32 PROCEDURE del_accounts
33 (p_acc_id accounts.acc_id%type)
34 IS
35 BEGIN
36 DELETE FROM accounts WHERE acc_id = p_acc_id;
37 COMMIT;
38 EXCEPTION
39 WHEN OTHERS THEN
40 ROLLBACK;
41 RAISE;
42 END;
43 FUNCTION get_amount
44 (p_acc_id accounts.acc_id%type)
45 return Number is res number;
46 begin
47 select acc_amount into res
48 from accounts where acc_id =p_acc_id;
49 return res;
50 end;
51 FUNCTION get_date
52 (p_acc_id accounts.acc_id%type)
53 RETURN date IS res1 date;
54 BEGIN
55 SELECT acc_date INTO res1
56 FROM accounts WHERE acc_id = p_acc_id;
57 RETURN res1;
58 end;
59 end account_api;
60 /
The function is executed but does not return the data.
SQL> set serveroutput on
SQL> declare
2 res1 date;
3 begin
4 res1 := account_api.get_date(1);
5 end;
6 /
PL/SQL procedure successfully completed.
Try to check date by printing. It will show you the output.
set serveroutput on
declare
res1 date;
begin
res1 := account_api.get_date(1);
dbms_output.put_line(res1);
end;
/
Relative newbie here. I have a 3 part assignment and can't seem to get the last section to populate properly. I am attempting to use 2 functions to output both the average number of guests (which works) and the total number of booked nights (not working).
Here is the working code for the two parts:
SQL> DECLARE
2 DDI_REC DDI.LEDGER_VIEW %ROWTYPE; --declaring DDI_REC
3 DDI_ROOM DDI.LEDGER_VIEW.ROOMNUM%type := 0; --declaring DDI_ROOM and assiging it to ZERO
4 FOUND_ROWS BOOLEAN := FALSE; --variable to test for no_data_found exception in the FOR LOOP
5
6 FUNCTION AVG_GUESTS(ROOM IN NUMBER)
7 RETURN NUMBER
8 AS AVERAGE NUMBER;
9
10 BEGIN
11 SELECT AVG(ADULTCNT + CHILDCNT)
12 INTO AVERAGE
13 FROM DDI.LEDGER_VIEW
14 WHERE ROOMNUM = ROOM;
15
16 RETURN AVERAGE;
17
18 EXCEPTION
19 WHEN OTHERS THEN
20 RETURN NULL;
21 END;
22
23
24 BEGIN
25 DBMS_OUTPUT.NEW_LINE;
26 DBMS_OUTPUT.PUT_LINE (' AVG ROOM RENTALS');
27 DBMS_OUTPUT.PUT_LINE (' PER DDI.LEDGER_VIEW');
28 DBMS_OUTPUT.NEW_LINE;
29 DBMS_OUTPUT.PUT_LINE (' ROOM AVG NUMBER BOOKED');
30 DBMS_OUTPUT.PUT_LINE (' NUM OF GUESTS NIGHTS');
31 DBMS_OUTPUT.PUT_LINE (' ---- ---------- ------');
32
33 -- LOOP CALLS FUNCTION AVG_GUESTS ON EACH CHANGE IN ROOMNUM
34
35 FOR DDI_REC IN
36 (SELECT *
37 FROM DDI.LEDGER_VIEW
38 ORDER BY ROOMNUM)
39 LOOP
40 FOUND_ROWS := TRUE; --if data exist, sets variable to TRUE, so IF statement doesn't run
41 IF DDI_ROOM != DDI_REC.ROOMNUM THEN
42 DDI_ROOM := DDI_REC.ROOMNUM;
43 DBMS_OUTPUT.PUT_LINE(' ' ||DDI_ROOM||' '||TO_CHAR(AVG_GUESTS(DDI_ROOM), '9.99'));
44 END IF;
45 END LOOP; -- End of loop
46
47
48 IF NOT FOUND_ROWS THEN -- trigger the exception below when no results are returned
49 RAISE NO_DATA_FOUND;
50 END IF;
51
52 EXCEPTION
53 WHEN NO_DATA_FOUND THEN
54 DBMS_OUTPUT.PUT_LINE('No data found.');
55 END;
56 /
AVG ROOM RENTALS
PER DDI.LEDGER_VIEW
ROOM AVG NUMBER BOOKED
NUM OF GUESTS NIGHTS
---- ---------- ------
101 1.00
102 2.00
103 2.14
104 2.17
105 2.40
106 1.75
107 1.67
108 1.80
PL/SQL procedure successfully completed.
and now the broken code as I try to get "Booked Nights" working. I might even be barking up the wrong tree for how to go about it.
SQL> DECLARE
2 DDI_REC DDI.LEDGER_VIEW %ROWTYPE; --declaring DDI_REC
3 DDI_ROOM DDI.LEDGER_VIEW.ROOMNUM%type := 0; --declaring DDI_ROOM and assiging it to ZERO
4 FOUND_ROWS BOOLEAN := FALSE; --variable to test for no_data_found exception in the FOR LOOP
5 BOOKED_ROOMS DDI.LEDGER_VIEW.ROOMNUM%type := 0; --declaring BOOKED_ROOMS
6
7 FUNCTION AVG_GUESTS(ROOM IN NUMBER)
8 RETURN NUMBER
9 AS AVERAGE NUMBER;
10
11 BEGIN
12 SELECT AVG(ADULTCNT + CHILDCNT)
13 INTO AVERAGE
14 FROM DDI.LEDGER_VIEW
15 WHERE ROOMNUM = ROOM;
16
17 RETURN AVERAGE;
18
19 EXCEPTION
20 WHEN OTHERS THEN
21 RETURN NULL;
22 END;
23
24 BEGIN
25 SELECT COUNT(*)
26 FROM DDI.LEDGER_VIEW
27 WHERE ROOMNUM = BOOKED_ROOMS;
28
29 EXCEPTION
30 WHEN OTHERS THEN
31 RETURN NULL;
32 END;
33
34
35 BEGIN
36 DBMS_OUTPUT.NEW_LINE;
37 DBMS_OUTPUT.PUT_LINE (' AVG ROOM RENTALS');
38 DBMS_OUTPUT.PUT_LINE (' PER DDI.LEDGER_VIEW');
39 DBMS_OUTPUT.NEW_LINE;
40 DBMS_OUTPUT.PUT_LINE (' ROOM AVG NUMBER BOOKED');
41 DBMS_OUTPUT.PUT_LINE (' NUM OF GUESTS NIGHTS');
42 DBMS_OUTPUT.PUT_LINE (' ---- ---------- ------');
43
44 -- LOOP CALLS FUNCTION AVG_GUESTS ON EACH CHANGE IN ROOMNUM
45
46 FOR DDI_REC IN
47 (SELECT *
48 FROM DDI.LEDGER_VIEW
49 ORDER BY ROOMNUM)
50 LOOP
51 FOUND_ROWS := TRUE; --if data exist, sets variable to TRUE, so IF statement doesn't run
52 IF DDI_ROOM != DDI_REC.ROOMNUM THEN
53 DDI_ROOM := DDI_REC.ROOMNUM;
54 DBMS_OUTPUT.PUT_LINE(' ' ||DDI_ROOM||' '||TO_CHAR(AVG_GUESTS(DDI_ROOM), '9.99')|| ' '||TO_CHAR(BOOKED_ROOMS));
55 END IF;
56 END LOOP; -- End of loop
57
58
59 IF NOT FOUND_ROWS THEN -- trigger the exception below when no results are returned
60 RAISE NO_DATA_FOUND;
61 END IF;
62
63 EXCEPTION
64 WHEN NO_DATA_FOUND THEN
65 DBMS_OUTPUT.PUT_LINE('No data found.');
66 END;
67 /
BEGIN
*
ERROR at line 35:
ORA-06550: line 35, column 5:
PLS-00103: Encountered the symbol "BEGIN"
Any and all suggestions welcome... Thank you in advance.
I reformatted your code and added BEGIN-END outmost block (which is missing), along with some comments. Have a look.
DECLARE
ddi_rec ddi.ledger_view%rowtype; --declaring DDI_REC
ddi_room ddi.ledger_view.roomnum%TYPE := 0; --declaring DDI_ROOM and assiging it to ZERO
found_rows BOOLEAN := false; --variable to test for no_data_found exception in the FOR LOOP
booked_rooms ddi.ledger_view.roomnum%TYPE := 0; --declaring BOOKED_ROOMS
FUNCTION avg_guests (
room IN NUMBER
) RETURN NUMBER AS
average NUMBER;
BEGIN
SELECT AVG(adultcnt + childcnt) INTO
average
FROM ddi.ledger_view
WHERE roomnum = room;
RETURN average;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
BEGIN --> you miss this outmost BEGIN ...
-- the 1st begin-exception-end block has to be part of the outmost begin-end
BEGIN
SELECT COUNT(*)
FROM ddi.ledger_view
WHERE roomnum = booked_rooms;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END; -- the 1st block ends here
-- the 2nd begin-exception-end block begins here
BEGIN
dbms_output.new_line;
dbms_output.put_line(' AVG ROOM RENTALS');
dbms_output.put_line(' PER DDI.LEDGER_VIEW');
dbms_output.new_line;
dbms_output.put_line(' ROOM AVG NUMBER BOOKED');
dbms_output.put_line(' NUM OF GUESTS NIGHTS');
dbms_output.put_line(' ---- ---------- ------');
-- LOOP CALLS FUNCTION AVG_GUESTS ON EACH CHANGE IN ROOMNUM
FOR ddi_rec IN ( SELECT * FROM ddi.ledger_view
ORDER BY roomnum ) LOOP
found_rows := true; --if data exist, sets variable to TRUE, so IF statement doesn't run
IF
ddi_room != ddi_rec.roomnum
THEN
ddi_room := ddi_rec.roomnum;
dbms_output.put_line(' '
|| ddi_room
|| ' '
|| TO_CHAR(avg_guests(ddi_room),'9.99')
|| ' '
|| TO_CHAR(booked_rooms) );
END IF;
END LOOP; -- End of loop
IF
NOT found_rows
THEN -- trigger the exception below when no results are returned
RAISE no_data_found;
END IF;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('No data found.');
END; -- the 2nd begin-exception-end block ends here
END; --> ... and you miss this END, for the outmost begin-end block
/
I see two problems. You have two BEGIN blocks in the main body. I believe Littlefoot corrected that. Also:
25 SELECT COUNT(*)
26 FROM DDI.LEDGER_VIEW
27 WHERE ROOMNUM = BOOKED_ROOMS;
You need an INTO clause after the count(*). You'll need to select the value into a variable and do something with the variable. You can't have a naked SELECT in PL/SQL like you can just at the sqlplus command line.
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.