ORA-01861: literal does not match format string? - oracle

I have this trigger
CREATE OR REPLACE TRIGGER TRIGGER_MAYOR
BEFORE INSERT OR UPDATE ON VENTAS_MENSUALES
FOR EACH ROW
DECLARE
V_PRODUCTO VARCHAR2(30);
V_FECHA DATE;
V_USUARIO VARCHAR2(50);
BEGIN
SELECT DESCRIPCION INTO V_PRODUCTO FROM PRODUCTO;
SELECT SYSDATE INTO V_FECHA FROM DUAL;
SELECT USER INTO V_USUARIO FROM DUAL;
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO) VALUES (V_PRODUCTO,' '||V_FECHA,V_USUARIO);
END;
I compile and i don't get errors but when i'm going to test by inserting values i get this
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO) VALUES ('testing','2019-05-02',user);
ERROR
ORA-01861: literal does not match format string
This is my table and columns ... I'm just trying to insert values there to check if it's works or not ! producto can be anything , fecha is supposed to be the sysdate and usuario is supposed to be the user who is doing the query (in this case PRUEBA2)

The error doesn't have anything to do with the trigger; the insert is erroring before it gets as far as firing that.
'2019-05-02' is a string, not a date. You should use a date literal like date '2019-05-02', or to_date().
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO)
VALUES ('testing', date '2019-05-02', user);
or if you prefer:
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO)
VALUES ('testing', to_date('2019-05-02', 'YYYY-MM-DD'), user);
but unless you need a non-midnight time, that's just more typing.
Your trigger does have some issues though. Sticking with dates, in this part:
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO)
VALUES (V_PRODUCTO,' '||V_FECHA,V_USUARIO);
The ' '||V_FECHA will implicitly convert the date value to a string using the current session's NLS settings, then prepend a space, then implicitly convert it back to a date. The only reason I can imagine to want to do that is to strip off the time portion, but that relies on NLS, which is never safe; and can be much more easily achieved with trunc(V_FECHA). If that is what you want; if this is for auditing/history then you probably want the full time to be preserved.
Then, SELECT DESCRIPCION INTO V_PRODUCTO FROM PRODUCTO; will try to return all rows from the PRODUCTO table into a single string value. If the table only has one row that will work, but that seems unlikely. If the table is empty you'll get a no-data-found error. If it has more than one row you'll get too-many-rows ("ORA-01422: exact fetch returns more than requested number of rows").
Possibly you intended to look up a single product, but if so it isn't clear how you would identify it. Perhaps the table has another column you haven't shown, and you meant to use the :new pseudorecord.
Both of these queries:
SELECT SYSDATE INTO V_FECHA FROM DUAL;
SELECT USER INTO V_USUARIO FROM DUAL;
can be done with assignments:
V_FECHA := SYSDATE;
V_USUARIO := USER;
though you don't need the variables at all as you can refer to them directly in the insert:
INSERT INTO VENTAS_MENSUALES(PRODUCTO,FECHA,USUARIO)
VALUES (V_PRODUCTO, SYSDATE, USER);
But there is the biggest problem - you are trying to insert into the same table the trigger is against, which will either throw a mutating-table error or, more likely, loop until Oracle kills it.
I suspect you're actually trying to modify the row that is being updated, with the product info from a look-up (though that implies denormalised data?) and the user taking the action. Your initial, erroring, insert statement is only referring to those same rows though, so it's not clear how it should be corrected.
You probably want something more like:
CREATE OR REPLACE TRIGGER TRIGGER_MAYOR
BEFORE INSERT OR UPDATE ON VENTAS_MENSUALES
FOR EACH ROW
BEGIN
SELECT DESCRIPCION INTO :new.PRODUCTO FROM PRODUCTO
WHERE some_column = :new.some_column;
:new.FECHA := SYSDATE;
:new.USUARIO := USER;
END;
If you include the table definition (DDL) and a more realistic insert statement, it may become clearer.

Related

ID get NULL when i try to update data

