Custom SQL Monitor

Run any SQL query on a schedule and alert when the result crosses a threshold.

Overview

Run any SQL query on a schedule and alert when the result crosses a threshold. The custom SQL monitor is your escape hatch when none of the purpose-built types (metric, validation, comparison) can express the check you need.

๐Ÿ“˜

Reference scope

This page covers MaC YAML configuration. For UI-based creation and SQL templates, see Custom SQL Monitors.

Your SQL must return exactly one row with one numeric column. Monte Carlo compares that value to the operator and threshold in your alert_conditions and opens an incident when the condition is met.

Quick Start

montecarlo:
  custom_sql:
    - name: orphan_orders
      description: Alert when orphan orders exist
      warehouse: my-warehouse-name
      sql: |
        SELECT COUNT(*) FROM orders
        WHERE customer_id NOT IN (SELECT id FROM customers)
      schedule:
        type: fixed
        interval_minutes: 720
      alert_conditions:
        - type: threshold
          operator: GT
          threshold_value: 0
      domains:
        - my-domain

Create interactively with create_or_update_sql_monitor(dry_run=True) via the Monte Carlo MCP server.

๐Ÿ“˜

MCP vs MaC naming

The MCP tool parameter is alert_condition (singular object), while MaC YAML uses alert_conditions (plural array). The tool wraps it automatically. The warehouse parameter takes a UUID โ€” use get_warehouses to resolve a name.

Configuration

sql โ€” SQL query to execute

string ยท required

Must return exactly one row with one numeric column. Avoid trailing semicolons and SQL comments (--, /* */) โ€” some warehouses reject them when executed programmatically. Wrap NULLable results with COALESCE.

sql: |
  SELECT COUNT(*) FROM orders
  WHERE customer_id NOT IN (SELECT id FROM customers)
alert_conditions โ€” threshold for alerting

array of objects (exactly one entry) ยท required

Exactly one alert condition is required (maxItems: 1). Create separate monitors for multiple thresholds.

Available operators: EQ ยท NEQ ยท GT ยท GTE ยท LT ยท LTE ยท OUTSIDE_RANGE ยท INSIDE_RANGE ยท NOOP ยท AUTO ยท AUTO_HIGH ยท AUTO_LOW

AUTO operators (AUTO, AUTO_HIGH, AUTO_LOW) are only supported with type: dynamic_threshold, not with type: threshold.

Threshold types:

  • Static (threshold) โ€” fixed numeric value via threshold_value with an explicit operator.
  • Range โ€” lower_threshold and upper_threshold with INSIDE_RANGE or OUTSIDE_RANGE.
  • Dynamic threshold (dynamic_threshold) โ€” ML anomaly detection comparing current value against a rolling baseline. Accepts AUTO/AUTO_HIGH/AUTO_LOW, or explicit operators with a threshold relative to the baseline.
  • Change (change) โ€” compares the current value against the previous run's value. Requires is_threshold_relative, baseline_agg_function, and baseline_interval_minutes.

Noise reduction: Set event_rollup_count (min 2), event_rollup_until_changed, or alert_grouping at the monitor level to suppress notifications for subsequent breaches. These fields are mutually exclusive โ€” only one can be set at a time.

๐Ÿ“˜

UI name mapping

The UI uses different names for threshold types: Automatic = dynamic_threshold, Absolute = threshold, Relative = change.

alert_conditions:
  - type: threshold
    operator: GT
    threshold_value: 0

Properties


type โ€” threshold type

enum ยท optional ยท default: threshold

Accepted values: threshold ยท dynamic_threshold ยท change ยท absolute_volume ยท growth_volume ยท noop

alert_conditions:
  - type: threshold

operator โ€” comparison operator

enum ยท required (for threshold and change types)

Accepted values: EQ ยท NEQ ยท GT ยท GTE ยท LT ยท LTE ยท OUTSIDE_RANGE ยท INSIDE_RANGE ยท NOOP ยท AUTO ยท AUTO_HIGH ยท AUTO_LOW

