Oracle SQL insert query - into parent and child tables - sql

for an assignment I had something similar to the following (simplified for brevity):
STUDENT(StudentID, Fname. Lname) StudentID PK
UNIT(UnitID, UnitName) UnitID PK
STUDENT_UNIT((StudentID, UnitID) StudentID PK/FK UnitID PK/FK
Needed to insert info about a student and the units that he/she had completed.
As it is only beginner level SQL the following was accepted
INSERT INTO STUDENT
VALUES(seqStudID.NextVal, 'Bob', 'Brown');
INSERT INTO STUDENT_UNIT(seqStudID.CurrVal, 111);
INSERT INTO STUDENT_UNIT(seqStudID.CurrVal, 222);
INSERT INTO STUDENT_UNIT(seqStudID.CurrVal, 333);
But I was wondering what would be the real way to enter this data, would it be a procedure with a loop? If so what sort of loop (so that it could handle any amount of units).
Thanks in advance

One of the best approach to do this is by using stored procedure. The below procedure will do everything for you.
CREATE OR REPLACE
PROCEDURE set_stud_unit(
i_fname IN VARCHAR2,
i_lname IN VARCHAR2,
i_unitid IN VARCHAR2)
IS
l_studentid student.studentid%TYPE;
BEGIN
INSERT INTO student(studentid, fname, lname)
VALUES(seqstudid.NEXTVAL, i_fname, i_lname)
RETURNING studentid INTO l_studentid;
INSERT INTO student_unit (studentid, unitid)
(SELECT l_studentid, (COLUMN_VALUE).getNumberVal() vid FROM xmltable(i_unitid));
COMMIT;
END;
/
You can pass the unitid as comma separated as below,
EXECUTE set_stud_unit('Bob', 'Brown', '111,222,333');

you can use select in your insert:
INSERT INTO STUDENT_UNIT select t1.StudentID ,t2.UnitID from STUDENT t1 ,UNIT t2;
and you can use where to limit this selection ;-)

Probably what you need is a procedure that accept:
First Name
Last Name
an array of Units
The procedure will:
Insert into STUDENT (I suggest you to use a trigger to populate StudentID, maybe you are already doing it),
Loop over the array and insert into STUDENT_UNIT each of the element of the array and the StudentID for First Name and Last Name, but without using the sequence. My pseudo code:
FOR i IN 1..input_array.count LOOP
INSERT INTO STUDENT_UNIT
SELECT StudentID, input_array(i)
FROM STUDENT
WHERE Fname = FirstNameParam
AND Lname = LastNameParam;
END LOOP;
I suggest you to query the student table to get the actual ID to avoid problems in case of concurrency. An optimization would be to query the STUDENT table only once and save the StudentID in a variable.
You can find more info about passing an array to an Oracle procedure here:
Passing an array of data as an input parameter to an Oracle procedure

Related

SQL - How to create a trigger for two joined tables which is used for inserting

