1

So basically I've got this static table that people update information

Then I need it to give it some geometry from a view table. This geometry is dynamic, that's why can't just make a new geom column on the static table

Both layers have matching ids, but different info. I'd like to give the geometry from the view to the table, but when doing a join on QGIS, it doesn't offer the option of adding the geometry column.

Is there a way to join the geometry?

geozelot
  • 30,050
  • 4
  • 32
  • 56
Luffydude
  • 2,308
  • 3
  • 18
  • 38
  • @andy thats a very old answer, and I'm not using any CSVs whatsoever – Luffydude Jan 08 '19 at 17:29
  • 1
    what kind of table is it? – MrXsquared Jan 08 '19 at 17:45
  • Will the Join be temporary or permanent? What format do you want the Joined data to be? – klewis Jan 08 '19 at 18:16
  • 1
    couldn't you just add a virtual layer (or query result as layer) with a PG join on that id? any QGIS canvas update will refresh the layers underlying SQL. – geozelot Jan 08 '19 at 22:01
  • @MrXsquared one is a table with data some people work with, the other one is a view that people from a different department update its base layer for – Luffydude Jan 09 '19 at 09:23
  • @klewis I literally just want to give geometry to my joined table permanently. – Luffydude Jan 09 '19 at 09:23
  • @ThingumaBob will this virtual layer be updateable? – Luffydude Jan 09 '19 at 09:24
  • well, yes and no, depending on what you mean by 'updateable'; a Virtual layer in QGIS is much like a View in PG, that is, a stored query at it's core. a Views main purpose is to give easy access to dynamic cross-table data; each time you query them, the stored SQL will be executed on the current state of the underlying relations. however, Views and Virtual layers store no data, so manipulating the underlying tables through it needs quite a bit of work (e.g. via Rules and/or Triggers). – geozelot Jan 09 '19 at 09:42
  • if you intend to make the 'static table' 'updateable', that is, let people being able to alter/manipulate it's data and save changes through QGIS, while having the joined views 'dynamic geometry' up-to-date, you're facing some work in-DB to allow that. I'm not sure right now how QGIS would handle a case like this, but I suggest to dive into the use of the necessary INSTEAD OF triggers on Views and set-up your DB design this way, to get a consistent workflow. any form of workaround within QGIS might lead to unexpected results/behaviour. – geozelot Jan 09 '19 at 10:07

1 Answers1

4

I'll add a simple example of what I think you need; I assume that

  • a first set of users will update the 'static table' (attributes only) in QGIS and save back to DB
  • a second set of users updates the 'dynamic view's base layers and its geometry independently
  • the edits of the first user group must not not affect the geometry; edits to it will be ignored

If so, INSERT & DELETE statements need to be treated with care; you'd have to decide what to do with non-matching ids in the 'dynamic view'...and work out a similar approach with it if you want to be able to alter that views underlying data.

If you are not interested in the ability to update attributes and save changes to the DB by the first group of users, this is overkill...instead, simply create the view below without any triggers and load that into QGIS.


First off, let's set up a (very limited) log table to keep track of changes made to the 'static table':

CREATE TABLE _composite_log(
    _uid    SERIAL PRIMARY KEY,
    op_user TEXT,
    op_type TEXT,
    op_time TIMESTAMP,
    id_ref  INTEGER
);

Then set up the actual view that will be the working layer for the first user group (I'll call it and prefix all related items with composite):

CREATE OR REPLACE VIEW composite AS
    SELECT  a.*,
            b.geom
    FROM    <static_table> AS a
    JOIN    <dynamic_view> AS b
      ON    a.id = b.id
    ORDER BY
            a.id
;

Then create the trigger procedure...:

CREATE OR REPLACE FUNCTION composite_delupsert_row()
    RETURNS TRIGGER AS

    $$
    BEGIN

        IF (TG_OP = 'UPDATE') THEN

            UPDATE <static_table>
                SET (<col1>, <col2>, ...) = (NEW.<col1>, NEW.<col2>, ...)
                WHERE id = OLD.id;

            INSERT INTO _composite_log(op_user, op_type, op_time, id_ref)
                VALUES (USER, 'UPDATE', NOW(), OLD.id);

            RETURN NEW;

        ELSIF (TG_OP = 'INSERT') THEN

            RETURN NULL;

        ELSIF (TG_OP = 'DELETE') THEN

            RETURN NULL;

        END IF;

    END;
    $$

    LANGUAGE plpgsql
;

...and the trigger on composite:

CREATE TRIGGER composite_delupsert
    INSTEAD OF UPDATE OR INSERT OR DELETE
    ON  composite
    FOR EACH ROW
    EXECUTE PROCEDURE composite_delupsert_row()
;

Done.

Note: The role that owns the trigger needs all relevant privileges on the 'static table'!

The trigger will catch any UPDATE (and INSERT & DELETE, but will currently simply ignore them; add the respective functionality yourself if you need) statements on thecomposite view and redirects the data to <static_table> only; you need to refer each relevant column in the SET command, all not mentioned columns will stay as they were, and the NEW.geom value, even if changed, will be dropped silently if not handled elsewhere.

Additionally, the name of the operating user, the operation type, the timestamp and the (old) id of the changed row will be added to the _composite_log table.

Now you can add composite as your first groups working layer, and they can alter any attribute that is present in the 'static table' (and is mentioned in the SET column list) via QGIS Edit functionality. The geometry is 'permanently' joined, cannot be updated by the first group (edits to it will be ignored), but will reflect all changes made to it by the second group (in QGIS as soon as the canvas is updated, e.g. via zoom, pan, ...).


This is a very basic implementation, and if you are new to this the whole thing gets very large pretty soon. Take your time with it to see what's happening before implementing it in production. You can find some more basic examples (including views) in the docs, starting with 'Example 43.5'.

geozelot
  • 30,050
  • 4
  • 32
  • 56