AUTO operators (AUTO, AUTO_HIGH, AUTO_LOW) are only supported with type: dynamic_threshold, not with type: threshold.

alert_conditions:
  - type: threshold
    operator: GT

threshold_value โ€” static threshold

number ยท required (for threshold and change types)

alert_conditions:
  - type: threshold
    operator: GT
    threshold_value: 0

lower_threshold โ€” lower bound for range operators

number ยท optional

Used with OUTSIDE_RANGE or INSIDE_RANGE operators.

alert_conditions:
  - type: threshold
    operator: OUTSIDE_RANGE
    lower_threshold: 100
    upper_threshold: 1000

upper_threshold โ€” upper bound for range operators

number ยท optional

Used with OUTSIDE_RANGE or INSIDE_RANGE operators.

alert_conditions:
  - type: threshold
    operator: INSIDE_RANGE
    lower_threshold: 0
    upper_threshold: 50

baseline_agg_function โ€” aggregation for baseline window

enum ยท required (for change and dynamic_threshold types)

Accepted values: AVG ยท MIN ยท MAX

alert_conditions:
  - type: change
    baseline_agg_function: AVG

baseline_interval_minutes โ€” lookback window in minutes

integer ยท required (for change and dynamic_threshold types)

Range: 0โ€“129600.

alert_conditions:
  - type: change
    baseline_interval_minutes: 10080

is_threshold_relative โ€” percentage vs absolute change

boolean ยท required (when type is change)

Set true for percentage change, false for absolute change.

alert_conditions:
  - type: change
    is_threshold_relative: true

threshold_sensitivity โ€” sensitivity for dynamic thresholds

enum ยท optional

Accepted values: low ยท medium ยท high

For dynamic_threshold type only.

alert_conditions:
  - type: dynamic_threshold
    threshold_sensitivity: medium

min_buffer_value โ€” minimum buffer for dynamic threshold band

number ยท optional

alert_conditions:
  - type: dynamic_threshold
    min_buffer_value: 10

max_buffer_value โ€” maximum buffer for dynamic threshold band

number ยท optional

alert_conditions:
  - type: dynamic_threshold
    max_buffer_value: 100

min_buffer_modifier_type โ€” unit for min buffer

enum ยท optional

Accepted values: METRIC ยท PERCENTAGE

alert_conditions:
  - type: dynamic_threshold
    min_buffer_modifier_type: PERCENTAGE

max_buffer_modifier_type โ€” unit for max buffer

enum ยท optional

Accepted values: METRIC ยท PERCENTAGE

alert_conditions:
  - type: dynamic_threshold
    max_buffer_modifier_type: PERCENTAGE

number_of_agg_periods โ€” aggregation periods in baseline

integer ยท optional

Range: 1โ€“1000.

alert_conditions:
  - type: dynamic_threshold
    number_of_agg_periods: 7

threshold_lookback_minutes โ€” lookback window for threshold computation

integer ยท optional

alert_conditions:
  - type: dynamic_threshold
    threshold_lookback_minutes: 10080

is_percentage_threshold โ€” evaluate the threshold as a percentage

boolean ยท optional (default: false )

Measures threshold_value as a percentage rather than a row count. With is_percentage_threshold: true, threshold_value: 5 means 5%, not 5 rows.

Requires:

  • type: threshold
  • percentage_baseline_sql
  • threshold_value of 0 or more
  • query_result_type of ROW_COUNT, or unset
alert_conditions:
  - type: threshold
    operator: GT
    threshold_value: 5
    is_percentage_threshold: true
    percentage_baseline_sql: |
      SELECT COUNT(*) FROM orders
      WHERE status = 'completed'

percentage_baseline_sql โ€” SQL counting the population the monitor evaluates

string ยท required when is_percentage_threshold is true, optional otherwise

