<@U088WCP3B4P> the column `elementary_test_results...
# ask-elementary-ai
j
@kapa.ai the column
elementary_test_results.test_results_query
has queries like
Copy code
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?
k
No, this is not intended behavior. The
(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.
j
@kapa.ai what's the difference between
data_monitoring_metrics
and
metrics_anomaly_score
tables
k
Here's a clear breakdown of the two: data_monitoring_metrics • An incremental table that stores raw metric values over time (e.g., row counts, freshness, null percentages, standard deviations, etc.) for each anomaly detection test. [package models] • On each anomaly test run, the test queries this table for historical metrics and compares them to the latest values. • New metrics are written to this table via the
handle_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 elements
j
@kapa.ai I have this in my dbt_project.yml
Copy code
on-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
Copy code
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
Copy code
select
    max(updated_at)
from elementary.metrics_anomaly_score
which returns the timestamp from the last daily prod run/test job
k
The behavior you're seeing makes sense given your setup, but it points to a schema configuration issue. Your
on-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]
Copy code
models:
  elementary:
    +schema: "{{ 'elementary' if target.name == 'production' else 'dev_elementary' }}"
Then rebuild the Elementary models in your dev environment:
Copy code
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.
j
@kapa.ai can i configure the on_run_end that it will upload artifacts from production runs to the production schema and dev runs to the dev schema
k
Yes, this is exactly the recommended approach. You can configure Elementary's on-run-end hooks to behave differently per environment by combining schema configuration with hook disablement vars. Here's a complete setup in your `dbt_project.yml`:
Copy code
models:
  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:
Copy code
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.
j
@kapa.ai is it a known bug for
test_result_rows.result_row::jsonb ->> 'training_end'
to return null where
result_row::jsonb ->> 'is_anomalous'
= true?
k
The knowledge sources do not contain enough information to confirm this as a known bug specifically for
training_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`:
Copy code
{
  "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.
j
@kapa.ai I wonder if there's a reliable way to coalesce the
training_end
value using other values in the
result_row
json?
k
The knowledge sources do not contain enough information to provide a reliable method for coalescing
training_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:
Copy code
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`:
Copy code
{
  "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.
j
@kapa.ai in the result_row json what is the difference between start_time/end_time and bucket_start_/bucket_end
k
The knowledge sources do not explicitly document the difference between `start_time`/`end_time` and `bucket_start`/`bucket_end` in the
result_row
JSON. However, the knowledge sources do show a real example of a
result_row
payload where both pairs appear together:
Copy code
{
  "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`:
Copy code
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.
j
@kapa.ai for tests which are configured with dimensions, I notice that training periods and detection periods are the same for each value of the dimension(s). is there a way to configure the test to determine for each dimension value what the latest complete time bucket is and use that for the detection period? the model i'm applying a test to doesn't have the same latest time bucket for each dimension value and so i am getting a lot of false positives (e.g. result_row::jsonb ->> 'value' = 0 for dimension value x because the latest time bucket for which x has values is 2 months ago, whereas some dimension values have rows in the time bucket for one month ago)
k
This is a well-understood pain point, but unfortunately the knowledge sources do not describe a built-in Elementary configuration option to dynamically determine the latest complete time bucket per dimension value and use that as the detection period. Elementary's detection period is applied uniformly across all dimension values — it uses the same time window regardless of whether a given dimension value has data in that bucket. [dimension_anomalies config] What you can do today 1. Use anomaly_exclude_metrics to filter out zero-value buckets You can use
anomaly_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:
Copy code
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:
Copy code
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:
Copy code
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.