Reputation: 15318
I realize there are is no TRIGGER ON SELECT in Postgres. Given a table like so
CREATE TABLE t (
a INTEGER PRIMARY KEY,
b TEXT,
entered_by INTEGER,
qry_count INTEGER
);
I want to increment "qry_count" for every SELECT for every "entered_by", essentially keeping track of how many times any record is queried for every "enterer". For example,
SELECT * a, b FROM t WHERE <condition>;
might return "n" rows entered by different enterers. For each enterer, I want to qry_count++. Pseudocode ahead
FOR EVERY entered_by IN SELECT
UPDATE t
SET qry_count = qry_count + 1
WHERE entered_by = <entered_by>
I could do this most easily in my application, but I am wondering if doing this in the database itself might be best. I found an example of where I think I want to go, but it is for PL/SQL. What is the best way to accomplish this with Pg?
Update: In Perl I would do it like so
$sth_sel = $dbh->prepare( .. complicated SELECT includes "entered_by" ..);
$sth_upd = $dbh->prepare("UPDATE t SET qry_count = qry_count + 1 WHERE entered_by = ?");
$sth_sel->execute( .. bind params ..);
while (my $r = $sth_sel->fetchrow_arrayref) {
my $entered_by = $r->[ 7 ]; # or whatever
$sth_upd->execute($entered_by);
.. do other things with $sth_sel, perhaps build a JSON obj to return ..
}
That does seem the easiest, but it would be nice to build this functionality right as a core part of the data schema.
UPDATE2: Most examples (including suggestions below) rely on creating a PL/PgSQL function. The problem with this approach is that my query is hard-coded in the function. Even if it can take input params, it is still a pre-declared query. This means I have to create a separate function for each query. In reality, in my application I construct the queries dynamically based on what is requested by the user (via the web). The requested columns can change, the supplied params can change. I think I am looking for a SQL analogous to the Perl pseudocode above without a predeclared function (SQL pseudocode below)
BEGIN
FOR row IN
SELECT to my hearts content
FROM whatever tables I want JOINed horrendously
WHERE any arbitrary param
LOOP
eb := row[entered_by]
UPDATE t SET qry_count = qry_count + 1 WHERE entered_by = eb
RETURN NEXT row;
END LOOP;
RETURN;
END
Hope this makes my objective clearer.
Upvotes: 2
Views: 17542
Reputation: 4965
PostgreSQL UPDATE features FROM clause that provides data driven updates. Example:
UPDATE table1 t1
SET blah = t2.c1
FROM table2 t2
WHERE t1.id = t2.t1id
from_list ::= A list of table expressions, allowing columns from other tables to appear in the WHERE condition and the update expressions. This is similar to the list of tables that can be specified in the FROM Clause of a SELECT statement. Note that the target table must not appear in the from_list, unless you intend a self-join (in which case it must appear with an alias in the from_list).
However for your particular case simple update with WHERE ... IN ...
will do the trick:
UPDATE t
SET qry_count = qry_count + 1
WHERE entered_by IN ( SELECT entered_by FROM ... WHERE [your condition] )
Upvotes: 1
Reputation: 125544
Notice the default value for the qry_count column:
CREATE TABLE t (
a INTEGER PRIMARY KEY,
b TEXT,
entered_by INTEGER,
qry_count INTEGER default 0
);
create function select_and_update(parameter text)
returns setof t as $$
update t
set qry_count = qry_count + 1
from (
select a
from t
where b = $1
) s
where t.a = s.a
;
select *
from t
where b = $1
;
$$ language sql;
Now query the table using the above function:
select * from select_and_update('a');
Update according to comment:
You can build it dynamically and in instead of a function just wrap the sql code, whatever it is, in a transaction. No need for cursors.
begin;
update t
set qry_count = qry_count + 1
from (
select a
from t
where b = 'a'
) s
where t.a = s.a
;
select *
from t
where b = 'a'
;
commit;
Upvotes: 2
Reputation: 127556
Instead of an SELECT query, you could use an UPDATE using RETURNING:
UPDATE t
SET
qry_count = qry_count + 1
WHERE
entered_by = <entered_by>
RETURNING a, b;
If you want to update the counter and join the result to another table, you can use a Common Table Expression. CTE's using UPDATE's are available as of version 9.1.
WITH cte AS (
UPDATE t
SET
qry_count = qry_count + 1
WHERE
entered_by = <entered_by>
RETURNING a, b
)
SELECT
*
FROM cte
JOIN other_table ON <..> = <..>;
Upvotes: 2