Your sql returns only the rows that fail. The baseline sql query returns the total population those rows came from. It must return exactly one row with one numeric column.

If you want to...SetWhat happens
Alert when a percentage of rows failis_percentage_threshold: true and percentage_baseline_sqlThe baseline is used as the denominator that the percentage is measured against.
Keep alerting on a row count, and record how many rows each run evaluatedpercentage_baseline_sql onlyAlerting is unchanged. Each run records a Rows evaluated count, so a breach reads "1,000 of 20,000" rather than "1,000". See Measuring evaluated rows for more information.

Supported on type: threshold conditions only. Send an empty string to remove a stored baseline. The percentage_ prefix is historical โ€” the field isn't limited to percentage thresholds.

alert_conditions:
  - type: threshold
    operator: GT
    threshold_value: 5
    is_percentage_threshold: true
    percentage_baseline_sql: |
      SELECT COUNT(*) FROM orders
      WHERE status = 'completed'
alert_conditions:
  - type: threshold
    operator: GT
    threshold_value: 5
    percentage_baseline_sql: |
      SELECT COUNT(*) FROM orders
      WHERE status = 'completed'
๐Ÿ“˜

Recording evaluated row counts is opt-in. It's off by default and enabled per account โ€” see Measuring evaluated rows. Monitors with query_result_type: SINGLE_NUMERIC don't record row counts.

Percentage thresholds do not require this setting and work without it.

schedule โ€” when and how often to run

object ยท required

Supported modes: fixed, dynamic, manual. Crontab (interval_crontab) is supported. Both the JSON Schema and the CLI require schedule โ€” apply aborts if it is omitted.

schedule:
  type: fixed
  interval_minutes: 720

Properties


type โ€” schedule type

enum ยท optional ยท default: fixed

Accepted values: fixed ยท dynamic ยท manual

schedule:
  type: fixed

interval_minutes โ€” run interval for fixed schedules

integer ยท optional

Required when type is fixed.

schedule:
  type: fixed
  interval_minutes: 720

interval_crontab โ€” cron expressions

array of strings ยท optional

5-field cron format.

schedule:
  type: fixed
  interval_crontab:
    - "0 8 * * *"

interval_crontab_day_operator โ€” day-of-week/day-of-month combination

enum ยท optional

Accepted values: AND ยท OR

schedule:
  interval_crontab_day_operator: AND

start_time โ€” ISO 8601 start time

string ยท optional

schedule:
  start_time: "2024-01-01T08:00:00Z"

timezone โ€” IANA timezone

string ยท optional

schedule:
  timezone: America/New_York

dynamic_schedule_tables โ€” tables that trigger the monitor

array of strings ยท optional (required when type is dynamic)

Custom SQL monitors support only one table in dynamic_schedule_tables.

schedule:
  type: dynamic
  dynamic_schedule_tables:
    - analytics:public.orders

dynamic_schedule_jobs โ€” jobs that trigger the monitor

array of objects ยท optional

PropertyTypeRequiredDescription
job_typeenumyesAdfJob ยท AirflowDag ยท DatabricksJob ยท DbtJob
job_namestringyesName of the job
project_namestringyesProject containing the job
task_namestringnoSpecific task within the job
mconstringnoMCON identifier for the job
schedule:
  type: dynamic
  dynamic_schedule_jobs:
    - job_type: AirflowDag
      job_name: etl_orders
      project_name: my-airflow

min_interval_minutes โ€” minimum interval for dynamic schedules

integer ยท optional

schedule:
  type: dynamic
  min_interval_minutes: 30
description โ€” what this monitor checks

string ยท required

Displayed in the Monte Carlo UI and in incident notifications. Max 512 characters.

description: Alert when orphan orders exist
domains โ€” domain for this monitor

array of strings (exactly one entry) ยท required when creating a monitor

Required on all accounts, regardless of when the account was created. Monitors that already exist without a domain can still be updated without adding one.

