RECIPE GALLERY

24 ready-made Watch This alert recipes.

By Chris Davidson, founder of yForest · Updated September 26, 2026

Real SQL, correct dialect, the watch type and threshold already picked. Copy one, adjust the table names, done.

24 ready-made Watch This recipes, six per warehouse. Each one is real SQL you can paste straight into a new Watch, with the type, condition, and frequency already picked. Copy the query, adjust the table and threshold to match your own schema, and set it up in a couple of minutes.

Freshness lag.

Snowflake Freshness lag

Orders table gone stale

Your loader silently stopped, or a job upstream is failing, and nobody noticed because the table still has old rows in it.

SELECT DATEDIFF('minute', MAX(loaded_at), CURRENT_TIMESTAMP()) AS minutes_since_last_load
FROM ANALYTICS.RAW.ORDERS;
Watch typeA value
ConditionGreater than 90
Frequency30m
BigQuery Freshness lag

Events stream stopped landing

A streaming or batch load into your events table has gone quiet longer than it should.

SELECT TIMESTAMP_DIFF(CURRENT_TIMESTAMP(), MAX(loaded_at), MINUTE) AS minutes_since_last_load
FROM `project.raw.events`;
Watch typeA value
ConditionGreater than 60
Frequency15m
PostgreSQL Freshness lag

Nightly sync didn't run

A cron job or ETL step that's supposed to touch this table nightly never fired.

SELECT EXTRACT(EPOCH FROM (NOW() - MAX(loaded_at))) / 60 AS minutes_since_last_load
FROM raw.orders;
Watch typeA value
ConditionGreater than 1440
Frequency1h
Databricks Freshness lag

Bronze table not refreshing

An upstream ingestion job into a bronze/raw table has silently stopped writing new rows.

SELECT (unix_timestamp(current_timestamp()) - unix_timestamp(max(loaded_at))) / 60 AS minutes_since_last_load
FROM main.raw.orders;
Watch typeA value
ConditionGreater than 120
Frequency30m

Row count drop.

Snowflake Row count drop

Daily order volume collapsed

Today's row count for a table that should grow steadily has dropped hard, often a sign a filter or WHERE clause upstream broke.

SELECT COUNT(*) AS row_count
FROM ANALYTICS.PUBLIC.ORDERS
WHERE order_date = CURRENT_DATE();
Watch typeRow count
ConditionDecreased by 20%
Frequency1h
BigQuery Row count drop

Partition came in empty

A partitioned table's newest partition landed with far fewer rows than the partition before it.

SELECT COUNT(*) AS row_count
FROM `project.dataset.shipments`
WHERE ship_date = CURRENT_DATE();
Watch typeRow count
ConditionDecreased by 30%
Frequency4h
PostgreSQL Row count drop

Signups table stopped growing

A table that normally gains rows every check now shows the same or a shrinking count, which usually means the write path broke, not that growth stopped.

SELECT COUNT(*) AS row_count
FROM public.signups
WHERE created_at >= NOW() - INTERVAL '1 day';
Watch typeRow count
ConditionDecreased by 15%
Frequency1d
Databricks Row count drop

Silver table row count fell

A transform step feeding a silver table produced far fewer output rows than its usual run, suggesting a broken join or filter.

SELECT COUNT(*) AS row_count
FROM main.silver.orders_clean
WHERE process_date = current_date();
Watch typeRow count
ConditionDecreased by 25%
Frequency4h

Duplicate keys.

Snowflake Duplicate keys

Duplicate order IDs after a sync

A merge or upsert step didn't dedupe correctly, and the same primary key now has more than one row.

SELECT COUNT(*) AS duplicate_key_count
FROM (
  SELECT order_id
  FROM ANALYTICS.PUBLIC.ORDERS
  GROUP BY order_id
  HAVING COUNT(*) > 1
);
Watch typeA value
ConditionGreater than 0
Frequency1h
BigQuery Duplicate keys

Duplicate customer records

A CDC or full-refresh load appended instead of replacing, leaving duplicate customer_id rows behind.

SELECT COUNT(*) AS duplicate_key_count
FROM (
  SELECT customer_id
  FROM `project.dataset.customers`
  GROUP BY customer_id
  HAVING COUNT(*) > 1
);
Watch typeA value
ConditionGreater than 0
Frequency4h
PostgreSQL Duplicate keys

