POstgresSQL: ERROR: cannot delete from view "view_name" - sql

I am trying to delete rows from a VIEW that appear in another VIEW. This is a trial operation with TABLE, but couldn't figure out how it works with VIEW.
For example, consider these two VIEWS:
SELECT * FROM trips;
id session_ids distance
535780 {8024,8026} 74695.31
535268 {4567} 455.84
543477 {63331} 18546.94
540797 {43350} 412.65
SELECT * FROM sessions
session_id timestamp
4567 2016-04-07 15:39:31.578
8024 2016-04-09 14:31:19.068
1526 2016-04-04 07:50:24.544
10311 2016-04-10 16:48:14.883
Note that the column trips.session_ids is type integer array, and the column sessions.session_id is type integer.
I want to delete rows from trips that are in sessions, so:
DELETE FROM trips t
USING sessions s
WHERE s.session_id = ANY(t.session_ids)
Error:
ERROR: cannot delete from view "trips"
DETAIL: Views that do not select from a single table or view are not automatically updatable.
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
SQL state: 55000
Expected result (after delete):
SELECT * FROM trips;
id session_ids distance
543477 {63331} 18546.94
540797 {43350} 412.65
EDIT
To shed more light to my question, the trips VIEW was created from a large table (a subset of raw table), something like:
CREATE VIEW trips
AS
SELECT * FROM raw_table
WHERE some_condition;
Now I need a way to further filter trips to exclude rows that appear in sessions.
EDIT-2
As illustrated in this dbfiddle,it worked fine, but not in my real database.
CREATE TABLE raw_table(id int, session_ids integer[], distance double precision);
INSERT INTO raw_table(id, session_ids, distance)
VALUES (535780,'{8024,8026}',4695.31),
(535268,'{4567}',455.84),
(543477,'{63331}',18546.94),
(544400,'{15304}',25546.24),
(544210,'{12012,17577}',32546.24),
(540797,'{43350}',412.65);
CREATE VIEW trips
AS
SELECT * FROM raw_table
WHERE distance < 25000;
CREATE TABLE sessions (session_id int, timestamp TIMESTAMP);
INSERT INTO sessions (session_id, timestamp)
VALUES (4567,'2016-04-07 15:39:31.578+01'),
(8024,'2016-04-09 14:31:19.068+01'),
(1526,'2016-04-04 07:50:24.544+01'),
(10311,'2016-04-10 16:48:14.883+01');
DELETE FROM trips t
USING sessions s
WHERE s.session_id = ANY(t.session_ids)

Only very simple views allow data modifications.
If you want to run DELETE on a more complicated view, you have to write an INSTEAD OF DELETE trigger that performs some appropriate action instead instead, for example delete something from the tables underlying the view.

Related

Creating a SQL view that will accept inserted values

I have created a SQL view that retrieves the result set I desire but when I try to add a new set of values it fails. When searching around on the net it appears that VIEW has a few limitations and some types of joins may not be allowed, I have tried to create the view a few different ways but with no success yet, would anyone happen to know a way I could rewrite this create view query that would allow me to insert new values into it?
The view:
CREATE VIEW NATIONAL_ITEMS
AS
SELECT item.itemno, item.itemdesc, item.itemprice
FROM item
FULL OUTER JOIN invoiceitem ON item.itemno = invoiceitem.itemno
WHERE item.itemdesc LIKE '%National%'
AND invoiceitem.invoiceno IS NULL
WITH CHECK OPTION;
My insert statement that does not work:
INSERT INTO NATIONAL_ITEMS
VALUES ('123-456', 'National TV', 100);
I get this error:
Error starting at line : 1 in command -
INSERT INTO NATIONAL_ITEMS VALUES ('123-456', 'National TV', 100)
Error at Command Line : 1 Column : 1
Error report -
SQL Error: ORA-01733: virtual column not allowed here
01733. 00000 - "virtual column not allowed here"
Any help would be greatly appreciated, thank you.
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;

Explicitly set ROWNUM in column

