diff --git a/sqlglot/transforms.py b/sqlglot/transforms.py index 639c0c19b2..1063bbe33a 100644 --- a/sqlglot/transforms.py +++ b/sqlglot/transforms.py @@ -606,7 +606,7 @@ def epoch_cast_to_ts(expression: exp.Expr) -> exp.Expr: def eliminate_semi_and_anti_joins(expression: exp.Expr) -> exp.Expr: """Convert SEMI and ANTI joins into equivalent forms that use EXIST instead.""" if isinstance(expression, exp.Select): - for join in expression.args.get("joins") or []: + for join in list(expression.args.get("joins") or []): on = join.args.get("on") if on and join.kind in ("SEMI", "ANTI"): subquery = exp.select("1").from_(join.this).where(on) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 251b3ec846..69bc146695 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,6 +5,7 @@ eliminate_distinct_on, eliminate_join_marks, eliminate_qualify, + eliminate_semi_and_anti_joins, eliminate_window_clause, inherit_struct_field_names, remove_precision_parameterized_types, @@ -279,6 +280,33 @@ def test_eliminate_join_marks(self): AssertionError, eliminate_join_marks, parse_one(script, dialect=dialect) ) + def test_eliminate_semi_and_anti_joins(self): + self.validate( + eliminate_semi_and_anti_joins, + "SELECT t1.id FROM t1 JOIN t2 ON t1.id = t2.id ANTI JOIN t3 ON t1.id = t3.id", + "SELECT t1.id FROM t1 JOIN t2 ON t1.id = t2.id WHERE NOT EXISTS(SELECT 1 FROM t3 WHERE t1.id = t3.id)", + ) + self.validate( + eliminate_semi_and_anti_joins, + "SELECT t1.id FROM t1 JOIN t2 ON t1.id = t2.id SEMI JOIN t3 ON t1.id = t3.id", + "SELECT t1.id FROM t1 JOIN t2 ON t1.id = t2.id WHERE EXISTS(SELECT 1 FROM t3 WHERE t1.id = t3.id)", + ) + self.validate( + eliminate_semi_and_anti_joins, + "SELECT t1.id FROM t1 ANTI JOIN t2 ON t1.id = t2.id ANTI JOIN t3 ON t1.id = t3.id", + "SELECT t1.id FROM t1 WHERE NOT EXISTS(SELECT 1 FROM t2 WHERE t1.id = t2.id) AND NOT EXISTS(SELECT 1 FROM t3 WHERE t1.id = t3.id)", + ) + self.validate( + eliminate_semi_and_anti_joins, + "SELECT t1.id FROM t1 ANTI JOIN t2 ON t1.id = t2.id ANTI JOIN t3 ON t1.id = t3.id ANTI JOIN t4 ON t1.id = t4.id", + "SELECT t1.id FROM t1 WHERE (NOT EXISTS(SELECT 1 FROM t2 WHERE t1.id = t2.id) AND NOT EXISTS(SELECT 1 FROM t3 WHERE t1.id = t3.id)) AND NOT EXISTS(SELECT 1 FROM t4 WHERE t1.id = t4.id)", + ) + self.validate( + eliminate_semi_and_anti_joins, + "SELECT t1.id FROM t1 SEMI JOIN t2 ON t1.id = t2.id SEMI JOIN t3 ON t1.id = t3.id", + "SELECT t1.id FROM t1 WHERE EXISTS(SELECT 1 FROM t2 WHERE t1.id = t2.id) AND EXISTS(SELECT 1 FROM t3 WHERE t1.id = t3.id)", + ) + def test_eliminate_window_clause(self): self.validate( eliminate_window_clause,