ID get NULL when i try to update data - oracle

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

Related

SYS_REFCURSOR is returning all the rows from table without considering the IN parameter

I am facing a weird problem here.
PROCEDURE USL_EMPLOYEEBYID (
EMPLOYEE_ID IN NUMBER,
EMPIDCURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN EMPIDCURSOR FOR
SELECT emp.employee_id,emp.employee_name,emp.present_address,emp.permanent_address,emp.status
FROM Employee_Info emp
WHERE emp.employee_id = EMPLOYEE_ID;
END;
This procedure should give me a single employee upon entering the employee Id. But it is returning all the employees.
What am I doing wrong here?
In your query, Oracle interprets EMPLOYEE_ID as the column EMPLOYEE_ID, not the input parameter, here you find something more; in this way, your where condition is something like a=a.
Change the parameter name to distinguish it from the table column:
PROCEDURE USL_EMPLOYEEBYID (
p_EMPLOYEE_ID IN NUMBER,
po_EMPIDCURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN po_EMPIDCURSOR FOR
SELECT emp.employee_id,emp.employee_name,emp.present_address,emp.permanent_address,emp.status
FROM Employee_Info emp
WHERE emp.employee_id = p_EMPLOYEE_ID;
END;
this is a good practice, to always know in your code whether you are handling an input parameter, a local variable, a column and so on

pl sql insert into within a procedure and dynamic variables

I need some help with PL SQL. I have to insert some data into table. Another application is calling my procedure and caller is passing few details which I also need to insert into my table.
here is the syntax I am struggling with:
PROCEDURE invform_last2orders_item_insert( p_userId IN NUMBER
,p_accountId IN NUMBER
,p_site_Id IN NUMBER
,p_return_message OUT VARCHAR2) IS
Begin
insert into mytable
(p_userId , p_accountId , p_site_Id , sku, description, 'Cart', 1, unitId)
as
select sku, description, unitId
from mycatalogtable where site_id= p_site_Id ) ;
End;
Can you help me with syntax? I need to pass three parameters from called in parameter and some values returned from select query. How can I achieve this?
thank you for your help.
That would be something like this; see comments within code:
PROCEDURE invform_last2orders_item_insert
( p_userId IN NUMBER
,p_accountId IN NUMBER
,p_site_Id IN NUMBER
,p_return_message OUT VARCHAR2)
IS
Begin
insert into mytable
-- first name all columns you'll be inserting into; I don't know their
-- names so I just guessed
(userid,
accountid,
siteid,
sku,
description,
col1,
col2,
unitid
)
-- if you were to insert only values you got via parameters, you'd use the
-- VALUE keyword and insert those values separately.
-- As some of them belong to a table, use SELECT statement
(select p_userid,
p_accountid,
p_siteid,
c.sku,
c.description,
'Cart',
1,
c.unitid
from mycatalogtable c
where c.site_id = p_site_Id
);
-- I don't know what you are supposed to return; this is just an example
p_return_message := sql%rowcount || ' row(s) inserted';
End;
in your select statement you should have the same number of columns as you are inserting into the table, your code should be something like this example,
DECLARE
userid varchar2(20) := 'Jack';
Begin
INSERT INTO mytable (SELECT userid, SPORT from OLYM.OLYM_SPORTS);
commit;
end;

ORA-01861: literal does not match format string?

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.

How does Oracle Insert Into work when order of values is not defined?

I came across some code that looks like this. I understand that it will return the auto-generated id, but what I don't understand is when I pass cursor data when I call this function, how does it identify what values are to be inserted in which columns when the column order is not defined?
FUNCTION INSERT_ROW(DATA IN OWNER.TABLE%ROWTYPE)
RETURN OWNER.TABLE.ID%TYPE
IS
l_ID OWNER.MY_TABLE.ID%TYPE;
l_Data OWNER.MY_TABLE%ROWTYPE := DATA;
BEGIN
INSERT INTO OWNER.MY_TABLE
VALUES l_Data
RETURNING ID INTO l_ID;
I tried to look up many examples and I only come across ones where the values are defined in order like this
INSERT INTO my_table (val2, val3, val4) VALUES (2, 3, 4) RETURNING val1
INTO val1;
The order of columns in a table in Oracle IS defined. Take a look at the ALL_TAB_COLUMNS view - there's a COLUMN_ID column which defines the order of columns within the table. If a field list is not given in a SELECT (i.e. SELECT * FROM MY_TABLE) the columns from MY_TABLE will be returned in ALL_TAB_COLUMNS.COLUMN_ID order. This is also the same way columns are ordered in a %ROWTYPE variable, and it's the way that an INSERT which doesn't have a field list specified expects fields to be ordered.
The insert values statement in your code is a PL/SQL extension to the standard insert values clause that has parentheses. This is a page from the 12.2 manual about this topic:
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/lnpls/INSERT-statement-extension.html#GUID-D81224C4-06DE-4635-A850-41D29D4A8E1B
The OWNER.TABLE%ROWTYPE data type defines a record with the same columns as the table and in the same order. You are just passing the data into the function in that format and passing it into a variable and then into the insert statement.
The main purpose of the RETURNING clause is to obtain the value of a derived column, a value which is generated during the insert process. Usually this is a technical primary key derived from a sequence, or since 12c an IDENTITY column.
So for instance:
create table my_table (
val1 number generated as identity primary key
, val2 varchar2(16)
, val3 varchar2(16)
, val4 date)
/
declare
id number;
begin
INSERT INTO my_table (val2, val3, val4)
VALUES ('one', 'test', sysdate)
RETURNING val1 INTO id;
dbms_output.put_line('new id = ' || id);
end;
/
This is why the examples you found specify columns in the INSERT projection: the value of the primary key is generated automatically, so there's no point in us assigning it a value in our code.
Now your function uses a record type in its insert statement. We can't do that with IDENTITY columns. This variant ...
declare
lrec my_table%rowtype;
id number;
begin
lrec.val2 := 'two';
lrec.val3 := 'test again';
lrec.val4 := sysdate;
INSERT INTO my_table
VALUES lrec
RETURNING val1 INTO id;
dbms_output.put_line('new id = ' || id);
end;
/
... will hurl
ORA-32795: cannot insert into a generated always identity column
But we can use a %rowtype with the old-fashioned sequence and trigger combo:
create table my_table (
val1 number primary key
, val2 varchar2(16)
, val3 varchar2(16)
, val4 date)
/
create sequence my_seq start with 42;
create or replace trigger my_trg
before insert on my_table for each row
begin
:new.val1 := my_seq.nextval;
end;
/
declare
lrec my_table%rowtype;
id number;
begin
lrec.val1 := 1;
lrec.val2 := 'three';
lrec.val3 := 'test again';
lrec.val4 := sysdate;
INSERT INTO my_table
VALUES lrec
RETURNING val1 INTO id;
dbms_output.put_line('new id = ' || id);
end;
/
Here is a LiveSQL demo (free Oracle OTN account required, alas). If you run it you will see that the trigger overrides the assigned value and the val1 column has the value from the sequence.

How to programmatically set table name in PL/SQL?

I created the following simple PL/SQL stored procedure example to ask a specific question. This procedure inserts an employee name and id number into a table called employees_???. The ??? is explained below.
PROCEDURE hire_employee (emp_id IN INTEGER, name IN VARCHAR2, country IN VARCHAR2)
AS
BEGIN
INSERT INTO employees_??? VALUES (emp_id, name, 1000);
END hire_employee;
What I need is to set the table name based on the IN variable country. For example,
If country = 'usa', I want the INSERT line to read:
INSERT INTO employees_usa VALUES (emp_id, name, 1000);
If country = 'germany', I want the INSERT line to read:
INSERT INTO employees_germany VALUES (emp_id, name, 1000);
If country = 'france', I want the INSERT line to read:
INSERT INTO employees_france VALUES (emp_id, name, 1000);
etc...
Is there a way to do this in PL/SQL by substituting something in place of employee_??? so only one line of code for INSERT is used? Or is using a case or if/then/else statement the best way?
To answer your question, you have to use execute immediate and create your statement dynamically.
create or replace procedure hire_employee (
emp_id IN INTEGER
, name IN VARCHAR2
, country IN VARCHAR2 ) is
-- maximum length of an object name in Oracle is 30
l_table_name varchar2(30) := 'employees_' || country;
begin
execute immediate 'insert into ' || l_table_name
|| ' values (:1, :2, 1000)'
using emp_id, name;
end hire_employee;
However, this is a massively over-complicated way of storing the data. If you want to select all data you have to union large numbers of tables.
It would be far better to normalise the database properly and add country to an employees table.
Something like the following:
create table employees (
emp_id number(16)
, country varchar2(3) -- ISO codes
, name varchar2(4000) -- maximum who knows what name people might have
, < other_columns >
, constraint pk_employees primary key ( emp_id )
);
Your procedure then becomes a very simple insert statement:
create or replace procedure hire_employee (
emp_id in integer
, name in varchar2
, country in varchar2 ) is
insert into employees
values ( emp_id, country, name, 1000 );
end hire_employee;
You can use dynamic SQL and the EXECUTE IMMEDIATE construct. In this, you construct the query as a string and then execute it. A good example is at http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg09dyn.htm

Resources