pgvet check. Each rule detects a specific SQL anti-pattern.
Quick Reference
| Rule | Severity | Category | Description |
|---|---|---|---|
| select-star | warning | Best Practice | SELECT * is fragile — list columns explicitly |
| limit-without-order | warning | Correctness | LIMIT without ORDER BY is non-deterministic |
| not-in-subquery | error | Correctness | NOT IN (SELECT ...) broken with NULLs |
| for-update-no-skip | warning | Performance | FOR UPDATE without SKIP LOCKED/NOWAIT |
| distinct-on-order | warning | Correctness | DISTINCT ON needs matching ORDER BY |
| null-comparison | error | Correctness | = NULL or <> NULL always yields NULL |
| update-without-where | warning | Safety | UPDATE without WHERE updates all rows |
| delete-without-where | warning | Safety | DELETE without WHERE deletes all rows |
| insert-without-columns | warning | Best Practice | INSERT without column list is fragile |
| ban-char-type | warning | Best Practice | char(n) pads with spaces |
| timestamp-without-timezone | warning | Best Practice | Use timestamptz to preserve timezone |
| order-by-ordinal | warning | Best Practice | ORDER BY 1 is fragile |
| group-by-ordinal | warning | Best Practice | GROUP BY 1 is fragile |
| like-starts-with-wildcard | warning | Performance | LIKE '%...' prevents index usage |
| offset-without-limit | warning | Correctness | OFFSET without LIMIT likely a mistake |
Correctness Rules
not-in-subquery
Rule Details
Rule Details
Name:
not-in-subqueryNOT IN (SELECT ...) is broken when the subquery can return NULLs. PostgreSQL’s NULL semantics mean that if any subquery value is NULL, the entire expression yields NULL (not TRUE or FALSE), causing rows to be incorrectly excluded.Solution: Use NOT EXISTS instead.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
null-comparison
Rule Details
Rule Details
Name:
null-comparisonUsing = NULL or <> NULL always yields NULL (not TRUE or FALSE) in SQL’s three-valued logic. These comparisons will never match any rows.Solution: Use IS NULL or IS NOT NULL.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
limit-without-order
Rule Details
Rule Details
Name:
limit-without-orderUsing LIMIT without ORDER BY produces non-deterministic results — PostgreSQL may return different rows on each execution.Exception: LIMIT 1 is exempted as it’s commonly used for existence checks.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
distinct-on-order
Rule Details
Rule Details
Name:
distinct-on-orderDISTINCT ON without a matching leading ORDER BY produces non-deterministic results. PostgreSQL returns the “first” row for each distinct group, but without ordering, which row is “first” is arbitrary.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
offset-without-limit
Rule Details
Rule Details
Name:
offset-without-limitOFFSET without LIMIT skips rows but then returns all remaining rows. This is rarely intentional and usually indicates a pagination bug.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
Safety Rules
update-without-where
Rule Details
Rule Details
Name:
update-without-whereUPDATE without a WHERE clause updates every row in the table. This is rarely intentional in application code.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
delete-without-where
Rule Details
Rule Details
Name:
delete-without-whereDELETE without a WHERE clause deletes every row in the table. Use TRUNCATE if this is intentional.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
Performance Rules
for-update-no-skip
Rule Details
Rule Details
Name:
for-update-no-skipFOR UPDATE without SKIP LOCKED or NOWAIT can cause lock contention. If another transaction holds a lock, your query will wait indefinitely.Note: Only flags exclusive lock modes (FOR UPDATE and FOR NO KEY UPDATE). FOR SHARE is exempted.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
like-starts-with-wildcard
Rule Details
Rule Details
Name:
like-starts-with-wildcardLIKE or ILIKE patterns starting with % cannot use B-tree indexes, forcing a full table scan.Solution: Use PostgreSQL’s full-text search or trigram indexes for substring searches.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
Best Practice Rules
select-star
Rule Details
Rule Details
Name:
select-starSELECT * in the outermost query is fragile — if table columns change, your application code may break. Explicitly list the columns you need.Note: SELECT * inside CTEs is allowed.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
insert-without-columns
Rule Details
Rule Details
Name:
insert-without-columnsINSERT without an explicit column list depends on column order, which may change if columns are added or reordered.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
ban-char-type
Rule Details
Rule Details
Name:
ban-char-typechar(n) (or character(n)) pads values with spaces to the specified length. This behavior is surprising and rarely desired. Use text or varchar instead.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
timestamp-without-timezone
Rule Details
Rule Details
Name:
timestamp-without-timezonetimestamp without time zone loses timezone context. When users in different timezones interact with your data, this causes ambiguity and bugs.Solution: Use timestamptz (or timestamp with time zone).Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
order-by-ordinal
Rule Details
Rule Details
Name:
order-by-ordinalORDER BY using ordinal positions (e.g., ORDER BY 1) is fragile — if the SELECT list changes, the ordering silently breaks.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples
group-by-ordinal
Rule Details
Rule Details
Name:
group-by-ordinalGROUP BY using ordinal positions (e.g., GROUP BY 1) is fragile — if the SELECT list changes, the grouping silently breaks.Problematic Examples
Problematic Examples
Correct Examples
Correct Examples