I'm trying to split what was a large table update into multiple inserts into working tables. One of the queries needs uses the row number in it. On an INSERT in oracle, can I explicitly add the ROWNUM as an explicit column? This is a working table ultimately used in a reporting operation with a nasty partion over clause and having a true row number is helpful.
create table MY_TABLE(KEY number,SOMEVAL varchar2(30),EXPLICIT_ROW_NUMBER NUMBER);
INSERT /*+PARALLEL(AUTO) */ INTO MY_TABLE(KEY,SOMEVAL,EXPLICIT_ROW_NUMBER) (
SELECT /*+PARALLEL(AUTO) */ KEY,SOMEVAL,ROWNUM
FROM PREVIOUS_VERSION_OF_MY_TABLE
);
where PREVIOUS_VERSION_OF_MY_TABLE has both a KEY and SOMEVAL fields.
I'd like it to number the rows in the order that the inner select statement does it. So, the first row in the select, had it been explicitly run, would have a ROWNUM of 1, etc. I don't want it reversed, etc.
The table above has over 80MM records. Originally I used an UPDATE, and when I ran it, I got some ORA error saying that I ran out of UNDO space. I do not have the exact error message at this point anymore.
I'm trying to accomplish the same thing with multiple working tables that I would have done with one or more updates. Apparently it is either hard, impossible, etc to add UNDO space, for this query (our company DB team says), without making me a DBA, or spending about $100 on a hard drive and attaching it to the instance. So I need to write a harder query to get around this limitation. The goal is to have a session id and timestamps within that session, but for each timestamp within a session (except the last timestamp), show the next session. The original query is included below:
update sc_hub_session_activity schat
set session_time_stamp_rank = (
select /*+parallel(AUTO) */ order_number
from (
select /*+parallel(AUTO) */ schat_all.explicit_row_number as explicit_row_number,row_number() over (partition by schat_all.session_key order by schat_all.session_key,schat_all.time_stamp) as order_number
from sc_hub_session_activity schat_all
where schat_all.session_key=schat.session_key
) schat_all_group
where schat.explicit_row_number = schat_all_group.explicit_row_number
);
commit;
update sc_hub_session_activity schat
set session_next_time_stamp = (
select /*+parallel(AUTO) */ time_stamp
from sc_hub_session_activity schat2
where (schat2.session_time_stamp_rank = schat.session_time_stamp_rank+1) and (schat2.session_key = schat.session_key)
);
commit;

which delete statement is better for deleting millions of rows

I have table which contains millions of rows.
I want to delete all the data which is over a week old based on the value of column last_updated.
so here are my two queries,
Approach 1:
Delete from A where to_date(last_updated,''yyyy-mm-dd'')< sysdate-7;
Approach 2:
l_lastupdated varchar2(255) := to_char(sysdate-nvl(p_days,7),'YYYY-MM-DD');
insert into B(ID) select ID from A where LASTUPDATED < l_lastupdated;
delete from A where id in (select id from B);
which one is better considering performance, safety and locking?
Assuming the delete removes a significant fraction of the data & millions of rows, approach three:
create table tmp
Delete from A where to_date(last_updated,''yyyy-mm-dd'')< sysdate-7;
drop table a;
rename tmp to a;
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:2345591157689
Obviously you'll need to copy over all the indexes, grants, etc. But online redefinition can help with this https://oracle-base.com/articles/11g/online-table-redefinition-enhancements-11gr1
When you get to 12.2, there's another simpler option: a filtered move.
This is an alter table move operation, with an extra clause stating which rows you want to keep:
create table t (
c1 int
);
insert into t values ( 1 );
insert into t values ( 2 );
commit;
alter table t
move including rows where c1 > 1;
select * from t;
C1
2
While you're waiting to upgrade to 12.2+ and if you don't want to use the create-as-select method for some reason then approach 1 is superior:
Both methods delete the same rows from A* => it's the same amount of work to do the delete
Option 1 has one statement; Option 2 has two statements; 2 > 1 => option 2 is more work
*Statement level consistency means you might get different results running the processes. Say another session tries to update an old row that your process will remove.
With just the delete, the update will be blocked until the delete finishes. At which point the row's gone, so the update does nothing.
Whereas if you do the insert first, the other session can update & commit the row before the insert completes. So the update "succeeds". But the delete will then remove it! Which can lead to some unhappy customers...
Your stored dateformat seems suitable for proper sorting, so you could go the other way round and convert sysdate to string:
--this is false today
select * from dual where '2019-06-05' < to_char(sysdate-7, 'YYYY-MM-DD');
--this is true today
select * from dual where '2019-05-05' < to_char(sysdate-7, 'YYYY-MM-DD');
So it would be:
Delete from A where last_updated < to_char(sysdate-7, ''yyyy-mm-dd'');
It has the benefit that your default index (if there is any) will be used.
It has the disadvantage on relying on the String/Varchar ordering which might be changed i.e. bei NLS changes (if i remember right), so in any case you should do a little testing before...
In the long term, you should of cource alter the colum to a proper date-datatype, but I guess that doesn't help you right now ;)
If you are trying to delete most of the rows in the table, I would advise you go with a different approach, namely:
create <new table name> as
select *
from <old table name>
where <predicates for the data you want to keep>;
then
drop table <old table name>;
and finally you can rename the new table back to the old table.
You could always partition the new table (i.e. create the new table with a separate statement containing the partitioning clauses, and then have an insert as select into the new table from the old table).
That way, when you need to delete rows, it's a simple matter of dropping the relevant partition(s).

