@@ -13,10 +13,11 @@ Online: https://sqlclean.io/docs/rules/reference/
1313- SQC005: unused CTEs
1414- SQC018: unstable row numbers
1515- SQC019: NULL in NOT IN
16+ - SQC046 to SQC053: queries that cannot do what they say
1617- SQC039: inline subqueries
1718- SQC034, SQC035 and SQC041: query structure
1819
19- SQLClean has 45 built-in Rules. ` Default ` Rules run when ` [rules] select ` is not set; the others
20+ SQLClean has 53 built-in Rules. ` Default ` Rules run when ` [rules] select ` is not set; the others
2021run when ` select ` names them by code or prefix. A Rule with a ` Safe ` fix is fixed by
2122` sqlclean fix ` ; an ` Unsafe ` fix also needs ` --unsafe-fixes ` . See
2223[ Selecting Rules] ( overview.md#selecting-rules ) and
@@ -71,6 +72,14 @@ Run `sqlclean rules <CODE>` for a Rule's remediation.
7172| ` SQC043 ` | Ranking window sorts by more expressions than max_ranking_order_by | No | — |
7273| ` SQC044 ` | String literal is longer than max_literal_length | No | — |
7374| ` SQC045 ` | JSON payload is parsed more than once in one SELECT | No | — |
75+ | ` SQC046 ` | WITH defines the same CTE name more than once | Yes | — |
76+ | ` SQC047 ` | CASE WHEN NULL or IN (NULL) never matches | Yes | — |
77+ | ` SQC048 ` | Expression divides by a literal zero | Yes | — |
78+ | ` SQC049 ` | UPDATE or DELETE has no limiting predicate and changes every row | No | — |
79+ | ` SQC050 ` | INSERT does not list its target columns | No | — |
80+ | ` SQC051 ` | NATURAL JOIN matches on every column the relations share | Yes | — |
81+ | ` SQC052 ` | BETWEEN lower bound is greater than its upper bound | Yes | — |
82+ | ` SQC053 ` | Aggregate or window function is used in WHERE, ON or GROUP BY | Yes | — |
7483
7584` SQC999 ` is the [ parse error] ( overview.md#parse-errors-sqc999 ) finding. It is not a
7685selectable Rule.
@@ -225,6 +234,48 @@ error[SQC019]: NOT IN list contains NULL and can reject every row
225234 = help: Remove NULL from the list or express the anti-join with a NULL-safe NOT EXISTS.
226235```
227236
237+ ## SQC046 to SQC053: queries that cannot do what they say
238+
239+ These Rules report a query that runs differently from how it reads. None of them has a fix,
240+ because only the author knows the intended query.
241+
242+ ``` sql
243+ WITH paid AS (
244+ SELECT order_id, amount FROM orders WHERE amount BETWEEN 100 AND 10 -- SQC052
245+ ),
246+
247+ paid AS ( -- SQC046
248+ SELECT order_id, amount / 0 AS ratio FROM orders -- SQC048
249+ )
250+
251+ SELECT
252+ customer_id,
253+ CASE region WHEN NULL THEN ' unknown' END AS region_label -- SQC047
254+ FROM paid
255+ NATURAL JOIN customers -- SQC051
256+ WHERE SUM (amount) > 100 -- SQC053
257+ GROUP BY customer_id
258+ ```
259+
260+ - ` SQC046 ` compares CTE names within one ` WITH ` the way the dialect resolves them, so quoted
261+ ` "paid" ` and ` "PAID" ` are different names in PostgreSQL but the same name in DuckDB. A nested
262+ ` WITH ` may reuse an outer name.
263+ - ` SQC047 ` reports ` CASE x WHEN NULL ` and ` x IN (NULL) ` , which compare with ` = ` and so never
264+ match. ` NOT IN ` lists with NULL are ` SQC019 ` .
265+ - ` SQC048 ` reports ` / ` , ` % ` , ` // ` and ` MOD ` with a literal zero divisor. Depending on the dialect
266+ the query fails or returns NULL.
267+ - ` SQC052 ` compares number and ` DATE 'YYYY-MM-DD' ` bounds exactly and skips ` BETWEEN SYMMETRIC ` .
268+ A reversed ` NOT BETWEEN ` is reported too, because it matches every non-NULL value.
269+ - ` SQC053 ` reports aggregates in ` WHERE ` , ` ON ` and ` GROUP BY ` , and window functions anywhere in
270+ those clauses. Aggregates in ` HAVING ` , windows in ` QUALIFY ` , ` FILTER (WHERE ...) ` and
271+ subqueries pass.
272+
273+ Two write-safety Rules are opt-in, because changing every row is sometimes intended:
274+
275+ - ` SQC049 ` reports an ` UPDATE ` or ` DELETE ` whose rows nothing limits. A ` WHERE ` without a column
276+ or subquery, such as ` WHERE TRUE ` , does not count; a join condition or a ` LIMIT ` does.
277+ - ` SQC050 ` reports ` INSERT ` without a column list. ` DEFAULT VALUES ` and DuckDB's ` BY NAME ` pass.
278+
228279## SQC039: inline subqueries
229280
230281A subquery in ` FROM ` hides a step of the query. Name it as a CTE instead:
0 commit comments