Repository: great-expectations/great_expectations
Pull request #11281 (2025-06-28 02:04:59)

[BUGFIX] Fix renderer for ExpectTableColumnsToMatchSet


- [ ] Description of PR changes above includes a link to [an existing GitHub issue](https://github.com/great-expectations/great_expectations/issues)
- [ ] PR title is prefixed with one of: [BUGFIX], [FEATURE], [DOCS], [MAINTENANCE], [CONTRIB], [MINORBUMP]
- [ ] Code is linted - run `invoke lint` (uses `ruff format` + `ruff check`)
- [ ] Appropriate tests and docs have been updated

For more information about contributing, visit our [community resources](https://docs.greatexpectations.io/docs/core/introduction/community_resources#contribute-code-or-documentation).

After you submit your PR, keep the page open and **monitor the statuses of the various checks made by our continuous integration process at the bottom of the page. Please fix any issues that come up** and [reach out on Slack](https://greatexpectations.io/slack) if you need help. Thanks for contributing!


netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *niobium-lead-7998* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | b2adc67145a50e25d3ed179edefaecf7ff1d53a2 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/projects/niobium-lead-7998/deploys/685f4dceea6ff10008a9da1e |
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 72.90%. Comparing base [(`bffa99e`)](https://app.codecov.io/gh/great-expectations/great_expectations/commit/bffa99e0a6f4cf7e875c395d6638b6fc7135ead3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) to head [(`77bac18`)](https://app.codecov.io/gh/great-expectations/great_expectations/commit/77bac1805a1a2fdfde99ac8d50213b3c3a96293c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations).

> :exclamation:  There is a different number of reports uploaded between BASE (bffa99e) and HEAD (77bac18). Click for more details.
> 
> <details><summary>HEAD has 74 uploads less than BASE</summary>
>
>| Flag | BASE (bffa99e) | HEAD (77bac18) |
>|------|------|------|
>|3.11|2|1|
>|3.10|2|0|
>|3.9|2|0|
>|3.12|2|0|
>|3.9 clickhouse|2|0|
>|3.12 big|2|0|
>|3.9 big|2|0|
>|3.12 aws_deps|2|0|
>|3.9 aws_deps|2|0|
>|3.12 athena or openpyxl or pyarrow or project or sqlite or aws_creds|2|0|
>|3.12 mysql|2|0|
>|3.9 spark_connect|2|0|
>|3.12 mssql|2|0|
>|3.9 athena or openpyxl or pyarrow or project or sqlite or aws_creds|2|0|
>|3.9 mysql|2|0|
>|3.12 filesystem|2|0|
>|3.9 mssql|2|0|
>|3.9 filesystem|2|0|
>|3.12 spark_connect|2|0|
>|3.9 postgresql|2|0|
>|3.12 postgresql|2|0|
>|3.9 trino|1|0|
>|3.9 spark|1|0|
>|3.12 trino|1|0|
>|3.12 spark|1|0|
>|cloud|1|0|
>|docs-spark|1|0|
>|docs-basic|1|0|
>|3.12 gx-redshift|1|0|
>|3.9 snowflake|1|0|
>|3.9 bigquery|1|0|
>|3.12 bigquery|1|0|
>|3.12 snowflake|1|0|
>|docs-creds-needed|1|0|
>|3.9 databricks|1|0|
>|3.12 databricks|1|0|
>|3.10 clickhouse|1|0|
>|3.10 big|1|0|
>|3.11 big|1|0|
>|3.11 aws_deps|1|0|
>|3.10 aws_deps|1|0|
>|3.10 athena or openpyxl or pyarrow or project or sqlite or aws_creds|1|0|
>|3.11 mysql|1|0|
>|3.11 athena or openpyxl or pyarrow or project or sqlite or aws_creds|1|0|
>|3.11 mssql|1|0|
>|3.10 mssql|1|0|
>|3.10 mysql|1|0|
>|3.11 clickhouse|1|0|
>|3.10 filesystem|1|0|
>|3.10 spark_connect|1|0|
>|3.11 filesystem|1|0|
>|3.11 spark_connect|1|0|
>|3.11 postgresql|1|0|
>|3.10 postgresql|1|0|
></details>

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##           develop   #11281       +/-   ##
============================================
- Coverage    84.00%   72.90%   -11.10%     
============================================
  Files          463      461        -2     
  Lines        38775    38773        -2     
============================================
- Hits         32571    28268     -4303     
- Misses        6204    10505     +4301     
```

| [Flag](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | Coverage Δ | |
|---|---|---|
| [3.10](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 athena or openpyxl or pyarrow or project or sqlite or aws_creds](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 aws_deps](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 big](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 clickhouse](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 filesystem](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 mssql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 mysql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 postgresql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.10 spark_connect](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `72.90% <100.00%> (+<0.01%)` | :arrow_up: |
| [3.11 athena or openpyxl or pyarrow or project or sqlite or aws_creds](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 aws_deps](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 big](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 clickhouse](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 filesystem](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 mssql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 mysql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 postgresql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.11 spark_connect](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 athena or openpyxl or pyarrow or project or sqlite or aws_creds](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 aws_deps](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 big](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 bigquery](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 databricks](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 filesystem](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 gx-redshift](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 mssql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 mysql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 postgresql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 snowflake](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 spark](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 spark_connect](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.12 trino](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 athena or openpyxl or pyarrow or project or sqlite or aws_creds](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 aws_deps](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 big](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 bigquery](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 clickhouse](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 databricks](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 filesystem](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 mssql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 mysql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 postgresql](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 snowflake](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 spark](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 spark_connect](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [3.9 trino](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [cloud](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [docs-basic](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [docs-creds-needed](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |
| [docs-spark](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations) | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/great-expectations/great_expectations/pull/11281?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=great-expectations).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>

Patch:
diff --git a/great_expectations/expectations/core/expect_table_columns_to_match_set.py b/great_expectations/expectations/core/expect_table_columns_to_match_set.py
index e90311c9c..34101ca93 100644
--- a/great_expectations/expectations/core/expect_table_columns_to_match_set.py
+++ b/great_expectations/expectations/core/expect_table_columns_to_match_set.py
@@ -371,11 +371,6 @@ class ExpectTableColumnsToMatchSet(BatchExpectation):
             renderer_configuration=renderer_configuration,
         )
 
-        expected_column_set = set(renderer_configuration.kwargs.get("column_set", []))
-        observed_column_set = set(
-            result.get("result", {}).get("observed_value", []) if result else []
-        )
-
         observed_columns = (
             (name, sch)
             for name, sch in renderer_configuration.params
@@ -386,19 +381,26 @@ class ExpectTableColumnsToMatchSet(BatchExpectation):
             for name, sch in renderer_configuration.params
             if name.startswith(expected_param_prefix)
         )
+        mismatched_columns = {"unexpected": [], "missing": []}
+        if (
+            "details" in result["result"]
+            and "mismatched" in result["result"]["details"]
+            and result["result"]["details"]["mismatched"]
+        ):
+            mismatched_columns.update(result["result"]["details"]["mismatched"])
 
         template_str_list = []
         for name, schema in observed_columns:
             render_state = (
-                ObservedValueRenderState.EXPECTED.value
-                if schema.value in expected_column_set
-                else ObservedValueRenderState.UNEXPECTED.value
+                ObservedValueRenderState.UNEXPECTED.value
+                if schema.value in mismatched_columns["unexpected"]
+                else ObservedValueRenderState.EXPECTED.value
             )
             renderer_configuration.params.__dict__[name].render_state = render_state
             template_str_list.append(f"${name}")
 
         for name, schema in expected_columns:
-            if schema.value not in observed_column_set:
+            if schema.value in mismatched_columns["missing"]:
                 renderer_configuration.params.__dict__[
                     name
                 ].render_state = ObservedValueRenderState.MISSING.value
@@ -540,15 +542,15 @@ def _validate_result(
     exact_match: bool,
 ) -> Dict[str, Any]:
     empty_set = set()
-    observed_value = sorted(list(actual_column_set))
+    observed_value = sorted([str(col) for col in actual_column_set])
 
     if ((expected_column_set is None) and (exact_match is not True)) or (
         unmatched_expected_column_set == empty_set and unmatched_actual_column_set == empty_set
     ):
         return {"success": True, "result": {"observed_value": observed_value}}
     else:
-        unexpected_list = sorted(list(unmatched_actual_column_set))
-        missing_list = sorted(list(unmatched_expected_column_set))
+        unexpected_list = sorted([str(col) for col in unmatched_actual_column_set])
+        missing_list = sorted([str(col) for col in unmatched_expected_column_set])
 
         mismatched = {}
         if len(unexpected_list) > 0:


Test patch:
diff --git a/tests/expectations/test_expectation_atomic_renderers.py b/tests/expectations/test_expectation_atomic_renderers.py
index 6744bf462..03177dcf4 100644
--- a/tests/expectations/test_expectation_atomic_renderers.py
+++ b/tests/expectations/test_expectation_atomic_renderers.py
@@ -1,7 +1,7 @@
 import re
 from datetime import datetime
 from pprint import pprint
-from typing import Callable, Dict, Union
+from typing import Any, Callable, Dict, Optional, Tuple, Union
 
 import pytest
 
@@ -2750,6 +2750,26 @@ def test_expect_column_distinct_values_to_contain_set_atomic_diagnostic_observed
         assert res["value"]["params"][name]["render_state"] == status
 
 
+def _create_result_details_from_expected_result(
+    expected_result: Tuple[str, str, str],
+) -> Optional[Dict[str, Any]]:
+    unexpected = []
+    missing = []
+    for _, col, state in expected_result:
+        if state == "unexpected":
+            unexpected.append(col)
+        elif state == "missing":
+            missing.append(col)
+    if not unexpected and not missing:
+        return None
+    details = {"mismatched": {}}
+    if unexpected:
+        details["mismatched"]["unexpected"] = unexpected
+    if missing:
+        details["mismatched"]["missing"] = missing
+    return details
+
+
 @pytest.mark.unit
 @pytest.mark.parametrize(
     "description, value_set, observed_value, expected_result",
@@ -2943,6 +2963,9 @@ def test_expect_table_columns_to_match_set_atomic_diagnostic_observed_value(
         ),
         "result": {"observed_value": observed_value},
     }
+    details = _create_result_details_from_expected_result(expected_result)
+    if details:
+        x["result"]["details"] = details
 
     expected_template_string = " ".join([f"${name}" for name, _, _ in expected_result])
 
diff --git a/tests/integration/data_sources_and_expectations/expectations/test_expect_table_columns_to_match_set.py b/tests/integration/data_sources_and_expectations/expectations/test_expect_table_columns_to_match_set.py
index cadcc365d..a114d3416 100644
--- a/tests/integration/data_sources_and_expectations/expectations/test_expect_table_columns_to_match_set.py
+++ b/tests/integration/data_sources_and_expectations/expectations/test_expect_table_columns_to_match_set.py
@@ -1,9 +1,11 @@
-from typing import Sequence
+from enum import Enum
+from typing import Dict, List, Optional, Sequence
 
 import pandas as pd
 import pytest
 
 import great_expectations.expectations as gxe
+from great_expectations.core.expectation_validation_result import ExpectationValidationResult
 from great_expectations.core.result_format import ResultFormat
 from great_expectations.datasource.fluent.interfaces import Batch
 from tests.integration.conftest import parameterize_batch_for_data_sources
@@ -124,37 +126,8 @@ def test_success_with_suite_param_exact_match_(
 
 
 # Case insenstivity tests
-CASE_INSENSITIVE_DATA = pd.DataFrame(
-    {
-        "column_a": [1],
-        "COLUMN_B": [2],
-        "CoLuMn_C": [3],
-    }
-)
-
-
-@parameterize_batch_for_data_sources(
-    data_source_configs=SQL_DATA_SOURCES,
-    data=CASE_INSENSITIVE_DATA,
-)
-def test_case_insensitive_success(batch_for_datasource: Batch) -> None:
-    expectation = gxe.ExpectTableColumnsToMatchSet(column_set=["COLUMN_A", "column_b", "COLumN_c"])
-    result = batch_for_datasource.validate(expectation)
-    assert result.success
-
-
-@parameterize_batch_for_data_sources(
-    data_source_configs=SQL_DATA_SOURCES,
-    data=CASE_INSENSITIVE_DATA,
-)
-def test_case_insensitive_failure(batch_for_datasource: Batch) -> None:
-    expectation = gxe.ExpectTableColumnsToMatchSet(column_set=["COLUMN_Az", "column_b", "COLumN_c"])
-    result = batch_for_datasource.validate(expectation)
-    assert not result.success
-
-
-# For most of our tests we use all sql datasources. However, for some of them we exclude
-# snowflake and redshift for the following reasons:
+# For some of our case insensitive tests we break out Snowflake and Redshift into separate
+# tests for the following reasons:
 ##### Snowflake #####
 # In test setup, sqlalchemy's CREATE TABLE will not quote lowercase column names
 # but will quote uppercase. Since snowflake stores case insensitive strings
@@ -169,6 +142,43 @@ def test_case_insensitive_failure(batch_for_datasource: Batch) -> None:
 # looks like it would handle this successfully but I haven't verified since I need to
 # change our redshift CI cluster. There is tracked in GX-1197.
 
+CASE_INSENSITIVE_DATA = pd.DataFrame(
+    {
+        "column_a": [1],
+        "COLUMN_B": [2],
+        "CoLuMn_C": [3],
+    }
+)
+
+
+def observed_column_names(datasource_type: str) -> List[str]:
+    """Returns observed column name base on type in A, B, C order."""
+    if datasource_type == "snowflake":
+        # Since SQLAlchemy doesn't quote lowercase names in CREATE TABLE and since uppercase is
+        # case insenstive in Snowflake, both column_a and column_b will be case insensitive.
+        # SQLAlchemy will return case insensitive names in lowercase when inspecting the table.
+        return [
+            "column_a",
+            "column_b",
+            "CoLuMn_C",
+        ]
+    elif datasource_type == "redshift":
+        # Redshift is case insensitive by default
+        # SQLAlchemy will return case insensitive names in lowercase when inspecting the table.
+        return [
+            "column_a",
+            "column_b",
+            "column_c",
+        ]
+    else:
+        # For most datasources we expected the observed column names to match CASE_INSENSITIVE_DATA.
+        return [
+            "column_a",
+            "COLUMN_B",
+            "CoLuMn_C",
+        ]
+
+
 SQL_DATA_SOURCES_WITHOUT_SNOWFLAKE_REDSHIFT: Sequence[DataSourceTestConfig] = [
     BigQueryDatasourceTestConfig(),
     DatabricksDatasourceTestConfig(),
@@ -189,6 +199,106 @@ def test_sql_data_sources_without_snowflake_redshift() -> None:
         assert datasource in SQL_DATA_SOURCES
 
 
+@parameterize_batch_for_data_sources(
+    data_source_configs=SQL_DATA_SOURCES,
+    data=CASE_INSENSITIVE_DATA,
+)
+def test_case_insensitive_success(batch_for_datasource: Batch) -> None:
+    # Arrange
+    expectation = gxe.ExpectTableColumnsToMatchSet(column_set=["COLUMN_A", "column_b", "COLumN_c"])
+
+    # Act
+    result = batch_for_datasource.validate(expectation)
+    result.render()  # creates rendered content
+
+    # Assert
+    assert result.success
+
+    # Assert rendered content is as expected
+    observed_values = _extract_observed_state(result)
+    assert observed_values == dict(
+        zip(
+            observed_column_names(batch_for_datasource.datasource.type),
+            ["expected", "expected", "expected"],
+        )
+    )
+    expected_values = _extract_expected_state(result)
+    assert expected_values == {"COLUMN_A": None, "column_b": None, "COLumN_c": None}
+
+
+@parameterize_batch_for_data_sources(
+    data_source_configs=SQL_DATA_SOURCES,
+    data=CASE_INSENSITIVE_DATA,
+)
+def test_case_insensitive_failure(batch_for_datasource: Batch) -> None:
+    # Arrange
+    expectation = gxe.ExpectTableColumnsToMatchSet(column_set=["COLUMN_Az", "column_b", "COLumN_c"])
+
+    # Act
+    result = batch_for_datasource.validate(expectation)
+    result.render()  # creates rendered content
+
+    # Assert
+    assert not result.success
+
+    # Assert rendered content is as expected
+    observed_values = _extract_observed_state(result)
+    assert observed_values == dict(
+        zip(
+            observed_column_names(batch_for_datasource.datasource.type),
+            ["unexpected", "expected", "expected"],
+        )
+    )
+    expected_values = _extract_expected_state(result)
+    assert expected_values == {"COLUMN_Az": "missing", "column_b": None, "COLumN_c": None}
+
+
+def _extract_observed_state(result: ExpectationValidationResult) -> Dict[str, str]:
+    """Extracts observed column name to rendered state from validation result"""
+    # ExpectationValidationResult is not narrowly typed so we need to a lot of
+    # isinstance assertions for our expectation.
+    assert isinstance(result.rendered_content, list)
+    assert len(result.rendered_content) == 1
+    value = result.rendered_content[0].to_json_dict()["value"]
+    assert isinstance(value, dict)
+    rendered_params = value["params"]
+    assert isinstance(rendered_params, dict)
+    # We don't use a comprehension because we need to do type assertions with
+    # isinstance calls.
+    observed_state: Dict[str, str] = {}
+    for k, v in rendered_params.items():
+        assert isinstance(v, dict)
+        if k.startswith("ov__"):
+            assert isinstance(v["value"], str)
+            assert isinstance(v["render_state"], Enum)
+            observed_state[v["value"]] = v["render_state"].value
+    return observed_state
+
+
+def _extract_expected_state(result: ExpectationValidationResult) -> Dict[str, Optional[str]]:
+    """Extracts expected column name to rendered state from validation result"""
+    # ExpectationValidationResult is not narrowly typed so we need to a lot of
+    # isinstance assertions for our expectation.
+    assert isinstance(result.rendered_content, list)
+    assert len(result.rendered_content) == 1
+    value = result.rendered_content[0].to_json_dict()["value"]
+    assert isinstance(value, dict)
+    rendered_params = value["params"]
+    assert isinstance(rendered_params, dict)
+    # We don't use a comprehension because we need to do type assertions with
+    # isinstance calls.
+    expected_state: Dict[str, Optional[str]] = {}
+    for k, v in rendered_params.items():
+        assert isinstance(v, dict)
+        if k.startswith("exp__"):
+            assert isinstance(v["value"], str)
+            if "render_state" in v and isinstance(v["render_state"], Enum):
+                expected_state[v["value"]] = v["render_state"].value
+            else:
+                expected_state[v["value"]] = None
+    return expected_state
+
+
 @parameterize_batch_for_data_sources(
     data_source_configs=SQL_DATA_SOURCES_WITHOUT_SNOWFLAKE_REDSHIFT,
     data=CASE_INSENSITIVE_DATA,