DylanBaker: One error: 
```============== Testing 1 explore [batch mode] [concurrency = 10] ===============

✗ jaffle_shop.fct_orders failed

Completed SQL validation in 2 seconds.

================================== fct_orders ==================================

The Google BigQuery Standard SQL database encountered an error while running
this query. Failed to retrieve data - Name customer_lifetime_value_ not found
inside dim_customers at [3:23]

Test SQL: logs/queries/jaffle_shop__fct_orders.sql


To determine the exact dimensions responsible for this error, you can re-run 
Spectacles in single-dimension mode, with `--mode single`.

You can also run this original validation with `--mode hybrid` to do this automatically.
```

Multiple errors:
```
============== Testing 2 explores [batch mode] [concurrency = 10] ==============

✗ jaffle_shop.dim_customers failed
✗ jaffle_shop.fct_orders failed

Completed SQL validation in 3 seconds.

================================== fct_orders ==================================

The Google BigQuery Standard SQL database encountered an error while running
this query. Failed to retrieve data - Name customer_lifetime_value_ not found
inside dim_customers at [3:23]

Test SQL: logs/queries/jaffle_shop__fct_orders.sql

================================= dim_customers ================================

The Google BigQuery Standard SQL database encountered an error while running
this query. Failed to retrieve data - Name customer_lifetime_value_ not found
inside dim_customers at [3:23]

Test SQL: logs/queries/jaffle_shop__dim_customers.sql


To determine the exact dimensions responsible for these errors, you can re-run 
Spectacles in single-dimension mode, with `--mode single`.

You can also run this original validation with `--mode hybrid` to do this automatically.```
codecov[bot]: # [Codecov](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=h1) Report
> Merging [#190](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=desc) into [master](https://codecov.io/gh/spectacles-ci/spectacles/commit/66892cc1d35068162b29040f9034733c983059f8&el=desc) will **decrease** coverage by `0.59%`.
> The diff coverage is `25.00%`.

[![Impacted file tree graph](https://codecov.io/gh/spectacles-ci/spectacles/pull/190/graphs/tree.svg?width=650&height=150&src=pr&token=EWgmmQti5j)](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #190      +/-   ##
==========================================
- Coverage   70.68%   70.08%   -0.60%     
==========================================
  Files          10       10              
  Lines         904      916      +12     
==========================================
+ Hits          639      642       +3     
- Misses        265      274       +9     
```


| [Impacted Files](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [spectacles/cli.py](https://codecov.io/gh/spectacles-ci/spectacles/pull/190/diff?src=pr&el=tree#diff-c3BlY3RhY2xlcy9jbGkucHk=) | `69.38% <0.00%> (-0.48%)` | :arrow_down: |
| [spectacles/lookml.py](https://codecov.io/gh/spectacles-ci/spectacles/pull/190/diff?src=pr&el=tree#diff-c3BlY3RhY2xlcy9sb29rbWwucHk=) | `71.81% <27.27%> (-3.56%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=footer). Last update [66892cc...b046cd6](https://codecov.io/gh/spectacles-ci/spectacles/pull/190?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/spectacles/cli.py b/spectacles/cli.py
index 3bbaa9e..33e702d 100644
--- a/spectacles/cli.py
+++ b/spectacles/cli.py
@@ -560,6 +560,13 @@ def run_sql(
                 dimension=error["metadata"].get("dimension"),
                 lookml_url=error["metadata"].get("lookml_url"),
             )
+        if mode == "batch":
+            logger.info(
+                f"\n\nTo determine the exact dimensions responsible for {'this error' if project.number_of_errors == 1 else 'these errors'}, "
+                f"you can re-run \nSpectacles in single-dimension mode, with `--mode single`.\n\n"
+                "You can also run this original validation with `--mode hybrid` to do this automatically."
+            )
+
         logger.info("")
         raise GenericValidationError
     else:
diff --git a/spectacles/lookml.py b/spectacles/lookml.py
index 0bac8c9..63a967e 100644
--- a/spectacles/lookml.py
+++ b/spectacles/lookml.py
@@ -131,6 +131,19 @@ class Explore(LookMlObject):
     def add_dimension(self, dimension: Dimension):
         self.dimensions.append(dimension)
 
+    @property
+    def number_of_errors(self):
+        if self.errored:
+            if self.error:
+                errors = 1
+            else:
+                errors = len(
+                    [dimension for dimension in self.dimensions if dimension.errored]
+                )
+            return errors
+        else:
+            return 0
+
 
 class Model(LookMlObject):
     def __init__(self, name: str, project_name: str, explores: List[Explore]):
@@ -194,6 +207,12 @@ class Model(LookMlObject):
         ]
         return cls(name, project, explores)
 
+    @property
+    def number_of_errors(self):
+        return sum(
+            [explore.number_of_errors for explore in self.explores if explore.errored]
+        )
+
 
 class Project(LookMlObject):
     def __init__(self, name, models: Sequence[Model]):
@@ -272,3 +291,10 @@ class Project(LookMlObject):
             "tested": tested,
             "errors": errors,
         }
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}(name={self.name}, models={self.models})"
+
+    @property
+    def number_of_errors(self):
+        return sum([model.number_of_errors for model in self.models if model.errored])

Recommend re-running in hybrid/single mode when errors found in batch mode
When spectacles is run in batch mode and encounters an error, we should suggest the user re-run the explore (maybe even pre-populating the command with the explore argument in the output) in single-dimension mode to zero in on the dimensions causing the error.

Here's a suggested output:

```
To determine the exact dimensions responsible for this error, you can re-run this explore in single-dimension mode.

You can also run this original validation with `--mode hybrid` to do this automatically.
```
diff --git a/tests/test_lookml.py b/tests/test_lookml.py
index 196d91c..5c41859 100644
--- a/tests/test_lookml.py
+++ b/tests/test_lookml.py
@@ -113,3 +113,114 @@ def test_parent_queried_behavior_should_depend_on_its_child(
         assert parent.queried is True
         children.append(b)
         assert parent.queried is True
+
+
+def test_comparison_to_mismatched_type_object_fails(dimension, explore, model, project):
+    assert dimension != 1
+    assert explore != 1
+    assert model != 1
+    assert project != 1
+
+
+def test_explore_number_of_errors_batch_with_errors(dimension, explore, sql_error):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    explore.error = sql_error
+    assert explore.number_of_errors == 1
+
+
+def test_explore_number_of_errors_batch_with_no_errors(dimension, explore, sql_error):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    assert explore.number_of_errors == 0
+
+
+def test_explore_number_of_errors_single_with_errors(dimension, explore, sql_error):
+    dimension.error = sql_error
+    dimension.queried = True
+    explore.dimensions = [dimension, dimension]
+    assert explore.number_of_errors == 2
+
+
+def test_explore_number_of_errors_single_with_no_errors(dimension, explore, sql_error):
+    dimension.queried = True
+    explore.dimensions = [dimension, dimension]
+    assert explore.number_of_errors == 0
+
+
+def test_model_number_of_errors_batch_with_errors(dimension, explore, model, sql_error):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    explore.error = sql_error
+    model.explores = [explore, explore]
+    assert model.number_of_errors == 2
+
+
+def test_model_number_of_errors_batch_with_no_errors(
+    dimension, explore, model, sql_error
+):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    assert model.number_of_errors == 0
+
+
+def test_model_number_of_errors_single_with_errors(
+    dimension, explore, model, sql_error
+):
+    dimension.error = sql_error
+    explore.dimensions = [dimension, dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    assert model.number_of_errors == 4
+
+
+def test_model_number_of_errors_single_with_no_errors(
+    dimension, explore, model, sql_error
+):
+    explore.dimensions = [dimension, dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    assert model.number_of_errors == 0
+
+
+def test_project_number_of_errors_batch_with_errors(
+    dimension, explore, model, project, sql_error
+):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    explore.error = sql_error
+    model.explores = [explore, explore]
+    project.models = [model]
+    assert project.number_of_errors == 2
+
+
+def test_project_number_of_errors_batch_with_no_errors(
+    dimension, explore, model, project, sql_error
+):
+    explore.dimensions = [dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    project.models = [model]
+    assert project.number_of_errors == 0
+
+
+def test_project_number_of_errors_single_with_errors(
+    dimension, explore, model, project, sql_error
+):
+    dimension.error = sql_error
+    explore.dimensions = [dimension, dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    project.models = [model, model]
+    assert project.number_of_errors == 8
+
+
+def test_project_number_of_errors_single_with_no_errors(
+    dimension, explore, model, project, sql_error
+):
+    explore.dimensions = [dimension, dimension]
+    explore.queried = True
+    model.explores = [explore, explore]
+    project.models = [model, model]
+    assert project.number_of_errors == 0

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest mypy flake8 black pytest-cov pytest-recording pytest-randomly vcrpy jsonschema
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiohappyeyeballs==2.6.1
aiohttp==3.11.14
aiosignal==1.3.2
async-timeout==5.0.1
attrs==25.3.0
backoff==1.8.0
black==25.1.0
certifi==2025.1.31
chardet==3.0.4
click==8.1.8
colorama==0.4.1
coverage==7.8.0
exceptiongroup==1.2.2
flake8==7.2.0
frozenlist==1.5.0
idna==2.8
importlib_metadata==8.6.1
iniconfig==2.1.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
mccabe==0.7.0
multidict==6.2.0
mypy==1.15.0
mypy-extensions==1.0.0
packaging==24.2
pathspec==0.12.1
platformdirs==4.3.7
pluggy==1.5.0
propcache==0.3.1
pycodestyle==2.13.0
pyflakes==3.3.1
pytest==8.3.5
pytest-cov==6.0.0
pytest-randomly==3.16.0
pytest-recording==0.13.2
PyYAML==5.1.2
referencing==0.36.2
requests==2.22.0
rpds-py==0.24.0
-e git+https://github.com/spectacles-ci/spectacles.git@9c8176df0f24042d2cd489eb662729f077ebc48c#egg=spectacles
tomli==2.2.1
typing_extensions==4.13.0
urllib3==1.25.11
vcrpy==7.0.0
wrapt==1.17.2
yarl==1.18.3
zipp==3.21.0

name: spectacles
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiohappyeyeballs==2.6.1
      - aiohttp==3.11.14
      - aiosignal==1.3.2
      - async-timeout==5.0.1
      - attrs==25.3.0
      - backoff==1.8.0
      - black==25.1.0
      - certifi==2025.1.31
      - chardet==3.0.4
      - click==8.1.8
      - colorama==0.4.1
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - frozenlist==1.5.0
      - idna==2.8
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - mccabe==0.7.0
      - multidict==6.2.0
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - packaging==24.2
      - pathspec==0.12.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - propcache==0.3.1
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-randomly==3.16.0
      - pytest-recording==0.13.2
      - pyyaml==5.1.2
      - referencing==0.36.2
      - requests==2.22.0
      - rpds-py==0.24.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==1.25.11
      - vcrpy==7.0.0
      - wrapt==1.17.2
      - yarl==1.18.3
      - zipp==3.21.0
prefix: /opt/conda/envs/spectacles

tests/test_lookml.py::test_explore_number_of_errors_batch_with_errors tests/test_lookml.py::test_model_number_of_errors_batch_with_errors tests/test_lookml.py::test_explore_number_of_errors_single_with_errors tests/test_lookml.py::test_model_number_of_errors_single_with_errors tests/test_lookml.py::test_model_number_of_errors_batch_with_no_errors tests/test_lookml.py::test_project_number_of_errors_batch_with_no_errors tests/test_lookml.py::test_project_number_of_errors_single_with_no_errors tests/test_lookml.py::test_explore_number_of_errors_batch_with_no_errors tests/test_lookml.py::test_explore_number_of_errors_single_with_no_errors tests/test_lookml.py::test_project_number_of_errors_batch_with_errors tests/test_lookml.py::test_project_number_of_errors_single_with_errors tests/test_lookml.py::test_model_number_of_errors_single_with_no_errors
tests/test_lookml.py::test_dimension_from_json tests/test_lookml.py::test_explore_from_json tests/test_lookml.py::test_dimensions_with_different_sql_can_be_equal tests/test_lookml.py::test_should_not_be_able_to_set_errored_on_dimension tests/test_lookml.py::test_dimension_should_not_be_errored_if_not_queried tests/test_lookml.py::test_comparison_to_mismatched_type_object_should_fail[explore] tests/test_lookml.py::test_parent_queried_behavior_should_depend_on_its_child tests/test_lookml.py::test_ignored_dimension_with_no_whitespace tests/test_lookml.py::test_comparison_to_mismatched_type_object_should_fail[model] tests/test_lookml.py::test_comparison_to_mismatched_type_object_should_fail[dimension] tests/test_lookml.py::test_comparison_to_mismatched_type_object_should_fail[project] tests/test_lookml.py::test_comparison_to_mismatched_type_object_fails tests/test_lookml.py::test_model_from_json tests/test_lookml.py::test_should_not_be_able_to_set_errored_on_explore tests/test_lookml.py::test_ignored_dimension_with_whitespace
spectacles/lookml.py spectacles/cli.py
spectacles/lookml.py spectacles/cli.py