Set default_domain in montecarlo.yml to avoid repeating it on every monitor.

domains:
  - my-domain
warehouse โ€” which warehouse to use

string ยท optional (yes if multiple warehouses)

Warehouse name or UUID. Overrides default_resource from montecarlo.yml.

warehouse: my-warehouse
name โ€” unique identifier within the namespace

string ยท required

Required for monitors created after Jan 29, 2024 (existing monitors keep working). Changing the name creates a new monitor and deletes the old one โ€” incident history does not transfer.

name: orphan_orders
connection_name โ€” named connection

string ยท optional

Overrides the default connection for this warehouse.

connection_name: analytics-readonly
variables โ€” template variables substituted into SQL

object ยท optional ยท default: {}

Variables are substituted into the sql field using {{ variable_name }} syntax. When variables are defined, Monte Carlo runs the SQL once per combination of variable values and evaluates each result independently. Two formats are supported: static (list of values) and runtime (object with type: runtime and default).

variables:
  schema_name:
    - analytics
    - staging
sampling_sql โ€” investigation query on breach

string ยท optional

SQL returning sample rows on breach for incident context. Called "investigation query" in the UI. Runs only on breach โ€” zero load on clean runs.

sampling_sql: |
  SELECT * FROM orders
  WHERE customer_id NOT IN (SELECT id FROM customers)
  LIMIT 100
query_result_type โ€” how to interpret the SQL result

enum ยท optional

Accepted values: ROW_COUNT ยท SINGLE_NUMERIC

ROW_COUNT evaluates the row count of the result set instead of the scalar value.

query_result_type: ROW_COUNT
event_rollup_count โ€” consecutive breaches before alerting

integer ยท optional

Minimum 2. Mutually exclusive with event_rollup_until_changed and alert_grouping.

event_rollup_count: 3
event_rollup_until_changed โ€” suppress repeat notifications

boolean ยท optional ยท default: false

Roll up breaches until the result changes. Mutually exclusive with event_rollup_count and alert_grouping.

event_rollup_until_changed: true
alert_grouping โ€” group subsequent breaches into the currently open alert

object ยท optional ยท default: no grouping

Groups subsequent breaches into the currently open alert rather than creating a new one, for as long as the alert remains open and unresolved. Mutually exclusive with event_rollup_count and event_rollup_until_changed.

mode
Accepted values: group_into_open_alert

alert_grouping:
  mode: group_into_open_alert
severity โ€” incident severity level

enum ยท optional

Accepted values: SEV-0 ยท SEV-1 ยท SEV-2 ยท SEV-3 ยท SEV-4

severity: SEV-2
priority โ€” incident priority level

enum ยท optional

Accepted values: P1 ยท P2 ยท P3 ยท P4 ยท P5

priority: P2
audiences โ€” notification channels

array of strings or mappings ยท optional ยท default: []

Audience names linking this monitor to channels defined in Notifications as Code. In exported/rendered YAML, appears as labels.

An entry can instead be a mapping that narrows an audience to specific triage priorities, so it is only notified about alerts scored at those priorities. Some audience must cover NOT_TRIAGED. See Routing alerts by triage priority.

audiences:
  - data-eng-oncall
  - platform-alerts
  - audience: oncall
    triage_priority: [HIGH]
failure_audiences โ€” notification channels for run failures

array of strings ยท optional

Separate audiences for run-failure notifications. Falls back to audiences when omitted.

failure_audiences:
  - data-eng-oncall
notify_run_failure โ€” notify when the query itself fails

boolean ยท optional

notify_run_failure: true
exception_primary_key_column โ€” column for per-key incident grouping

string ยท optional

exception_primary_key_column: order_id
timeout โ€” query timeout in seconds

integer ยท optional

timeout: 300
tags โ€” key-value pairs for organizing monitors

array of objects ยท optional ยท default: []

