简体   繁体   中英

Using NEW and COPY in a Postgres Trigger

I'm trying to copy the last inserted row from a table into a csv file using a trigger.

CREATE TRIGGER new_tbc_order
  AFTER INSERT
  ON trig_test
  FOR EACH ROW
  EXECUTE PROCEDURE write_last_tbc_order();



CREATE OR REPLACE FUNCTION write_last_tbc_order()
RETURNS TRIGGER 
LANGUAGE plpgsql
as $$
BEGIN
    EXECUTE 'COPY (
             select i.id, i.paid, i.no_items FROM (SELECT NEW.*) AS i 
         ) TO ''/Users/fred/Desktop/last_tbc_order.csv'' csv;' USING NEW;
    RETURN NEW;
END; $$

I've tried this in various incarnations, with or without EXECUTE but I'm still getting the error.

ERROR:  missing FROM-clause entry for table "new"
LINE 1: ...opy (select i.id, i.paid, i.no_items FROM (SELECT NEW.*) AS ...
                                                         ^

Just cannot get it to access the NEW data.

Where am I going wrong?

The only way I could get this to work is something like this:

CREATE OR REPLACE FUNCTION write_last_tbc_order()
RETURNS TRIGGER 
LANGUAGE plpgsql
as $$
BEGIN
    EXECUTE 'COPY (
             select id, paid, no_items FROM trig_test WHERE id = ' || NEW.id ||  
         ') TO ''/Users/fred/Desktop/last_tbc_order.csv'' csv;';
    RETURN NEW;
END; $$

COPY does not seem to 'see' the NEW record. Be aware that the above will fail if the Postgres server does not have permissions on /Users/fred/Desktop/ as COPY runs as the server user. Personally I think a better solution is to write to a audit table and periodically harvest the records from there.

Adrian's answer inspired me to experiment more with the NEW record where it was actually available. The actual answer, as usual, turned out to be simple-ish:

CREATE OR REPLACE FUNCTION write_last_tbc_order()
RETURNS TRIGGER 
AS 
$$
BEGIN
    EXECUTE 'copy (select '''||NEW.id||''','''||NEW.paid||''','''||NEW.no_items||''') 
to ''/Users/shaun/Desktop/last_tbc_order.csv'' csv;' USING NEW;
    RETURN NEW;
END; 
$$
LANGUAGE plpgsql;

Thus keeping the NEW record outside of the copy statement and building it dynamically before the execute.

This still required the USING NEW part at the end to make it work correctly. Also required all the quoting.

UPDATE:

The above code does require almost everything to be running as a Postgres Superuser, (as mentioned by several commenters), which is not ideal.

To get around this, you can create a PlPython function as follows:

CREATE OR REPLACE FUNCTION write_last_tbc_order() 
RETURNS TRIGGER AS
'
import os
row = str(TD["new"]["id"]) + "," + TD["new"]["paid"] + "," + str(TD["new"]["noitems"])
path = "/tmp/db-data/last_order.csv";
with open(path,"w") as o:
    os.chmod(path, 0o644)
    o.write(row)
return None
'
LANGUAGE 'plpythonu';

This function must be created as a Superuser, but it can be used in triggers created by standard users, and those triggers also work when inserts are fired by standard users.

The place to where the file is written must have write permission for the postgres user in every part of the path, so I created a subdirectory of /tmp with 777 permissions.

Also needed to add in the os chmod 644 statement to make the file readable by other users on the system. Without this, the file is created with permissions of 600.

FURTHER NOTES:

In the end Apache doesn't like you to setup a virtual directory inside the /tmp directory so in the end had to create another called /tmp-wh specifically for this purpose.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM