Cannot update view in PostgreSQL? - postgresql

My site was developed using Drupal 6 running on a Postgresql 8.3 server on Ubuntu 11.10. Also webmin version 1.590.
Now I want to update records in a table, but when I run:
UPDATE uac_institution_view SET status = '2' WHERE nid = '9950'
it gives me an error like:
Failed to execute SQL : SQL UPDATE uac_institution_view SET status =
'2' WHERE nid = '9950' failed : ERROR: cannot update a view HINT: You
need an unconditional ON UPDATE DO INSTEAD rule.
The problem is that only SELECT queries work. UPDATE, INSERT and DELETE commands are not working; they fail with the above error.
Is this a permisssion problem? A syntax error? Something else?

PostgreSQL views are not updateable by default. You must tell PostgreSQL how you want the view to be updated.
Do this using "an unconditional ON UPDATE DO INSTEAD rule" (as the error message you pasted said) or preferably on PostgreSQL 9.1 and above using a view trigger. I provided links to all that in my answer to your previous post, but here's some more info:
updateable views in PostgreSQL 9.1 using INSTEAD OF trigger
updateable views (for Pg 9.0 and below using rules)
CREATE TRIGGER
CREATE VIEW
rules vs triggers
rules
triggers in PL/pgSQL
In many cases it's better to leave the view read-only and just update the underlying table. Since you have not provided a definition of the view it's hard to say what that would actually involve. Update your question with the output of running \d uac_institution_view in psql and comment to say you've done so; maybe I can point out a way to run the update directly on the underlying table(s).
You are using a very obsolete version of PostgreSQL (8.3) so you cannot use the preferred INSTEAD OF trigger approach, you must either use rules or update the underlying table directly.

FYI, after the answer involving rules/triggers was posted, PostgreSQL 9.3 came out with auto-updatable views. Version 9.3 is in beta 2 as of June 27, 2013, so it's not yet GA.
Here is an example: https://web.archive.org/web/20160322164044/http://michael.otacoo.com/postgresql-2/postgres-9-3-feature-highlight-auto-updatable-views/

I am on postgres 9.5 and views are updatable by default.
Example :
CREATE TABLE UP_DATE (id number, name varchar2(29));
insert into up_date values(1, 'Foo');
select * from up_date;
CREATE OR REPLACE VIEW UPDATE
AS
Select
name from up_date;
select * from update;
insert into update values('Bar');
select * from update;
Will out put Foo and Bar

Everything just works from PG 9.3 onwards as noted by Jeff French... With some exceptions (more info below).
Simple example
You can test this code on your PostgreSQL. Use cascade drop when you are done (to drop view with the table).
-- create table
--DROP TABLE user_table CASCADE;
CREATE TABLE user_table (
id serial,
lastname varchar(100),
user_type varchar(2) DEFAULT 'nn',
PRIMARY KEY (id)
);
-- initial data
INSERT INTO user_table(lastname) VALUES('Foo');
SELECT * FROM user_table;
-- simple view (note, no id here)
CREATE OR REPLACE VIEW user_view
AS
SELECT lastname, user_type
FROM user_table
;
-- check view (will have initial data)
SELECT * FROM user_view;
-- insert into user_table via view
INSERT INTO user_view VALUES('Bar');
-- check (both will have both records)
SELECT * FROM user_view;
SELECT * FROM user_table;
-- you can run above many times
-- (id will auto-increment even though it is not in the view)
-- update user_table via view
UPDATE user_view SET user_type='v' WHERE lastname = 'Bar';
SELECT * FROM user_table;
Limitations
There are some limitations though and that will depend on you PG version.
In PG 9.3 views cannot have any expressions etc. Also only one table is allowed... So more or less a simple select limiting, reordering, or renaming columns.
In PG 9.4 views can be partially updatable. So you can have expression but you will not be able to update them (not out of the box at least).
If you have WHERE in your view then you might get a bit weird results. So this will still work with insert:
CREATE OR REPLACE VIEW user_view
AS
SELECT lastname as last_name, user_type
FROM user_table
WHERE user_type = 'v'
;
INSERT INTO user_view VALUES('Bar');
But update might not work. At least this will not work in that it will update 0 rows:
UPDATE user_view SET user_type='v';
Because effectively that would be equivalent to below query (so makes sense if you think about it):
UPDATE user_view SET user_type='v' WHERE user_type = 'v';
I wonder if at some point they might support joins... But at the time of writing PG 14 is out and it doesn't support joined tabled in views (for updates I mean).
Alternatives
You can still use INSTEAD OF triggers, especially for more complicated views. And you can use rules... But (as noted in the CREATE RULE docs) automatically updatable views will be faster then manually created rules.

