sql-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sql-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Reach for these patterns any time you're writing transformations, aggregations, or analytical queries on a columnar warehouse (BigQuery, Snowflake, Redshift, DuckDB, Spark SQL). The goal is SQL that is readable, correct, and runs efficiently at scale — not just SQL that produces the right answer on a small sample.
Break complex logic into named stages. Each CTE should do one thing and have a name that reads like a sentence fragment explaining what it holds.
WITH
active_users AS (
SELECT user_id, MAX(event_time) AS last_seen
FROM events
WHERE event_date >= DATE_SUB(CURRENT_DATE, INTERVAL 90 DAY)
GROUP BY user_id
),
churned AS (
SELECT u.user_id
FROM users u
LEFT JOIN active_users a USING (user_id)
WHERE a.user_id IS NULL
)
SELECT * FROM churned;Why this matters: Deep subqueries collapse context. A CTE chain lets any reader (including future you) audit each stage independently. Optimizers on modern warehouses inline CTEs anyway — readability is free.
SELECT
event_date,
revenue,
SUM(revenue) OVER (ORDER BY event_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_revenue,
AVG(revenue) OVER (ORDER BY event_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS revenue_7d_avg
FROM daily_revenue;SELECT *
FROM (
SELECT
user_id,
session_id,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY started_at DESC) AS rn
FROM sessions
)
WHERE rn = 1; -- latest session per userUse ROW_NUMBER() when you need exactly one row. Use RANK() when ties should share a rank. Use DENSE_RANK() when there should be no gaps after ties.
SELECT
user_id,
event_date,
LAG(event_date) OVER (PARTITION BY user_id ORDER BY event_date) AS prev_event_date,
DATE_DIFF(event_date, LAG(event_date) OVER (PARTITION BY user_id ORDER BY event_date), DAY) AS days_since_prev
FROM events;Tip: define the window once with a named window clause to avoid repeating it:
SELECT
user_id,
event_date,
LAG(event_date) OVER w AS prev_date,
LEAD(event_date) OVER w AS next_date
FROM events
WINDOW w AS (PARTITION BY user_id ORDER BY event_date);The safest way to deduplicate is ROW_NUMBER(), not DISTINCT — DISTINCT works only when every column is identical.
WITH deduped AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id, event_type ORDER BY created_at DESC) AS rn
FROM raw_events
)
SELECT * EXCEPT (rn) FROM deduped WHERE rn = 1;Always filter before joining. Move WHERE conditions as close to the raw table as possible, ideally inside a CTE.
-- Good: filter before joining
WITH recent_orders AS (
SELECT * FROM orders WHERE order_date >= '2024-01-01'
)
SELECT u.name, o.amount FROM users u JOIN recent_orders o USING (user_id);
-- Bad: filter after join
SELECT u.name, o.amount
FROM users u JOIN orders o USING (user_id)
WHERE o.order_date >= '2024-01-01';Always filter on the partition column when it exists. On BigQuery, this is usually a DATE or TIMESTAMP column declared in PARTITION BY. Without it, the query scans the entire table.
-- Include partition filter to prune scan
WHERE DATE(created_at) BETWEEN '2024-01-01' AND '2024-03-31'SELECT * in productionColumnar warehouses charge/measure by bytes scanned. Name every column you actually need.
APPROX_COUNT_DISTINCT for cardinality estimationWhen exact uniqueness counts aren't required (e.g., dashboards), APPROX_COUNT_DISTINCT is 10-100x faster and within ~1% accuracy.
| Anti-pattern | Why it hurts | Fix |
|---|---|---|
SELECT * in CTEs | Scans unused columns; wastes bytes | Name columns explicitly |
| Correlated subqueries | Executes once per row — O(n) | Rewrite as a JOIN or window function |
Multiple COUNT(DISTINCT) in one query | Forces multiple passes | Use HyperLogLog approx or subquery |
| Non-SARGable predicates | Prevents partition/index use | Avoid CAST or functions on filter columns |
| Implicit type coercion in JOINs | Silent cartesian or missed joins | Ensure join key types match |
ORDER BY without LIMIT | Sorts entire result set for nothing | Remove or add LIMIT |
DATE_TRUNC, TIMESTAMP_TRUNC for time bucketingUNNEST over string splittingQUALIFY for post-window filtering instead of a wrapper CTE when availableFLATTEN for semi-structured JSONQUALIFY ROW_NUMBER() OVER (...) = 1 directly in the SELECTEXCLUDE and REPLACE column modifiers reduce boilerplatePIVOT / UNPIVOT are nativeread_parquet() or read_csv_auto()Before finalizing a query, verify:
ROWS BETWEEN or RANGE BETWEEN when order mattersSELECT * in final output unless prototyping~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.