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

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.

Related

Is it possible to refer a column in a view as foreign key (PostgreSQL 9.4)?

I know in older versions it was impossible, is it the same with version 9.4?
I'm trying to do something like this:
CREATE VIEW products AS
SELECT d1.id AS id, d1.price AS pr FROM dup.freshProducts AS d1
UNION
SELECT d2.id AS id, d2.price AS pr FROM dup.cannedProducts AS d2;
CREATE TABLE orderLines
(
line_id integer PRIMARY KEY,
product_no integer REFERENCES productView.id
);
I'm trying to implement an inheritance relationship where freshProducts and cannedProducts both inherit from products. I implemented it using two different tables and I created a view products that has only the common properties between freshProducts and cannedProducts. In addition, each row in orderLines has a relationship with a product, either a freshProduct or a cannedProduct. See image for clarification.
If referencing to a view is yet not possible, which solution do you think is best? I've thought of eihter a materialized view or implementing the restriction using triggers. Could you recommend any good example of such triggers to use as a basis?
Thank-you very much!
Referencing a (materialized) view wouldn't work and a trigger might look like this:
CREATE OR REPLACE FUNCTION reject_not_existing_id()
RETURNS "trigger" AS
$BODY$
BEGIN
IF NEW.product_no NOT IN (SELECT id FROM dup.freshProducts UNION SELECT id FROM dup.cannedProducts) THEN
RAISE EXCEPTION 'The product id % does not exist', NEW.product_no;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
CREATE TRIGGER tr_before_insert_or_update
BEFORE INSERT OR UPDATE OF product_no
ON orderLines
FOR EACH ROW
EXECUTE PROCEDURE reject_not_existing_id();
(See also http://www.tek-tips.com/viewthread.cfm?qid=1116256)
A materialized view might look like a good approach but fails for two reasons: Like a view you simply can't reference it, because it is no table (go ahead and try). Assuming you could, there would still be the problem of preventing two equal ids in freshProducts and cannedProducts. Yes you can define an UNIQUE INDEX on a materialized view, but how to make sure the same id isn't used both in fresh an canned in the first place?
That's something you still have to solve if using the trigger in orderLines.
That brings me to suggest to rethink your model. 'Fresh' and 'canned' might as well be values of an attribute of a single table products, hence making all the trouble superfluous. If fresh and canned product significantly differ in (the number of) their attributes (can't think of any other reason to create two different tables) then reference the product id in two other tables. Like
CREATE TABLE products
(
id ... PRIMARY KEY
, fresh_or_canned ...
, price ...
, another_common_attribute_1 ...
, ...
, another_common_attribute_n ...
);
CREATE TABLE canned_specific_data
(
canned_id ... REFERENCES products (id)
, type_of_can ...
, ...
, another_attribute_that_does_not_apply_to_fresh ...
);
CREATE TABLE fresh_specific_data
(
fresh_id ... REFERENCES products (id)
, date_of_harvest ...
, ...
, another_attribute_that_does_not_apply_to_canned ...
);
The simple answer to preventing ID duplication is to simply use the same sequence as the default value for IDs in both freshProducts and cannedProducts.
Now, there comes the question, why do you need a foreign key at all? Typically this is to prevent deletion of data that another table depends upon, however, you can write a trigger to prevent that. Alsowise, you have updating that value to something that doesn't exist in the keyed-to table, but you can write a trigger for that too.
So basically you can write triggers to implement all the desired functionality of a foreign key without actually needing a foreign key, with the added benefit that they WILL work with such a view.

postgresql trigger (strategy) to update table based on entries of another table

I am fairly new to PostgreSQL (spoilt by django ORM!), and I would like to create a trigger which updates a table based on entries of another table.
So, I have the following table on my schema:
collection_myblogs(id, col1,col2,title,col4,col5)
..where field id is autogenerated. Now, I have a new table created like so:
CREATE TABLE FullText(id SERIAL NOT NULL, content text NOT NULL);
ALTER TABLE ONLY FullText ADD CONSTRAINT fulltext_pkey PRIMARY KEY (id);
and I insert values from collection_myblogs like so:
INSERT INTO FullText(content) SELECT title FROM collection_myblogs;
All fine so far...I would now like a trigger on FullText such that FullText updates itself with new entries everytime collection_myblogs has a new entry. So, I attempted creating a trigger as following:
CREATE TRIGGER collection_ft_update BEFORE INSERT OR UPDATE ON collection_myblogs FOR EACH ROW EXECUTE PROCEDURE ft_update();
Now, I am not entirely sure what should go on ft_update() function, and at the moment, I have:
CREATE FUNCTION ft_update() RETURNS trigger AS '
BEGIN
INSERT INTO FullText(content) SELECT new.title;
return new;
END
' LANGUAGE plpgsql;
..which works fine for INSERTS but not UPDATES. i.e if I update the title of the orginal column collections_myblog(title) it appears as a new entry on FullText I am unsure how to deal with ids here.
I would like the ids i.e primary keys to be the same on each table. So, the idea for me is to have FullText(id, content) == collection_myblogs(id, title) - if this makes sense. So, the id and the content should be replicated from collection_myblogs table. How would one go about achieving this?
My understanding is that I can use a trigger before any insert or an update on my collection_myblogs and somehow maintain FullText(id, content) == collection_myblogs(id, title)
I would appreciate any guidance on this.
There are actually a large number of ways to handle this problem. Some examples:
Use table inheritance to create an "interface" to your data (no trigger needed, the abstract table ends up functioning like a view). This is complicated territory though.
Use the trigger approach like you do and then handle UPDATE and DELETE separately. The big issue here is that if you have two areas of text that are identical, your update trigger needs to be able to separate them.
There are many others but those should get you started.
Actually, it turned out to be quite simple. Just had to follow this

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.

Cannot update view in 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.

What is the difference between these two T-SQL statements?

In a SSIS package at work there are some SQL tasks that create staging tables for holding import data. All the statements take this form:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.tbNewTable') AND type in (N'U'))
BEGIN
TRUNCATE TABLE dbo.tbNewTable
END
ELSE
BEGIN
CREATE TABLE dbo.tbNewTable (
ColumnA VARCHAR(10) NULL,
ColumnB VARCHAR(10) NULL,
ColumnC INT NULL
) ON PRIMARY
END
In Itzik Ben-Gan's T-SQL Fundamentals I see a different form of statement for creating a table:
IF OBJECT_ID('dbo.tbNewTable', 'U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbNewTable
END
CREATE TABLE dbo.tbNewTable (
ColumnA VARCHAR(10) NULL,
ColumnB VARCHAR(10) NULL,
ColumnC INT NULL
) ON PRIMARY
Each of these appears to do the same thing. After execution, there will be a empty table called tbNewTable in the dbo schema.
Are there any practical or theoretical differences between the two? What implications might they have?
The first one assumes that if the table exists, it has the same columns as those it would create. The second one does not make that assumption. So if a table with that name happened to exist and had a different set of columns, the two would have very different results.
The first will not actually DROP the table -- it merely TRUNCATES all the data in said table. Hence why the CREATE is guarded.
Thus the form with the DROP will allow the subsequent CREATE to change the schema (when the new table is created) even if tbNewTable previously existed.
Because the DROP/CREATE alters the database schema it may not also be allowed in all cases. For instance, a view created with a SCHEMABINDING will prevent the table from being dropped. (This also hold true for more general FK relationships, should any exist.)
...when SCHEMABINDING is specified, the base table or tables cannot be modified in a way that would affect the view definition.
The TRUNCATE should be marginally faster in one of those constant "don't care" ways: there should be no performance consideration given to one over the other.
There are also permission differences. TRUNCATE only requires the ALTER permission.
The minimum permission required is ALTER on table_name. TRUNCATE TABLE permissions default to the table owner...
Happy coding.
These are very different..
The first does an equality check on the sys.objects system table and looks to see if there is a matching table name. If so, it truncates the table. Basically removing all rows but maintaining the table structure itself - i.e. the actual table is never dropped.
In the second, the check to make sure that the table exists is implicitly done using the OBJECT_ID() method. If so, the table is dropped completely - rows and structure.
If you have a primary and foreign key constraint on the table, you'll certainly have issues dropping it completely... and if you have other tables that are linked to the table you are trying to 'truncate' you'll have issues there too, unless you have cascade deletion turned on.
I tend to dislike either construction in an SSIS package. I create the tables in a deployment script and I want the package to fail if one of the tables I use is missing later on because then something drastically wrong has happened and I want to investigate what before I try putting data anywhere.