Duplicate rows in a dimension table

A dimension table that should have one row per natural key picked up duplicates from a bad backfill.

SELECT COUNT(*) AS duplicate_key_count
FROM (
  SELECT product_sku
  FROM public.products
  GROUP BY product_sku
  HAVING COUNT(*) > 1
) d;
Watch typeA value
ConditionGreater than 0
Frequency1d
Databricks Duplicate keys

Duplicate event IDs post-merge

A MERGE INTO on event_id didn't fully dedupe, and the same event now appears more than once.

SELECT COUNT(*) AS duplicate_key_count
FROM (
  SELECT event_id
  FROM main.gold.events
  GROUP BY event_id
  HAVING COUNT(*) > 1
);
Watch typeA value
ConditionGreater than 0
Frequency1h

Null spike.

Snowflake Null spike

Customer emails going null

An upstream form or integration started dropping a required field, and a growing share of new rows have it null.

SELECT ROUND(100.0 * SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS pct_null
FROM ANALYTICS.PUBLIC.CUSTOMERS
WHERE created_at >= DATEADD('hour', -24, CURRENT_TIMESTAMP());
Watch typeA value
ConditionGreater than 5
Frequency4h
BigQuery Null spike

Attribution field null spike

A tracking parameter that normally populates on nearly every row started coming through empty.

SELECT ROUND(100.0 * SUM(CASE WHEN utm_source IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS pct_null
FROM `project.dataset.sessions`
WHERE event_date = CURRENT_DATE();
Watch typeA value
ConditionGreater than 10
Frequency1h
PostgreSQL Null spike

Shipping address nulls climbing

A checkout flow change stopped requiring a field your fulfillment process depends on.

SELECT ROUND(100.0 * SUM(CASE WHEN shipping_address IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS pct_null
FROM public.orders
WHERE created_at >= NOW() - INTERVAL '1 day';
Watch typeA value
ConditionGreater than 3
Frequency1d
Databricks Null spike

Device ID null rate rising

A mobile SDK update or app version stopped sending a device identifier your joins rely on.

SELECT round(100.0 * sum(case when device_id is null then 1 else 0 end) / count(*), 2) AS pct_null
FROM main.silver.app_events
WHERE event_date = current_date();
Watch typeA value
ConditionGreater than 8
Frequency4h

Failed orders spike.

Snowflake Failed orders spike

Payment failures spiking

A payment processor or webhook is degraded, and failed orders are piling up faster than usual.

SELECT COUNT(*) AS failed_orders
FROM ANALYTICS.PUBLIC.ORDERS
WHERE status = 'failed'
  AND created_at >= DATEADD('hour', -1, CURRENT_TIMESTAMP());
Watch typeA value
ConditionIncreased by 20
Frequency15m
PostgreSQL Failed orders spike

Refund rate jumping

A pricing bug or fulfillment problem is driving refunds up faster than the recent baseline.

SELECT COUNT(*) AS refunded_orders
FROM public.orders
WHERE status = 'refunded'
  AND created_at >= NOW() - INTERVAL '1 hour';
Watch typeA value
ConditionIncreased by 10
Frequency30m
Databricks Failed orders spike

Batch job failure count rising

A pipeline stage is failing more often than usual, tracked from its own run-log table.

SELECT count(*) AS failed_jobs
FROM main.ops.job_runs
WHERE status = 'FAILED'
  AND run_ts >= current_timestamp() - INTERVAL 1 HOURS;
Watch typeA value
ConditionIncreased by 5
Frequency30m

Revenue threshold.

Snowflake Revenue threshold

Daily revenue below floor

A slow sales day, or more often, a tracking or attribution bug undercounting revenue that already happened.

SELECT SUM(amount) AS daily_revenue
FROM ANALYTICS.PUBLIC.ORDERS
WHERE order_date = CURRENT_DATE()
  AND status = 'completed';
Watch typeA value
ConditionLess than 5000
Frequency1d
BigQuery Revenue threshold

Hourly revenue pace check

Revenue for the current hour is running well below the same hour on a normal day, an early warning before a full-day miss.

SELECT SUM(amount) AS hourly_revenue
FROM `project.dataset.orders`
WHERE TIMESTAMP_TRUNC(order_ts, HOUR) = TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), HOUR)
  AND status = 'completed';
Watch typeA value
ConditionLess than 500
Frequency1h
PostgreSQL Revenue threshold

Subscription MRR dropped

Active subscription revenue fell below a floor you set, worth checking before it shows up in a monthly report.

SELECT SUM(monthly_amount) AS active_mrr
FROM public.subscriptions
WHERE status = 'active';
Watch typeA value
ConditionLess than 50000
Frequency1d
Databricks Revenue threshold

Gold revenue table below target

Your aggregated daily revenue table, built by a transform job, came in under the floor you expect on a normal day.

SELECT sum(amount) AS daily_revenue
FROM main.gold.daily_revenue
WHERE revenue_date = current_date();
Watch typeA value
ConditionLess than 8000
Frequency1d

Why a recipe beats a description

Most write-ups of an alerting feature describe what it can do in the abstract: "watch a value, get notified when it crosses a threshold." That's true and mostly useless until you're staring at a blank Watch form wondering what query to actually type. Every recipe above is copy-paste-ready SQL for a real, specific problem, in the dialect you're actually running, with the watch type, condition, and check frequency already filled in from what's worked for that kind of check in practice.

Reading a recipe's settings

Watch This has three watch types: Row count watches how many rows a query returns over time, A value watches a single number your query returns (a sum, a percentage, a count), and Any change fires whenever the result differs from the last check at all, useful for a query that should return the exact same thing every time. Conditions are Changed, Greater than, Less than, Equals, Increased by, and Decreased by, compared either against a fixed threshold or against the previous check's result depending on the condition. Frequency options are 5m, 15m, 30m, 1h, 4h, and 1d; the recipes above default to a frequency that matches how fast the underlying problem usually needs catching; a payment failure spike is checked every 15 minutes, a monthly revenue floor once a day.

Adjusting a recipe for your own schema

Every table name above is realistic but made up: swap ANALYTICS.PUBLIC.ORDERS, project.dataset.orders, public.orders, or main.gold.daily_revenue for your own database, schema, and table names, and check that the column names (status, created_at, order_date, amount) match what your table actually calls those fields. Thresholds are starting points, not universal constants; a duplicate-key count watch is safe to copy verbatim since zero duplicates is zero duplicates everywhere, but a revenue floor or a null-percentage threshold needs tuning to your own normal range before it's useful instead of noisy.

A note on false positives

A freshness check with too tight a threshold fires every time a job runs a few minutes late for an ordinary reason, and a team that gets paged for nothing three times a week stops trusting the alert within a month. Start thresholds looser than your instinct says, watch a week of real check history, then tighten once you know what normal actually looks like for that specific table, not what you assumed it would look like before you had data.

Sources

Free tools
QueryFlow Studio $9.99/mo · $99/yr
QueryFlow Pipelines $29.99/mo · $199.99/yr

Frequently asked

Can I use these queries as-is?

The SQL is realistic and correct for its dialect, but table and column names are illustrative. Swap in your own before saving the Watch.

Which watch type should I pick for a simple existence check, like zero duplicates?

"A value" watching a COUNT(*) with condition "Greater than 0" is usually the right shape: the query returns one number, and any non-zero result means something's wrong.

Why do some recipes check every 15 minutes and others once a day?

Frequency matches how fast the underlying problem needs catching. A payment failure spike is worth knowing about within 15 minutes; a monthly revenue floor doesn't need checking more than once a day.

Do these recipes work outside the four listed warehouses?

The SQL syntax is dialect-specific, but the underlying pattern (freshness, row count, duplicates, nulls, spikes, thresholds) applies to any connector Watch This supports; you'd adapt the syntax to match.

What happens when a Watch condition is met?

Watch This sends to whatever destination you've configured for that Watch, including Slack and Teams, per QueryFlow's own destination options.

Pick a recipe. Set it up in two minutes.

Watch This ships in every QueryFlow plan, with Slack and Teams destinations.

Start 14-day free trial

No credit card. 14 days. Cancel in one click.