delete statement not deleting records - sql

Anyone know why its not deleting the records from the student table. When the table is empty and i first run the anonymous block it runs fine, but then when i run it again i get errors about duplicate primary keys, but this shouldn't matter as each time i run the block it should delete all records from the table?? I'm relatively new to SQL so any help is appreciated.
I should add, the procedure seems to work fine as the dopl appears when i run the code.
This is my function:
CREATE OR REPLACE FUNCTION DELETE_ALL_STUDENTS RETURN NUMBER AS
BEGIN
DELETE FROM STUDENTS;
END;
and this is my procedure:
create or replace PROCEDURE DELETE_ALL_STUDENTS_VIASQLDEV AS
BEGIN
dbms_output.put_line('--------------------------------------------');
dbms_output.put_line('Deleting all student rows');
END;
and this is the anonymous block i was running to see if it worked:
begin
DELETE_ALL_STUDENTS_VIASQLDEV;
ADD_STUDENT_VIASQLDEV(1,'Fred Smith');
ADD_STUDENT_VIASQLDEV(2,'Sue Davis');
ADD_STUDENT_VIASQLDEV(3,'Emma Jones');
end;

"doesnt my function carry out the deleting?"
It would if you called it, but alas you don't.
This is not a SQL problem, it's a logic problem. If we don't do the washing up the dishes remain dirty. Similarly if you don't call the routine which deletes the records the records are not deleted.
You need to call the function in the procedure. Not sure why you've made it a function, and it won't compile anyway, because it doesn't have a RETURN clause. So, let's fix that too.
CREATE OR REPLACE FUNCTION DELETE_ALL_STUDENTS RETURN NUMBER AS
BEGIN
DELETE FROM STUDENTS;
return sql%rowcount; -- how many rows were deleted
END;
/
Now we call it:
create or replace PROCEDURE DELETE_ALL_STUDENTS_VIASQLDEV AS
n number;
BEGIN
dbms_output.put_line('--------------------------------------------');
dbms_output.put_line('Deleting all student rows');
n := DELETE_ALL_STUDENTS;
dbms_output.put_line('No of students deleted = '|| to_char(n));
END;
So, when you run your anonymous block the existing students will be deleted and replaced with the new ones.

Related

Count(*) not working properly

I create the trigger A1 so that an article with a certain type, that is 'Bert' cannot be added more than once and it can have only 1 in the stock.
However, although i create the trigger, i can still add an article with the type 'Bert'. Somehow, the count returns '0' but when i run the same sql statement, it returns the correct number. It also starts counting properly if I drop the trigger and re-add it. Any ideas what might be going wrong?
TRIGGER A1 BEFORE INSERT ON mytable
FOR EACH ROW
DECLARE
l_count NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT COUNT(*) INTO l_count FROM mytable WHERE article = :new.article;
dbms_output.put_line('Count: ' || l_count);
IF l_count >0 THEN
IF(:new.TYPEB = 'Bert') THEN
dbms_output.put_line('article already exists!');
ROLLBACK;
END IF;
ELSIF (:new.TYPEB = 'Bert' AND :new.stock_count>1) THEN
dbms_output.put_line('stock cannot have more than 1 of this article with type Bert');
ROLLBACK;
END IF;
END;
This is the insert statement I use:
INSERT INTO mytable VALUES('Chip',1,9,1,'Bert');
A couple of points. First, you are misusing the autonomous transaction pragma. It is meant for separate transactions you need to commit or rollback independently of the main transaction. You are using it to rollback the main transaction -- and you never commit if there is no error.
And those "unforeseen consequences" someone mentioned? One of them is that your count always returns 0. So remove the pragma both because it is being misused and so the count will return a proper value.
Another thing is don't have commits or rollbacks within triggers. Raise an error and let the controlling code do what it needs to do. I know the rollbacks were because of the pragma. Just don't forget to remove them when you remove the pragma.
The following trigger works for me:
CREATE OR REPLACE TRIGGER trg_mytable_biu
BEFORE INSERT OR UPDATE ON mytable
FOR EACH ROW
WHEN (NEW.TYPEB = 'Bert') -- Don't even execute unless this is Bert
DECLARE
L_COUNT NUMBER;
BEGIN
SELECT COUNT(*) INTO L_COUNT
FROM MYTABLE
WHERE ARTICLE = :NEW.ARTICLE
AND TYPEB = :NEW.TYPEB;
IF L_COUNT > 0 THEN
RAISE_APPLICATION_ERROR( -20001, 'Bert already exists!' );
ELSIF :NEW.STOCK_COUNT > 1 THEN
RAISE_APPLICATION_ERROR( -20001, 'Can''t insert more than one Bert!' );
END IF;
END;
However, it's not a good idea for a trigger on a table to separately access that table. Usually the system won't even allow it -- this trigger won't execute at all if changed to "after". If it is allowed to execute, one can never be sure of the results obtained -- as you already found out. Actually, I'm a little surprised the trigger above works. I would feel uneasy using it in a real database.
The best option when a trigger must access the target table is to hide the table behind a view and write an "instead of" trigger on the view. That trigger can access the table all it wants.
You need to do an AFTER trigger, not a BEFORE trigger. Doing a count(*) "BEFORE" the insert occurs results in zero rows because the data hasn't been inserted yet.