Restrict Insert based on previous insertion date

I want to restrict insertion in my table based on some condition.
My table is like
col1 col2 Date Create
A 1 04/05/2016
B 2 04/06/2016
A 3 04/08/2016 -- Do not allow insert
A 4 04/10/2016 -- Allow insert
So I want to restrict insert based on the number of days the same record was inserted earlier.
As shown in able example, A can be inserted again in table only after 4 days of previous insertion not before that.
Any pointers how I can do this in SQL/Oracle.
You only want to insert when there not exists a record with the same col1 and a too recent date:
insert into mytable (col1, col2, date_create)
select 'B' as col1, 4 as col2, trunc(sysdate) as date_create from dual ins
where not exists
(
select *
from mytable other
where other.col1 = ins.col1
and other.date_create > ins.date_create - 4
);
An undesired record would not be inserted thus. However, no exception would be raised. If you want that, I'd suggest a PL/SQL block or a before insert trigger.
If several processes write to your table simultaneously with possibly conflicting data then oracle database should do the job.
This can be solved by defining a constraint to check if there already exists an entry with the same col1 value younger than four days.
As far as I know, it is not possible to define such a constraint directly. Instead, define a materialized view and add a constraint on this view.
create materialized view mytable_mv refresh on commit as
select f2.col1, f2.date_create, f1.date_create as date_create_conflict
from mytable f2, mytable f1
where f2.col1 = f1.col1
and f2.date_create > f1.date_create
and f2.date_create - f1.date_create < 4;
This materialized view will contain an entry, if and only if a conflict exists.
Now define a constraint on this view:
alter table mytable_mv add constraint check_date_create
check(date_create=
date_create_conflict) deferrable;
It is executed when the current transaction is commited (because the materialized view is refreshed - as declared above refresh on commit).
This works fine if you insert into your table mytable in an autonomous transaction, e.g. for a logging table.
In other cases, you can force the refresh on the materialized view by dbms_mview.refresh('mytable_mv') or use another option than refresh on commit.

SQL.Working with views

I have a table 'Goods' with different information about goods (name, price, etc). I need to create a view at the same scheme as table 'Goods' has. But the view must be empty. And when user adds new good to the view it is saved in table 'Goods', but the view remains empty when user opens it next time. So main idea is not to show existing data to the user which has access to the view.
Assuming your on a database system that supports a concept like SQL Server's CHECK OPTION, and you're allowed to create a view that doesn't have that option set, you should be fine:
create table T (ID int not null)
go
create view V
as
select * from T where 1=0
go
select * from V
go
insert into V(ID) values (10)
go
select * from V
go
select * from T
The two selects from V return 0 rows. The select from T returns one row:
ID
----
10
CHECK OPTION:
Forces all data modification statements executed against the view to follow the criteria set within select_statement. When a row is modified through a view, the WITH CHECK OPTION makes sure the data remains visible through the view after the modification is committed.
And you want the opposite - you want to allow data modifications performed through the view to create rows which are invisible through the view.
Create table Goods1 with "insert trigger" on it which make insert into Goods and delete from Goods1
As far as I know this isn't possible. The whole point of a view is that it is a view to a table or grouping of tables, ie. it must show the data that matches the view.
http://www.w3schools.com/sql/sql_view.asp
What you could do is create another table called GoodsView and add a trigger to it to INSERT into Goods table and DELETE from GoodsView afterwards.
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fsqlp%2Frbafysqltrig.htm