omgkit 2.1.0 → 2.2.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/package.json +1 -1
- package/plugin/skills/SKILL_STANDARDS.md +743 -0
- package/plugin/skills/databases/mongodb/SKILL.md +797 -28
- package/plugin/skills/databases/postgresql/SKILL.md +494 -18
- package/plugin/skills/databases/prisma/SKILL.md +776 -30
- package/plugin/skills/databases/redis/SKILL.md +885 -25
- package/plugin/skills/devops/aws/SKILL.md +686 -28
- package/plugin/skills/devops/docker/SKILL.md +466 -18
- package/plugin/skills/devops/github-actions/SKILL.md +684 -29
- package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
- package/plugin/skills/frameworks/django/SKILL.md +920 -20
- package/plugin/skills/frameworks/express/SKILL.md +1361 -35
- package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
- package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
- package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
- package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
- package/plugin/skills/frameworks/rails/SKILL.md +594 -28
- package/plugin/skills/frameworks/react/SKILL.md +1006 -32
- package/plugin/skills/frameworks/spring/SKILL.md +528 -35
- package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
- package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
- package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
- package/plugin/skills/frontend/responsive/SKILL.md +847 -21
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
- package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
- package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
- package/plugin/skills/languages/javascript/SKILL.md +935 -31
- package/plugin/skills/languages/python/SKILL.md +489 -25
- package/plugin/skills/languages/typescript/SKILL.md +379 -30
- package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
- package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
- package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
- package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
- package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
- package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
- package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
- package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
- package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
- package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
- package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
- package/plugin/skills/security/better-auth/SKILL.md +1065 -28
- package/plugin/skills/security/oauth/SKILL.md +968 -31
- package/plugin/skills/security/owasp/SKILL.md +894 -33
- package/plugin/skills/testing/playwright/SKILL.md +764 -38
- package/plugin/skills/testing/pytest/SKILL.md +873 -36
- package/plugin/skills/testing/vitest/SKILL.md +980 -35
|
@@ -1,43 +1,519 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: postgresql
|
|
3
|
-
description: PostgreSQL database
|
|
3
|
+
description: PostgreSQL database design, queries, optimization, and best practices
|
|
4
|
+
category: databases
|
|
5
|
+
triggers:
|
|
6
|
+
- postgresql
|
|
7
|
+
- postgres
|
|
8
|
+
- sql
|
|
9
|
+
- database
|
|
10
|
+
- query
|
|
11
|
+
- schema
|
|
4
12
|
---
|
|
5
13
|
|
|
6
|
-
# PostgreSQL
|
|
14
|
+
# PostgreSQL
|
|
15
|
+
|
|
16
|
+
Enterprise-grade **PostgreSQL database** design and optimization following industry best practices. This skill covers schema design, query optimization, indexing strategies, and production-ready patterns.
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
|
|
20
|
+
Build performant, scalable database systems:
|
|
21
|
+
|
|
22
|
+
- Design efficient schemas
|
|
23
|
+
- Write optimized queries
|
|
24
|
+
- Implement proper indexing
|
|
25
|
+
- Handle transactions correctly
|
|
26
|
+
- Optimize for performance
|
|
27
|
+
- Ensure data integrity
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
### 1. Schema Design
|
|
7
32
|
|
|
8
|
-
## Schema Design
|
|
9
33
|
```sql
|
|
34
|
+
-- Users table with proper constraints
|
|
10
35
|
CREATE TABLE users (
|
|
11
36
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
12
37
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
13
38
|
password_hash VARCHAR(255) NOT NULL,
|
|
39
|
+
name VARCHAR(100) NOT NULL,
|
|
40
|
+
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin', 'moderator')),
|
|
41
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
42
|
+
email_verified_at TIMESTAMPTZ,
|
|
43
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
44
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Products table with foreign key
|
|
48
|
+
CREATE TABLE products (
|
|
49
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
50
|
+
name VARCHAR(255) NOT NULL,
|
|
51
|
+
description TEXT,
|
|
52
|
+
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
|
|
53
|
+
stock_quantity INTEGER DEFAULT 0 CHECK (stock_quantity >= 0),
|
|
54
|
+
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
|
55
|
+
seller_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
56
|
+
is_published BOOLEAN DEFAULT FALSE,
|
|
57
|
+
published_at TIMESTAMPTZ,
|
|
58
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
59
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
-- Orders table with status tracking
|
|
63
|
+
CREATE TABLE orders (
|
|
64
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
65
|
+
user_id UUID NOT NULL REFERENCES users(id),
|
|
66
|
+
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN (
|
|
67
|
+
'pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled'
|
|
68
|
+
)),
|
|
69
|
+
total_amount DECIMAL(12, 2) NOT NULL,
|
|
70
|
+
shipping_address JSONB NOT NULL,
|
|
71
|
+
notes TEXT,
|
|
14
72
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
15
73
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
16
74
|
);
|
|
17
75
|
|
|
18
|
-
|
|
76
|
+
-- Order items junction table
|
|
77
|
+
CREATE TABLE order_items (
|
|
78
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
79
|
+
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
|
80
|
+
product_id UUID NOT NULL REFERENCES products(id),
|
|
81
|
+
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
|
82
|
+
unit_price DECIMAL(10, 2) NOT NULL,
|
|
83
|
+
UNIQUE(order_id, product_id)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
-- Updated at trigger function
|
|
87
|
+
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
88
|
+
RETURNS TRIGGER AS $$
|
|
89
|
+
BEGIN
|
|
90
|
+
NEW.updated_at = NOW();
|
|
91
|
+
RETURN NEW;
|
|
92
|
+
END;
|
|
93
|
+
$$ LANGUAGE plpgsql;
|
|
94
|
+
|
|
95
|
+
-- Apply trigger to tables
|
|
96
|
+
CREATE TRIGGER users_updated_at
|
|
97
|
+
BEFORE UPDATE ON users
|
|
98
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
99
|
+
|
|
100
|
+
CREATE TRIGGER products_updated_at
|
|
101
|
+
BEFORE UPDATE ON products
|
|
102
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
103
|
+
|
|
104
|
+
CREATE TRIGGER orders_updated_at
|
|
105
|
+
BEFORE UPDATE ON orders
|
|
106
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
19
107
|
```
|
|
20
108
|
|
|
21
|
-
|
|
109
|
+
### 2. Indexing Strategies
|
|
110
|
+
|
|
22
111
|
```sql
|
|
23
|
-
--
|
|
24
|
-
|
|
112
|
+
-- Primary indexes (created automatically)
|
|
113
|
+
-- B-tree indexes for equality and range queries
|
|
114
|
+
CREATE INDEX idx_products_category ON products(category_id);
|
|
115
|
+
CREATE INDEX idx_products_seller ON products(seller_id);
|
|
116
|
+
CREATE INDEX idx_orders_user ON orders(user_id);
|
|
117
|
+
CREATE INDEX idx_orders_status ON orders(status);
|
|
118
|
+
|
|
119
|
+
-- Composite indexes for common query patterns
|
|
120
|
+
CREATE INDEX idx_products_category_price ON products(category_id, price);
|
|
121
|
+
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
|
|
122
|
+
CREATE INDEX idx_orders_created_status ON orders(created_at DESC, status);
|
|
123
|
+
|
|
124
|
+
-- Partial indexes for filtered queries
|
|
125
|
+
CREATE INDEX idx_products_published ON products(category_id, created_at)
|
|
126
|
+
WHERE is_published = TRUE;
|
|
127
|
+
|
|
128
|
+
CREATE INDEX idx_orders_pending ON orders(user_id, created_at)
|
|
129
|
+
WHERE status = 'pending';
|
|
130
|
+
|
|
131
|
+
-- Full-text search indexes
|
|
132
|
+
CREATE INDEX idx_products_search ON products
|
|
133
|
+
USING GIN(to_tsvector('english', name || ' ' || COALESCE(description, '')));
|
|
134
|
+
|
|
135
|
+
-- JSONB indexes
|
|
136
|
+
CREATE INDEX idx_orders_shipping_city ON orders
|
|
137
|
+
USING GIN((shipping_address->'city'));
|
|
138
|
+
|
|
139
|
+
-- Expression indexes
|
|
140
|
+
CREATE INDEX idx_users_email_lower ON users(LOWER(email));
|
|
141
|
+
|
|
142
|
+
-- Covering indexes (index-only scans)
|
|
143
|
+
CREATE INDEX idx_products_list ON products(category_id, is_published)
|
|
144
|
+
INCLUDE (name, price);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 3. Query Patterns
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
-- Pagination with cursor (more efficient than OFFSET)
|
|
151
|
+
SELECT id, name, price, created_at
|
|
152
|
+
FROM products
|
|
153
|
+
WHERE created_at < '2024-01-01' -- cursor value
|
|
154
|
+
AND is_published = TRUE
|
|
155
|
+
ORDER BY created_at DESC
|
|
156
|
+
LIMIT 20;
|
|
157
|
+
|
|
158
|
+
-- Efficient offset pagination when needed
|
|
159
|
+
SELECT id, name, price
|
|
160
|
+
FROM products
|
|
161
|
+
WHERE is_published = TRUE
|
|
25
162
|
ORDER BY created_at DESC
|
|
26
|
-
LIMIT
|
|
163
|
+
LIMIT 20 OFFSET 40;
|
|
164
|
+
|
|
165
|
+
-- Search with full-text search
|
|
166
|
+
SELECT id, name, description,
|
|
167
|
+
ts_rank(to_tsvector('english', name || ' ' || COALESCE(description, '')),
|
|
168
|
+
plainto_tsquery('english', 'laptop gaming')) as rank
|
|
169
|
+
FROM products
|
|
170
|
+
WHERE to_tsvector('english', name || ' ' || COALESCE(description, ''))
|
|
171
|
+
@@ plainto_tsquery('english', 'laptop gaming')
|
|
172
|
+
ORDER BY rank DESC
|
|
173
|
+
LIMIT 20;
|
|
174
|
+
|
|
175
|
+
-- Aggregation with filtering
|
|
176
|
+
SELECT
|
|
177
|
+
DATE_TRUNC('day', created_at) as date,
|
|
178
|
+
COUNT(*) as order_count,
|
|
179
|
+
SUM(total_amount) as revenue,
|
|
180
|
+
AVG(total_amount) as avg_order_value
|
|
181
|
+
FROM orders
|
|
182
|
+
WHERE status = 'delivered'
|
|
183
|
+
AND created_at >= NOW() - INTERVAL '30 days'
|
|
184
|
+
GROUP BY DATE_TRUNC('day', created_at)
|
|
185
|
+
ORDER BY date DESC;
|
|
186
|
+
|
|
187
|
+
-- Window functions
|
|
188
|
+
SELECT
|
|
189
|
+
id, name, price, category_id,
|
|
190
|
+
RANK() OVER (PARTITION BY category_id ORDER BY price DESC) as price_rank,
|
|
191
|
+
LAG(price) OVER (PARTITION BY category_id ORDER BY price) as prev_price,
|
|
192
|
+
AVG(price) OVER (PARTITION BY category_id) as category_avg
|
|
193
|
+
FROM products
|
|
194
|
+
WHERE is_published = TRUE;
|
|
195
|
+
|
|
196
|
+
-- Common Table Expressions (CTE)
|
|
197
|
+
WITH monthly_sales AS (
|
|
198
|
+
SELECT
|
|
199
|
+
DATE_TRUNC('month', o.created_at) as month,
|
|
200
|
+
SUM(oi.quantity * oi.unit_price) as revenue
|
|
201
|
+
FROM orders o
|
|
202
|
+
JOIN order_items oi ON o.id = oi.order_id
|
|
203
|
+
WHERE o.status = 'delivered'
|
|
204
|
+
GROUP BY DATE_TRUNC('month', o.created_at)
|
|
205
|
+
),
|
|
206
|
+
sales_with_growth AS (
|
|
207
|
+
SELECT
|
|
208
|
+
month,
|
|
209
|
+
revenue,
|
|
210
|
+
LAG(revenue) OVER (ORDER BY month) as prev_revenue,
|
|
211
|
+
revenue - LAG(revenue) OVER (ORDER BY month) as growth
|
|
212
|
+
FROM monthly_sales
|
|
213
|
+
)
|
|
214
|
+
SELECT * FROM sales_with_growth
|
|
215
|
+
ORDER BY month DESC;
|
|
216
|
+
|
|
217
|
+
-- Recursive CTE for hierarchical data
|
|
218
|
+
WITH RECURSIVE category_tree AS (
|
|
219
|
+
-- Base case
|
|
220
|
+
SELECT id, name, parent_id, 0 as depth, ARRAY[id] as path
|
|
221
|
+
FROM categories
|
|
222
|
+
WHERE parent_id IS NULL
|
|
223
|
+
|
|
224
|
+
UNION ALL
|
|
225
|
+
|
|
226
|
+
-- Recursive case
|
|
227
|
+
SELECT c.id, c.name, c.parent_id, ct.depth + 1, ct.path || c.id
|
|
228
|
+
FROM categories c
|
|
229
|
+
JOIN category_tree ct ON c.parent_id = ct.id
|
|
230
|
+
)
|
|
231
|
+
SELECT * FROM category_tree
|
|
232
|
+
ORDER BY path;
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 4. Transactions and Locking
|
|
236
|
+
|
|
237
|
+
```sql
|
|
238
|
+
-- Basic transaction
|
|
239
|
+
BEGIN;
|
|
240
|
+
|
|
241
|
+
UPDATE accounts SET balance = balance - 100 WHERE id = 'sender_id';
|
|
242
|
+
UPDATE accounts SET balance = balance + 100 WHERE id = 'receiver_id';
|
|
243
|
+
|
|
244
|
+
INSERT INTO transactions (from_id, to_id, amount)
|
|
245
|
+
VALUES ('sender_id', 'receiver_id', 100);
|
|
246
|
+
|
|
247
|
+
COMMIT;
|
|
248
|
+
|
|
249
|
+
-- Transaction with savepoint
|
|
250
|
+
BEGIN;
|
|
27
251
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
WHERE email ILIKE '%@example.com';
|
|
252
|
+
UPDATE products SET stock_quantity = stock_quantity - 1 WHERE id = 'product_id';
|
|
253
|
+
SAVEPOINT after_stock_update;
|
|
31
254
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
255
|
+
INSERT INTO orders (user_id, total_amount)
|
|
256
|
+
VALUES ('user_id', 99.99)
|
|
257
|
+
RETURNING id INTO order_id;
|
|
258
|
+
|
|
259
|
+
-- If something goes wrong
|
|
260
|
+
ROLLBACK TO SAVEPOINT after_stock_update;
|
|
261
|
+
|
|
262
|
+
COMMIT;
|
|
263
|
+
|
|
264
|
+
-- Pessimistic locking (FOR UPDATE)
|
|
265
|
+
BEGIN;
|
|
266
|
+
|
|
267
|
+
SELECT * FROM products WHERE id = 'product_id' FOR UPDATE;
|
|
268
|
+
-- Row is now locked until transaction ends
|
|
269
|
+
|
|
270
|
+
UPDATE products SET stock_quantity = stock_quantity - 1 WHERE id = 'product_id';
|
|
271
|
+
|
|
272
|
+
COMMIT;
|
|
273
|
+
|
|
274
|
+
-- Skip locked rows (for job queues)
|
|
275
|
+
SELECT * FROM jobs
|
|
276
|
+
WHERE status = 'pending'
|
|
277
|
+
ORDER BY created_at
|
|
278
|
+
LIMIT 1
|
|
279
|
+
FOR UPDATE SKIP LOCKED;
|
|
280
|
+
|
|
281
|
+
-- Advisory locks
|
|
282
|
+
SELECT pg_advisory_lock(hashtext('unique_process_name'));
|
|
283
|
+
-- Do exclusive work
|
|
284
|
+
SELECT pg_advisory_unlock(hashtext('unique_process_name'));
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 5. Performance Optimization
|
|
288
|
+
|
|
289
|
+
```sql
|
|
290
|
+
-- Analyze query performance
|
|
291
|
+
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
|
|
292
|
+
SELECT p.*, c.name as category_name
|
|
293
|
+
FROM products p
|
|
294
|
+
LEFT JOIN categories c ON p.category_id = c.id
|
|
295
|
+
WHERE p.is_published = TRUE
|
|
296
|
+
AND p.price BETWEEN 10 AND 100
|
|
297
|
+
ORDER BY p.created_at DESC
|
|
298
|
+
LIMIT 20;
|
|
299
|
+
|
|
300
|
+
-- Table statistics
|
|
301
|
+
ANALYZE products;
|
|
302
|
+
|
|
303
|
+
-- Vacuum to reclaim space
|
|
304
|
+
VACUUM (VERBOSE, ANALYZE) products;
|
|
305
|
+
|
|
306
|
+
-- Check index usage
|
|
307
|
+
SELECT
|
|
308
|
+
schemaname, tablename, indexname,
|
|
309
|
+
idx_scan, idx_tup_read, idx_tup_fetch
|
|
310
|
+
FROM pg_stat_user_indexes
|
|
311
|
+
WHERE tablename = 'products'
|
|
312
|
+
ORDER BY idx_scan DESC;
|
|
313
|
+
|
|
314
|
+
-- Find unused indexes
|
|
315
|
+
SELECT
|
|
316
|
+
schemaname, tablename, indexname, idx_scan
|
|
317
|
+
FROM pg_stat_user_indexes
|
|
318
|
+
WHERE idx_scan = 0
|
|
319
|
+
AND schemaname = 'public';
|
|
320
|
+
|
|
321
|
+
-- Table bloat check
|
|
322
|
+
SELECT
|
|
323
|
+
schemaname, tablename,
|
|
324
|
+
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) as total_size,
|
|
325
|
+
n_dead_tup as dead_tuples,
|
|
326
|
+
n_live_tup as live_tuples
|
|
327
|
+
FROM pg_stat_user_tables
|
|
328
|
+
WHERE n_dead_tup > 1000
|
|
329
|
+
ORDER BY n_dead_tup DESC;
|
|
330
|
+
|
|
331
|
+
-- Connection monitoring
|
|
332
|
+
SELECT
|
|
333
|
+
datname, usename, application_name,
|
|
334
|
+
client_addr, state, query_start,
|
|
335
|
+
NOW() - query_start as query_duration
|
|
336
|
+
FROM pg_stat_activity
|
|
337
|
+
WHERE state = 'active'
|
|
338
|
+
ORDER BY query_start;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 6. JSONB Operations
|
|
342
|
+
|
|
343
|
+
```sql
|
|
344
|
+
-- JSONB column
|
|
345
|
+
CREATE TABLE events (
|
|
346
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
347
|
+
type VARCHAR(50) NOT NULL,
|
|
348
|
+
payload JSONB NOT NULL,
|
|
349
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
-- Insert JSONB data
|
|
353
|
+
INSERT INTO events (type, payload)
|
|
354
|
+
VALUES ('user.created', '{"user_id": "123", "email": "test@example.com", "metadata": {"source": "web"}}');
|
|
355
|
+
|
|
356
|
+
-- Query JSONB
|
|
357
|
+
SELECT * FROM events
|
|
358
|
+
WHERE payload->>'user_id' = '123';
|
|
359
|
+
|
|
360
|
+
SELECT * FROM events
|
|
361
|
+
WHERE payload @> '{"metadata": {"source": "web"}}';
|
|
362
|
+
|
|
363
|
+
-- JSONB operators
|
|
364
|
+
SELECT
|
|
365
|
+
payload->'user_id' as user_id, -- Get as JSONB
|
|
366
|
+
payload->>'email' as email, -- Get as text
|
|
367
|
+
payload#>'{metadata,source}' as source,
|
|
368
|
+
payload ? 'email' as has_email,
|
|
369
|
+
payload ?& ARRAY['user_id', 'email'] as has_all
|
|
370
|
+
FROM events;
|
|
371
|
+
|
|
372
|
+
-- Update JSONB
|
|
373
|
+
UPDATE events
|
|
374
|
+
SET payload = payload || '{"processed": true}'
|
|
375
|
+
WHERE id = 'event_id';
|
|
376
|
+
|
|
377
|
+
UPDATE events
|
|
378
|
+
SET payload = jsonb_set(payload, '{metadata,updated_at}', '"2024-01-01"')
|
|
379
|
+
WHERE id = 'event_id';
|
|
380
|
+
|
|
381
|
+
-- Remove key from JSONB
|
|
382
|
+
UPDATE events
|
|
383
|
+
SET payload = payload - 'temporary_field'
|
|
384
|
+
WHERE id = 'event_id';
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 7. Stored Procedures
|
|
388
|
+
|
|
389
|
+
```sql
|
|
390
|
+
-- Function to get user stats
|
|
391
|
+
CREATE OR REPLACE FUNCTION get_user_stats(p_user_id UUID)
|
|
392
|
+
RETURNS TABLE (
|
|
393
|
+
total_orders BIGINT,
|
|
394
|
+
total_spent DECIMAL,
|
|
395
|
+
avg_order_value DECIMAL,
|
|
396
|
+
last_order_date TIMESTAMPTZ
|
|
397
|
+
) AS $$
|
|
398
|
+
BEGIN
|
|
399
|
+
RETURN QUERY
|
|
400
|
+
SELECT
|
|
401
|
+
COUNT(*)::BIGINT,
|
|
402
|
+
COALESCE(SUM(total_amount), 0),
|
|
403
|
+
COALESCE(AVG(total_amount), 0),
|
|
404
|
+
MAX(created_at)
|
|
405
|
+
FROM orders
|
|
406
|
+
WHERE user_id = p_user_id
|
|
407
|
+
AND status = 'delivered';
|
|
408
|
+
END;
|
|
409
|
+
$$ LANGUAGE plpgsql;
|
|
410
|
+
|
|
411
|
+
-- Usage
|
|
412
|
+
SELECT * FROM get_user_stats('user-uuid-here');
|
|
413
|
+
|
|
414
|
+
-- Procedure for order creation
|
|
415
|
+
CREATE OR REPLACE PROCEDURE create_order(
|
|
416
|
+
p_user_id UUID,
|
|
417
|
+
p_items JSONB,
|
|
418
|
+
OUT p_order_id UUID
|
|
419
|
+
)
|
|
420
|
+
LANGUAGE plpgsql AS $$
|
|
421
|
+
DECLARE
|
|
422
|
+
v_total DECIMAL := 0;
|
|
423
|
+
v_item JSONB;
|
|
424
|
+
BEGIN
|
|
425
|
+
-- Calculate total
|
|
426
|
+
FOR v_item IN SELECT * FROM jsonb_array_elements(p_items)
|
|
427
|
+
LOOP
|
|
428
|
+
v_total := v_total + (v_item->>'price')::DECIMAL * (v_item->>'quantity')::INTEGER;
|
|
429
|
+
END LOOP;
|
|
430
|
+
|
|
431
|
+
-- Create order
|
|
432
|
+
INSERT INTO orders (user_id, total_amount, status)
|
|
433
|
+
VALUES (p_user_id, v_total, 'pending')
|
|
434
|
+
RETURNING id INTO p_order_id;
|
|
435
|
+
|
|
436
|
+
-- Create order items
|
|
437
|
+
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
|
|
438
|
+
SELECT
|
|
439
|
+
p_order_id,
|
|
440
|
+
(value->>'product_id')::UUID,
|
|
441
|
+
(value->>'quantity')::INTEGER,
|
|
442
|
+
(value->>'price')::DECIMAL
|
|
443
|
+
FROM jsonb_array_elements(p_items);
|
|
444
|
+
|
|
445
|
+
-- Update stock
|
|
446
|
+
UPDATE products p
|
|
447
|
+
SET stock_quantity = stock_quantity - (i.value->>'quantity')::INTEGER
|
|
448
|
+
FROM jsonb_array_elements(p_items) i
|
|
449
|
+
WHERE p.id = (i.value->>'product_id')::UUID;
|
|
450
|
+
END;
|
|
451
|
+
$$;
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Use Cases
|
|
455
|
+
|
|
456
|
+
### E-commerce Analytics
|
|
457
|
+
```sql
|
|
458
|
+
SELECT
|
|
459
|
+
p.category_id,
|
|
460
|
+
c.name as category_name,
|
|
461
|
+
COUNT(DISTINCT o.id) as order_count,
|
|
462
|
+
SUM(oi.quantity) as units_sold,
|
|
463
|
+
SUM(oi.quantity * oi.unit_price) as revenue
|
|
464
|
+
FROM products p
|
|
465
|
+
JOIN categories c ON p.category_id = c.id
|
|
466
|
+
JOIN order_items oi ON p.id = oi.product_id
|
|
467
|
+
JOIN orders o ON oi.order_id = o.id
|
|
468
|
+
WHERE o.status = 'delivered'
|
|
469
|
+
AND o.created_at >= NOW() - INTERVAL '30 days'
|
|
470
|
+
GROUP BY p.category_id, c.name
|
|
471
|
+
ORDER BY revenue DESC;
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### User Activity Report
|
|
475
|
+
```sql
|
|
476
|
+
WITH user_activity AS (
|
|
477
|
+
SELECT
|
|
478
|
+
u.id,
|
|
479
|
+
u.email,
|
|
480
|
+
COUNT(o.id) as order_count,
|
|
481
|
+
COALESCE(SUM(o.total_amount), 0) as total_spent,
|
|
482
|
+
MAX(o.created_at) as last_order_date
|
|
483
|
+
FROM users u
|
|
484
|
+
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'delivered'
|
|
485
|
+
GROUP BY u.id, u.email
|
|
486
|
+
)
|
|
487
|
+
SELECT
|
|
488
|
+
*,
|
|
489
|
+
CASE
|
|
490
|
+
WHEN total_spent >= 1000 THEN 'VIP'
|
|
491
|
+
WHEN total_spent >= 500 THEN 'Regular'
|
|
492
|
+
ELSE 'New'
|
|
493
|
+
END as customer_tier
|
|
494
|
+
FROM user_activity
|
|
495
|
+
ORDER BY total_spent DESC;
|
|
36
496
|
```
|
|
37
497
|
|
|
38
498
|
## Best Practices
|
|
39
|
-
|
|
40
|
-
|
|
499
|
+
|
|
500
|
+
### Do's
|
|
501
|
+
- Use UUIDs for primary keys
|
|
502
|
+
- Add indexes for common queries
|
|
41
503
|
- Use EXPLAIN ANALYZE
|
|
42
|
-
- Use transactions
|
|
504
|
+
- Use transactions for data integrity
|
|
43
505
|
- Use connection pooling
|
|
506
|
+
- Regular VACUUM and ANALYZE
|
|
507
|
+
|
|
508
|
+
### Don'ts
|
|
509
|
+
- Don't use SELECT *
|
|
510
|
+
- Don't ignore query plans
|
|
511
|
+
- Don't forget foreign keys
|
|
512
|
+
- Don't skip migrations
|
|
513
|
+
- Don't use raw SQL without parameterization
|
|
514
|
+
|
|
515
|
+
## References
|
|
516
|
+
|
|
517
|
+
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
|
518
|
+
- [Use The Index, Luke](https://use-the-index-luke.com/)
|
|
519
|
+
- [PostgreSQL Wiki](https://wiki.postgresql.org/)
|