PropertyTypeRequiredDescription
namestringyesTag key
valuestringnoTag value
tags:
  - name: team
    value: analytics
  - name: environment
    value: production
data_quality_dimension โ€” data quality category

enum ยท optional

Accepted values: ACCURACY ยท COMPLETENESS ยท CONSISTENCY ยท TIMELINESS ยท UNIQUENESS ยท VALIDITY

data_quality_dimension: CONSISTENCY
notes โ€” internal notes

string ยท optional

Visible in the Monte Carlo UI. Not included in notifications.

notes: Owned by the analytics team. Reviewed quarterly.
is_draft โ€” create as draft without activating

boolean ยท optional ยท default: false

Creates the monitor in a paused state. Omitting this on a later update resets to false (active) due to PUT semantics โ€” always include it if you want the monitor to stay in draft.

is_draft: true
uuid โ€” update an existing monitor

string ยท optional

Include the UUID of an existing monitor to update it instead of creating a new one.

uuid: 0dae7702-0950-45c7-909c-7e183bddca19
auto_tuning โ€” override auto-apply tuning for this monitor

boolean ยท optional

Force auto-apply tuning on (true) or off (false) for this monitor, overriding the account or domain default. Omit to inherit. See the Tuning Agent.

auto_tuning: false
auto_triage โ€” override auto-triage for this monitor

boolean ยท optional

Force auto-triage on (true) or off (false) for this monitor, overriding the account or domain default. Omit to inherit. See the Triage Agent.

auto_triage: false
Deprecated fields
FieldUse instead
resourcewarehouse
domaindomains
domain_uuidsdomains
labelsaudiences
notify_rule_run_failurenotify_run_failure
comparisonsalert_conditions
๐Ÿ“˜

API-only fields

Some fields visible in the API or JSON Schema (skip_reset, fail_on_reset, metadata) are present in the schema but silently stripped during MaC YAML processing. They have no effect and should not be included.

Examples

Threshold check โ€” alert when orphan rows exist

montecarlo:
  custom_sql:
    - name: orphan_orders
      description: Detect orders referencing deleted customers
      warehouse: my-warehouse
      sql: |
        SELECT COUNT(*) FROM orders
        WHERE customer_id NOT IN (SELECT id FROM customers)
      schedule:
        type: fixed
        interval_minutes: 720
      alert_conditions:
        - type: threshold
          operator: GT
          threshold_value: 0
      priority: P2
      data_quality_dimension: CONSISTENCY
      audiences:
        - data-eng-oncall
      domains:
        - my-domain

Threshold check that also records how many rows were evaluated

Alerts when any row fails, and records the size of the population on each run โ€” so an incident reads "12 of 300,000" rather than "12".

montecarlo:
  custom_sql:
    - name: invalid_shipment_status
      description: Detect shipments with an unrecognized status
      warehouse: my-warehouse
      sql: |
        SELECT COUNT(*) FROM shipments
        WHERE status NOT IN ('pending', 'in_transit', 'delivered', 'cancelled')
      schedule:
        type: fixed
        interval_minutes: 720
      alert_conditions:
        - type: threshold
          operator: GT
          threshold_value: 0
          percentage_baseline_sql: "SELECT COUNT(*) FROM shipments"
      priority: P2
      data_quality_dimension: VALIDITY
      audiences:
        - data-eng-oncall
      domains:
        - my-domain

Requires evaluated row counts to be enabled on your account โ€” see percentage_baseline_sql. Add is_percentage_threshold: true to alert on a percentage of the population instead of an absolute count.

Percentage threshold โ€” alert when a share of rows fails

Useful when absolute counts are misleading because volume varies. 500 missing addresses matters on a slow day and not on Black Friday; a percentage holds either way.

