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 scopeThis 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-domainCreate interactively with create_or_update_sql_monitor(dry_run=True) via the Monte Carlo MCP server.
MCP vs MaC namingThe MCP tool parameter is
alert_condition(singular object), while MaC YAML usesalert_conditions(plural array). The tool wraps it automatically. Thewarehouseparameter takes a UUID โ useget_warehousesto resolve a name.
Configuration
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)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 viathreshold_valuewith an explicit operator. - Range โ
lower_thresholdandupper_thresholdwithINSIDE_RANGEorOUTSIDE_RANGE. - Dynamic threshold (
dynamic_threshold) โ ML anomaly detection comparing current value against a rolling baseline. AcceptsAUTO/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. Requiresis_threshold_relative,baseline_agg_function, andbaseline_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 mappingThe UI uses different names for threshold types: Automatic =
dynamic_threshold, Absolute =threshold, Relative =change.
alert_conditions:
- type: threshold
operator: GT
threshold_value: 0Properties
type โ threshold type
enum ยท optional ยท default: threshold
Accepted values: threshold ยท dynamic_threshold ยท change ยท absolute_volume ยท growth_volume ยท noop
alert_conditions:
- type: thresholdoperator โ 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: GTthreshold_value โ static threshold
number ยท required (for threshold and change types)
alert_conditions:
- type: threshold
operator: GT
threshold_value: 0lower_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: 1000upper_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: 50baseline_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: AVGbaseline_interval_minutes โ lookback window in minutes
integer ยท required (for change and dynamic_threshold types)
Range: 0โ129600.
alert_conditions:
- type: change
baseline_interval_minutes: 10080is_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: truethreshold_sensitivity โ sensitivity for dynamic thresholds
enum ยท optional
Accepted values: low ยท medium ยท high
For dynamic_threshold type only.
alert_conditions:
- type: dynamic_threshold
threshold_sensitivity: mediummin_buffer_value โ minimum buffer for dynamic threshold band
number ยท optional
alert_conditions:
- type: dynamic_threshold
min_buffer_value: 10max_buffer_value โ maximum buffer for dynamic threshold band
number ยท optional
alert_conditions:
- type: dynamic_threshold
max_buffer_value: 100min_buffer_modifier_type โ unit for min buffer
enum ยท optional
Accepted values: METRIC ยท PERCENTAGE
alert_conditions:
- type: dynamic_threshold
min_buffer_modifier_type: PERCENTAGEmax_buffer_modifier_type โ unit for max buffer
enum ยท optional
Accepted values: METRIC ยท PERCENTAGE
alert_conditions:
- type: dynamic_threshold
max_buffer_modifier_type: PERCENTAGEnumber_of_agg_periods โ aggregation periods in baseline
integer ยท optional
Range: 1โ1000.
alert_conditions:
- type: dynamic_threshold
number_of_agg_periods: 7threshold_lookback_minutes โ lookback window for threshold computation
integer ยท optional
alert_conditions:
- type: dynamic_threshold
threshold_lookback_minutes: 10080is_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: thresholdpercentage_baseline_sqlthreshold_valueof 0 or morequery_result_typeofROW_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... | Set | What happens |
|---|---|---|
| Alert when a percentage of rows fail | is_percentage_threshold: true and percentage_baseline_sql | The 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 evaluated | percentage_baseline_sql only | Alerting 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_NUMERICdon't record row counts.Percentage thresholds do not require this setting and work without it.
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: 720Properties
type โ schedule type
enum ยท optional ยท default: fixed
Accepted values: fixed ยท dynamic ยท manual
schedule:
type: fixedinterval_minutes โ run interval for fixed schedules
integer ยท optional
Required when type is fixed.
schedule:
type: fixed
interval_minutes: 720interval_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: ANDstart_time โ ISO 8601 start time
string ยท optional
schedule:
start_time: "2024-01-01T08:00:00Z"timezone โ IANA timezone
string ยท optional
schedule:
timezone: America/New_Yorkdynamic_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.ordersdynamic_schedule_jobs โ jobs that trigger the monitor
array of objects ยท optional
| Property | Type | Required | Description |
|---|---|---|---|
job_type | enum | yes | AdfJob ยท AirflowDag ยท DatabricksJob ยท DbtJob |
job_name | string | yes | Name of the job |
project_name | string | yes | Project containing the job |
task_name | string | no | Specific task within the job |
mcon | string | no | MCON identifier for the job |
schedule:
type: dynamic
dynamic_schedule_jobs:
- job_type: AirflowDag
job_name: etl_orders
project_name: my-airflowmin_interval_minutes โ minimum interval for dynamic schedules
integer ยท optional
schedule:
type: dynamic
min_interval_minutes: 30string ยท required
Displayed in the Monte Carlo UI and in incident notifications. Max 512 characters.
description: Alert when orphan orders existarray 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-domainstring ยท optional (yes if multiple warehouses)
Warehouse name or UUID. Overrides default_resource from montecarlo.yml.
warehouse: my-warehousestring ยท 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_ordersstring ยท optional
Overrides the default connection for this warehouse.
connection_name: analytics-readonlyobject ยท 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
- stagingstring ยท 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 100enum ยท 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_COUNTinteger ยท optional
Minimum 2. Mutually exclusive with event_rollup_until_changed and alert_grouping.
event_rollup_count: 3boolean ยท optional ยท default: false
Roll up breaches until the result changes. Mutually exclusive with event_rollup_count and alert_grouping.
event_rollup_until_changed: trueobject ยท 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_alertenum ยท optional
Accepted values: SEV-0 ยท SEV-1 ยท SEV-2 ยท SEV-3 ยท SEV-4
severity: SEV-2enum ยท optional
Accepted values: P1 ยท P2 ยท P3 ยท P4 ยท P5
priority: P2array 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]array of strings ยท optional
Separate audiences for run-failure notifications. Falls back to audiences when omitted.
failure_audiences:
- data-eng-oncallboolean ยท optional
notify_run_failure: truestring ยท optional
exception_primary_key_column: order_idinteger ยท optional
timeout: 300array of objects ยท optional ยท default: []
| Property | Type | Required | Description |
|---|---|---|---|
name | string | yes | Tag key |
value | string | no | Tag value |
tags:
- name: team
value: analytics
- name: environment
value: productionenum ยท optional
Accepted values: ACCURACY ยท COMPLETENESS ยท CONSISTENCY ยท TIMELINESS ยท UNIQUENESS ยท VALIDITY
data_quality_dimension: CONSISTENCYstring ยท optional
Visible in the Monte Carlo UI. Not included in notifications.
notes: Owned by the analytics team. Reviewed quarterly.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: truestring ยท optional
Include the UUID of an existing monitor to update it instead of creating a new one.
uuid: 0dae7702-0950-45c7-909c-7e183bddca19boolean ยท 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: falseboolean ยท 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: falseDeprecated fields
| Field | Use instead |
|---|---|
resource | warehouse |
domain | domains |
domain_uuids | domains |
labels | audiences |
notify_rule_run_failure | notify_run_failure |
comparisons | alert_conditions |
API-only fieldsSome 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-domainThreshold 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-domainRequires 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-domainthreshold_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-domainAnomaly 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-domainTroubleshooting
Alert conditions and thresholds
- AUTO operators not supported for
thresholdtype. AUTO operators (AUTO,AUTO_HIGH,AUTO_LOW) requiretype: dynamic_threshold. If your alert condition usestype: threshold(the default), switch totype: dynamic_thresholdto use anomaly detection, or use an explicit operator (GT, LT, etc.). - Incomplete
changeconditions.type: changerequires all four:baseline_agg_function,baseline_interval_minutes,is_threshold_relative, andthreshold_value. Omittingis_threshold_relativeproduces: "is_threshold_relativeis 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_typeisROW_COUNTor unset.
- Also confirm
- Monitor started alerting differently after adding a baseline. Check whether
is_percentage_threshold: truewas set alongside it. That switchesthreshold_valuefrom an absolute count to a percentage โthreshold_value: 5becomes 5%, not 5 rows. To record row counts without changing alerting, setpercentage_baseline_sqlalone.
SQL pitfalls
- SQL returns NULL. A
NULLsilently passes. Wrap withCOALESCE: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, andalert groupingare 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.
Updated 7 days ago
