Many things that can be done using triggers can also be
    implemented using the PostgreSQL
    rule system. What currently cannot be implemented by
    rules are some kinds of constraints. It is possible,
    to place a qualified rule that rewrites a query to NOTHING
    if the value of a column does not appear in another table.
    But then the data is silently thrown away and that's
    not a good idea. If checks for valid values are required,
    and in the case of an invalid value an error message should
    be generated, it must be done by a trigger for now.
    On the other hand a trigger that is fired on INSERT 
    on a view can do the same as a rule, put the data somewhere
    else and suppress the insert in the view. But it cannot
    do the same thing on UPDATE or DELETE, because there is
    no real data in the view relation that could be scanned
    and thus the trigger would never get called. Only a rule
    will help.
    For the things that can be implemented by both,
    it depends on the usage of the database, which is the best.
    A trigger is fired for any row affected once. A rule manipulates
    the parse tree or generates an additional one. So if many
    rows are affected in one statement, a rule issuing one extra
    query would usually do a better job than a trigger that is
    called for any single row and must execute his operations
    this many times.
    For example: There are two tables
CREATE TABLE computer (
    hostname        text,    -- indexed
    manufacturer    text     -- indexed
);
CREATE TABLE software (
    software        text,    -- indexed
    hostname        text     -- indexed
);
    Both tables have many
    thousands of rows and the index on hostname is unique.
    The hostname column contains the full qualified domain
    name of the computer. The rule/trigger should constraint
    delete rows from software that reference the deleted host.
    Since the trigger is called for each individual row
    deleted from computer, it can use the statement
DELETE FROM software WHERE hostname = $1;
    in a prepared and saved plan and pass the hostname in
    the parameter. The rule would be written as
CREATE RULE computer_del AS ON DELETE TO computer
    DO DELETE FROM software WHERE hostname = OLD.hostname;
    Now we look at different types of deletes. In the case of a 
    
DELETE FROM computer WHERE hostname = 'mypc.local.net';
    the table computer is scanned by index (fast) and the
    query issued by the trigger would also be an index scan (fast too).
    The extra query from the rule would be a
DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
                       AND software.hostname = computer.hostname;
    Since there are appropriate indexes setup, the planner
    will create a plan of
Nestloop
  ->  Index Scan using comp_hostidx on computer
  ->  Index Scan using soft_hostidx on software
    So there would be not that much difference in speed between
    the trigger and the rule implementation. With the next delete
    we want to get rid of all the 2000 computers where the hostname starts
    with 'old'. There are two possible queries to do that. One is
DELETE FROM computer WHERE hostname >= 'old'
                       AND hostname <  'ole'
    Where the plan for the rule query will be a 
Hash Join
  ->  Seq Scan on software
  ->  Hash
    ->  Index Scan using comp_hostidx on computer
    The other possible query is a
DELETE FROM computer WHERE hostname ~ '^old';
    with the execution plan
Nestloop
  ->  Index Scan using comp_hostidx on computer
  ->  Index Scan using soft_hostidx on software
    This shows, that the planner does not realize that the
    qualification for the hostname on computer could also be
    used for an index scan on software when there are
    multiple qualification expressions combined with AND, what
    he does in the regexp version of the query. The trigger will
    get invoked once for any of the 2000 old computers that
    have to be deleted and that will result in one index scan
    over computer and 2000 index scans for the software. The
    rule implementation will do it with two queries over indexes.
    And it depends on the overall size of the software table if
    the rule will still be faster in the sequential scan situation. 2000
    query executions over the SPI manager take some time, even
    if all the index blocks to look them up will soon appear in
    the cache.
    The last query we look at is a
DELETE FROM computer WHERE manufacurer = 'bim';
    Again this could result in many rows to be deleted from
    computer. So the trigger will again fire many queries into
    the executor. But the rule plan will again be the nested loop over
    two index scans. Only using another index on computer:
Nestloop
  ->  Index Scan using comp_manufidx on computer
  ->  Index Scan using soft_hostidx on software
    resulting from the rules query
DELETE FROM software WHERE computer.manufacurer = 'bim'
                       AND software.hostname = computer.hostname;
    In any of these cases, the extra queries from the rule system will be
    more or less independent from the number of affected rows
    in a query. 
    Another situation is cases on UPDATE where it depends on the
    change of an attribute if an action should be performed or
    not. In PostgreSQL version 6.4, the
    attribute specification for rule events is disabled (it will have
    its comeback latest in 6.5, maybe earlier 
    - stay tuned). So for now the only way to
    create a rule as in the shoelace_log example is to do it with
    a rule qualification. That results in an extra query that is
    performed always, even if the attribute of interest cannot
    change at all because it does not appear in the target list
    of the initial query. When this is enabled again, it will be
    one more advantage of rules over triggers. Optimization of
    a trigger must fail by definition in this case, because the
    fact that its actions will only be done when a specific attribute
    is updated is hidden in its functionality. The definition of
    a trigger only allows to specify it on row level, so whenever a
    row is touched, the trigger must be called to make its
    decision. The rule system will know it by looking up the
    target list and will suppress the additional query completely
    if the attribute isn't touched. So the rule, qualified or not,
    will only do its scans if there ever could be something to do.
    Rules will only be significantly slower than triggers if
    their actions result in large and bad qualified joins, a situation
    where the planner fails. They are a big hammer.
    Using a big hammer without caution can cause big damage. But
    used with the right touch, they can hit any nail on the head.