John Walsh
04/02/2026, 2:45 PMelementary_test_results.test_results_query has queries like
select * from (None) results
where
anomaly_score is not null and
upper(full_table_name) = upper(cast('<my_model>' as varchar(4096))) and
metric_name = cast('average' as varchar(4096))
and upper(column_name) = upper(cast('<column_name>' as varchar(4096)))
is this intended/expected?kapa.ai
04/02/2026, 2:45 PM(None) in the result query indicates a known bug in Elementary where the test_results_query column references a temporary table that no longer exists after the test run completes.
Here's what's happening: during an anomaly test run, Elementary creates a temporary table (with a name ending in tmp_<timestamp>) to store intermediate anomaly scores. The test_results_query is generated referencing this temp table, but by the time you try to run the query, the temp table has already been dropped. In some versions, this manifests as (None) instead of the temp table reference. [Github Issue]
This has been acknowledged as an open bug by the Elementary team. The root cause is that metrics are only saved to the permanent data_monitoring_metrics table in the on_run_end dbt hook ā after the tests actually run ā so the saved query ends up referencing a temp table that no longer exists. [Github Issue]
Users have reported this issue persisting across multiple versions (including 0.18). [Slack thread]
Workaround: Until this is fixed, you can inspect the anomaly scores directly by querying the data_monitoring_metrics or metrics_anomaly_score tables, or check elementary_test_results for test pass/fail status. For column anomaly tests specifically, results are typically found in elementary_test_results rather than metrics_anomaly_score. [Slack thread] š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/02/2026, 2:50 PMdata_monitoring_metrics and metrics_anomaly_score tableskapa.ai
04/02/2026, 2:50 PMhandle_test_results on-run-end hook at the end of dbt test invocations. [package models]
⢠Think of it as the raw data store ā it holds the time-series of metric values per table/column/bucket. metrics_anomaly_score ⢠A view built on top of data_monitoring_metrics. [package models]
⢠It runs the same anomaly detection query that the tests themselves use, computing derived statistics like training_avg, training_stddev, anomaly_score, and is_anomaly.
⢠Its purpose is to provide visibility into anomaly detection results ā it's what you query to inspect scores and thresholds.
⢠It only shows data from the last 7 days (based on bucket_end), not the full history. Summary In short: data_monitoring_metrics is the source of truth for historical metrics, and metrics_anomaly_score is the analytical layer on top of it that tells you whether something is anomalous. [Slack thread] š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help., with interactive elementsJohn Walsh
04/02/2026, 2:57 PMon-run-end:
- "{% if target.name == 'production' %}{{ dbt_artifacts.upload_results(results) }}{% endif %}"
I'm in a dev environment on a local branch and want to see the results of tests in my dev schema
when i do
⢠dbt test -s <my_model>
⢠dbt run -s elementary
and then run this query
select
max(created_at)
from <dev_schema>.elementary_test_results as etr
where etr.created_at >= current_date;
i get the expected ts.
but not when i run
select
max(updated_at)
from elementary.metrics_anomaly_score
which returns the timestamp from the last daily prod run/test jobkapa.ai
04/02/2026, 2:58 PMon-run-end hook with dbt_artifacts.upload_results is conditional on target.name == 'production', so in dev it won't upload artifacts. However, your dbt test -s <my_model> run is populating <dev_schema>.elementary_test_results ā which means Elementary's own on-run-end hook (separate from dbt_artifacts) is still running and writing test results to your dev schema. That part is working correctly.
The issue with metrics_anomaly_score returning prod timestamps is almost certainly a schema mismatch: metrics_anomaly_score is a view built on top of data_monitoring_metrics, and it's likely resolving to the production data_monitoring_metrics table rather than your dev schema's version.
This happens when:
⢠The elementary models (including data_monitoring_metrics and the metrics_anomaly_score view) were built in a fixed schema (e.g., elementary) rather than your dev schema.
⢠When you query elementary.metrics_anomaly_score, you're hitting the prod-built view, which points to prod data.
To fix this, you need to ensure Elementary's models are built in your dev schema. The recommended approach is to configure the schema conditionally in `dbt_project.yml`: [Dev/Prod Config]
models:
elementary:
+schema: "{{ 'elementary' if target.name == 'production' else 'dev_elementary' }}"
Then rebuild the Elementary models in your dev environment:
dbt run -s elementary
This will create the metrics_anomaly_score view (and data_monitoring_metrics) in your dev schema, pointing to your dev data rather than prod. [FAQ]
Also note: inserts to data_monitoring_metrics happen as part of anomaly test execution itself (not just the on-run-end hook), so as long as Elementary models exist in your dev schema, anomaly test results should populate there correctly. š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/02/2026, 3:01 PMkapa.ai
04/02/2026, 3:01 PMmodels:
elementary:
+schema: "{{ 'elementary' if target.name == 'production' else 'dev_elementary' }}"
vars:
# Disable artifact uploads for local/non-prod runs
disable_run_results: "{{ target.name not in ['production', 'staging'] }}"
disable_tests_results: "{{ target.name not in ['production', 'staging'] }}"
disable_dbt_artifacts_autoupload: "{{ target.name not in ['production', 'staging'] }}"
disable_dbt_invocation_autoupload: "{{ target.name not in ['production', 'staging'] }}"
Adjust the target names (production, staging, etc.) to match what you have in your profiles.yml.
How this works:
⢠The +schema config ensures Elementary models (including data_monitoring_metrics and metrics_anomaly_score) are built in the correct schema per environment ā prod writes to elementary, dev writes to dev_elementary. [Dev/Prod Config]
⢠The disable_* vars control whether the on-run-end hooks upload artifacts. When enabled for a given target, uploads go to whichever schema Elementary is configured to use for that target. [Reduce on-run-end time]
Important: After updating the schema config, rebuild Elementary models in each environment:
dbt run -s elementary
This ensures the views (like metrics_anomaly_score) in each schema point to the correct underlying tables for that environment.
> Note: The disable_* vars approach is preferred over disabling the entire package (+enabled: false), because it allows Elementary anomaly tests to still run while controlling artifact collection separately. [Dev/Prod Config] š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/04/2026, 4:03 PMtest_result_rows.result_row::jsonb ->> 'training_end' to return null where result_row::jsonb ->> 'is_anomalous' = true?kapa.ai
04/04/2026, 4:03 PMtraining_end being null in test_result_rows.result_row when is_anomalous = true.
However, the knowledge sources do show a real example of a result_row JSON payload where training_end is populated alongside `is_anomalous: false`:
{
"training_start": "2024-03-01T00:00:00",
"training_end": "2025-02-01T00:00:00",
"is_anomalous": false,
...
}
[Slack thread]
This suggests training_end can be populated in result_row. Whether it being null specifically when is_anomalous = true is a known bug is not something I can confirm from the available knowledge sources.
A few things worth checking based on what the knowledge sources do cover:
1. Column datatype issues ā one community member found that a wrong column datatype (e.g., a timestamp column stored as string) caused anomaly detection to behave unexpectedly. [Slack thread]
2. Not enough training data ā if there aren't enough data points, some fields in the result row may not be populated correctly.
3. Elementary version ā some anomaly-related bugs were resolved by upgrading to a newer version. [Slack thread]
I'd recommend opening a GitHub issue or asking in the Elementary Slack community with a sample of the affected result_row JSON, as this specific behavior isn't documented in the available knowledge sources. š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/04/2026, 4:06 PMtraining_end value using other values in the result_row json?kapa.ai
04/04/2026, 4:06 PMtraining_end from other fields in the result_row JSON.
What the knowledge sources do show is that training_end is computed in the metrics_anomaly_score logic as:
last_value(bucket_end) over (
partition by metric_name, full_table_name, column_name
order by bucket_start asc
rows between unbounded preceding and current row
) as training_end
[Slack thread]
So conceptually, training_end represents the bucket_end of the most recent bucket in the training window. If training_end is null in your result_row, you might consider using max_bucket_end or bucket_end from the same JSON as a proxy ā the knowledge sources show both fields are present in result row payloads alongside `training_end`:
{
"bucket_end": "2025-02-01T00:00:00",
"training_end": "2025-02-01T00:00:00",
"max_bucket_end": "2025-02-01T00:00:00"
}
[Slack thread]
In that example, all three values are identical. However, I cannot confirm from the knowledge sources whether this equivalence holds reliably in all cases, especially when is_anomalous = true. I'd recommend verifying this against your own data before relying on it, and raising the underlying null issue with the Elementary team directly. š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/04/2026, 4:10 PMkapa.ai
04/04/2026, 4:10 PMresult_row JSON.
However, the knowledge sources do show a real example of a result_row payload where both pairs appear together:
{
"value": 138.0,
"average": 131.0,
"start_time": "2025-01-01T00:00:00",
"end_time": "2025-02-01T00:00:00",
"bucket_start": "2025-01-01T00:00:00",
"bucket_end": "2025-02-01T00:00:00",
...
}
[Slack thread]
In this example, start_time equals bucket_start and end_time equals bucket_end. Based on the knowledge sources, I cannot confirm whether this is always the case or whether there are scenarios where they diverge.
You can also see in the metrics_anomaly_score SQL (from the knowledge sources) that start_time and end_time are explicitly aliased from bucket_start and `bucket_end`:
bucket_start as start_time,
bucket_end as end_time,
*
[Slack thread]
So `start_time`/`end_time` appear to be aliases for `bucket_start`/`bucket_end` ā they represent the same values, just surfaced under different names in the result output. The * in the select means both the aliased names and the original column names end up in the result, which is why you see both in the JSON. š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.John Walsh
04/04/2026, 4:24 PMkapa.ai
04/04/2026, 4:24 PManomaly_exclude_metrics with a SQL expression to exclude data points where the metric value is 0 (or below a threshold), which would suppress the false positives caused by dimension values with no data in the detection bucket:
data_tests:
- elementary.dimension_anomalies:
arguments:
dimensions:
- your_dimension_column
anomaly_exclude_metrics: "metric_value = 0"
However, this is a blunt instrument ā it would also suppress legitimate anomalies where the value genuinely drops to 0. [anomaly_exclude_metrics] 2. Use where_expression to pre-filter the data You could use a where_expression to restrict the data Elementary sees to only rows within a recent window where you expect all dimension values to have data:
data_tests:
- elementary.dimension_anomalies:
arguments:
dimensions:
- your_dimension_column
where_expression: "your_timestamp_column >= dateadd(month, -3, current_date)"
This won't solve the per-dimension-value bucket problem directly, but it can reduce the scope of stale dimension values being evaluated. [dimension_anomalies config] 3. Use ignore_small_changes If the false positives are driven by drops to near-zero values, ignore_small_changes with a drop_failure_percent_threshold may help suppress them:
data_tests:
- elementary.dimension_anomalies:
arguments:
dimensions:
- your_dimension_column
ignore_small_changes:
drop_failure_percent_threshold: 50
[dimension_anomalies config] The root issue ā that Elementary doesn't support per-dimension-value dynamic detection periods ā is not addressed in the available knowledge sources as a planned or existing feature. I'd recommend raising this as a feature request on the Elementary GitHub or in their Slack community, as it's a legitimate gap for sparse/irregular dimension data. š 0 button š 0 button ā¹ļø Tip: Mention @elementary.ai in the thread for follow-ups or DM the app for 1-to-1 help.