21

I have a table called assignments. I would like to be able to read/write to all the columns in this table using either assignments.column or homework.column, how can I do this?

I know this is not something you would normally do. I need to be able to do this to provide backwards compatibility for a short period of time.

We have an iOS app that currently does direct postgresql queries against the DB. We're updating all of our apps to use an API. In the process of building the API the developer decided to change the name of the tables because we (foolishly) thought we didn't need backwards compatibility.

Now, V1.0 and the API both need to be able to write to this table so I don't have to do some voodoo later to transfer/combine data later... We're using Ruby on Rails for the API.

6

3 Answers 3

29

With Postgres 9.3 the following should be enough:

CREATE VIEW homework AS SELECT * FROM assignments;

It works because simple views are automatically updatable (see docs).

3
  • Unfortunately I need to be compatible with PostgreSQL 9.1.9 and 9.2.4 (the built-in PostgreSQL in OS X 10.8 and 9. Commented Apr 26, 2014 at 23:56
  • 1
    In that case you are doomed with triggers or rules, however an upgrade might be easier than implementing those.
    – hegemon
    Commented May 8, 2014 at 14:21
  • I would love to upgrade to 9.3, but this is the built-in PostgreSQL database that all OS X Services use... So, that might be a bit dangerous. Commented May 8, 2014 at 16:50
5

In Postgres 9.3 or later, a simple VIEW is "updatable" automatically. The manual:

Simple views are automatically updatable: the system will allow INSERT, UPDATE and DELETE statements to be used on the view in the same way as on a regular table. A view is automatically updatable if it satisfies all of the following conditions:

  • The view must have exactly one entry in its FROM list, which must be a table or another updatable view.

  • The view definition must not contain WITH, DISTINCT, GROUP BY, HAVING, LIMIT, or OFFSET clauses at the top level.

  • The view definition must not contain set operations (UNION, INTERSECT or EXCEPT) at the top level.

  • The view's select list must not contain any aggregates, window functions or set-returning functions.

If one of these conditions is not met (or for the now outdated Postgres 9.2 or older), a manual setup may do the job.

Building on your work in progress:

Trigger function

CREATE OR REPLACE FUNCTION trg_ia_insupdel()
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
DECLARE
   _tbl  CONSTANT regclass := 'iassignments_assignments';
   _cols text;
   _vals text;
BEGIN
   CASE TG_OP
   WHEN 'INSERT' THEN
      INSERT INTO iassignments_assignments
      VALUES (NEW.*);

      RETURN NEW;

   WHEN 'UPDATE' THEN
      SELECT INTO _cols, _vals
             string_agg(quote_ident(attname), ', ')   -- incl. pk col!
           , string_agg('n.' || quote_ident(attname), ', ')
      FROM   pg_attribute
      WHERE  attrelid = _tbl        -- _tbl converted to oid automatically
      AND    attnum > 0             -- no system columns
      AND    NOT attisdropped;      -- no dropped (dead) columns

      EXECUTE format('
         UPDATE %s t
         SET   (%s) = (%s)
         FROM  (SELECT ($1).*) n
         WHERE    t.published_assignment_id
             = ($2).published_assignment_id' -- match to OLD value of pk
       , _tbl, _cols, _vals)        -- _tbl converted to text automatically
      USING NEW, OLD;

      RETURN NEW;

   WHEN 'DELETE' THEN
      DELETE FROM iassignments_assignments
      WHERE  published_assignment_id = OLD.published_assignment_id;

      RETURN OLD;
   END CASE;

   RETURN NULL;  -- control should never reach this
END
$func$;

Trigger

CREATE TRIGGER insupbef
INSTEAD OF INSERT OR UPDATE OR DELETE ON assignments_published
FOR EACH ROW EXECUTE PROCEDURE trg_ia_insupdel();

Notes

  • assignments_published must be a VIEW, an INSTEAD OF trigger is only allowed for views.

  • Dynamic SQL (in the UPDATE section) is not strictly necessary, only to cover future changes to the table layout automatically. The names of table and PK are still hard coded.

  • Simpler and probably cheaper without sub-block (like you had).

  • Using (SELECT ($1).*) instead of the shorter VALUES ($1.*) to preserve column names.

  • My naming convention: I prepend trg_ for trigger functions, followed by an abbreviation indicating the target table and finally one or more of the the tokens ins, up and del for INSERT, UPDATE and DELETE respectively. The name of the trigger is a copy of the function name, stripped of the first two parts. This is purely a matter of convention and taste but has proven useful for me since the names tell the purpose and are still short.

  • More explanation in the related answer that has already been mentioned:

  • Update multiple columns in a trigger function in plpgsql

6
  • Is there a method to the function/trigger names? They seem rather cryptic to me. I'll test this out on my test DB and see how it works. Commented Apr 28, 2014 at 3:14
  • @PhillipBoushy: Ah right, I added some explanation for that. Commented Apr 28, 2014 at 10:41
  • I believe this does answer it, unfortunately, I haven't had the opportunity to test it fully. I'll try to do that this week and mark your answer as correct. Commented May 12, 2014 at 1:45
  • @ErwinBrandstetter. I tried to replicate your result. But. I believe you should INSERT INTO iassignments_assignments VALUES (NEW.*); now. Another point is, when I tried to make assignments_published as a table then trigger would have error. ERROR: "assignments_published" is a table DETAIL: Tables cannot have INSTEAD OF triggers. Even though I am still not getting the function's intention....
    – jian
    Commented Dec 8, 2021 at 11:19
  • @Mark: Yes, VALUES (NEW.*). INSTEAD OF triggers are only allowed for views, not for tables. Anyway, this answer is old. If possible just use an automatically updatable view (since Postgres 9.3). I only made the UPDATE part dynamic because the OP brought it up in his answer. Commented Dec 9, 2021 at 1:32
0

This is where I am with the trigger functions so far, any feedback would be greatly appreciated. It's a combination of http://vibhorkumar.wordpress.com/2011/10/28/instead-of-trigger/ and Update multiple columns in a trigger function in plpgsql

Table: iassignments_assignments

Columns:

published_assignment_id
name
filepath
filename
link
teacher
due date
description
published
classrooms

View: assignments_published - SELECT * FROM iassignments_assignments

Trigger Function for assignments_published

CREATE OR REPLACE FUNCTION assignments_published_trigger_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $function$
   BEGIN
      IF TG_OP = 'INSERT' THEN
        EXECUTE format('INSERT INTO %s SELECT ($1).*', 'iassignments_assignments')
        USING NEW;
        RETURN NEW;
      ELSIF TG_OP = 'UPDATE' THEN
        DECLARE
          tbl = 'iassignments_assignments';
          cols text;
          vals text;
        BEGIN
          SELECT INTO cols, vals
           string_agg(quote_ident(attname), ', ')
           ,string_agg('x.' || quote_ident(attname), ', ')
          FROM   pg_attribute
          WHERE  attrelid = tbl
          AND    NOT attisdropped   -- no dropped (dead) columns
          AND    attnum > 0;        -- no system columns

          EXECUTE format('
          UPDATE %s t
          SET   (%s) = (%s)
          FROM  (SELECT ($1).*) x
          WHERE  t.published_assignment_id = ($2).published_assignment_id'
          , tbl, cols, vals)
          USING NEW, OLD;

          RETURN NEW;
        END
      ELSIF TG_OP = 'DELETE' THEN
       DELETE FROM iassignments_assignments WHERE published_assignment_id=OLD.published_assignment_id;
       RETURN NULL;
      END IF;
      RETURN NEW;
    END;
$function$;

Trigger

CREATE TRIGGER assignments_published_trigger
INSTEAD OF INSERT OR UPDATE OR DELETE ON
assignments_published FOR EACH ROW EXECUTE PROCEDURE assignments_published_trigger_func();

Table: iassignments_classes

Columns:

class_assignment_id
guid
assignment_published_id

View: assignments_class - SELECT * FROM assignments_classes

Trigger Function for assignments_class

**I'll create this function once I have received feedback on the other and know it's create, so I (hopefully) need very little changes to this function.

Trigger

CREATE TRIGGER assignments_class_trigger
INSTEAD OF INSERT OR UPDATE OR DELETE ON
assignments_class FOR EACH ROW EXECUTE PROCEDURE assignments_class_trigger_func();
2
  • Why the dynamic statements? Do you want to cover future changes to the tables? Would a (simpler & faster) static version be good enough? You'd change the trigger if you should change the table later ... Commented Apr 27, 2014 at 1:54
  • I'm not in control of the queries that need to be made to it or the database structure, I'm just trying to come up with a solution for making it backwards compatible, due to that, I suspect it needs to be dynamic. I would be fine with having it be more static as long as it is easy to update in the future. Commented Apr 28, 2014 at 3:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.