inserting into with an incrementing value - sql

The best way I can think to explain this is: I'm trying to give each robot a unique id. Then, when I run the procedure, I only have to give the robot a name and id. The status are generated for me.
However, I am reaching a total roadblock because I can't find out the proper way to do this. I've tried counting the rows and putting it into tempid. That doesn't work. A new row is made every time I run it. So, I should always get a new id. I might have done it wrong though.
I get this error:
Error(23,44): PL/SQL: ORA-00984: column not allowed here
Here is the procedure:
CREATE OR replace PROCEDURE Checkbot
(nameinput IN VARCHAR2)
IS
partfound NUMBER(4);
foundall BOOLEAN;
tempid NUMBER;
BEGIN
--Where I am having issues:
SELECT Count(*)
INTO tempid
FROM robotinventory;
INSERT INTO robotinventory VALUES (tempid, nameinput, NULL);
foundall := TRUE;
FOR i IN 1..8 LOOP
partfound := 0;
SELECT Max(prtserial)
INTO partfound
FROM partinventory
WHERE parttypeid = i;
IF partfound > 0 THEN
DELETE FROM partinventory
WHERE prtserial = partfound;
INSERT INTO robotprt VALUES (tempid, idinput, i);
ELSE
foundall := FALSE;
END IF;
END LOOP;
IF foundall THEN
UPDATE robotinventory
SET status = 'ready for assembly'
WHERE robotid = tempid;
ELSE
UPDATE robotinventory
SET status = 'waiting on parts'
WHERE robotid = tempid;
END IF;
END;/
robot inventory table:
CREATE TABLE RobotInventory
(RobotID Number(4) PRIMARY KEY,
RobotName VARCHAR2(24),
Status VARCHAR2(64));
Previous version of my code, took out what I did wrong in mine, my goal is to replace idInput with an auto incrementing number:
create or replace procedure checkbot
(idInput in number, nameInput in varchar2)
is
partFound number(4);
foundAll boolean;
begin
insert into robotInventory values (idInput, nameInput, null);
foundAll := true;
for i in 1..8 loop
partFound := 0;
select max(prtSerial)
into partFound
from partInventory
where ParttypeID = i;
if partFound > 0 then
delete from partInventory
where prtSerial = partFound;
insert into robotPrt values (partFound, idInput, i);
else
foundAll := false;
end if;
end loop;
if foundAll then
update robotInventory
set status = 'ready for assembly'
where robotID = idInput;
else
update robotInventory
set status = 'waiting on parts'
where robotID = idInput;
end if;
END;
/

Create a sequence (called seq below) and use it in your INSERT SQL to populate robot_id.
INSERT INTO robotinventory VALUES (seq.nextval, nameinput, NULL);
Look at Oracle docs on how to create a sequence in more detail. Here is an example that you can change to fit your needs.
create sequence seq
start with 1
increment by 1
maxvalue 9999;
HTH

Related

Unexpected OUT value from Postgres Stored Procedure

Using Postgres10.
I'm having an issue where I am calling a stored procedure and expecting a specific value for my OUT param and I am not getting it. I am calling the Items stored procedure with the call code below.
PROBLEM
I expect the first time I call the Item stored procedure to get an insert with a rtn value of 1 but I get a 4... This means the IF EXISTS is finding a row in the table with the same name but my table is empty.
I am expecting there is something weird going on where the IF EXISTS statement is being re-evaluated after the INSERT statement and entering the block where rtn gets set to 4. Is this something to do with plpgsql? It's acting as if the order of the stored procedure is not going top to bottom always when I put in Raise commands to test values at certain points.
SCHEMA/TABLE
CREATE TABLE aips.Item (
ItemPk SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
CONSTRAINT UNI_Item_Name UNIQUE(Name)
);
STORED PROCEDURE
CREATE OR REPLACE FUNCTION aips.Item(
INOUT p_ItemPk INT,
INOUT p_Name VARCHAR(100),
OUT rtn INT
) AS
$$
DECLARE rowcnt INT;
BEGIN
-- Insert or Find Path
IF p_ItemPk IS NULL THEN
-- Check for Find
IF EXISTS (SELECT * FROM aips.Item where Name = p_Name) THEN
SELECT ItemPk, Name
INTO p_ItemPk, p_Name
FROM aips.Item
WHERE Name = p_Name;
rtn := 4;
RETURN;
END IF;
-- Perform insert
INSERT INTO aips.Item (Name)
VALUES (p_Name)
RETURNING ItemPk INTO p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 1;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to insert a single row and rows returned --> %', rowcnt;
END IF;
ELSE -- Update or No Operation Path
-- Check for no changes
IF EXISTS (SELECT ItemPk
FROM aips.Item
WHERE ItemPk = p_ItemPk
AND Name = p_Name) THEN
rtn := 5;
RETURN;
END IF;
-- Perform Update
UPDATE aips.Item
SET Name = p_Name
WHERE ItemPk = p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 2;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to update a single row and rows returned --> %', rowcnt;
END IF;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CALL
select (aips.Item(NULL, 'Test 1')).*;
The problem is the way you call the function:
select (aips.Item(NULL, 'Test 1')).*; -- WRONG!
because it is executed three times, once for each output column. The function should be called in the FROM clause:
select * from aips.Item(NULL, 'Test 1');