I created stored procedure in Oracle SQL to update data in my table:
create or replace procedure UpdateProduct(product_id int
,product_name VARCHAR2
,product_price int
,product_description varchar2)
as
begin
update product
set name = product_name,
price = product_price,
description = product_description
where id = product_id;
--
insert into product(update_date)
values (TO_CHAR(SYSDATE, 'DD.MM.YYYY hh24:mi:ss'));
end;
When I run the procedure, an error is thrown as the ID is NULL
begin
UpdateProduct(26, 'шщйыа', 9845, 'ыгаз');
end;
Why do you have that insert statement in the procedure? Are you trying to update the update_date in the same row? That's not what an insert does - insert inserts an entirely new row, which has nothing to do with the values you call the procedure with. Instead, add the assignment to update_date to the other assignments, in the update statement:
create or replace procedure UpdateProduct(product_id int
,product_name VARCHAR2
,product_price int
,product_description varchar2)
as
begin
update product
set name = product_name,
price = product_price,
description = product_description,
update_date = TO_CHAR(SYSDATE, 'DD.MM.YYYY hh24:mi:ss')
where id = product_id;
--
end;
As an aside, what is the data type of update_date? You are assigning a string to it. The data type should be date (in which case you should simply assign SYSDATE to it); the way you wrote it, either update_date is indeed of date data type, and then TO_CHAR is unnecessary (or, worse, it may cause problems), or update_date is of varchar2 data type, which is in itself a huge mistake, which perhaps you can correct.
It is the insert that causes the error. You're trying to insert a line with update_date column filled only. Like that
id
name
price
description
update_date
null
null
null
null
01.01.2001
I assume the insert statement has to be at least like this:
insert into product(id, update_date)
values (some_id_goes_here, TO_CHAR(SYSDATE, 'DD.MM.YYYY hh24:mi:ss'));
Or maybe you need an update statement, I'm not sure what your logic is

Insert into not working on plsql in oracle

declare
vquery long;
cursor c1 is
select * from temp_name;
begin
for i in c1
loop
vquery :='INSERT INTO ot.temp_new(id)
select '''||i.id||''' from ot.customers';
dbms_output.put_line(i.id);
end loop;
end;
/
Output of select * from temp_name is :
ID
--------------------------------------------------------------------------------
customer_id
1 row selected.
I have customers table which has customer_id column.I want to insert all the customer_id into temp_new table but it is not being inserted. The PLSQL block executes successfully but the temp_new table is empty.
The output of dbms_output.put_line(i.id); is
customer_id
What is wrong there?
The main problem is that you generate a dynamic statement that you never execute; at some point you need to do:
execute immediate vquery;
But there are other problems. If you output the generated vquery string you'll see it contains:
INSERT INTO ot.temp_new(id)
select 'customer_id' from ot.customers
which means that for every row in customers you'll get one row in temp_new with ID set to the same fixed literal 'customer_id'. It's unlikely that's what you want; if customer_id is a column name from customers then it shouldn't be in single quotes.
As #mathguy suggested, long is not a sensible data type to use; you could use a CLOB but only really need a varchar2 here. So something more like this, where I've also switched to use an implicit cursor:
declare
l_stmt varchar2(4000);
begin
for i in (select id from temp_name)
loop
l_stmt := 'INSERT INTO temp_new(id) select '||i.id||' from customers';
dbms_output.put_line(i.id);
dbms_output.put_line(l_stmt);
execute immediate l_stmt;
end loop;
end;
/
db<>fiddle
The loop doesn't really make sense though; if your temp_name table had multiple rows with different column names, you'd try to insert the corresponding values from those columns in the customers table into multiple rows in temp_new, all in the same id column, as shown in this db<>fiddle.
I guess this is the starting point for something more complicated, but still seems a little odd.

trigger after insert on table

