Repository: great-expectations/great_expectations

Issue:
SQLAlchemy `expect_column_values_to_be_unique` errors out when run on an empty table
**Describe the bug**
Using an `InferredAssetSqlDataConnector` with a SQLAlchemy connection to a Postgres DB and running `expect_column_values_to_be_unique` fails with a `TypeError` when run against an empty table. 

The error occurs in the `map_metric_provider` module:

```
Traceback (most recent call last):
  File "/Users/douglas/.virtualenvs/dags/lib/python3.7/site-packages/great_expectations/execution_engine/execution_engine.py", line 391, in resolve_metrics
    **metric_provider_kwargs
  File "/Users/douglas/.virtualenvs/dags/lib/python3.7/site-packages/great_expectations/expectations/metrics/map_metric_provider.py", line 1994, in _sqlalchemy_map_condition_unexpected_count_value
    unexpected_count = int(unexpected_count)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
```

It looks like this would happen with any DB, but I have only tested it with Postgres.

**To Reproduce**
1. Create a table in a Postgres DB without any rows populated
2. Configure an `InferredAssetSqlDataConnector` pointed to the DB
3. Add an `expect_column_values_to_be_unique` using the table
4. Run the expectation and the above error will be returned in the `exception_info`

**Expected behavior**
If the data asset is empty then the expectation should pass by default - it cannot contain any unexpected rows if there are no rows.

**Environment (please complete the following information):**
 - Operating System: MacOS
 - Python 3.7.13
 - Great Expectations version 0.14.11
 - SQLAlchemy version 1.4.9



Discussion:

Resolving patch:
diff --git a/great_expectations/expectations/metrics/map_metric_provider.py b/great_expectations/expectations/metrics/map_metric_provider.py
--- a/great_expectations/expectations/metrics/map_metric_provider.py
+++ b/great_expectations/expectations/metrics/map_metric_provider.py
@@ -1992,7 +1992,13 @@ def _sqlalchemy_map_condition_unexpected_count_value(
                 ]
             )
         ).scalar()
-        unexpected_count = int(unexpected_count)
+        # Unexpected count can be None if the table is empty, in which case the count
+        # should default to zero.
+        try:
+            unexpected_count = int(unexpected_count)
+        except TypeError:
+            unexpected_count = 0
+
     except OperationalError as oe:
         exception_message: str = f"An SQL execution Exception occurred: {str(oe)}."
         raise ge_exceptions.InvalidMetricAccessorDomainKwargsKeyError(


Test patch:
diff --git a/tests/expectations/metrics/test_core.py b/tests/expectations/metrics/test_core.py
--- a/tests/expectations/metrics/test_core.py
+++ b/tests/expectations/metrics/test_core.py
@@ -1270,6 +1270,46 @@ def test_map_unique_column_does_not_exist_sa(sa):
     )
 
 
+def test_map_unique_empty_query_sa(sa):
+    """If the table contains zero rows then there must be zero unexpected values."""
+    engine = build_sa_engine(
+        pd.DataFrame({"a": [], "b": []}),
+        sa,
+    )
+
+    table_columns_metric: MetricConfiguration
+    metrics: dict
+    table_columns_metric, metrics = get_table_columns_metric(engine=engine)
+
+    condition_metric = MetricConfiguration(
+        metric_name="column_values.unique.condition",
+        metric_domain_kwargs={"column": "a"},
+        metric_value_kwargs=None,
+        metric_dependencies={
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(condition_metric,), metrics=metrics
+    )
+    metrics.update(results)
+
+    desired_metric = MetricConfiguration(
+        metric_name="column_values.unique.unexpected_count",
+        metric_domain_kwargs={"column": "a"},
+        metric_value_kwargs=None,
+        metric_dependencies={
+            "unexpected_condition": condition_metric,
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(desired_metric,),
+        metrics=metrics,
+    )
+    assert results[desired_metric.id] == 0
+
+
 def test_map_unique_column_exists_spark(spark_session):
     engine: SparkDFExecutionEngine = build_spark_engine(
         spark=spark_session,