montecarlo:
  custom_sql:
    - name: orders_missing_address
      description: Alert when more than 2% of orders have no shipping address
      warehouse: my-warehouse
      sql: |
        SELECT COUNT(*) FROM orders
        WHERE shipping_address IS NULL
          AND created_at >= CURRENT_DATE - 1
      schedule:
        type: fixed
        interval_minutes: 1440
      alert_conditions:
        - type: threshold
          operator: GT
          threshold_value: 2
          is_percentage_threshold: true
          percentage_baseline_sql: |
            SELECT COUNT(*) FROM orders
            WHERE created_at >= CURRENT_DATE - 1
      priority: P3
      data_quality_dimension: COMPLETENESS
      audiences:
        - data-eng-oncall
      domains:
        - my-domain

threshold_value: 2 means 2%, not 2 rows.

โš ๏ธ Match your filters. The baseline query runs exactly as written. Here, both queries scope to created_at >= CURRENT_DATE - 1. Leaving the date filter off the baseline would compare yesterday's failures against every order ever written.

Change detection โ€” alert when metric changes more than 20% from baseline

montecarlo:
  custom_sql:
    - name: revenue_change
      description: Alert on significant daily revenue change
      warehouse: my-warehouse
      sql: |
        SELECT COALESCE(SUM(amount), 0)
        FROM transactions
        WHERE created_at >= CURRENT_DATE - 1
          AND created_at < CURRENT_DATE
      schedule:
        type: fixed
        interval_minutes: 1440
      alert_conditions:
        - type: change
          operator: GT
          threshold_value: 0.2
          is_threshold_relative: true
          baseline_agg_function: AVG
          baseline_interval_minutes: 10080
      domains:
        - my-domain

Anomaly detection โ€” alert when result deviates from learned baseline

montecarlo:
  custom_sql:
    - name: daily_revenue_anomaly
      description: Detect anomalous daily revenue using ML
      warehouse: my-warehouse
      sql: |
        SELECT COALESCE(SUM(amount), 0)
        FROM transactions
        WHERE created_at >= CURRENT_DATE - 1
          AND created_at < CURRENT_DATE
      schedule:
        type: fixed
        interval_minutes: 1440
      alert_conditions:
        - type: dynamic_threshold
          operator: AUTO
          baseline_agg_function: AVG
          baseline_interval_minutes: 10080
          threshold_sensitivity: medium
      domains:
        - my-domain

Troubleshooting

Alert conditions and thresholds

  • AUTO operators not supported for threshold type. AUTO operators (AUTO, AUTO_HIGH, AUTO_LOW) require type: dynamic_threshold. If your alert condition uses type: threshold (the default), switch to type: dynamic_threshold to use anomaly detection, or use an explicit operator (GT, LT, etc.).
  • Incomplete change conditions. type: change requires all four: baseline_agg_function, baseline_interval_minutes, is_threshold_relative, and threshold_value. Omitting is_threshold_relative produces: "is_threshold_relative is a required field."
  • Multiple alert conditions. Exactly one is required. Create separate monitors for multiple thresholds.
  • Baseline set but no row counts appear. Recording evaluated row counts is off by default and must be enabled on your account.
    • Also confirm query_result_type is ROW_COUNT or unset.
  • Monitor started alerting differently after adding a baseline. Check whether is_percentage_threshold: true was set alongside it. That switches threshold_value from an absolute count to a percentage โ€” threshold_value: 5 becomes 5%, not 5 rows. To record row counts without changing alerting, set percentage_baseline_sql alone.

SQL pitfalls

  • SQL returns NULL. A NULL silently passes. Wrap with COALESCE: SELECT COALESCE(SUM(amount), 0) ...
  • Trailing semicolons or comments. Avoid trailing semicolons and SQL comments (--, /* */) โ€” some warehouses reject them when executed programmatically.

Noise reduction and updates

  • event_rollup_count , event_rollup_until_changed , and alert grouping are mutually exclusive. Setting more than one causes a validation error. Choose one strategy.
  • Forgetting PUT semantics on updates. When providing uuid, omitted fields revert to defaults. Always include every field you want to preserve.



Did this page help you?