create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
when i try to run the insert statement to the table "marcacoes_refeicoes" this procedure gives error: like the table is mutating
create or replace
procedure insert_pagamentos
(nmarcacaoa in number, ncartaoa in number)
AS
BEGIN
insert into pagamentos (nmarcacao, datapagamento, ncartao) values (nmarcacaoa, sysdate, ncartaoa);
commit;
END INSERT_PAGAMENTOS;
Short (oversimplified) answer:
You can't modify a table in a trigger that changes the table.
Long answer:
http://www.oracle-base.com/articles/9i/mutating-table-exceptions.php has a more in-depth explanation, including suggestions how to work around the problem.
You're hitting the mutating-table problem because you're selecting from the same table the trigger is on, but what you seem to be trying to do doesn't make sense. Your queries to get a value for nmarcacaoa and ncartaoa will return a no_data_found or too_many_rows error unless the table had exactly 2 rows in it before your insert:
select nmarcacao from marcacoes_refeicoes
where rownum < (select count(*) from marcacoes_refeicoes);
will return all rows except one; and the one that's excluded will be kind of random as you have no ordering. Though the state is undetermined within the trigger, so you can't really how many rows there are, and it won't let you do this query anyway. You won't normally be able to get a single value, anyway, and it's not obvious which value you actually want.
I can only imagine that you're trying to use the values from the row you are currently inserting, and putting them in your separate payments (pagamentos) table. If so there is a built-in mechanism to do that using correlation names, which lets you refer to the newly inserted row as :new (by default):
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert_pagamentos(:new.nmarcacaoa, :new.ncartaoa);
end addpagamento;
The :new is referring to the current row, so :new.nmarcacaoa is the nmarcacaoa being inserted. You don't need to (and can't) get that value from the table itself. (Even with the suggested autonomous pragma, that would be a separate transaction and would not be able to see your newly inserted and uncommitted data).
Or you can just do the insert directly; not sure what the procedure is adding here really:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert into pagamentos(nmarcacao, datapagamento, ncartao)
values (:new.nmarcacaoa, sysdate, :new.ncartaoa);
end addpagamento;
I've removed the exception handler as all it was doing was masking the real error, which is rather unhelpful.
Editing this answer in view of the comments below:
You can use PRAGMA AUTONOMOUS_TRANSACTION to get rid of the error, but DONOT USE it as it will NOT solve any purpose.
Use PRAGMA AUTONOMOUS_TRANSACTION:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
However in this case , select count(*) from marcacoes_refeicoes will give you the new count after the current insertion into the table.

Create trigger to insert into another table

I have some problem executing the trigger below:
CREATE OR REPLACE TRIGGER AFTERINSERTCREATEBILL
AFTER INSERT
ON READING
FOR EACH ROW
DECLARE
varReadNo Int;
varMeterID Int;
varCustID Varchar(10);
BEGIN
SELECT SeqReadNo.CurrVal INTO varReadNo FROM DUAL;
Select MeterID INTO varMeterID
From Reading
Where ReadNo = varReadNo;
Select CustID INTO varCustID
From Address A
Join Meter M
on A.postCode = M.postCode
Where M.MeterID = varMeterID;
INSERT INTO BILL VALUES
(SEQBILLNO.NEXTVAL, SYSDATE, 'UNPAID' , 100 , varCustID , SEQREADNO.CURRVAL);
END;
Error Message:
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
Does it mean that I am not suppose to retrieve any details from table Reading?
I believe that the issue occur as I retrieving data from Table Reading while it is inserting the value:
SELECT SeqReadNo.CurrVal INTO varReadNo FROM DUAL;
Select MeterID INTO varMeterID
From Reading
Where ReadNo = varReadNo;
It's there anyway to resolve this? Or is there a better method to do this? I need to insert a new row into table BILL after entering the table Reading where the ReadNo need to reference to the ReadNo I just insert.
You cannot retrieve records from the same table in a row trigger. You can access values from actual record using :new and :old (is this your case?). The trigger could then be rewritten to
CREATE OR REPLACE TRIGGER AFTERINSERTCREATEBILL
AFTER INSERT
ON READING
FOR EACH ROW
DECLARE
varCustID Varchar(10);
BEGIN
Select CustID INTO varCustID
From Address A
Join Meter M
on A.postCode = M.postCode
Where M.MeterID = :new.MeterID;
INSERT INTO BILL VALUES
(SEQBILLNO.NEXTVAL, SYSDATE, 'UNPAID' , 100 , varCustID , SEQREADNO.CURRVAL);
END;
If you need to query other record from READING table you have to use a combination of statement triggers, row trigger and a PLSQL collection. Good example of this is on AskTom.oracle.com
Make sure that you have the necessary permissions on all the tables and access to the sequences you're using in the insert.
I haven't done Oracle in awhile, but you can also try querying dba_errors (or all_errors) in order to try and get more information on why your SP isn't compiling.
Something to the tune of:
SELECT * FROM dba_errors WHERE owner = 'THEOWNER_OF_YOUR_SP';
Add exception handling in your trigger and see what is happening, by doing it would be easy for you to track the exceptions.
CREATE OR REPLACE TRIGGER AFTERINSERTCREATEBILL
AFTER INSERT
ON READING
FOR EACH ROW
DECLARE
varCustID Varchar(10);
BEGIN
-- your code
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20298)));
END;
we can combined insert and select statement
CREATE OR REPLACE TRIGGER AFTERINSERTCREATEBILL
AFTER INSERT
ON READING
FOR EACH ROW
DECLARE
varCustID Varchar(10);
BEGIN
insert into bill
values
select SEQBILLNO.NEXTVAL,
SYSDATE,
'UNPAID' ,
100 ,
CustID,SEQREADNO.CURRVAL
From Address A
Join Meter M
on A.postCode = M.postCode
Where M.MeterID = :new.MeterID;
END;
try the above code.

