# Database Triggers

import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components';

Use database triggers to automatically start pgflow workflows when data changes, enabling event-driven architecture.

<Aside type="note">
For complete PostgreSQL trigger documentation including trigger timing, events, and advanced patterns, see the [PostgreSQL Trigger Documentation](https://www.postgresql.org/docs/current/sql-createtrigger.html).
</Aside>

## Basic Pattern

The recommended pattern uses `FOR EACH STATEMENT` triggers with bulk processing:

```sql
-- Trigger function
CREATE OR REPLACE FUNCTION trigger_article_processing()
RETURNS TRIGGER AS $$
BEGIN
  -- Start one workflow per newly inserted article
  -- PERFORM FROM executes once per row, discards results
  PERFORM pgflow.start_flow(
    flow_slug => 'process_article',
    input => jsonb_build_object(
      'articleId', new_articles.id,
      'url', new_articles.url,
      'site', new_articles.site
    )
  )
  FROM new_articles;

  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- Trigger fires once per INSERT statement
CREATE TRIGGER on_new_articles
  AFTER INSERT ON articles
  REFERENCING NEW TABLE AS new_articles
  FOR EACH STATEMENT
  EXECUTE FUNCTION trigger_article_processing();
```

<Aside type="tip">
**Why FOR EACH STATEMENT?**

This pattern is more efficient than `FOR EACH ROW` because:
- Trigger function runs once per SQL statement, not once per row
- Can process multiple rows in a single function call
- Avoids overhead of function invocation per row
- Better performance for bulk inserts
</Aside>

## Reacting to Status Changes

Trigger workflows when order status changes to "shipped":

```sql
CREATE OR REPLACE FUNCTION on_order_shipped()
RETURNS TRIGGER AS $$
BEGIN
  -- Only process orders that just changed to shipped status
  PERFORM pgflow.start_flow(
    flow_slug => 'send_shipping_notification',
    input => jsonb_build_object(
      'orderId', updated_orders.id,
      'trackingNumber', updated_orders.tracking_number
    )
  )
  FROM new_orders AS updated_orders
  JOIN old_orders ON old_orders.id = updated_orders.id
  WHERE updated_orders.status = 'shipped'
    AND old_orders.status != 'shipped';

  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER on_order_status_change
  AFTER UPDATE OF status ON orders
  REFERENCING NEW TABLE AS new_orders OLD TABLE AS old_orders
  FOR EACH STATEMENT
  EXECUTE FUNCTION on_order_shipped();
```

<Aside type="caution">
**Avoiding Infinite Loops**

Triggers that modify the same table they're attached to can cause infinite loops. Design your workflows to update status in separate tables or use conditional logic to prevent re-triggering.
</Aside>

## Learn More

<CardGrid>
  <LinkCard
    title="Schedule Flows with pg_cron"
    href="/build/starting-flows/pg-cron/"
    description="Run workflows on a recurring schedule"
  />
</CardGrid>