Updating 600k rows, most of the columns are Varchar2(45). This update is taking too long ( 30 minutes ). Is there a better way to write the update?

--Update address fields in address table with corrects address
the columns in the temp table are columns from address table and to be modified columns in address table. This update takes 30 minutes which will not work for our prod environment. How can i write this code to make it run faster?
DECLARE
--Fetching data from temp table created for address update
CURSOR My_Cursor
IS
--Fetching records which have a valid error code which is 2,4,5,20,46
SELECT A.*
FROM Fix_Dba.Temp_Address_Cleanup A, Fix_Dba.Addresses B
WHERE A.Address_Id = B.Address_Id
ORDER BY A.Address_Id;
TYPE Plt_Cursor IS TABLE OF My_Cursor%ROWTYPE
INDEX BY BINARY_INTEGER;
Plt_Cursorarr Plt_Cursor; --Array declaration for the cursor
--Track the address_ids which are not found in address_table
CURSOR Record_Not_Found
IS
SELECT A.Address_Id
FROM Fix_Dba.Temp_Address_Cleanup A
WHERE A.Address_Id NOT IN (SELECT B.Address_Id
FROM Fix_Dba.Addresses B)
ORDER BY A.Address_Id;
--declaring counter to commit records at certain intervals
Counter NUMBER := 0;
V_Address_Id NUMBER := 0;
V_Err_Message VARCHAR2 (5000);
V_Efacts_Log_Id NUMBER;
BEGIN
OPEN My_Cursor; --Open cursor
FETCH My_Cursor BULK COLLECT INTO Plt_Cursorarr; --Fetching data which is required to update address fields
CLOSE My_Cursor;
IF Plt_Cursorarr.COUNT = 0 --Return if the count is zero and there are no records to process
THEN
RETURN;
END IF;
FOR I IN Plt_Cursorarr.FIRST .. Plt_Cursorarr.LAST --For loop declaration to update records
LOOP
BEGIN
--Updating address fileds in address table after fetching correct data from temporary tables
UPDATE Fix_Dba.Addresses A
SET A.Address1 = Plt_Cursorarr (I).Address1_New, --Updating address line 1
A.Address2 = Plt_Cursorarr (I).Address2_New, --Updating address line 2
A.City = Plt_Cursorarr (I).City_New, --Updating city
A.State_Code = Plt_Cursorarr (I).State_Code_New, --Updating State
A.Zip_Code = Plt_Cursorarr (I).Zip_Code_Addon, --Updating Zip code
A.Update_Date = SYSDATE,
A.Sysuser_Id_Updated_By = 1,
A.Update_Source = 'LOG17690'
WHERE A.Address_Id = Plt_Cursorarr (I).Address_Id; --Where clause for update
Counter := Counter + 1; --Increment the counter to account for number of records
IF MOD (Counter, 10000) = 0
THEN -- Commit every 10000 records
COMMIT;
Counter := 0; --Putting the counter value back to zero so that the counter for commit can restart
END IF;
EXCEPTION --Handling exceptions... do we need to track address_ids?
WHEN OTHERS
THEN
V_Err_Message := 'ERROR: ' || SQLCODE || ';' || SQLERRM;
Fix_Dba.Dbpc_Blaster_Inserts.Write_Log (
NULL,
NULL,
NULL,
17690,
'ADDRESS',
V_Err_Message,
'E',
'AddressID',
Plt_Cursorarr (I).Address_Id,
V_Efacts_Log_Id);
-- DBMS_OUTPUT.Put_Line ('Updated ' || SQL%ROWCOUNT || ' rows.');
-- DBMS_OUTPUT.Put_Line ('Error occurred with error code: ' || SQL%BULK_EXCEPTIONS (I).ERROR_CODE);
END;
END LOOP;
COMMIT;
BEGIN
OPEN Record_Not_Found;
LOOP
FETCH Record_Not_Found INTO V_Address_Id;
EXIT WHEN Record_Not_Found%NOTFOUND;
V_Err_Message := 'Address Id not found';
Fix_Dba.Dbpc_Blaster_Inserts.Write_Log (NULL,
NULL,
NULL,
17690,
'ADDRESS',
V_Err_Message,
'E',
'AddressID',
V_Address_Id,
V_Efacts_Log_Id);
END LOOP;
CLOSE Record_Not_Found;
END;
END;
/
You could do the update via a single MERGE statement, e.g.:
MERGE INTO fix_dba.addresses tgt
USING fix_dba.temp_address_cleanup src
ON (tgt.address_id = src.address_id)
WHEN MATCHED THEN
UPDATE SET tgt.address1 = src.address1_new,
tgt.address2 = src.address2_new,
tgt.city = src.city_new,
tgt.state_code = src.state_code,
tgt.zip_code = src.zip_code_new,
tgt.update_date = SYSDATE,
tgt.sysuser_id_updated_by = 1,
tgt.update_source = 'LOG17690';
If you are concerned about logging errors, you could always use DML error logging to capture errors (although there are restrictions on this). However, I would hope that any constraints you have on your addresses table are also on your cleanup table, so the data should be ok to merge as is.