Simple oracle insert

I am trying to simply insert some information to a table in Oracle using Forms. Sometimes the insert statement works, sometimes it doesn't. I'm just not experienced enough with Oracle to understand what's not working. Here's the code:
PROCEDURE create_account IS
temp_name varchar2(30);
temp_street varchar2(30);
temp_zip number(5);
temp_phone varchar2(30);
temp_login passuse.login%type;
temp_pass varchar2(30);
temp_total number(4);
temp_lgn passuse.lgn%type;
cursor num_cursor is
select MAX(ano)
from accounts;
cursor lgn_cursor is
select MAX(lgn)
from passuse;
BEGIN
temp_name:= Get_Item_Property('ACCOUNTS.A_NAME', database_value);
temp_street:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_zip:= Get_Item_Property('ACCOUNTS.ZIP', database_value);
temp_phone:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_login:= Get_Item_Property('PASSUSE.LOGIN', database_value);
temp_pass:= Get_Item_Property('PASSUSE.PASS', database_value);
open num_cursor;
fetch num_cursor into temp_total;
open lgn_cursor;
fetch lgn_cursor into temp_lgn;
if(lgn_cursor%found) then
if(num_cursor%found) then
temp_lgn := temp_lgn + 20;
--the trouble maker..
INSERT INTO passuse (lgn, a_type, login, pass)
VALUES (temp_lgn, 1, temp_login, temp_pass);
temp_total := temp_total+1;
INSERT INTO accounts(ano,lgn,a_name,street,zip,phone)
VALUES (temp_total,temp_lgn,temp_name,temp_street,temp_zip,temp_phone);
end if;
end if;
close lgn_cursor;
close num_cursor;
commit;
END;
To expand on #Mikpa's comment - it certainly appears that getting the values for ACCOUNTS.ANO and PASSUSE.LGN from sequences would be a good idea. Populating these fields automatically by using a trigger would also be helpful. Something like the following:
-- Note that the following is intended as a demo. When executing these statements
-- you'll probably have to modify them for your particular circumstances.
SELECT MAX(ANO) INTO nMax_ano FROM ACCOUNTS;
SELECT MAX(LGN) INTO nMax_lgn FROM PASSUSE;
CREATE SEQUENCE ACCOUNTS_SEQ START WITH nMax_ano+1;
CREATE SEQUENCE PASSUSE_SEQ START WITH nMax_lgn+1;
CREATE TRIGGER ACCOUNTS_BI
BEFORE INSERT ON ACCOUNTS
FOR EACH ROW
BEGIN
SELECT ACCOUNTS_SEQ.NEXTVAL
INTO :NEW.ANO
FROM DUAL;
END ACCOUNTS_BI;
CREATE TRIGGER PASSUSE_BI
BEFORE INSERT ON PASSUSE
FOR EACH ROW
BEGIN
SELECT PASSUSE_SEQ.NEXTVAL
INTO :NEW.LGN
FROM DUAL;
END PASSUSE_BI;
Having done the above you can now write your inserts into these tables as
INSERT INTO passuse (a_type, login, pass)
VALUES (1, temp_login, temp_pass)
RETURNING LGN INTO temp_lgn;
and
INSERT INTO accounts(lgn, a_name, street, zip, phone)
VALUES (temp_lgn, temp_name, temp_street, temp_zip, temp_phone);
Note that in the both statements the key values (PASSUSE.LGN and ACCOUNTS.ANO) are not mentioned in the field list as the new triggers should take care of filling them in correctly. Also note that when inserting into PASSUSE the RETURNING clause is used to get back the new value for LGN so it can be used in the insert into the ACCOUNTS table.
Share and enjoy.

Resources