I have a tsv column on my campaign
table and I also added a trigger to update it every time the row changes. Tsv is currently constructed from a few table columns. The table also has multiple tags
via many-to-many relation. My question is, is it possible to write such a trigger that would also pull in all the tags on row INSERT and update the tsv column when tags
are added or removed from a campaign
? I already know how to construct a concat document in a query (eg: Full text search on multiple joined tables). I imagine this could work if I wrote another trigger on the many-to-many intermediate table that would update campaigns
tsv or something like that.
Edit:
This is what I came up with so far and it seems to work but I would like to get an opinion about the whole thing. I have triggers for campaign INSERT, campaign column name
UPDATE, INSERT/UPDATE on campaign_tags and one for campaign_tags DELETE. Each one updates the campaign.tsv
but in slightly different way.
ALTER TABLE campaigns ADD COLUMN tsv tsvector;
CREATE INDEX campaigns_tsv_idx ON newsletters USING GIN (tsv);
campaigns_tsv_update
CREATE OR REPLACE FUNCTION campaigns_tsv_update() RETURNS trigger AS $$
begin
SELECT setweight(to_tsvector(unaccent(coalesce(name, ' '))), 'A') ||
setweight(to_tsvector(coalesce((string_agg(tags.value, ' ')), '')), 'D')
INTO new.tsv
FROM campaigns
LEFT JOIN campaign_tags ON campaign_tags.campaign_id = campaigns.id
LEFT JOIN tags ON tags.id = campaign_tags.campaign_tags_id
WHERE campaigns.id=new.id
GROUP BY campaigns.id;
RAISE NOTICE 'campaigns_tsv_trigger new is %', new;
return new;
end
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS campaigns_tsv_trigger_update ON campaigns;
CREATE TRIGGER campaigns_tsv_trigger_update BEFORE UPDATE OF name
ON campaigns FOR EACH ROW EXECUTE PROCEDURE campaigns_tsv_update();
campaigns_tsv_insert
CREATE OR REPLACE FUNCTION campaigns_tsv_insert() RETURNS trigger AS $$
begin
new.tsv := setweight(to_tsvector(unaccent(coalesce(new.name, ' '))), 'A');
return new;
end
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS campaigns_tsv_trigger_insert ON campaigns;
CREATE TRIGGER campaigns_tsv_trigger_insert BEFORE INSERT
ON campaigns FOR EACH ROW EXECUTE PROCEDURE campaigns_tsv_insert();
campaigns_tags_tsv_update
CREATE OR REPLACE FUNCTION campaign_tags_tsv_update() RETURNS trigger AS $$
begin
UPDATE campaigns SET tsv=subquery.document FROM (
SELECT setweight(to_tsvector(unaccent(coalesce(name, ' '))), 'A') ||
setweight(to_tsvector(coalesce((string_agg(tags.value, ' ')), '')), 'D') AS document
FROM campaigns
LEFT JOIN campaign_tags ON campaign_tags.campaign_id = campaigns.id
LEFT JOIN tags ON tags.id = campaign_tags.campaign_tags_id
WHERE campaigns.id=new.campaign_id
GROUP BY campaigns.id
) AS subquery WHERE campaigns.id=new.campaign_id;
RAISE NOTICE 'campaign_tags_tsv_update new is %', new;
return new;
end;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS campaigns_tags_tsv_trigger ON campaign_tags;
CREATE TRIGGER campaigns_tags_tsv_trigger AFTER INSERT OR UPDATE
ON campaign_tags FOR EACH ROW EXECUTE PROCEDURE campaign_tags_tsv_update();
campaigns_tags_tsv_update_del
CREATE OR REPLACE FUNCTION campaign_tags_tsv_update_del() RETURNS trigger AS $$
begin
UPDATE campaigns SET tsv=subquery.document FROM (
SELECT setweight(to_tsvector(unaccent(coalesce(name, ' '))), 'A') ||
setweight(to_tsvector(coalesce((string_agg(tags.value, ' ')), '')), 'D') AS document
FROM campaigns
LEFT JOIN campaign_tags ON campaign_tags.campaign_id = campaigns.id
LEFT JOIN tags ON tags.id = campaign_tags.campaign_tags_id
WHERE campaigns.id=old.campaign_id
GROUP BY campaigns.id
) AS subquery WHERE campaigns.id=old.campaign_id;
RAISE NOTICE 'campaign_tags_tsv_update_del old is %', old;
return old;
end;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS campaigns_tags_tsv_trigger_old ON campaign_tags;
CREATE TRIGGER campaigns_tags_tsv_trigger_old AFTER DELETE
ON campaign_tags FOR EACH ROW EXECUTE PROCEDURE campaign_tags_tsv_update_del();
Best Answer
Incoming.. opinion. Don't use triggers for this. It's wayyy to complex. You have three piece of functionality that will make life easier.
REFRESH MATERIALIZED VIEW mymatview;
to update it. How often do you have to reflect tag updates anyway? A materialized view can itself use function indexes, or any other kind of index. For instance, you can create a Materialized View of the tsvectors of multiple columns from multiple tables, and then write a function index that uses native functionality over those multiple tsvector columns. Then when you want, periodically runREFRESH MATERIALIZED VIEW mymatview
.You may also want to restructure how you're doing tags. In PG, you can easily represent them on the table itself with an array type which is easily indexed.
Update
This code just strikes me as awkward. We call this a
DEFAULT
. Don't implement that as a trigger.Instead