Repository: apache/airflow

Issue:
SQLTableCheckOperator fails for Postgres
### Apache Airflow version

2.3.3

### What happened

`SQLTableCheckOperator` fails when used with Postgres.

### What you think should happen instead

From the logs:
```
[2022-08-19, 09:28:14 UTC] {taskinstance.py:1910} ERROR - Task failed with exception
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/airflow/providers/common/sql/operators/sql.py", line 296, in execute
    records = hook.get_first(self.sql)
  File "/usr/local/lib/python3.9/site-packages/airflow/hooks/dbapi.py", line 178, in get_first
    cur.execute(sql)
psycopg2.errors.SyntaxError: subquery in FROM must have an alias
LINE 1: SELECT MIN(row_count_check) FROM (SELECT CASE WHEN COUNT(*) ...
                                         ^
HINT:  For example, FROM (SELECT ...) [AS] foo.
```

### How to reproduce

```python
import pendulum
from datetime import timedelta

from airflow import DAG
from airflow.decorators import task
from airflow.providers.common.sql.operators.sql import SQLTableCheckOperator
from airflow.providers.postgres.operators.postgres import PostgresOperator

_POSTGRES_CONN = "postgresdb"
_TABLE_NAME = "employees"

default_args = {
    "owner": "cs",
    "retries": 3,
    "retry_delay": timedelta(seconds=15),
    }

with DAG(
    dag_id="sql_data_quality",
    start_date=pendulum.datetime(2022, 8, 1, tz="UTC"),
    schedule_interval=None,
) as dag:

    create_table = PostgresOperator(
        task_id="create_table",
        postgres_conn_id=_POSTGRES_CONN,
        sql=f"""
        CREATE TABLE IF NOT EXISTS {_TABLE_NAME} (
            employee_name VARCHAR NOT NULL,
            employment_year INT NOT NULL
        );
        """
    )

    populate_data = PostgresOperator(
        task_id="populate_data",
        postgres_conn_id=_POSTGRES_CONN,
        sql=f"""
            INSERT INTO {_TABLE_NAME} VALUES ('Adam', 2021);
            INSERT INTO {_TABLE_NAME} VALUES ('Chris', 2021);
            INSERT INTO {_TABLE_NAME} VALUES ('Frank', 2021);
            INSERT INTO {_TABLE_NAME} VALUES ('Fritz', 2021);
            INSERT INTO {_TABLE_NAME} VALUES ('Magda', 2022);
            INSERT INTO {_TABLE_NAME} VALUES ('Phil', 2021);
        """,
    )

    check_row_count = SQLTableCheckOperator(
        task_id="check_row_count",
        conn_id=_POSTGRES_CONN,
        table=_TABLE_NAME,
        checks={
            "row_count_check": {"check_statement": "COUNT(*) >= 3"}
        },
    )

    drop_table = PostgresOperator(
        task_id="drop_table",
        trigger_rule="all_done",
        postgres_conn_id=_POSTGRES_CONN,
        sql=f"DROP TABLE {_TABLE_NAME};",
    )

    create_table >> populate_data >> check_row_count >> drop_table
```

### Operating System

macOS

### Versions of Apache Airflow Providers

`apache-airflow-providers-common-sql==1.0.0`

### Deployment

Astronomer

### Deployment details

_No response_

### Anything else

_No response_

### Are you willing to submit PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)



Discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!


Resolving patch:
diff --git a/airflow/providers/common/sql/operators/sql.py b/airflow/providers/common/sql/operators/sql.py
--- a/airflow/providers/common/sql/operators/sql.py
+++ b/airflow/providers/common/sql/operators/sql.py
@@ -350,7 +350,7 @@ class SQLTableCheckOperator(BaseSQLOperator):
 
     sql_check_template = """
         SELECT '_check_name' AS check_name, MIN(_check_name) AS check_result
-        FROM(SELECT CASE WHEN check_statement THEN 1 ELSE 0 END AS _check_name FROM table)
+        FROM (SELECT CASE WHEN check_statement THEN 1 ELSE 0 END AS _check_name FROM table) AS sq
     """
 
     def __init__(
@@ -382,8 +382,10 @@ def execute(self, context: 'Context'):
             ]
         )
         partition_clause_statement = f"WHERE {self.partition_clause}" if self.partition_clause else ""
-        self.sql = f"SELECT check_name, check_result FROM ({checks_sql}) "
-        f"AS check_table {partition_clause_statement};"
+        self.sql = f"""
+            SELECT check_name, check_result FROM ({checks_sql})
+            AS check_table {partition_clause_statement}
+        """
 
         records = hook.get_pandas_df(self.sql)
 


Test patch:
diff --git a/tests/providers/common/sql/operators/test_sql.py b/tests/providers/common/sql/operators/test_sql.py
--- a/tests/providers/common/sql/operators/test_sql.py
+++ b/tests/providers/common/sql/operators/test_sql.py
@@ -128,6 +128,43 @@ def get_pandas_df_return(*arg):
         monkeypatch.setattr(MockHook, "get_pandas_df", get_pandas_df_return)
         return operator
 
+    @pytest.mark.parametrize(
+        ['conn_id'],
+        [
+            pytest.param('postgres_default', marks=[pytest.mark.backend('postgres')]),
+            pytest.param('mysql_default', marks=[pytest.mark.backend('mysql')]),
+        ],
+    )
+    def test_sql_check(self, conn_id):
+        operator = SQLTableCheckOperator(
+            task_id="test_task",
+            table="employees",
+            checks={"row_count_check": {"check_statement": "COUNT(*) >= 3"}},
+            conn_id=conn_id,
+        )
+
+        hook = operator.get_db_hook()
+        hook.run(
+            [
+                """
+                CREATE TABLE IF NOT EXISTS employees (
+                    employee_name VARCHAR(50) NOT NULL,
+                    employment_year INT NOT NULL
+                );
+                """,
+                "INSERT INTO employees VALUES ('Adam', 2021)",
+                "INSERT INTO employees VALUES ('Chris', 2021)",
+                "INSERT INTO employees VALUES ('Frank', 2021)",
+                "INSERT INTO employees VALUES ('Fritz', 2021)",
+                "INSERT INTO employees VALUES ('Magda', 2022)",
+                "INSERT INTO employees VALUES ('Phil', 2021)",
+            ]
+        )
+        try:
+            operator.execute({})
+        finally:
+            hook.run(["DROP TABLE employees"])
+
     def test_pass_all_checks_check(self, monkeypatch):
         df = pd.DataFrame(
             data={