sql insert procedure: insert values into table and control if value is existing in another table

I have little problem. I am trying to insert value into table. This is working. But I would like to control if value id_trainer is existing in another table. I want this -> execute insertClub(1, 5, 'someName'); -> and if id_trainer 5 not exists in table Trainer, procedure gives me message about this. (I tried to translate it into eng. lng., so you can find some gramm. mistakes)
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
begin
declare counter number;
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/
There is some error in the procedure. Please run below code to create procedure:
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
counter number;
begin
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/

How to avoid "table mutating" errors

I have an trigger that needs to read from a table after deleting a row. Essentially, I need to count up the remaining rows that are similar to the current row, and if that count is zero, update a field elsewhere.
After two days of hammering around, I haven't been able to figure out how to restructure my thought process to allow me to do this. Here is an example:
CREATE OR REPLACE TRIGGER Di_PatMustBeWell
AFTER DELETE
ON Diagnosis
FOR EACH ROW
Declare
--PRAGMA AUTONOMOUS_TRANSACTION;
NumDiseases Number;
BEGIN
SELECT NUMDISEASES INTO Numdiseases
FROM DiagnosisCount
where Di_Patient = :OLD.Di_Patient;
IF( NumDiseases != 1 ) THEN
UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = :OLD.Di_Patient;
END IF;
END;
/
Short answer - no trigger, no mutating.
Yow can use the trigger with pragma autonomous_transaction for counting of remaining diagnoses for certain patient, but it's is not recommended way to do this.
Better you create new function or procedure to implement your logic on deleted diagnosis. Something like this:
create table Diagnosis as select 456 idDiseases, 123 di_patient from dual;
/
create table diagnosisCount as select 1 numDiseases, 123 di_patient from dual;
/
create table Patient as select 123 Pat_Person, 1 Pat_Sick from dual;
/
drop trigger di_patmustbewell;
create or replace function deleteDiagnosis(idDiseases number) return number is
rows_ number;
di_patient number;
Numdiseases number;
begin
<<del>> begin
delete Diagnosis where IdDiseases = deleteDiagnosis.IdDiseases
returning Diagnosis.di_patient into deleteDiagnosis.di_patient
;
rows_ := sql%rowcount;
if rows_ != 1 then raise too_many_rows; end if;
end del;
select count(1) into deleteDiagnosis.numDiseases from Diagnosis where Di_Patient = deleteDiagnosis.di_patient;
if deleteDiagnosis.numdiseases = 0 then <<upd>> begin
update Patient set Pat_Sick = 0 where Pat_Person = deleteDiagnosis.di_patient;
exception when others then
dbms_output.put_line('Cannot update Patient di_patient='||di_patient);
raise;
end upd; end if;
return rows_;
end;
/
show errors
declare rows_ number := deleteDiagnosis(456);
begin dbms_output.put_line('deleted '||rows_||' rows'); end;
/
deleted 1 rows
select * from Patient;
PAT_PERSON PAT_SICK
---------- ----------
123 0
An alternative solution, if you prefer (or must) to use a trigger in your application - declare internal function returning count of patient's diagnoses in the trigger body:
create or replace trigger di_patmustbewell
after delete on diagnosis for each row
declare
numdiseases number;
function getNumDiagnosis (di_patient number) return number is
ret number;
pragma autonomous_transaction;
begin
select count(1) into ret from diagnosis where di_patient = getNumDiagnosis.di_patient;
return ret;
end getNumDiagnosis;
begin
numDiseases := getNumDiagnosis(:old.di_patient);
if(numdiseases = 0) then
update patient set pat_sick = 0 where pat_person = :old.di_patient;
end if;
end;
/
show errors;
Trigger DI_PATMUSTBEWELL compiled
Hope it helps you a bit.
You can create a COMPOUND trigger for such cases:
create or replace TRIGGER Di_PatMustBeWell
FOR DELETE ON Diagnosis
COMPOUND TRIGGER
TYPE Di_Patient_Table_type IS TABLE OF DiagnosisCount.Di_Patient%TYPE;
Di_Patient_Table Di_Patient_Table_type;
BEFORE STATEMENT IS
BEGIN
Di_Patient_Table := Di_Patient_Table_type();
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
Di_Patient_Table.EXTEND;
Di_Patient_Table(Di_Patient_Table.LAST) := :OLD.Di_Patient;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN Di_Patient_Table.FIRST..Di_Patient_Table.LAST LOOP
SELECT NUMDISEASES INTO Numdiseases
FROM DiagnosisCount
where Di_Patient = Di_Patient_Table(i);
IF NumDiseases != 1 THEN
UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = Di_Patient_Table(i);
END IF;
END LOOP;
Di_Patient_Table.DELETE;
END AFTER STATEMENT;
END;
/