Related

PostgreSQL table row goes to the bottom when updating

I am currently migrating from MySQL to PostgreSQL in a Laravel application, and I noticed that when updating, the row goes to the end of the table (bottom).
In the application I know I can use ORDER BY to sort, but I am referring to the internal behavior of the database while performing the UPDATE action.
In Mysql, it remains in the same position it occupied before the update.
Is there any way to apply this function? Would it be a InnoDB feature? Using Navicat Premium 12.1 DBMS.
I think this is just an aesthetic factor, but even so I would like to learn how to carry out this "permanent ordination".
The database is in UTF-8 encoding and pt_BR.UTF8 collation and ctype.
Following is the table:
CREATE TABLE `properties` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` varchar(255) NOT NULL,
`description` text NOT NULL,
`name` varchar(255),
`rental_price` decimal(10, 2),
`sale_price` decimal(10, 2)
);
Thank you all!
Part 1: Generally use ORDER BY
If you do not use the ORDER BY statement, both MySQL and PostgreSQL (and for that matter most relational DBMS systems) do not make any promises about the order of records.
You should refactor your application to use the ORDER BY statement. If you want your data set to be ordered by newest first, you could use something like:
SELECT * FROM yourtable ORDER BY id DESC;
SELECT * FROM yourtable ORDER BY creation_date DESC; -- if your table has such a column
Similarly, you can have oldest objects first by using one of the following:
SELECT * FROM yourtable ORDER BY id ASC;
SELECT * FROM yourtable ORDER BY creation_date ASC; -- if your table has such a column
Part 2: Looking into the mechanics
You added to your question a more detailed inquiry:
[...] I know I can use ORDER BY to sort, but I am referring to the internal behavior of the database while performing the UPDATE action.
There is multiple things that influence the sequence of database records displayed on your screen, when performing a query. In a real life application, it is not (practially) possible to predict this sequence.
I assume this is simply an effect of PostgreSQL creating a new record for the updated record as can be found here in the Updating a Row section. I suggest to not rely on this behvaiour in any of your applications.

temp tables in postgresql

I'm coming from a background in SQL Server where I would create temp tables using the:
select id
into #test
from table A
I've just moved into a PostGresql environment and I was hoping I could do the same, but I'm getting a syntax error. I did a search and it seems like you have to do a Create Table statement.
Is it not possible to easily create temp tables in Postgres?
Postgres supports SELECT INTO, so this should work fine:
SELECT id
INTO TEMP TABLE test
FROM a
You can also use CREATE TABLE AS:
CREATE TEMP TABLE test AS
SELECT id FROM a
This version is generally preferred, as the CREATE statement provides additional options, and can also be used in PL/pgSQL functions (where the SELECT INTO syntax has been hijacked for variable assignment).

How to write a trivial on update rule on a view which just forwards the given update to the table represented by the view

I got table a
CREATE TABLE test (
id SERIAL,
name character varying NOT NULL,
PRIMARY KEY (id)
);
a view
CREATE VIEW TEST_VIEW AS
SELECT id,name
FROM test;
and just want to forward a given update queue to the actual table behind the view
CREATE RULE TEST_VIEW_UPDATE
AS ON UPDATE TO TEST_VIEW
DO INSTEAD UPDATE TEST;
But this approach results in an error as the SET statement is probably missing. How can I do this correctly in the most generic (therefore no limitation on what is actually updated) way?
On PostgreSQL 9.3 this will work automatically and without changes. PostgreSQL will create simple views as updateable by default.
In prior versions, specify all columns in the UPDATE. There's no wildcard.
If you're on 9.1 or above (which you should always mention in every question - select version()) you should use an INSTEAD OF view trigger rather than a rule.
As far as I know, it's not possible to do it like this, you have to write actual command:
CREATE RULE TEST_VIEW_UPDATE
AS ON UPDATE TO TEST_VIEW
DO INSTEAD UPDATE TEST set name = NEW.name, col1 = NEW.col1 where id = NEW.id;
It's also possible to do what you want with triggers - check this and this links.

PostgreSQL SELECT-RULES , inheritance, row-level permissions

Here's what I've been reading:
http://www.postgresql.org/docs/9.2/static/rules-views.html
http://www.postgresql.org/docs/9.2/static/rules-privileges.html
My goal is to allow a login to see only those rows that it "owns", so to speak.
Let's say every table in the database inherits from this table:
create table WHOAMI
(
tenant varchar(25) not null default current_user
);
for example:
create table FOO
(
id int primary key,
invoicedate date
) inherits (WHOAMI);
insert into FOO(id, invoicedate) values(1,now()::date);
select * from FOO;
--abclogin|1|2013-02-01
Is there such a thing in PostgreSQL as a schema-level select rule, affecting all tables and views in the schema, that appends to every select, insert, update, or delete statement a condition that says, in effect, ..AND WHERE TENANT = current_user? If there isn't such a global rule, can it be done on a table-by-table basis? I am not having any success with my attempts, and am probably misunderstanding a few things about how rules are created. Here is what I have tried to do:
I try to create a select-rule:
CREATE RULE "_RETURN" AS ON SELECT TO FOO DO INSTEAD
SELECT * FROM FOO where tenant = current_user;
but get this error: ERROR: could not convert table "foo" to a view because it has indexes
I try to create a view with a security-barrier:
CREATE VIEW TENANTFOO WITH (security_barrier) AS
SELECT * FROM FOO WHERE tenant=current_user;
and then attempt an insert:
insert into TENANTFOO(id,invoicedate)
values(2,(now()::date);
but get this error:
`ERROR: cannot insert into view "tenantfoo"
HINT: You need an unconditional ON INSERT DO INSTEAD rule
or an INSTEAD OF INSERT trigger.`
What steps are required to implement row-level security barriers on tables?
In your last example, you'd need to run the INSERT against the table or create another RULE: ON INSERT TO TENANTFOO DO INSTEAD.
What you're looking for is a Row-Level Security, it is not yet available, although some work had been done on this thing. I hope this patch will make it into the upcoming 9.3 release.
Meanwhile, I've been working with the following design a while ago.
Requirements were similar, views should have been delivering only those rows intended for the CURRENT_USER. In our case access had been done quite simple: a table that specified whether given user had access for the given relation and given key, smth like:
CREATE TABLE user_grants (
user_id integer,
entity_name text, -- should exist in pg_class
entity_id integer
);
Then, say for the tasks, the following view had been created:
CREATE VIEW tasks_v AS
SELECT t.*
FROM tasks t
JOIN user_grants ug ON t.user_id = ug.user_id
AND ug.entity_name='TASKS' AND ug.entity_id = t.task_id;
Of course, the setup is not complete without a number of helper functions, triggers and rules. Also it was necessary to make sure some reasonable default privileges are always granted.

How do I INSERT and SELECT data with partitioned tables?

I set up a set of partitioned tables per the docs at http://www.postgresql.org/docs/8.1/interactive/ddl-partitioning.html
CREATE TABLE t (year, a);
CREATE TABLE t_1980 ( CHECK (year = 1980) ) INHERITS (t);
CREATE TABLE t_1981 ( CHECK (year = 1981) ) INHERITS (t);
CREATE RULE t_ins_1980 AS ON INSERT TO t WHERE (year = 1980)
DO INSTEAD INSERT INTO t_1980 VALUES (NEW.year, NEW.a);
CREATE RULE t_ins_1981 AS ON INSERT TO t WHERE (year = 1981)
DO INSTEAD INSERT INTO t_1981 VALUES (NEW.year, NEW.a);
From my understanding, if I INSERT INTO t (year, a) VALUES (1980, 5), it will go to t_1980, and if I INSERT INTO t (year, a) VALUES (1981, 3), it will go to t_1981. But, my understanding seems to be incorrect. First, I can't understand the following from the docs
"There is currently no simple way to specify that rows must not be inserted into the master table. A CHECK (false) constraint on the master table would be inherited by all child tables, so that cannot be used for this purpose. One possibility is to set up an ON INSERT trigger on the master table that always raises an error. (Alternatively, such a trigger could be used to redirect the data into the proper child table, instead of using a set of rules as suggested above.)"
Does the above mean that in spite of setting up the CHECK constraints and the RULEs, I also have to create TRIGGERs on the master table so that the INSERTs go to the correct tables? If that were the case, what would be the point of the db supporting partitioning? I could just set up the separate tables myself? I inserted a bunch of values into the master table, and those rows are still in the master table, not in the inherited tables.
Second question. When retrieving the rows, do I select from the master table, or do I have to select from the individual tables as needed? How would the following work?
SELECT year, a FROM t WHERE year IN (1980, 1981);
Update: Seems like I have found the answer to my own question
"Be aware that the COPY command ignores rules. If you are using COPY to insert data, you must copy the data into the correct child table rather than into the parent. COPY does fire triggers, so you can use it normally if you create partitioned tables using the trigger approach."
I was indeed using COPY FROM to load data, so RULEs were being ignored. Will try with TRIGGERs.
Definitely try triggers.
If you think you want to implement a rule, don't (the only exception that comes to mind is updatable views). See this great article by depesz for more explanation there.
In reality, Postgres only supports partitioning on the reading side of things. You're going to have setup the method of insertition into partitions yourself - in most cases TRIGGERing. Depending on the needs and applicaitons, it can sometimes be faster to teach your application to insert directly into the partitions.
When selecting from partioned tables, you can indeed just SELECT ... WHERE... on the master table so long as your CHECK constraints are properly setup (they are in your example) and the constraint_exclusion parameter is set corectly.
For 8.4:
SET constraint_exclusion = partition;
For < 8.4:
SET constraint_exclusion = on;
All this being said, I actually really like the way Postgres does it and use it myself often.
Does the above mean that in spite of
setting up the CHECK constraints and
the RULEs, I also have to create
TRIGGERs on the master table so that
the INSERTs go to the correct tables?
Yes. Read point 5 (section 5.9.2)
If that were the case, what would be
the point of the db supporting
partitioning? I could just set up the
separate tables myself?
Basically: the INSERTS in the child tables must be done explicitly (either creating TRIGGERS, or by specifying the correct child table in the query). But the partitioning
is transparent for SELECTS, and (given the storage and indexing advantages of this schema) that's the point.
(Besides, because the partitioned tables are inherited,
the schema is inherited from the parent, hence consistency
is enforced).
Triggers are definitelly better than rules.
Today I've played with partitioning of materialized view table and run into problem with triggers solution.
Why ?
I'm using RETURNING and current solution returns NULL :)
But here's solution which works for me - correct me if I'm wrong.
1. I have 3 tables which are inserted with some data, there's an view (let we call it viewfoo) which contains
data which need to be materialized.
2. Insert into last table have trigger which inserts into materialized view table
via INSERT INTO matviewtable SELECT * FROM viewfoo WHERE recno=NEW.recno;
That works fine and I'm using RETURNING recno; (recno is SERIAL type - sequence).
Materialized view (table) need to be partitioned because it's huge, and
according to my tests it's at least x10 faster for SELECT in this case.
Problems with partitioning:
* Current trigger solution RETURN NULL - so I cannot use RETURNING recno.
(Current trigger solution = trigger explained at depesz page).
Solution:
I've changed trigger of my 3rd table TO NOT insert into materialized view table (that table is parent of partitioned tables), but created new trigger which inserts
partitioned table directly FROM 3rd table and that trigger RETURN NEW.
Materialized view table is automagically updated and RETURNING recno works fine.
I'll be glad if this helped to anybody.