Oracle - Trigger and Procedure compilation errors - sql

I am trying to write a procedure to display a day of the week, but the error I get is that the "ORA-01841: The (full) year must be between -4713 and +9999 and cannot be 0".
The other problem is I made a trigger that checks if column in SCOTT.BONUS.SALARY has been updated and calculates "howmuch" - raise and returns it. It says that NEW.SAL should be declared, but how can it be declared if its a column name... ?
I think im pretty close but I am missing something, Can anyone help please? Much Appreciated.
-- trigger --
CREATE OR REPLACE TRIGGER Raise
BEFORE DELETE OR INSERT OR UPDATE ON SCOTT.BONUS
FOR EACH ROW
WHEN (NEW.SAL > 0.1*OLD.SAL)
DECLARE
howmuch number;
BEGIN
howmuch := 0.1*NEW.SAL;
dbms_output.put_line('Bonus changed to 10% - : ' || howmuch);
END;
/
-- Procedure --
CREATE OR REPLACE PROCEDURE Birth_Day(data IN varchar, Dey OUT varchar) IS
BEGIN
select to_char(date'data', 'Day') INTO Dey from dual;
END;
/
-- Starting procedure Birth_Day --
DECLARE
Dey varchar(20);
begin
Birth_Day('10/11/2020',Dey);
end;

This expression is not right:
to_char(date'data', 'Day')
The database tries to evaluate literal string 'data' as a date in ISO format ('YYYY-MM-DD'), and fails.
You need to use to_date() first to turn your variable string to a date, and then to_char():
to_char(to_date(data, 'DD/MM/YYYY'), 'Day')

Related

How to dynamically use month as part of a timestamp?

I'm looking to add partitions for the next X months whenever this script is run. At the moment, the script is updated manually with each run, to create partitions for the next 9 months:
ALTER TABLE AUDIT_TBL
ADD PARTITION "YEAR2020_M04" VALUES LESS THAN (TIMESTAMP' 2020-05-01 00:00:00') SEGMENT CREATION DEFERRED;
However, I'd like to get the current month, and change both the partition name and timestamp accordingly.
You can make a PL/SQL procedure with EXECUTE IMMEDIATE command to execute a string as a SQL:
CREATE OR REPLACE PROCEDURE MAKE_PARTITIONS(months_number NUMBER)
DECLARE
v_nextMonthDate DATETIME := SYSDATE;
v_year NUMBER;
v_monthCode CHAR(2);
v_limitDate DATETIME;
v_nextMonth NUMBER;
BEGIN
FOR v_nextMonth IN 0..months_number LOOP
v_year := EXTRACT(year FROM v_nextMonthDate);
v_monthCode := TO_CHAR(v_nextMonthDate, 'MM');
v_limitDate := TRUNC(ADD_MONTHS(v_nextMonthDate, 1),'mon');
EXECUTE IMMEDIATE "ALTER TABLE AUDIT_TBL ADD PARTITION 'YEAR:1_M:2' VALUES LESS THAN (TIMESTAMP ':3') SEGMENT CREATION DEFERRED"
USING v_year, v_monthCode, v_limitDate;
v_nextMonthDate := ADD_MONTHS(v_nextMonthDate, 1);
END LOOP;
END;
Note that my PL/SQL is a bit rusty, so I'm not sure if that compiles without a problem, but this is the idea.
You call this function like that:
BEGIN
MAKE_PARTITIONS(5);
END;
or like that
EXEC MAKE_PARTITIONS(5);

Trying to Create a Trigger to let the user insert only 5 times in a day

create or replace trigger trg_check_placement
before insert
on lds_placement
for each row
declare
plt_count number;
begin
select count(*) into plt_count from lds_placement where TO_CHAR(sysdate,'HH24:MI:SS');
if plt_count >5 then
raise_application_error(-20000, 'Sorry! you cannot create more than five placement a day');
end if;
end;
Keep getting the error invalid relational operator at line 4.Is this the correct way of solving this or am i completely wrong ?

Reduce the value of a vehicle every month

