Where is the bug? Compilation ends with errors and I have no idea where I'm going wrong.
create or replace
PROCEDURE make_payoff(user_id_argument number)
is
begin
payoff_amount:= 0;
CURSOR Clicks IS
SELECT c.cpc FROM click as c JOIN widget w ON w.id = c.widget_id JOIN website web ON web.id = w.website_id WHERE web.user_id = user_id_argument AND c.payoff_id IS NULL;
BEGIN
FOR Click IN Clicks
LOOP
payoff_amount:= payoff_amount + Click.cpc;
END LOOP;
INSERT INTO payoff (user_id, amount) VALUES (user_id_argument, payoff_amount);
COMMIT;
end;
I'm getting:
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00905: object S10306.MAKE_PAYOFF is invalid
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
EDIT:
I've fixed Cursor name but error is the same
This is the error you get when you try to use the procedure. Not the error you get when you compile it. You need to find the error you get when you compile the procedure, probably using show errors and attempt to solve that problem.
Your problem is that for click in click should be for click in clicks... Not the extra s, so you are looping through the cursor.
Additionally, in your cursor you've written FROM click as c, which is not valid in Oracle. This should be FROM click c
And two BEGINs... remove the first one.
Alex Poole has noted that you've also not declared the type of the variable payoff_amount. You should declare this as a number:
payoff_amount number := 0;
However, there is no need to do this, no need to loop, no need to use a procedure at all. This is possible with a single SQL statement:
insert into payoff (user_id, amount)
select 'user_id_argument', sum(c.cpc)
from click c
join widget w
on w.id = c.widget_id
join website web
on web.id = w.website_id
where web.user_id = user_id_argument
and c.payoff_id IS NULL;
You missed s.
FOR Click IN Clicks
LOOP
payoff_amount:= payoff_amount + Click.cpc;
END LOOP;
Anyway, don't name variables and cursors so close to database fields. Add some prefix, for example, to differentiate easily.
I created a procedure for my user under the user sys. When starting the procedure, I received the same errors as yours. The procedure was valid. Recompiling the object under different users did not help solve the problem. I drop the procedure and created it as my user. Problem solved.
Related
I have a search sales_orders/payment_details form ...
I am searching record through enter and execute query but in some cases detail record is not found
For Example: Enter Sales Order and Execute Payment record but there is no payment made yet...
then i would like to give message ('No record found')..
please guide me the trigger and its level of execution
Thanks
Javed
That's a master-detail form. I suggest you create it that way and let Forms worry about default data handling (querying, inserting, deleting) - use Data Block Wizard, it'll do everything right and create number of triggers which will enforce the master-detail relationship.
Once you're done, everything will just work - you don't have to write a single line of code.
As I don't have your tables, I used Scott's dept (master) and emp (detail). If you're familiar with that schema, you know that there are some employees in departments 10, 20 and 30, but nobody works in department 40.
Therefore, you'd want to display some message when you query department 40. On the left hand side is output for executing query on department 10; on the right is what we actually want (department 40, no employees there):
How to do that? Navigate to "Program units" in Object Navigator and edit the QUERY_MASTER_DETAILS trigger - it is one of those objects created by the Data Block Wizard.
Edit its code and add a few lines which will check whether detail rows exist; if not, display a message (see line #20):
PROCEDURE Query_Master_Details(rel_id Relation,detail VARCHAR2) IS
oldmsg VARCHAR2(2); -- Old Message Level Setting
reldef VARCHAR2(5); -- Relation Deferred Setting
BEGIN
--
-- Initialize Local Variable(s)
--
reldef := Get_Relation_Property(rel_id, DEFERRED_COORDINATION);
oldmsg := :System.Message_Level;
--
-- If NOT Deferred, Goto detail and execute the query.
--
IF reldef = 'FALSE' THEN
Go_Block(detail);
Check_Package_Failure;
:System.Message_Level := '10';
Execute_Query;
:System.Message_Level := oldmsg;
-- I added this 4 lines: --> here
if :employees.empno is null then
message('No employees in that department');
message('No employees in that department');
end if;
-- End of lines added by LF
ELSE
--
-- Relation is deferred, mark the detail block as un-coordinated
--
Set_Block_Property(detail, COORDINATION_STATUS, NON_COORDINATED);
END IF;
EXCEPTION
WHEN Form_Trigger_Failure THEN
:System.Message_Level := oldmsg;
RAISE;
END Query_Master_Details;
For simplicity, I used two consecutive message calls which then act as if it were an alert (otherwise, that message would be displayed in the status line at the bottom of the window). If you want, you can use "real" alert; I'll leave it to you.
I want my trigger to determined that ApprUserNo = SubmitUserNo or OrgParentNo. Here's my trigger so far :
CREATE OR REPLACE TRIGGER cteam_Trigger1
BEFORE INSERT OR UPDATE OF ApprUserNo
ON cteam_ExpenseReport
FOR EACH ROW
DECLARE
ApprOrgNo cteam_Users.UserOrgNo%TYPE;
SubUserOrgNo cteam_Users.UserOrgNo%TYPE;
UserOrgParentOrg cteam_OrgUnit.OrgParentNo%TYPE;
Unauthorized EXCEPTION;
ErrorMessage VARCHAR2(200);
BEGIN
SELECT UserOrgNo
INTO ApprOrgNo
FROM cteam_Users
WHERE UserOrgNo = :NEW.ApprUserNo;
SELECT UserOrgNo
INTO SubUserOrgNo
FROM cteam_Users
WHERE UserOrgNo = :NEW.SubmitUserNo;
SELECT DECODE OrgParentNo (UserOrgParentNo,'Null',Raise Unauthorized )
INTO UserOrgParentNo
FROM cteam_OrgUnit;
WHERE UserOrgParentNo = :NEW.OrgParentNo;
EXCEPTION
WHEN Unauthorized THEN
ErrorMessage := 'Unauthorised to update ' ;
END;
The trigger is fine up until the SELECT DECODE section.
FROM cteam_OrgUnit; - the semicolon shouldn't be there.
Your DECODE is formatted incorrectly. It might be that you wanted to write:
DECODE(OrgParentNo
UserOrgParentNo, 'Null',
Raise Unauthorized)
but you can't have an executable statement such as RAISE UNAUTHORIZED in a DECODE, which only deals with values. I suggest you rewrite your code as
CREATE OR REPLACE TRIGGER cteam_Trigger1
BEFORE INSERT OR UPDATE OF ApprUserNo ON cteam_ExpenseReport
FOR EACH ROW
DECLARE
vUserOrgParentNo cteam_OrgUnit.OrgParentNo%TYPE;
BEGIN
SELECT ou.OrgParentNo
INTO vUserOrgParentNo
FROM cteam_OrgUnit ou
WHERE ou.OrgParentNo = :NEW.OrgParentNo;
IF vUserOrgParentNo <> :NEW.ApprUserNo THEN
RAISE_APPLICATION_ERROR(-20000, 'Unauthorised to update');
END IF;
END;
I removed the SELECT statements where data was being fetched and never used; likewise, I removed the variables associated with those SELECTs. I replaced the DECODE with a CASE expression because I find a CASE expression is easier to read. I suggest that you prefix variables with an extra character, such as the v I added to vUserOrgParent, to prevent variable names from conflicting with column names in your tables. This is a potential source of problems which is easier to fix this way than any other. I also took some guesses as to the correct column names in your tables - if they're wrong feel free to fix them up.
Also - triggers cannot return error messages. The only way they can "communicate" with other code is by raising exceptions. I added a call to RAISE_APPLICATION_ERROR so the trigger can raise a known exception number.
I'm trying to create a PLSQL statement that updates the money in the inventory of the characters with the money from all the creatures they've fought
The following is the code I've been trying:
DECLARE
inv_money character.money%TYPE;
CURSOR updmoney IS
SELECT *
FROM character
WHERE id IN (SELECT character_id FROM inst_creature)
FOR UPDATE OF money;
BEGIN
FOR v_character IN updmoney LOOP
SELECT inst_creature.money
INTO inv_money
FROM inst_creature,character
WHERE inst_creature.character_id = character.ID;
UPDATE character
SET money = money+inv_money
WHERE CURRENT OF updmoney;
END LOOP;
COMMIT;
END;
There is a character_id in the inst_creature table which is used to define the character that fought that creature.
I'm getting the error
ORA-01422: exact fetch returns more than requested number of rows.
I've been trying to fix it by using Google to get a solution but nothing has been working so far. Any thoughts?
What happens when a character has killed more than one monster? ORA-01422, that's what. The SELECT ... INTO syntax populates a scalar value, that is one row. When the query returns more than one row PL/SQL hurls ORA-01422.
" Is there any solution to this for it to work?"
The easiest way is to fix the query so it returns one row. As you're just adding money to the character's trove, you can use an aggregate:
SELECT sum(inst_creature.money)
INTO inv_money
FROM inst_creature,character
WHERE inst_creature.character_id = character.ID;
So you get one row per character, and one update per character.
I have an oracle error executing this PL/SQL in the second line: SELECT ....
But for God's sake ! I've already check if there is some null values
IF zocRole IS NOT NULL and devices.unit_id IS NOT NULL THEN
SELECT unit_role_id INTO unitRoleId FROM T_UNIT_ROLE WHERE role_id = zocRole AND unit_id = devices.unit_id;
END IF;
As mentioned above, this exception is thrown because your implicit cursor returns no rows. You would also get an exception if more than one row is returned.
You could instead use an Explicit Cursor Oracle Documents This is really just a named SQL statement (into which you can pass parameters if you like).
You then open the cursor, fetch (each fetch will attempt to retrieve one row) and close. You can then check whether the fetch returned any data. It takes slightly longer to code but can look cleaner.
I remember that years ago there was some debate about the relative speed of implicit vs explicit cursors but I've not heard anyone talk about this for a long time so I assume they perform the same
The best way to control the execution in a procedure/function plsql is adding blocks: BEGIN/EXCEPTION.
IF zocRole IS NOT NULL AND devices.unit_id IS NOT NULL
THEN
BEGIN
SELECT unit_role_id
INTO unitRoleId
FROM T_UNIT_ROLE
WHERE role_id = zocRole
AND unit_id = devices.unit_id
;
EXCEPTION
WHEN OTHERS
THEN dbms_output.put_line(SQLCODE||'-'||SUBSTR(SQLERRM, 1, 200));
END
;
END IF
;
I modified the procedure to make it smaller but I really only want to run the select query once. This will reduce the cost of running the procedure. How can I get the prevContectID and nextContentID without running the query twice. This is replacing a previous procedure so I do not want to change the IN and OUT so I do not have to find every where it is being called.
procedure getSeq(theContentID IN table.contentID%type,
prevContentID OUT table.contentID%type,
nextContentID OUT table.contentID%type)
BEGIN
SELECT myPrev into prevContentID, myNext into nextContentID
from myTable
where contentID=theContentID;
RETURN;
END getSeq;
The shown procedure most likely doesn't compile. The correct syntax for SELECT ... INTO using several variables is:
SELECT myPrev, myNext INTO prevContentID, nextContentID
from myTable
where contentID = theContentID;
You can also use a cursor to fetch the values from myTable.For your approach you need to do proper exception handling ,when theContentID does not exists in myTable,because that will give you NO_DATA_FOUND exception.
PROCEDURE getSeq (theContentID IN table.contentID%TYPE,
prevContentID OUT table.contentID%TYPE,
nextContentID OUT table.contentID%TYPE)
IS
CURSOR getcontentID_cur
IS
SELECT myPrev, myNext
FROM myTable
WHERE contentID = theContentID;
BEGIN
OPEN getcontentID_cur;
FETCH getcontentID_cur
INTO prevContentID, nextContentID;
CLOSE getcontentID_cur;
END getSeq;