Mutating table error, even after compound trigger

In order to solve the mutating table error, I followed http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS2005, but still it isn't working. Can some help to identify where is the problem of the following trigger?
Thanks,
CREATE OR REPLACE TRIGGER trg_d_inq_2 FOR
UPDATE OF hu1_dimension_lvl1
ON d_inq_dimensions
COMPOUND TRIGGER
v_exists_d NUMBER;
v_exists_c NUMBER;
TYPE process_t IS TABLE OF d_inq_dimensions.dimension_value%TYPE;
process process_t;
TYPE process_pvoc_t IS TABLE OF NUMBER
INDEX BY VARCHAR2(100 BYTE);
process_pvoc process_pvoc_t;
TYPE nprocessvoc_t IS TABLE OF NUMBER;
nprocessvoc nprocessvoc_t;
BEFORE EACH ROW
IS
BEGIN
SELECT a.hu1_dimension_lvl1, COUNT(a.hu1_dimension_lvl1)
BULK COLLECT INTO process, nprocessvoc
FROM d_inq_dimensions a
WHERE a.dimension_name = 'Processo'
GROUP BY a.hu1_dimension_lvl1;
FOR j IN 1 .. process.COUNT
LOOP
process_pvoc(process(j)) := nprocessvoc(j);
END LOOP;
END
BEFORE EACH ROW;
AFTER EACH ROW
IS
BEGIN
IF :new.hu1_dimension_lvl1 IS NOT NULL AND
:new.dimension_name = 'Processo'
THEN
IF process_pvoc(:old.hu1_dimension_lvl1) IS NULL
THEN
DELETE external.c_parameters
WHERE proj_id = 79 AND
ind_id = 53 AND
lvl_2 = :old.hu1_dimension_lvl1;
END IF;
SELECT COUNT(1)
INTO v_exists_c
FROM external.c_parameters a
WHERE proj_id = 79 AND
ind_id = 53 AND
lvl_2 = :new.hu1_dimension_lvl1;
IF v_exists_c = 0
THEN
INSERT INTO external.c_parameters
/*and something more*/
END IF;
END IF;
END
AFTER EACH ROW;
END;
Use after statement instead of BEFORE EACH ROW.