What I am trying to do is to reduce the value of every vehicle in the 'Vehicle' table so that when the date reaches one month past the 'LastUpdate' value the 'Value' column is reduced by 2.5%. The trigger should run AFTER LOGON ON DATABASE. The problem is, a DBA may not logon to the database every day, so if it is has been six months since the last logon, the trigger will loop through, reduce the value by 2.5% and do ADD_MONTHS(LastUpdate, 1).
My code for the function that will calculate the value is:
REATE OR REPLACE FUNCTION fn_Vehicle_Value
(VehicleNumber IN NUMBER,
VehicleValue IN OUT NUMBER)
RETURN NUMERIC
IS
BEGIN
VehicleValue := VehicleValue - (VehicleValue * 0.025);
RETURN VehicleValue;
END;
/
This is my attempt at creating the system trigger:
CREATE OR REPLACE TRIGGER tg_VehicleDepreciate
AFTER LOGON ON DATABASE
IS
CURSOR vehicle_cur IS SELECT "VALUE", LastUpdate FROM Vehicle;
BEGIN
FOR vehicle_rec IN vehicle_cur LOOP
WHILE LastUpdate < SYSDATE LOOP
LastUpdate."Value" := fn_Vehicle_Update("VALUE");
UPDATE Vehicle
SET LastUpdate := ADD_MONTHS(LastUpdate, 1)
WHERE Vehicle# = vehicle_cur.Vehicle#;
END LOOP;
EXIT WHEN vehicle_cur%NOTFOUND;
END LOOP
END;
/
From what I can tell, my function is right. But the trigger does not compile and produces the following error report:
Error report -
ORA-04079: invalid trigger specification
04079. 00000 - "invalid trigger specification"
*Cause: The create TRIGGER statement is invalid.
*Action: Check the statement for correct syntax.
I am guessing it is a syntax error, but there may also be a logical error that I cannot work out.
SHOW ERRORS after compiling your trigger would reveal all errors.
Here are few..
1) in update, it is just SET somecolumn = Somevalue
2) oracle implicitly opens the cursor and closes to, when used with a FOR loop.
3) after using FOR loop,the fetched results should be used like.. Forloopvariable.column-name
4) added Vehicle# to your cursor.
5) added V_date logic.
CREATE OR REPLACE TRIGGER tg_VehicleDepreciate
AFTER LOGON ON DATABASE
IS
CURSOR vehicle_cur IS SELECT "VALUE", LastUpdate ,Vehicle# FROM Vehicle;
V_date DATE;
BEGIN
FOR vehicle_rec IN vehicle_cur LOOP
V_date := vehicle_rec.LastUpdate;
WHILE V_date < SYSDATE LOOP
/* what are you trying here? */
---vehicle_rec."Value" := fn_Vehicle_Update("VALUE");
UPDATE Vehicle
SET LastUpdate = ADD_MONTHS(LastUpdate, 1)
WHERE Vehicle# = vehicle_rec.Vehicle#;
V_date := ADD_MONTHS( V_date,1);
END LOOP;
END LOOP
END;
/

Stored Function in Oracle not Inserting Values into the desired table

