@evertonferreira96/agents 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/api.md +45 -0
- package/.claude/agents/app.md +41 -0
- package/.claude/agents/architect.md +49 -0
- package/.claude/agents/infra.md +27 -0
- package/.claude/agents/product.md +58 -0
- package/.claude/agents/quick.md +33 -0
- package/.claude/agents/security.md +39 -0
- package/.claude/agents/ui.md +54 -0
- package/.claude/agents/verifier.md +56 -0
- package/.claude/agents/web.md +41 -0
- package/.claude/hooks/config-update-check.mjs +14 -0
- package/.claude/hooks/context-budget.mjs +51 -0
- package/.claude/hooks/graphify-update.sh +16 -0
- package/.claude/hooks/guard-bash.sh +64 -0
- package/.claude/hooks/orchestrator-guard.sh +49 -0
- package/.claude/hooks/post-compact.sh +26 -0
- package/.claude/hooks/readonly-guard.sh +54 -0
- package/.claude/hooks/stop-verify.sh +52 -0
- package/.claude/rules/api.md +21 -0
- package/.claude/rules/db.md +9 -0
- package/.claude/rules/delivery-diagram.md +16 -0
- package/.claude/rules/git-workflow.md +34 -0
- package/.claude/rules/graphify.md +3 -0
- package/.claude/rules/mobile.md +22 -0
- package/.claude/rules/tickets.md +9 -0
- package/.claude/rules/ui.md +14 -0
- package/.claude/rules/web.md +27 -0
- package/.claude/settings.json +114 -0
- package/.claude/skills/animate/RECIPES.md +324 -0
- package/.claude/skills/animate/SKILL.md +208 -0
- package/.claude/skills/animate/agents/openai.yaml +5 -0
- package/.claude/skills/animate-expo/RECIPES.md +385 -0
- package/.claude/skills/animate-expo/SKILL.md +264 -0
- package/.claude/skills/animate-expo/agents/openai.yaml +5 -0
- package/.claude/skills/animation-performance/SKILL.md +223 -0
- package/.claude/skills/animation-performance/agents/openai.yaml +5 -0
- package/.claude/skills/apple-design/SKILL.md +291 -0
- package/.claude/skills/apple-design/agents/openai.yaml +5 -0
- package/.claude/skills/arch-api/SKILL.md +291 -0
- package/.claude/skills/arch-api/agents/openai.yaml +3 -0
- package/.claude/skills/arch-api/references/common.md +174 -0
- package/.claude/skills/arch-api/references/contract.md +289 -0
- package/.claude/skills/arch-api/references/domain.md +301 -0
- package/.claude/skills/arch-api/references/endpoints.md +184 -0
- package/.claude/skills/arch-api/references/review.md +228 -0
- package/.claude/skills/arch-api/references/security.md +151 -0
- package/.claude/skills/arch-app/SKILL.md +278 -0
- package/.claude/skills/arch-app/agents/openai.yaml +3 -0
- package/.claude/skills/arch-app/references/navigation-session.md +257 -0
- package/.claude/skills/arch-app/references/review.md +155 -0
- package/.claude/skills/arch-app/references/screens.md +214 -0
- package/.claude/skills/arch-app/references/services.md +301 -0
- package/.claude/skills/arch-app/references/ui-mobile.md +124 -0
- package/.claude/skills/arch-db/SKILL.md +181 -0
- package/.claude/skills/arch-db/agents/openai.yaml +3 -0
- package/.claude/skills/arch-db/references/indexes-queries.md +251 -0
- package/.claude/skills/arch-db/references/migrations.md +191 -0
- package/.claude/skills/arch-db/references/modeling.md +214 -0
- package/.claude/skills/arch-db/references/mysql.md +262 -0
- package/.claude/skills/arch-db/references/pooling-ops.md +112 -0
- package/.claude/skills/arch-db/references/review.md +143 -0
- package/.claude/skills/arch-db/references/transactions-locks.md +141 -0
- package/.claude/skills/arch-web/SKILL.md +307 -0
- package/.claude/skills/arch-web/agents/openai.yaml +3 -0
- package/.claude/skills/arch-web/references/auth-session-env.md +165 -0
- package/.claude/skills/arch-web/references/blocks.md +198 -0
- package/.claude/skills/arch-web/references/cache.md +124 -0
- package/.claude/skills/arch-web/references/review.md +142 -0
- package/.claude/skills/arch-web/references/services.md +297 -0
- package/.claude/skills/arch-web/references/tests.md +159 -0
- package/.claude/skills/arch-web/references/ui-forms.md +143 -0
- package/.claude/skills/break-ui/CATALOG.md +135 -0
- package/.claude/skills/break-ui/SKILL.md +176 -0
- package/.claude/skills/break-ui/agents/openai.yaml +5 -0
- package/.claude/skills/brief/SKILL.md +91 -0
- package/.claude/skills/brief/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/SKILL.md +138 -0
- package/.claude/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/.claude/skills/emil-design-eng/SKILL.md +675 -0
- package/.claude/skills/emil-design-eng/agents/openai.yaml +5 -0
- package/.claude/skills/find-animation-opportunities/SKILL.md +141 -0
- package/.claude/skills/find-animation-opportunities/agents/openai.yaml +5 -0
- package/.claude/skills/graphify/.graphify_version +1 -0
- package/.claude/skills/graphify/SKILL.md +742 -0
- package/.claude/skills/graphify/agents/openai.yaml +3 -0
- package/.claude/skills/graphify/references/add-watch.md +56 -0
- package/.claude/skills/graphify/references/exports.md +91 -0
- package/.claude/skills/graphify/references/extraction-spec.md +70 -0
- package/.claude/skills/graphify/references/github-and-merge.md +46 -0
- package/.claude/skills/graphify/references/hooks.md +33 -0
- package/.claude/skills/graphify/references/query.md +317 -0
- package/.claude/skills/graphify/references/transcribe.md +52 -0
- package/.claude/skills/graphify/references/update.md +221 -0
- package/.claude/skills/grill-me/SKILL.md +7 -0
- package/.claude/skills/grill-me/agents/openai.yaml +5 -0
- package/.claude/skills/grill-with-docs/SKILL.md +64 -0
- package/.claude/skills/grill-with-docs/agents/openai.yaml +5 -0
- package/.claude/skills/grilling/SKILL.md +30 -0
- package/.claude/skills/grilling/agents/openai.yaml +3 -0
- package/.claude/skills/handoff/SKILL.md +28 -0
- package/.claude/skills/handoff/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/SKILL.md +89 -0
- package/.claude/skills/impeccable/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/reference/adapt.md +318 -0
- package/.claude/skills/impeccable/reference/adapt.native.md +58 -0
- package/.claude/skills/impeccable/reference/android.md +46 -0
- package/.claude/skills/impeccable/reference/animate.md +89 -0
- package/.claude/skills/impeccable/reference/audit.md +137 -0
- package/.claude/skills/impeccable/reference/audit.native.md +139 -0
- package/.claude/skills/impeccable/reference/bolder.md +33 -0
- package/.claude/skills/impeccable/reference/clarify.md +94 -0
- package/.claude/skills/impeccable/reference/colorize.md +86 -0
- package/.claude/skills/impeccable/reference/component-review.md +63 -0
- package/.claude/skills/impeccable/reference/craft-floor.md +44 -0
- package/.claude/skills/impeccable/reference/craft.md +5 -0
- package/.claude/skills/impeccable/reference/critique.md +809 -0
- package/.claude/skills/impeccable/reference/degraded/asset-producer.md +42 -0
- package/.claude/skills/impeccable/reference/degraded/documenter.md +24 -0
- package/.claude/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/.claude/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/.claude/skills/impeccable/reference/delight.md +70 -0
- package/.claude/skills/impeccable/reference/distill.md +111 -0
- package/.claude/skills/impeccable/reference/doctor.md +54 -0
- package/.claude/skills/impeccable/reference/document.md +416 -0
- package/.claude/skills/impeccable/reference/extract.md +69 -0
- package/.claude/skills/impeccable/reference/generate.md +101 -0
- package/.claude/skills/impeccable/reference/harden.md +345 -0
- package/.claude/skills/impeccable/reference/hooks.md +113 -0
- package/.claude/skills/impeccable/reference/init.md +131 -0
- package/.claude/skills/impeccable/reference/ios.md +51 -0
- package/.claude/skills/impeccable/reference/layout.md +84 -0
- package/.claude/skills/impeccable/reference/live-setup.md +104 -0
- package/.claude/skills/impeccable/reference/live.md +325 -0
- package/.claude/skills/impeccable/reference/mode-operate.md +21 -0
- package/.claude/skills/impeccable/reference/mode-persuade.md +19 -0
- package/.claude/skills/impeccable/reference/mode-read.md +21 -0
- package/.claude/skills/impeccable/reference/new-work.md +154 -0
- package/.claude/skills/impeccable/reference/onboard.md +234 -0
- package/.claude/skills/impeccable/reference/operate.md +61 -0
- package/.claude/skills/impeccable/reference/optimize.md +258 -0
- package/.claude/skills/impeccable/reference/overdrive.md +127 -0
- package/.claude/skills/impeccable/reference/polish.md +105 -0
- package/.claude/skills/impeccable/reference/quieter.md +99 -0
- package/.claude/skills/impeccable/reference/region-map.md +26 -0
- package/.claude/skills/impeccable/reference/routing.md +24 -0
- package/.claude/skills/impeccable/reference/shape.md +59 -0
- package/.claude/skills/impeccable/reference/typeset.md +80 -0
- package/.claude/skills/impeccable/reference/visualize.md +47 -0
- package/.claude/skills/impeccable/scripts/VERSION +1 -0
- package/.claude/skills/impeccable/scripts/command-metadata.json +98 -0
- package/.claude/skills/impeccable/scripts/data/font-index-failures.json +121 -0
- package/.claude/skills/impeccable/scripts/data/font-index.json +1 -0
- package/.claude/skills/impeccable/scripts/impeccable +206 -0
- package/.claude/skills/impeccable/scripts/impeccable.cmd +214 -0
- package/.claude/skills/impeccable/scripts/live-browser-dom.js +281 -0
- package/.claude/skills/impeccable/scripts/live-browser-ignores.js +242 -0
- package/.claude/skills/impeccable/scripts/live-browser-session.js +148 -0
- package/.claude/skills/impeccable/scripts/live-browser.js +13532 -0
- package/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/.claude/skills/improve-animations/AUDIT.md +115 -0
- package/.claude/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/.claude/skills/improve-animations/SKILL.md +110 -0
- package/.claude/skills/improve-animations/agents/openai.yaml +5 -0
- package/.claude/skills/mobile-native/SKILL.md +311 -0
- package/.claude/skills/mobile-native/agents/openai.yaml +3 -0
- package/.claude/skills/orchestrate/SKILL.md +181 -0
- package/.claude/skills/orchestrate/agents/openai.yaml +3 -0
- package/.claude/skills/pick-ui-library/SKILL.md +85 -0
- package/.claude/skills/pick-ui-library/agents/openai.yaml +5 -0
- package/.claude/skills/ponytail/SKILL.md +52 -0
- package/.claude/skills/ponytail/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-audit/SKILL.md +103 -0
- package/.claude/skills/ponytail-audit/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-debt/SKILL.md +44 -0
- package/.claude/skills/ponytail-debt/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-review/SKILL.md +101 -0
- package/.claude/skills/ponytail-review/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/SKILL.md +42 -0
- package/.claude/skills/postgres-best-practices/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/references/backup-restore.md +372 -0
- package/.claude/skills/postgres-best-practices/references/bulk-loading.md +315 -0
- package/.claude/skills/postgres-best-practices/references/connection-pooling.md +310 -0
- package/.claude/skills/postgres-best-practices/references/hot-standby.md +221 -0
- package/.claude/skills/postgres-best-practices/references/indexing.md +318 -0
- package/.claude/skills/postgres-best-practices/references/logical-replication.md +610 -0
- package/.claude/skills/postgres-best-practices/references/major-version-upgrades.md +334 -0
- package/.claude/skills/postgres-best-practices/references/performance-diagnostics.md +553 -0
- package/.claude/skills/postgres-best-practices/references/query-optimization.md +294 -0
- package/.claude/skills/postgres-best-practices/references/query-patterns.md +566 -0
- package/.claude/skills/postgres-best-practices/references/schema-design.md +362 -0
- package/.claude/skills/postgres-best-practices/references/security-roles.md +489 -0
- package/.claude/skills/postgres-best-practices/references/transaction-isolation.md +344 -0
- package/.claude/skills/project-docs/SKILL.md +330 -0
- package/.claude/skills/project-docs/agents/openai.yaml +3 -0
- package/.claude/skills/prototype/PICKER.md +197 -0
- package/.claude/skills/prototype/SKILL.md +98 -0
- package/.claude/skills/prototype/agents/openai.yaml +5 -0
- package/.claude/skills/retro/SKILL.md +43 -0
- package/.claude/skills/retro/agents/openai.yaml +5 -0
- package/.claude/skills/review/SKILL.md +74 -0
- package/.claude/skills/review/agents/openai.yaml +3 -0
- package/.claude/skills/review/references/smells.md +23 -0
- package/.claude/skills/review-animations/SKILL.md +120 -0
- package/.claude/skills/review-animations/STANDARDS.md +187 -0
- package/.claude/skills/review-animations/agents/openai.yaml +5 -0
- package/.claude/skills/tdd/SKILL.md +38 -0
- package/.claude/skills/tdd/agents/openai.yaml +3 -0
- package/.claude/skills/tdd/mocking.md +59 -0
- package/.claude/skills/tdd/tests.md +77 -0
- package/.claude/skills/triage/SKILL.md +58 -0
- package/.claude/skills/triage/agents/openai.yaml +3 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +378 -0
- package/.claude/skills/ui-ux-pro-max/agents/openai.yaml +5 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/.claude/skills/writing-for-agents/SKILL.md +81 -0
- package/.claude/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/.worktreeinclude +3 -0
- package/AGENTS.md +44 -0
- package/README.md +14 -2
- package/VERSION +1 -0
- package/bin/agents.mjs +45 -0
- package/docs/adr/README.md +31 -0
- package/docs/brief-template.md +3 -0
- package/docs/plans/README.md +73 -0
- package/package.json +46 -4
- package/scripts/bootstrap.mjs +188 -0
- package/scripts/check-catalog.mjs +43 -0
- package/scripts/skills-update.mjs +51 -0
- package/skills-lock.json +269 -0
- package/templates/CLAUDE.project.md +41 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# SQL Query Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Common Table Expressions (CTEs)
|
|
5
|
+
- Window functions
|
|
6
|
+
- Lateral joins
|
|
7
|
+
- Recursive queries
|
|
8
|
+
- UPSERT (INSERT ON CONFLICT)
|
|
9
|
+
- Bulk operations
|
|
10
|
+
- JSONB queries
|
|
11
|
+
- Date/time patterns
|
|
12
|
+
- Anti-patterns to avoid
|
|
13
|
+
|
|
14
|
+
## Common Table Expressions (CTEs)
|
|
15
|
+
|
|
16
|
+
### Readability CTEs
|
|
17
|
+
|
|
18
|
+
```sql
|
|
19
|
+
WITH active_customers AS (
|
|
20
|
+
SELECT id, name, email
|
|
21
|
+
FROM customers
|
|
22
|
+
WHERE status = 'active'
|
|
23
|
+
),
|
|
24
|
+
recent_orders AS (
|
|
25
|
+
SELECT customer_id, count(*) AS order_count, max(created_at) AS last_order
|
|
26
|
+
FROM orders
|
|
27
|
+
WHERE created_at > now() - interval '90 days'
|
|
28
|
+
GROUP BY customer_id
|
|
29
|
+
)
|
|
30
|
+
SELECT ac.name, ac.email, ro.order_count, ro.last_order
|
|
31
|
+
FROM active_customers ac
|
|
32
|
+
JOIN recent_orders ro ON ro.customer_id = ac.id
|
|
33
|
+
ORDER BY ro.order_count DESC;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### CTE Materialization
|
|
37
|
+
|
|
38
|
+
By default the optimizer may inline CTEs. Force materialization when:
|
|
39
|
+
- The CTE is referenced multiple times
|
|
40
|
+
- You want to create an optimization fence
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
WITH expensive_calc AS MATERIALIZED (
|
|
44
|
+
SELECT ... -- complex aggregation
|
|
45
|
+
)
|
|
46
|
+
SELECT * FROM expensive_calc WHERE ...
|
|
47
|
+
UNION ALL
|
|
48
|
+
SELECT * FROM expensive_calc WHERE ...;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Force inlining (default for single-use):
|
|
52
|
+
```sql
|
|
53
|
+
WITH simple_filter AS NOT MATERIALIZED (
|
|
54
|
+
SELECT * FROM large_table WHERE status = 'active'
|
|
55
|
+
)
|
|
56
|
+
SELECT * FROM simple_filter WHERE created_at > '2024-01-01';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Window Functions
|
|
60
|
+
|
|
61
|
+
### Ranking
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
-- Row number (no ties)
|
|
65
|
+
SELECT *, row_number() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
|
|
66
|
+
FROM employees;
|
|
67
|
+
|
|
68
|
+
-- Rank (ties get same rank, gaps after)
|
|
69
|
+
SELECT *, rank() OVER (ORDER BY score DESC) AS rank
|
|
70
|
+
FROM leaderboard;
|
|
71
|
+
|
|
72
|
+
-- Dense rank (no gaps)
|
|
73
|
+
SELECT *, dense_rank() OVER (ORDER BY score DESC) AS dense_rank
|
|
74
|
+
FROM leaderboard;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Running Totals and Moving Averages
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
SELECT
|
|
81
|
+
date,
|
|
82
|
+
revenue,
|
|
83
|
+
sum(revenue) OVER (ORDER BY date) AS running_total,
|
|
84
|
+
avg(revenue) OVER (
|
|
85
|
+
ORDER BY date
|
|
86
|
+
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
|
87
|
+
) AS moving_avg_7d
|
|
88
|
+
FROM daily_revenue;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Lead/Lag (Access Adjacent Rows)
|
|
92
|
+
|
|
93
|
+
```sql
|
|
94
|
+
SELECT
|
|
95
|
+
event_time,
|
|
96
|
+
event_type,
|
|
97
|
+
lag(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS prev_event,
|
|
98
|
+
event_time - lag(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS time_since_last
|
|
99
|
+
FROM user_events;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### First/Last Value
|
|
103
|
+
|
|
104
|
+
```sql
|
|
105
|
+
SELECT DISTINCT
|
|
106
|
+
department,
|
|
107
|
+
first_value(name) OVER (
|
|
108
|
+
PARTITION BY department ORDER BY salary DESC
|
|
109
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
110
|
+
) AS highest_paid
|
|
111
|
+
FROM employees;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Gaps and Islands
|
|
115
|
+
|
|
116
|
+
Find consecutive sequences:
|
|
117
|
+
|
|
118
|
+
```sql
|
|
119
|
+
WITH numbered AS (
|
|
120
|
+
SELECT *,
|
|
121
|
+
date - (row_number() OVER (PARTITION BY user_id ORDER BY date))::int * interval '1 day' AS grp
|
|
122
|
+
FROM daily_logins
|
|
123
|
+
)
|
|
124
|
+
SELECT user_id, min(date) AS streak_start, max(date) AS streak_end,
|
|
125
|
+
count(*) AS streak_length
|
|
126
|
+
FROM numbered
|
|
127
|
+
GROUP BY user_id, grp
|
|
128
|
+
HAVING count(*) >= 7; -- streaks of 7+ days
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Lateral Joins
|
|
132
|
+
|
|
133
|
+
LATERAL lets a subquery reference columns from preceding tables. Essential for "top-N per group" queries.
|
|
134
|
+
|
|
135
|
+
### Top-N Per Group
|
|
136
|
+
|
|
137
|
+
```sql
|
|
138
|
+
-- Get latest 3 orders per customer
|
|
139
|
+
SELECT c.name, o.*
|
|
140
|
+
FROM customers c
|
|
141
|
+
CROSS JOIN LATERAL (
|
|
142
|
+
SELECT id, total, created_at
|
|
143
|
+
FROM orders
|
|
144
|
+
WHERE customer_id = c.id
|
|
145
|
+
ORDER BY created_at DESC
|
|
146
|
+
LIMIT 3
|
|
147
|
+
) o;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This is much faster than window function approaches when you have an index on `orders(customer_id, created_at DESC)`.
|
|
151
|
+
|
|
152
|
+
### Calling Set-Returning Functions
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
SELECT j.id, elem.key, elem.value
|
|
156
|
+
FROM journal_entries j
|
|
157
|
+
CROSS JOIN LATERAL jsonb_each_text(j.metadata) AS elem;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Recursive Queries
|
|
161
|
+
|
|
162
|
+
### Tree Traversal (Adjacency List)
|
|
163
|
+
|
|
164
|
+
```sql
|
|
165
|
+
WITH RECURSIVE tree AS (
|
|
166
|
+
-- Base case: root nodes
|
|
167
|
+
SELECT id, name, parent_id, 0 AS depth, ARRAY[id] AS path
|
|
168
|
+
FROM categories
|
|
169
|
+
WHERE parent_id IS NULL
|
|
170
|
+
|
|
171
|
+
UNION ALL
|
|
172
|
+
|
|
173
|
+
-- Recursive step
|
|
174
|
+
SELECT c.id, c.name, c.parent_id, t.depth + 1, t.path || c.id
|
|
175
|
+
FROM categories c
|
|
176
|
+
JOIN tree t ON t.id = c.parent_id
|
|
177
|
+
WHERE NOT c.id = ANY(t.path) -- cycle prevention
|
|
178
|
+
)
|
|
179
|
+
SELECT * FROM tree ORDER BY path;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Generating Series
|
|
183
|
+
|
|
184
|
+
```sql
|
|
185
|
+
-- Date series for gap-filling
|
|
186
|
+
SELECT d::date, coalesce(o.total, 0) AS total
|
|
187
|
+
FROM generate_series('2024-01-01'::date, '2024-12-31'::date, '1 day') AS d
|
|
188
|
+
LEFT JOIN (
|
|
189
|
+
SELECT created_at::date AS day, sum(total) AS total
|
|
190
|
+
FROM orders
|
|
191
|
+
GROUP BY 1
|
|
192
|
+
) o ON o.day = d;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## UPSERT (INSERT ON CONFLICT)
|
|
196
|
+
|
|
197
|
+
### Basic Upsert
|
|
198
|
+
|
|
199
|
+
```sql
|
|
200
|
+
INSERT INTO user_settings (user_id, key, value)
|
|
201
|
+
VALUES (1, 'theme', 'dark')
|
|
202
|
+
ON CONFLICT (user_id, key)
|
|
203
|
+
DO UPDATE SET
|
|
204
|
+
value = EXCLUDED.value,
|
|
205
|
+
updated_at = now();
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Insert-or-Ignore
|
|
209
|
+
|
|
210
|
+
```sql
|
|
211
|
+
INSERT INTO tags (name)
|
|
212
|
+
VALUES ('postgres'), ('sql'), ('database')
|
|
213
|
+
ON CONFLICT (name) DO NOTHING;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Upsert with Conditional Update
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
INSERT INTO inventory (sku, quantity, last_restock)
|
|
220
|
+
VALUES ('ABC-123', 50, now())
|
|
221
|
+
ON CONFLICT (sku)
|
|
222
|
+
DO UPDATE SET
|
|
223
|
+
quantity = inventory.quantity + EXCLUDED.quantity,
|
|
224
|
+
last_restock = EXCLUDED.last_restock
|
|
225
|
+
WHERE inventory.quantity < 100; -- only restock if low
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Upsert Returning
|
|
229
|
+
|
|
230
|
+
```sql
|
|
231
|
+
INSERT INTO users (email, name)
|
|
232
|
+
VALUES ('a@b.com', 'Alice')
|
|
233
|
+
ON CONFLICT (email)
|
|
234
|
+
DO UPDATE SET name = EXCLUDED.name
|
|
235
|
+
RETURNING id;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Do not use `(xmax = 0)` as an inserted-versus-updated contract; it relies on implementation details. On PG18+, use `OLD`/`NEW` in `RETURNING`. On earlier versions, distinguish outcomes in application logic or use separate statements when the distinction matters.
|
|
239
|
+
|
|
240
|
+
### OLD/NEW in RETURNING (PG18+)
|
|
241
|
+
|
|
242
|
+
All DML commands support `OLD` and `NEW` references in RETURNING, making it easy to see before/after values:
|
|
243
|
+
|
|
244
|
+
```sql
|
|
245
|
+
UPDATE products SET price = price * 1.10
|
|
246
|
+
WHERE category = 'electronics'
|
|
247
|
+
RETURNING id, old.price AS old_price, new.price AS new_price;
|
|
248
|
+
|
|
249
|
+
DELETE FROM sessions WHERE expired_at < now()
|
|
250
|
+
RETURNING old.*;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## MERGE (PG15+)
|
|
254
|
+
|
|
255
|
+
SQL-standard command for conditional INSERT, UPDATE, or DELETE in a single statement. Replaces UPSERT for complex cases where you need different actions based on whether the row exists.
|
|
256
|
+
|
|
257
|
+
```sql
|
|
258
|
+
MERGE INTO inventory AS target
|
|
259
|
+
USING incoming_shipment AS source
|
|
260
|
+
ON target.sku = source.sku
|
|
261
|
+
WHEN MATCHED AND source.quantity = 0 THEN
|
|
262
|
+
DELETE
|
|
263
|
+
WHEN MATCHED THEN
|
|
264
|
+
UPDATE SET quantity = target.quantity + source.quantity,
|
|
265
|
+
last_restock = now()
|
|
266
|
+
WHEN NOT MATCHED THEN
|
|
267
|
+
INSERT (sku, quantity, last_restock)
|
|
268
|
+
VALUES (source.sku, source.quantity, now());
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### MERGE RETURNING (PG17+)
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
MERGE INTO inventory AS target
|
|
275
|
+
USING incoming_shipment AS source
|
|
276
|
+
ON target.sku = source.sku
|
|
277
|
+
WHEN MATCHED THEN
|
|
278
|
+
UPDATE SET quantity = target.quantity + source.quantity
|
|
279
|
+
WHEN NOT MATCHED THEN
|
|
280
|
+
INSERT (sku, quantity) VALUES (source.sku, source.quantity)
|
|
281
|
+
RETURNING merge_action(), target.*;
|
|
282
|
+
-- merge_action() returns 'INSERT', 'UPDATE', or 'DELETE'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**When to use MERGE vs UPSERT**: Use `INSERT ON CONFLICT` for simple insert-or-update on a single table. Use `MERGE` when you need different actions based on conditions, when the source is another table/query, or when you need DELETE as one of the outcomes.
|
|
286
|
+
|
|
287
|
+
## JSON_TABLE (PG17+)
|
|
288
|
+
|
|
289
|
+
Convert JSON data to a relational table, usable in FROM:
|
|
290
|
+
|
|
291
|
+
```sql
|
|
292
|
+
SELECT jt.*
|
|
293
|
+
FROM events,
|
|
294
|
+
JSON_TABLE(
|
|
295
|
+
payload, '$'
|
|
296
|
+
COLUMNS (
|
|
297
|
+
event_type text PATH '$.type',
|
|
298
|
+
city text PATH '$.address.city',
|
|
299
|
+
score int PATH '$.score'
|
|
300
|
+
)
|
|
301
|
+
) AS jt
|
|
302
|
+
WHERE jt.event_type = 'click';
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Bulk Operations
|
|
306
|
+
|
|
307
|
+
### Bulk Insert from Values
|
|
308
|
+
|
|
309
|
+
```sql
|
|
310
|
+
INSERT INTO products (name, price, category)
|
|
311
|
+
VALUES
|
|
312
|
+
('Widget A', 9.99, 'gadgets'),
|
|
313
|
+
('Widget B', 14.99, 'gadgets'),
|
|
314
|
+
('Widget C', 19.99, 'gadgets');
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Bulk Update with FROM
|
|
318
|
+
|
|
319
|
+
```sql
|
|
320
|
+
UPDATE products p
|
|
321
|
+
SET price = new_prices.price
|
|
322
|
+
FROM (VALUES
|
|
323
|
+
(1, 12.99),
|
|
324
|
+
(2, 15.99),
|
|
325
|
+
(3, 22.99)
|
|
326
|
+
) AS new_prices(id, price)
|
|
327
|
+
WHERE p.id = new_prices.id;
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Bulk Delete with Subquery
|
|
331
|
+
|
|
332
|
+
```sql
|
|
333
|
+
DELETE FROM sessions
|
|
334
|
+
WHERE id IN (
|
|
335
|
+
SELECT id FROM sessions
|
|
336
|
+
WHERE last_active < now() - interval '30 days'
|
|
337
|
+
ORDER BY last_active
|
|
338
|
+
LIMIT 10000 -- batch to avoid long locks
|
|
339
|
+
);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## JSONB Queries
|
|
343
|
+
|
|
344
|
+
### Querying JSONB
|
|
345
|
+
|
|
346
|
+
```sql
|
|
347
|
+
-- Key access
|
|
348
|
+
SELECT payload->>'name' AS name FROM events; -- text
|
|
349
|
+
SELECT payload->'address'->'city' FROM events; -- jsonb
|
|
350
|
+
SELECT payload#>>'{address,city}' FROM events; -- text via path
|
|
351
|
+
|
|
352
|
+
-- Containment (uses GIN index)
|
|
353
|
+
SELECT * FROM events WHERE payload @> '{"type": "click"}';
|
|
354
|
+
|
|
355
|
+
-- Key existence
|
|
356
|
+
SELECT * FROM events WHERE payload ? 'error_code';
|
|
357
|
+
|
|
358
|
+
-- Array element access
|
|
359
|
+
SELECT payload->'tags'->>0 FROM events;
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### JSONB Aggregation
|
|
363
|
+
|
|
364
|
+
```sql
|
|
365
|
+
-- Build JSON object from rows
|
|
366
|
+
SELECT jsonb_object_agg(key, value) FROM settings WHERE user_id = 1;
|
|
367
|
+
|
|
368
|
+
-- Build JSON array from rows
|
|
369
|
+
SELECT jsonb_agg(jsonb_build_object('id', id, 'name', name))
|
|
370
|
+
FROM products
|
|
371
|
+
WHERE category = 'gadgets';
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### JSONB Modification
|
|
375
|
+
|
|
376
|
+
```sql
|
|
377
|
+
-- Set/overwrite a key
|
|
378
|
+
UPDATE events SET payload = payload || '{"processed": true}';
|
|
379
|
+
|
|
380
|
+
-- Remove a key
|
|
381
|
+
UPDATE events SET payload = payload - 'temp_field';
|
|
382
|
+
|
|
383
|
+
-- Set nested key
|
|
384
|
+
UPDATE events SET payload = jsonb_set(payload, '{status,code}', '"200"');
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Date/Time Patterns
|
|
388
|
+
|
|
389
|
+
### JSONB Subscripting (PG14+)
|
|
390
|
+
|
|
391
|
+
Simpler syntax for JSONB access and assignment:
|
|
392
|
+
|
|
393
|
+
```sql
|
|
394
|
+
-- Read JSONB (equivalent to payload->'address'->'city')
|
|
395
|
+
SELECT payload['address']['city'] FROM events;
|
|
396
|
+
|
|
397
|
+
-- Update (equivalent to jsonb_set)
|
|
398
|
+
UPDATE events SET payload['processed'] = 'true' WHERE id = 1;
|
|
399
|
+
UPDATE events SET payload['address']['zip'] = '"90210"' WHERE id = 1;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### date_bin() (PG14+)
|
|
403
|
+
|
|
404
|
+
Bin timestamps into uniform intervals — more flexible than `date_trunc`:
|
|
405
|
+
|
|
406
|
+
```sql
|
|
407
|
+
-- 15-minute bins (date_trunc can only do hour/day/etc.)
|
|
408
|
+
SELECT
|
|
409
|
+
date_bin('15 minutes', created_at, '2024-01-01') AS bin,
|
|
410
|
+
count(*)
|
|
411
|
+
FROM orders
|
|
412
|
+
GROUP BY 1 ORDER BY 1;
|
|
413
|
+
|
|
414
|
+
-- 6-hour bins
|
|
415
|
+
SELECT date_bin('6 hours', occurred_at, '2024-01-01') AS bin, count(*)
|
|
416
|
+
FROM events GROUP BY 1;
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Truncation and Grouping
|
|
420
|
+
|
|
421
|
+
```sql
|
|
422
|
+
SELECT
|
|
423
|
+
date_trunc('month', created_at) AS month,
|
|
424
|
+
count(*) AS orders
|
|
425
|
+
FROM orders
|
|
426
|
+
GROUP BY 1
|
|
427
|
+
ORDER BY 1;
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Interval Arithmetic
|
|
431
|
+
|
|
432
|
+
```sql
|
|
433
|
+
SELECT * FROM subscriptions
|
|
434
|
+
WHERE expires_at BETWEEN now() AND now() + interval '7 days';
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Extract Components
|
|
438
|
+
|
|
439
|
+
```sql
|
|
440
|
+
SELECT
|
|
441
|
+
extract(dow FROM created_at) AS day_of_week, -- 0=Sun, 6=Sat
|
|
442
|
+
extract(hour FROM created_at) AS hour,
|
|
443
|
+
count(*)
|
|
444
|
+
FROM orders
|
|
445
|
+
GROUP BY 1, 2;
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Concurrency Patterns
|
|
449
|
+
|
|
450
|
+
### SKIP LOCKED (Queue Processing)
|
|
451
|
+
|
|
452
|
+
Multiple workers can process a queue table without blocking each other:
|
|
453
|
+
|
|
454
|
+
```sql
|
|
455
|
+
-- Worker claims and processes next N items atomically
|
|
456
|
+
WITH next_batch AS (
|
|
457
|
+
SELECT id FROM job_queue
|
|
458
|
+
WHERE status = 'pending'
|
|
459
|
+
ORDER BY created_at
|
|
460
|
+
LIMIT 10
|
|
461
|
+
FOR UPDATE SKIP LOCKED
|
|
462
|
+
)
|
|
463
|
+
UPDATE job_queue SET status = 'processing', started_at = now()
|
|
464
|
+
WHERE id IN (SELECT id FROM next_batch)
|
|
465
|
+
RETURNING *;
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Deadlock Prevention
|
|
469
|
+
|
|
470
|
+
Always acquire row locks in a consistent order (e.g., by ID):
|
|
471
|
+
|
|
472
|
+
```sql
|
|
473
|
+
-- Bad: two transactions locking rows in different order → deadlock
|
|
474
|
+
-- Good: always lock in ID order
|
|
475
|
+
SELECT * FROM accounts
|
|
476
|
+
WHERE id IN (5, 12, 3)
|
|
477
|
+
ORDER BY id
|
|
478
|
+
FOR UPDATE;
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Transaction Discipline
|
|
482
|
+
|
|
483
|
+
```sql
|
|
484
|
+
-- Set statement timeout to prevent runaway queries
|
|
485
|
+
SET statement_timeout = '30s';
|
|
486
|
+
|
|
487
|
+
-- Kill idle-in-transaction sessions (holds locks, blocks VACUUM)
|
|
488
|
+
SET idle_in_transaction_session_timeout = '60s';
|
|
489
|
+
|
|
490
|
+
-- Limit total transaction duration (PG17+)
|
|
491
|
+
SET transaction_timeout = '5min';
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Keep transactions short: move external calls (HTTP, file I/O) outside the transaction boundary.
|
|
495
|
+
|
|
496
|
+
### N+1 Query Elimination
|
|
497
|
+
|
|
498
|
+
```sql
|
|
499
|
+
-- Bad: one query per item (N+1 pattern)
|
|
500
|
+
-- SELECT * FROM orders WHERE customer_id = 1;
|
|
501
|
+
-- SELECT * FROM orders WHERE customer_id = 2;
|
|
502
|
+
-- ...
|
|
503
|
+
|
|
504
|
+
-- Good: single query with ANY
|
|
505
|
+
SELECT * FROM orders WHERE customer_id = ANY($1::bigint[]);
|
|
506
|
+
-- Pass array of IDs: ARRAY[1, 2, 3, ...]
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Useful PG18+ Functions
|
|
510
|
+
|
|
511
|
+
### array_sort() and array_reverse()
|
|
512
|
+
|
|
513
|
+
```sql
|
|
514
|
+
SELECT array_sort(ARRAY[3, 1, 4, 1, 5]); -- {1, 1, 3, 4, 5}
|
|
515
|
+
SELECT array_reverse(ARRAY[1, 2, 3]); -- {3, 2, 1}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### casefold() — Unicode Case-Insensitive Matching
|
|
519
|
+
|
|
520
|
+
More robust than `lower()` for internationalized text:
|
|
521
|
+
|
|
522
|
+
```sql
|
|
523
|
+
SELECT casefold('Hello World') = casefold('HELLO WORLD'); -- true
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Anti-Patterns to Avoid
|
|
527
|
+
|
|
528
|
+
### SELECT * in Production Queries
|
|
529
|
+
Use explicit column lists. `SELECT *` breaks when columns change and prevents index-only scans.
|
|
530
|
+
|
|
531
|
+
### NOT IN with NULLs
|
|
532
|
+
`NOT IN (subquery)` returns no rows if any subquery result is NULL. Use `NOT EXISTS` instead:
|
|
533
|
+
|
|
534
|
+
```sql
|
|
535
|
+
-- Bad: breaks if orders.customer_id has any NULL
|
|
536
|
+
SELECT * FROM customers WHERE id NOT IN (SELECT customer_id FROM orders);
|
|
537
|
+
|
|
538
|
+
-- Good: always correct
|
|
539
|
+
SELECT * FROM customers c
|
|
540
|
+
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Implicit Casts in WHERE Clauses
|
|
544
|
+
Casting a column prevents index use:
|
|
545
|
+
|
|
546
|
+
```sql
|
|
547
|
+
-- Bad: casts every row, no index
|
|
548
|
+
WHERE created_at::date = '2024-01-15'
|
|
549
|
+
|
|
550
|
+
-- Good: range scan, uses index
|
|
551
|
+
WHERE created_at >= '2024-01-15' AND created_at < '2024-01-16'
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### ORDER BY on Unindexed Large Result Sets
|
|
555
|
+
If you ORDER BY + LIMIT on a large table, ensure there's an index on the ORDER BY columns to avoid a full sort.
|
|
556
|
+
|
|
557
|
+
### Using OFFSET for Pagination
|
|
558
|
+
OFFSET scans and discards rows. Use keyset pagination instead:
|
|
559
|
+
|
|
560
|
+
```sql
|
|
561
|
+
-- Bad: slow at high offsets
|
|
562
|
+
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 10000;
|
|
563
|
+
|
|
564
|
+
-- Good: keyset pagination
|
|
565
|
+
SELECT * FROM products WHERE id > $last_seen_id ORDER BY id LIMIT 20;
|
|
566
|
+
```
|