In Oracle, is it possible to INSERT or UPDATE a record through a view?
Views in Oracle may be updateable under specific conditions. It can be tricky, and usually is not advisable.
From the Oracle 10g SQL Reference:
Notes on Updatable Views
An updatable view is one you can use to insert, update, or delete base table rows. You can create a view to be inherently updatable, or you can create an INSTEAD OF trigger on any view to make it updatable.
To learn whether and in what ways the columns of an inherently updatable view can be modified, query the USER_UPDATABLE_COLUMNS data dictionary view. The information displayed by this view is meaningful only for inherently updatable views. For a view to be inherently updatable, the following conditions must be met:
- Each column in the view must map to a column of a single table. For example, if a view column maps to the output of a TABLE clause (an unnested collection), then the view is not inherently updatable.
- The view must not contain any of the following constructs:
- A set operator
- a DISTINCT operator
- An aggregate or analytic function
- A GROUP BY, ORDER BY, MODEL, CONNECT BY, or START WITH clause
- A collection expression in a SELECT list
- A subquery in a SELECT list
- A subquery designated WITH READ ONLY
- Joins, with some exceptions, as documented in Oracle Database Administrator's Guide
In addition, if an inherently updatable view contains pseudocolumns or expressions, then you cannot update base table rows with an UPDATE statement that refers to any of these pseudocolumns or expressions.
If you want a join view to be updatable, then all of the following conditions must be true:
- The DML statement must affect only one table underlying the join.
- For an INSERT statement, the view must not be created WITH CHECK OPTION, and all columns into which values are inserted must come from a key-preserved table. A key-preserved table is one for which every primary key or unique key value in the base table is also unique in the join view.
- For an UPDATE statement, all columns updated must be extracted from a key-preserved table. If the view was created WITH CHECK OPTION, then join columns and columns taken from tables that are referenced more than once in the view must be shielded from UPDATE.
- For a DELETE statement, if the join results in more than one key-preserved table, then Oracle Database deletes from the first table named in the FROM clause, whether or not the view was created WITH CHECK OPTION.
How to update a view in oracle
You can create an instead of
trigger on the view, as described in the documentation:
A view presents the output of a query as a table. If you want to change a view as you would change a table, you must create
INSTEAD OF
triggers. Instead of changing the view, they change the underlying tables.
Once you have that trigger in place, you can update the view using the same syntax as if it was a table.
You haven't shown your view or table definitions so there isn't enough information to provide a useful example; fortunately the documentation has one you can use as a starting point.
How to update or create a new record in a table when a view is used to display existing records in Oracle Apex?
You can use views as a proxy for table updates, but they need to remain key-preserved, as the error suggests.
This means you should at least be selecting the primary key in the view, and don't join anywhere that loses that PK uniqueness.
Sometimes it make complain because ambiguity exists, such as a missing foreign key constraint. Further examples explored here
You can still include these vital columns in APEX so that you can define them in the meta-data accordingly as the primary key, you can just set them to 'hidden'.
But you've still got to set the value for new records somwhere, such as a computation, trigger, or use an identity column.
Creating a SQL view that will accept inserted values
You have two options for the view in order to insert in the view.
First, Your View must contain all the keys present in the table on which you are creating the view i.e there must be no DISTINCT
OR GROUP BY
clause.
If you performing JOIN
on the tables same is applicable for all join tables, all keys from the table must be present in the view and there must be no DISTINCT
OR GROUP BY
clause.
The table which has 1:1 row relationship with the rows in the view called Key preserving table
Second, you can create instead of trigger on the view. The trigger will be fired instead of INSERT
, UPDATE
, OR DELETE
and in the trigger, you can handle the DML statement.
Since your table is not key preserving then you can make use of INSTEAD OF TRIGGER
.
CREATE OR REPLACE TRIGGER NATIONAL_ITEMS_TRG
INSTEAD OF INSERT
ON NATIONAL_ITEMS
FOR EACH ROW
BEGIN
INSERT INTO ITEM(itemno, itemdesc, itemprice) VALUES (:NEW.itemno, :NEW.itemdesc, :NEW.itemprice);
END;
INSERT and UPDATE a record using cursors in oracle
This is a highly inefficient way of doing it. You can use the merge
statement and then there's no need for cursors, looping or (if you can do without) PL/SQL.
MERGE INTO studLoad l
USING ( SELECT studId, studName FROM student ) s
ON (l.studId = s.studId)
WHEN MATCHED THEN
UPDATE SET l.studName = s.studName
WHERE l.studName != s.studName
WHEN NOT MATCHED THEN
INSERT (l.studID, l.studName)
VALUES (s.studId, s.studName)
Make sure you commit
, once completed, in order to be able to see this in the database.
To actually answer your question I would do it something like as follows. This has the benefit of doing most of the work in SQL and only updating based on the rowid, a unique address in the table.
It declares a type, which you place the data within in bulk, 10,000 rows at a time. Then processes these rows individually.
However, as I say this will not be as efficient as merge
.
declare
cursor c_data is
select b.rowid as rid, a.studId, a.studName
from student a
left outer join studLoad b
on a.studId = b.studId
and a.studName <> b.studName
;
type t__data is table of c_data%rowtype index by binary_integer;
t_data t__data;
begin
open c_data;
loop
fetch c_data bulk collect into t_data limit 10000;
exit when t_data.count = 0;
for idx in t_data.first .. t_data.last loop
if t_data(idx).rid is null then
insert into studLoad (studId, studName)
values (t_data(idx).studId, t_data(idx).studName);
else
update studLoad
set studName = t_data(idx).studName
where rowid = t_data(idx).rid
;
end if;
end loop;
end loop;
close c_data;
end;
/
Insert into Oracle SQL or Update if value already exist?
Merge does not work with INSERT ALL. If you want to use merge, you should make from both table a view with instead of trigger and than use merge against the view. The whole logic will be inside the trigger.
EDIT: Merge does not work with such views
ORA-38106: MERGE bei Join View oder View mit INSTEAD OF-Trigger nicht unterstützt
You can make two merge statements (one for every table) or one for insert and one for update against view:
CREATE SEQUENCE MYDICT_SEQ START WITH 1 MAXVALUE 9999999999999999999999999999 MINVALUE 0;
CREATE VIEW mydict
AS
SELECT a.id, a.origterm, a.code, b.replaceterm, b.sysctlang
FROM table1 a LEFT OUTER JOIN table2 b ON a.id = b.id;
CREATE OR REPLACE TRIGGER mydict_io
INSTEAD OF INSERT OR UPDATE OR DELETE
ON mydict
FOR EACH ROW
DECLARE
cnt1 INTEGER := 0;
cnt2 INTEGER;
nid NUMBER;
BEGIN
IF INSERTING OR UPDATING THEN
IF :new.id IS NULL AND INSERTING THEN
nid := mydict_seq.NEXTVAL;
ELSE
nid := :new.id;
IF UPDATING THEN
nid := :old.id;
END IF;
SELECT COUNT (*)
INTO cnt1
FROM table1
WHERE id = nid;
END IF;
IF cnt1 = 0 THEN
INSERT INTO TABLE1 (ID, ORIGTERM, CODE)
VALUES (nID, :new.ORIGTERM, :new.CODE);
ELSIF cnt1 > 0 THEN
UPDATE TABLE1
SET ORIGTERM = :NEW.ORIGTERM, CODE = :NEW.CODE
WHERE id = nid;
END IF;
SELECT COUNT (*)
INTO cnt2
FROM table2
WHERE id = nid AND SYSCTLANG = :new.SYSCTLANG;
IF cnt2 = 0 THEN
INSERT INTO TABLE2 (ID, REPLACETERM, SYSCTLANG)
VALUES (nID, :new.REPLACETERM, :new.SYSCTLANG);
ELSE
UPDATE TABLE2
SET REPLACETERM = :new.REPLACETERM
WHERE id = nid AND SYSCTLANG = :new.SYSCTLANG;
END IF;
ELSIF DELETING THEN
DELETE FROM table2
WHERE id = :old.id AND SYSCTLANG = :old.SYSCTLANG;
SELECT COUNT (*)
INTO cnt2
FROM table2
WHERE id = nid;
IF cnt2 = 0 THEN
DELETE FROM table1
WHERE id = :old.id;
END IF;
END IF;
END;
DECLARE
nid NUMBER;
BEGIN
INSERT INTO mydict (ORIGTERM, CODE, REPLACETERM, SYSCTLANG)
VALUES ('Plant', 'TEST', 'Pflanze', 3);
nid := mydict_seq.currval;
UPDATE mydict
SET REPLACETERM = 'Fabrik'
WHERE id = nid;
UPDATE mydict
SET REPLACETERM = 'Usine', SYSCTLANG = 4
WHERE id = nid;
END;
The logic in the instead of trigger can be done better, but I leave this to you ;)
Related Topics
Get Count of Records Affected by Insert or Update in Postgresql
"Column Not Allowed Here" Error in Insert Statement
Opinions on Sensor/Reading/Alert Database Design
How to Group Timestamps into Islands (Based on Arbitrary Gap)
SQL Server Select into @Variable
How to Implement Soft Deletion
SQL Query If Parameter Is Null Select All
How to Create an Oracle Sequence Starting with Max Value from a Table
MySQL Convert Latin1 Data to Utf8
Using Bind Variables with Dynamic Select into Clause in Pl/Sql
Using If Else Statement Based on Count to Execute Different Insert Statements
The Conversion of a Datetime2 Data Type to a Datetime Data Type Resulted in an Out-Of-Range
Functions with Variable Number of Input Parameters
How to Use If/Else Statement to Update or Create New Xml Node Entry in SQL
How to List All the Columns in a Table