Here is my Code given below. I am trying to add 5 parameters which the function takes into the table Employee. But I am not successful in doing it and have tried a lot of things.
Error
ORA-01858: a non-numeric character was found where a numeric was
expected ORA-06512: at "xxxxxxx.A1SF_ADDEMP", line 14
01858. 00000 - "a non-numeric character was found where a numeric was expected"
*Cause: The input data to be converted using a date format model was
incorrect. The input data did not contain a number where a number was
required by the format model.
*Action: Fix the input data or the date format model to make sure the
elements match in number and type. Then retry the operation.
Plus how do I test a Stored function that has a Insert/Update or Delete Statement in it?
Execution Statement
Select A1SF_ADDEMP('Adesh', '33', 'M', 8000, '26/03/1990')
From dual;
Code
CREATE OR REPLACE Function A1SF_ADDEMP
(pEmpName In Varchar2,
pTaxFileNo In Varchar2,
pGender In Varchar2,
pSalary In Number,
pBirthdate In Varchar2
) Return Varchar2
Is
tEmpId Number(38,0);
tBirthDate Date;
BEGIN
tEmpId := A1Seq_Emp.nextval;
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values (tEmpId, pEmpName, pTaxFileNo, pGender, pSalary, tBirthdate);
Commit;
Return null;
END;
Firstly you cannot call a function with DML in it in a select statement. You have to assign the output to a variable in a PL/SQL block, something like:
declare
l_output number;
begin
l_output := my_function(variable1, variable2);
end;
It's bad practice to do DML in a function; partly because it causes the errors you're coming across. You should use a procedure as detailed below. The other reason for this is that you're as always returning null there's no need to return anything at all!
create or replace procedure my_procedure ( <variables> ) is
begin
insert into employees( <columns> )
values ( <values > );
end;
The specific reason for your error is this line:
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
pBirthdate is already a string; by putting a ' around it you're passing the string 'pBirthdate' to the function to_date and Oracle can't convert this string into a day, month or year so it's failing.
You should write this as:
tBirthdate := to_date(pBirthdate,'dd/mm/yyyy');
You also don't need to specify number(38,0), you can just write number instead.
It is possible to return a value from a procedure using the out keyword. If we assume that you want to return empid you could write is as something like this:
create or replace procedure A1SF_ADDEMP (
pEmpName in varchar2
, pTaxFileNo in varchar2
, pGender in varchar2
, pSalary in number
, pBirthdate in varchar2
, pEmpid out number
) return varchar2 is
begin
pempid := A1Seq_Emp.nextval;
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values ( pEmpId, pEmpName, pTaxFileNo, pGender
, pSalary, to_date(pBirthdate,'dd/mm/yyyy');
end;
To just execute the procedure call it like this:
begin
A1SF_ADDEMP( EmpName, TaxFileNo, Gender
, Salary, Birthdate);
commit;
end;
If you want to return the empid then you can call it like this:
declare
l_empid number;
begin
l_empid := A1SF_ADDEMP( EmpName, TaxFileNo, Gender
, Salary, Birthdate);
commit;
end;
Notice how I've moved the commit to the highest level, this is to avoid committing stuff in every procedure when you might have more things you need to do.
Incidentally, if you're using Oracle 11g then there's no need to assign the value A1Seq_Emp.nextval to a variable. You can just insert it directly into the table in the values list. You, of course won't be able to return it, but you could return A1Seq_Emp.curval, as long as there's nothing else getting values from the sequence.
You should use a procedure (instead of a function) if you are not returning any values.
If you look at the line mentioned in the error message you can spot your error:
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
You are passing the string literal 'pBirthdate' to the to_date() call. But you want to pass the parameter, so it should be
tBirthdate := to_date(pBirthdate,'dd/mm/yyyy');
(note the missing single quotes arount pBirthdate).
So as a procedure the whole thing would look like this:
CREATE OR REPLACE PROCEDURE A1SF_ADDEMP
(pEmpName In Varchar2,
pTaxFileNo In Varchar2,
pGender In Varchar2,
pSalary In Number,
pBirthdate In Varchar2
)
IS
BEGIN
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values (A1Seq_Emp.nextval, pEmpName, pTaxFileNo, pGender, pSalary, to_date(pBirthdate,'dd/mm/yyyy'));
Commit;
END;
To run it:
execute A1SF_ADDEMP('Adesh', '33', 'M', 8000, '26/03/1990');
In your situation you need to use procedure with out parameter where your out param is your returning param that contains your desirable value. This is I think best practice in this situation when you want to use DML in select statement.
Second way to do it but its not a good practice is to use one function like yours but before return a value you need to use if statement to check what is the value and if value is what you desire to call in this if statement PRAGMA AUTONOMOUS_TRANSACTION procedure which will do your DML independently of function calling to it. For more information about pragma transactions your can read here:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/autonotransaction_pragma.htm
Best Regards and hope I was helpful

pl/sql - to_date not working with execute immediate parameter

i wanna be able to execute my below proc like so:
exec procname('29-JAN-2011');
proc code is:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000);
BEGIN
SQLS := 'SELECT cola, colb
FROM tablea
WHERE TRUNC(coldate) = TRUNC(TO_DATE('''||pardate||''',''DD/MON/YYYY''))';
EXECUTE IMMEDIATE SQLS;
END;
It keeps throwing error:
ORA-00904: "JAN": invalid identifier.
It compiles, but it throws the error when I run this command:
EXEC procname('29-JAN-2011');
You declare a variable which casts the input parameter to a date: why not use it?
Also, the TRUNC() applied to a date removes the time element. You don't need it here because the value you're passing has no time.
So, your code should be:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000) := 'select cola, colb FROM tablea
WHERE TRUNC(coldate) = :1';
l_a tablea.cola%type;
l_b tablea.colb%type;
BEGIN
EXECUTE IMMEDIATE SQLS
into l_a, l_b
using vardate;
END;
Specifying the dynamic SQL statement with a bind variable and executing it with the USING syntax is a lot more efficient. Note that we still have to SELECT into some variables.
You're using two different notations in the two calls to to_date. I think one of them (the second) is wrong.