Oracle - Is there any way I can get around SELECT INTO on PL/SQL block

I am trying to create a trigger, which automatically updates a student's application state when the application status row in the application table changes. I have been browsing the web for a little over an hour or so now and despite finding a potential work around using EXECUTE IMMEDIATE I cannot achieve my desired result (EXECUTE IMMEDIATE was causing an unbound variable error).
Trigger code
CREATE OR REPLACE TRIGGER trg_applications
BEFORE INSERT OR UPDATE ON applications FOR EACH ROW
BEGIN
IF UPDATING THEN
/* If the status is ACCEPTED, then approve the students application */
SELECT CASE
WHEN get_status(:NEW.status_id) =
LOWER('Applicant Accepted Offer')
THEN student_accept_offer( :NEW.student_id )
END
FROM status;
END IF;
END;
The get status method returns a VARCHAR2 to check whether the new status matches the condition, if so I want to update the student_approved row using the autonomous_transaction below.
student_accept_offer code
CREATE OR REPLACE FUNCTION student_accept_offer( this_stu_id NUMBER )
RETURN VARCHAR2 IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE students
SET students.student_on_placement = 1
WHERE students.student_id = this_stu_id;
COMMIT;
RETURN 'Student has approved application';
END student_accept_offer;
This function works as intended when I test it outside of my trigger, however when it is embedded in the trigger an PLS-00428 error gets thrown. Could anyone point me in the right direction as to how can I work around this, to allow me to have this function fire automatically on an update if the status matches.
Thanks for your time
EDIT - Tables I am referencing
Changing your code slightly to remove the SELECT statement (as it seems unnecessary) then does this work?
CREATE OR REPLACE TRIGGER trg_applications
BEFORE INSERT OR UPDATE ON applications FOR EACH ROW
BEGIN
IF UPDATING THEN
/* If the status is ACCEPTED, then approve the students application */
IF get_status(:NEW.status_id) = 'applicant accepted offer' THEN
student_accept_offer( :NEW.student_id );
END IF;
END IF;
END;

Writing an SQL trigger to find if number appears in column more than X times?

I want to write a Postgres SQL trigger that will basically find if a number appears in a column 5 or more times. If it appears a 5th time, I want to throw an exception. Here is how the table looks:
create table tab(
first integer not null constraint pk_part_id primary key,
second integer constraint fk_super_part_id references bom,
price integer);
insert into tab values(1,NULL,100), (2,1,50), (3,1,30), (4,2,20), (5,2,10), (6,3,20);
Above are the original inserts into the table. My trigger will occur upon inserting more values into the table.
Basically if a number appears in the 'second' column more than 4 times after inserting into the table, I want to raise an exception. Here is my attempt at writing the trigger:
create function check() return trigger as '
begin
if(select first, second, price
from tab
where second in (
select second from tab
group by second
having count(second) > 4)
) then
raise exception ''Error, there are more than 5 parts.'';
end if;
return null;
end
'language plpgsql;
create trigger check
after insert or update on tab
for each row execute procedure check();
Could anyone help me out? If so that would be great! Thanks!
CREATE FUNCTION trg_upbef()
RETURN trigger as
$func$
BEGIN
IF (SELECT count(*)
FROM tab
WHERE second = NEW.second ) > 3 THEN
RAISE EXCEPTION 'Error: there are more than 5 parts.';
END IF;
RETURN NEW; -- must be NEW for BEFORE trigger
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER upbef
BEFORE INSERT OR UPDATE ON tab
FOR EACH ROW EXECUTE procedure trg_upbef();
Major points
Keyword is RETURNS, not RETURN.
Use the special variable NEW to refer to the newly inserted / updated row.
Use a BEFORE trigger. Better skip early in case of an exception.
Don't count everything for your test, just what you need. Much faster.
Use dollar-quoting. Makes your live easier.
Concurrency:
If you want to be absolutely sure, you'll have to take an exclusive lock on the table before counting. Else, concurrent inserts / updates might outfox each other under heavy concurrent load. While this is rather unlikely, it's possible.