Ok , so I know that inserting information in a view based on two joined tables is impossible.
In order to do so , I need to create a trigger to insert the information in both tables , when an insert is made in that view.
For example :
CREATE VIEW myJoinedView AS
SELECT name,g.value from students
JOIN grades g on g.id=students.id;
The trigger is not working :
CREATE TRIGGER myTrigger
INSTEAD OF INSERT ON myJoinedView
BEGIN
INSERT INTO students
(name,value)
SELECT i.myJoinedView
FROM inserted i
INNER JOIN grades
ON i.id = grades.id
END myTrigger;
Then I'm trying to insert :
INSERT INTO myJoinedView VALUES ('Alex',10);
I don't know if the syntax is correct , I did not find any helpful documentation on this specific type of trigger.
I'm getting this error:
Error(10,46): PLS-00103: Encountered the symbol "end-of-file" when
expecting one of the following: ( begin case declare end exception
exit for goto if loop mod null pragma raise return select update
while with
<< continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall merge
pipe purge
Any help will be well received.
Thank you!
You need to either perform the inserts separately with separate single table insert or merge statements or by using a multi table insert (insert all) statement. Assuming you have a sequence to generate the id you are joining on for example this code will work in a very rudimentary way, but has some significant issues:
create table students ( id number primary key
, name varchar2(60));
create table grades( id number not null
, value number
, constraint grades_fk1 foreign key (id) references students(id));
create sequence student_id_seq;
create or replace view studentgrades as
select name, value from students s join grades g on s.id = g.id;
create or replace trigger studentgrades_ii_trg
instead of insert on studentgrades
begin
insert all into students(id, name) values (student_id_seq.nextval, name)
into grades(id, value) values (student_id_seq.nextval, value)
select :new.name name, :new.value value from dual;
end;
/
insert into studentgrades values ('Alex',10);
insert into studentgrades values ('Alex',8);
The BIG issue with the above trigger is that every time a grade is inserted for 'Alex' a new student record for 'Alex' is also created instead of reusing the previous student record for 'Alex'. That's probably not the desired behavior. Instead it should probably just insert a new grade record for Alex. One way to acheive this is for the studentgrades view to include the id column from the students table so you can uniquely identify which student to add the grade to, updating the trigger as needed:
create or replace view studentgrades as
select s.id, name, value from students s join grades g on s.id = g.id;
create or replace trigger studentgrades_ii_trg
instead of insert on studentgrades
declare
newid students.id%type;
begin
if :new.id is null then
newid := student_id_seq.nextval;
else
newid := :new.id;
end if;
insert all when :new.id is null
then into students(id, name) values (id, name)
else into grades(id, value) values (id, value)
select newid id, :new.name name, :new.value value from dual;
end;
/
insert into studentgrades values (null, 'Paul',10);
insert into studentgrades values (student_id_seq.currval, 'Paul',8);
However, now what happens if you try this:
insert into studentgrades values (student_id_seq.currval, 'Mary',10);
In this case the name is effectively ignored and Paul gets a new grade so again this isn't quite right. The question is should Paul's name be updated to Mary, or should a new student record for Mary be created, or should an exception be raised?

ORACLE SQL: Inserting another ID from another row

I have write a series of SQL statements in an ORACLE database. In my database, I have 2 tables (book, publisher).
Below is the table structure
BOOK
----------
bk_id | title | pub_id
PUBLISHER
----------
pub_id | pub_name
If I insert into the publisher table first
INSERT INTO PUBLISHER (pub_name) VALUE ('ABC Publisher');
How do I retrieve the id of the publisher and enter it into the book table?
I usually do this with a stored procedure (SQL Server) or do it in the application.
How can I do it in ORACLE in SQL?
I think the easiest way would consist in creating a Trigger that would insert in other table after table.
create or replace trigger tr_ai_publisher
after insert on publisher
for each row
begin
--Here you can access the new publisher id using :new.pub_id
end;
That way, you would not have to handle yourself a call to the procedure.
However, if you really want to, you can also use a stored procedure in ORACLE, the general syntax is
CREATE [OR REPLACE] PROCEDURE proc_name [list of parameters]
IS
Declaration section
BEGIN
Execution section
EXCEPTION
Exception section
END;
From PL/SQL, you might want to use the RETURNING INTO clause to get back the newly inserted id:
DECLARE
my_id int;
BEGIN
INSERT INTO PUBLISHER (pub_name) VALUE ('ABC Publisher')
RETURNING id INTO my_id;
...
END;
where my_id is a PL/SQL variable declared accordingly to your column type.
Just use a select statement:
INSERT INTO BOOK VALUES
(bk_id, title, (SELECT pub_id FROM PUBLISHER WHERE pub_name = publisher_name))
…
;
Replace bk_id, title, and publisher_name with the appropriate data you want.

Procedure to insert data from one column into two columns in another table

I'm trying to get a procedure that will allow me to get data from a column and insert it into two different columns in a different table. the first table currently has both first and last name in a single column. I have another table with first name and last name in different columns and I need to separate and insert them from Column1/Table1 into the two columns in Table2 preferably using a procedure since I have a lot of names to migrate.
Column1(Name) in Table 1 looks like this
NAME
First_Name1 Last_name1
First_Name2 Last_Name2
First_Name3 Last_Name3
And I need the data to be separated like this in Table2 as FName/LName using the data from the first table:
F_Name | L_Name
First_Name1|Last_Name1
First_Name2|Last_Name2
First_Name3|Last_Name3
I figured out how to get the data from the last and the first name separated using SUBSTR and INSTR, but I can't figure out how to put this inside a Procedure, or how to Loop it since I want to use it for several rows.
select substr(staff.name, 0, instr(staff.name, ' ')-1) as Fname
from staff;
select substr(staff.name, instr(staff.name,' ')+1) as Lname
from Staff;
Any ideas/Help? Thanks guys.
Building a Looping PL/SQL Based DML Cursor For Multiple DML Targets
A PL/SQL Stored Procedure is a great way to accomplish your task. An alternate approach to breaking down your single name field into FIRST NAME and LAST NAME components could be to use an Oracle Regular Expression, as in:
SELECT REGEXP_SUBSTR('MYFIRST MYLAST','[^ ]+', 1, 1) from dual
-- Result: MYFIRST
SELECT REGEXP_SUBSTR('MYFIRST MYLAST','[^ ]+', 1, 2) from dual
-- Result: MYLAST
A procedure based approach is a good idea; first wrap this query into a cursor definition. Integrate the cursor within a complete PL/SQL stored procedure DDL script.
CREATE or REPLACE PROCEDURE PROC_MYNAME_IMPORT IS
-- Queries parsed name values from STAFF (the source) table
CURSOR name_cursor IS
SELECT REGEXP_SUBSTR(staff.name,...) as FirstName,
REGEXP_SUBSTR(... ) as LastName
FROM STAFF;
BEGIN
FOR i IN name_cursor LOOP
--DML Command 1:
INSERT INTO Table_One ( first_name, last_name )
VALUES (i.FirstName, i.LastName);
COMMIT;
--DML Command 2:
INSERT INTO Table_Two ...
COMMIT;
END LOOP;
END proc_myname_import;
As you can see from the example block, a long series of DML statements can take place (not just two) for a given cursor record and its values as it is handled by each loop iteration. Each field may be referenced by the name assigned to them within the cursor SQL statement. There is a '.' (dot) notation where the handle assigned to the cursor call is the prefix, as in:
CURSOR c1 IS
SELECT st.col1, st.col2, st.col3
FROM sample_table st
WHERE ...
Then the cursor call for looping through the main record set:
FOR my_personal_loop IN c1 LOOP
...do this
...do that
INSERT INTO some_other_table (column_one, column_two, column_three)
VALUES (my_personal_loop.col1, my_personal_loop.col2, ...);
COMMIT;
END LOOP;
... and so on.
This should work for you.
insert into newtable(FirstName, LastName)
select substr(staff.name, 0, instr(staff.name, ' ') - 1),
substr(staff.name, instr(staff.name, ' ') + 1)
from staff;

Can I create a trigger that involves a cursor on a table that the trigger is not made for?

For example if I have a trigger for table employee. I want to create a cursor loop from the table department. Then I want to take the attribute and insert it into the table company. I'm guessing the answer is no, because I get a runtime error that says table department cannot be found, but is there any way around this that gets the same effect?
CREATE TRIGGER myTrigger AFTER INSERT
ORDER 1 ON dba.employee
REFERENCING NEW AS newRow
FOR EACH ROW
BEGIN
FOR myloop AS getIDCursor INSENSITIVE CURSOR FOR SELECT department_id FROM department
DO
INSERT INTO company (...) VALUES (...);
END FOR
END
Why are you using SQL like a procedural language? Just do:
INSERT INTO company SELECT department_id FROM department
No need for loops.

ORA-00984 column not allowed here

I am getting error
"Execute-984 ORA-00984: column not allowed here"
while I am inserting values in my table Registred_Customer using Pro*C
Registred_Customer is defined as
CREATE TABLE Registred_Customer (
Cust_id NUMBER(6) PRIMARY KEY,
Name VARCHAR2(20) NOT NULL,
Age NUMBER,
Sex CHAR,
Addr VARCHAR2(50),
Contact NUMBER(10)
);
Inserting values using a pro*c method
addCustomer(i, name,age, gender, address,contectNo);
in Pro*C method I use following code to insert
EXEC SQL INSERT INTO REGISTRED_CUSTOMER VALUES
(cust_id, cust_name, age, sex, addr, contact);
here cust_name and addr are char *; and sex is char rest as int;
It reports error while using variable but works fine using direct values
like EXEC SQL INSERT INTO REGISTRED_CUSTOMER VALUES (10, 'Pankaj', 23, 'M', 'asdfs', 45875);
I tried changing few lines but in vain.
Thanks in advance.
Your Pro*C code is basically missing the colons (assuming that your formal parameters are called cust_id, cust_name, age etc.):
EXEC SQL INSERT INTO REGISTRED_CUSTOMER VALUES
(:cust_id, :cust_name, :age, :sex, :addr, :contact);
And it would be more robust to explicitly specify the columns name. Otherwise a change to the table schema can result in difficult to find bugs:
EXEC SQL INSERT INTO REGISTRED_CUSTOMER (Cust_Id, Name, Ag, Sex, Addr, Contact)
VALUES (:cust_id, :cust_name, :age, :sex, :addr, :contact);
If im seeing correct you are trying to insert into the columns, the columns??
"EXEC SQL INSERT INTO REGISTRED_CUSTOMER VALUES (cust_id, cust_name, age, sex, addr, contact);"??
it would be more helpful if you post your procedure complete.
Regards
As Mr. mentioned, you are trying to use the columns as input values. When you provide actual values it works. Are you perhaps meaning to use PL/SQL variables or the procedure arguments? In this case, whatever your procedure parameters are called is what you should put in the values section.
i.e if addCustomer looks like
PROCEDURE addCustomer (pId NUMBER, pName VARCHAR2, pAge NUMBER, pGender CHAR, pAddress VARCHAR2, pContact NUMBER)
Then you'd do something like
INSERT INTO registered_customer (cust_id, name, age, sex, addr, contact) VALUES (pId, pName, pAge, pGender, pAddress, pContact);
But if you are inserting into all columns you can leave out the column definition and just provide values
I also got this error message in a stored procedure doing an insert. I misspelled a parameter name in the values clause and the oracle interpreter saw the misspelled name as a column name and issued the 00984.