INSTEAD OF DELETE trigger (Postgresql)

I would like to disable the DELETE statement on a table.
What I need to do is a SET a field value instead of removing the respective record.
So far I have tried the following:
CREATE TRIGGER delete_trg
INSTEAD OF DELETE
ON schema.tbl
FOR EACH ROW
EXECUTE PROCEDURE schema.tbl_delete_fn();
My schema.tbl_delete_fn() function is as follows:
CREATE OR REPLACE FUNCTION schema.tbl_delete_fn()
RETURNS trigger AS
BEGIN
NEW.deleted := true;
RETURN NEW;
END;
So far this doesn't seem to work... any ideas?
You want a BEFORE DELETE trigger whose function returns NULL and the row variable is OLD, not NEW.
CREATE TRIGGER delete_trg
BEFORE DELETE
ON schema.tbl
FOR EACH ROW
EXECUTE PROCEDURE schema.tbl_delete_fn();
CREATE OR REPLACE FUNCTION schema.tbl_delete_fn()
RETURNS trigger AS '
BEGIN
UPDATE schema.tbl SET deleted=true WHERE ctid=OLD.ctid;
RETURN NULL;
END; ' language plpgsql;
Or...
CREATE RULE delete_rule
AS ON DELETE TO schema.tbl
DO INSTEAD NOTHING;
Pros: Clearer, no code is called for each row visited, and no SP required.
Cons: Less standard than the trigger solution.

Solution to "cannot perform a DML operation inside a query"?

I am using a Data Analysis tool and the requirement I have was to accept a value from the user, pass that as a parameter and store it in a table. Pretty straighforward so I sat to write this
create or replace
procedure complex(datainput in VARCHAR2)
is
begin
insert into dumtab values (datainput);
end complex;
I executed this in SQL Developer using the following statement
begin
complex('SomeValue');
end;
It worked fine, and the value was inserted into the table. However, the above statements are not supported in the Data Analysis tool, so I resorted to use a function instead. The following is the code of the function, it compiles.
create or replace
function supercomplex(datainput in VARCHAR2)
return varchar2
is
begin
insert into dumtab values (datainput);
return 'done';
end supercomplex;
Once again I tried executing it in SQL Developer, but I got cannot perform a DML operation inside a query upon executing the following code
select supercomplex('somevalue') from dual;
My question is
- I need a statement that can run the mentioned function in SQL Developer or
- A function that can perform what I am looking for which can be executed by the select statement.
- If it is not possible to do what I'm asking, I would like a reason so I can inform my manager as I am very new (like a week old?) to PL/SQL so I am not aware of the rules and syntaxes.
P.S. How I wish this was C++ or even Java :(
EDIT
I need to run the function on SQL Developer because before running it in DMine (which is the tool) in order to test if it is valid or not. Anything invalid in SQL is also invalid in DMine, but not the other way around.
Thanks for the help, I understood the situation and as to why it is illegal/not recommended
You could use the directive pragma autonomous_transaction. This will run the function into an independant transaction that will be able to perform DML without raising the ORA-14551.
Be aware that since the autonomous transaction is independent, the results of the DML will be commited outside of the scope of the parent transaction. In most cases that would not be an acceptable workaround.
SQL> CREATE OR REPLACE FUNCTION supercomplex(datainput IN VARCHAR2)
2 RETURN VARCHAR2 IS
3 PRAGMA AUTONOMOUS_TRANSACTION;
4 BEGIN
5 INSERT INTO dumtab VALUES (datainput);
6 COMMIT;
7 RETURN 'done';
8 END supercomplex;
9 /
Function created
SQL> SELECT supercomplex('somevalue') FROM dual;
SUPERCOMPLEX('SOMEVALUE')
--------------------------------------------------------------------------------
done
SQL> select * from dumtab;
A
--------------------------------------------------------------------------------
somevalue
Tom Kyte has a nice explanation about why the error is raised in the first place. It is not safe because it may depend upon the order in which the rows are processed. Furthermore, Oracle doesn't guarantee that the function will be executed at least once and at most once per row.
Just declare a variable to accept the return value, for example:
declare
retvar varchar2(4);
begin
retvar := supercomplex('somevalue');
end;
The select doesn't work because the function is performing an insert, if all it did was return a value then it would work.
Just execute the function in a dummy if ... end if; statement to ignore the return value:
exec if supercomplex('somevalue') then null; end if;
Or execute it as a parameter for put_line procedure to output the return value:
exec dbms_ouput ('result of supercomplex='||supercomplex('somevalue'));
result of supercomplex=done