PostgreSQL
Quick Reference
Schema Organization
- Use one database with multiple named schemas; avoid multiple databases
- Create application-specific schemas; avoid relying on
public
- Use
GRANT for schema-level permissions
Data Types
- Prefer
TEXT over VARCHAR(n) unless length constraints matter
- Use
UUID for primary keys when distributed generation is needed
- Use
JSONB for semi-structured data; JSON only when you need exact whitespace
- Use
TIMESTAMPTZ for timestamps (stores UTC, displays in session timezone)
Transactions
- Wrap related operations in
BEGIN / COMMIT; use SAVEPOINT for nested logic
- Keep transactions short; avoid long-running work inside a transaction
- Use
SET TRANSACTION ISOLATION LEVEL when needed (e.g. REPEATABLE READ for consistency)
Index Types and When to Use
| Type |
Use Case |
Syntax |
| B-tree (default) |
Equality, range, sort, LIKE 'prefix%' |
CREATE INDEX ON t (col) |
| GIN |
@>, ?, ?&, `? |
` on arrays/JSONB; full-text search |
| GiST |
Geometric types, full-text, tsvector; extensible |
CREATE INDEX ON t USING GiST (col) |
| BRIN |
Very large tables with natural order (time, sequence) |
CREATE INDEX ON t USING BRIN (col) |
| Hash |
Equality only; rarely needed over B-tree |
CREATE INDEX ON t USING HASH (col) |
Index Rules
- Index columns used in WHERE, JOIN, ORDER BY—avoid indexing rarely filtered columns
- Composite indexes: order matters; put equality columns before range columns
-- Good for WHERE a = ? AND b > ?
CREATE INDEX ON t (a, b);
- Partial indexes for filtered subsets:
CREATE INDEX ON orders (user_id) WHERE status = 'pending';
- Expression indexes when querying transformed values:
CREATE INDEX ON users (LOWER(email));
- Avoid over-indexing: each index adds write cost; measure before adding
GIN vs GiST for Full-Text
- GIN: better query speed, slower updates; prefer for read-heavy
- GiST: faster updates, smaller index; prefer for write-heavy or when index size matters
Vector Search (pgvector)
Enable: CREATE EXTENSION vector;
Distance Operators
| Operator |
Distance |
Operator Class |
Typical Use |
<-> |
L2 (Euclidean) |
vector_l2_ops |
Raw embeddings |
<=> |
Cosine |
vector_cosine_ops |
Normalized embeddings (common) |
<#> |
Negative inner product |
vector_ip_ops |
When similarity = dot product |
Index Types
HNSW (preferred for most cases):
- Better recall and robustness to data changes
- Tune
m (connections per layer) and ef_construction (build quality)
CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
IVFFlat:
- Faster build, smaller index; good for static or append-heavy data
- Requires
lists ≥ rows/1000; tune lists and probes at query time
CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- At query time: SET ivfflat.probes = 10;
Query Pattern
SELECT id, content, embedding <=> $1 AS distance
FROM embeddings
ORDER BY embedding <=> $1
LIMIT 10;
For detailed vector search patterns and hybrid search, see references/vector-search.md.
RAG with PostgreSQL
Table Schema
CREATE TABLE document_chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
document_id UUID NOT NULL,
chunk_index INT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536), -- match embedding model dimensions
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (document_id, chunk_index)
);
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON document_chunks (document_id);
Chunking Guidelines
- Chunk size: 256–512 tokens typical; tune for recall vs context
- Overlap: 10–20% between chunks to preserve context at boundaries
- Store
chunk_index and document_id for ordering and deduplication
RAG Query Flow
- Embed the user query with the same model used for chunks
- Run vector similarity search (e.g. top-k with
<=> or <->)
- Optionally combine with keyword/full-text (hybrid search)
- Pass retrieved chunks to the LLM as context
For chunking strategies, hybrid search, and RRF, see references/rag-patterns.md.
Security and Performance
Security
- Use parameterized queries or prepared statements; never concatenate user input into SQL
- Principle of least privilege: create roles with minimal
GRANTs
- Use
SECURITY DEFINER functions sparingly; audit carefully
Performance
- Use
EXPLAIN (ANALYZE, BUFFERS) to diagnose slow queries
- Prefer
EXISTS over IN for subqueries when checking existence
- Use
UNION ALL instead of UNION when duplicates are impossible
- Consider connection pooling (PgBouncer, pgpool) for high concurrency
Migrations
Source: iamjcabalejo/payoys-cursor-sub-agents — distributed by TomeVault.
1---2name: postgresql-83description: Apply PostgreSQL best practices for schema design, indexing, transactions, vector search (pgvector), and RAG pipelines. Use when designing schemas, writing queries, optimizing performance, implementing semantic search, or building RAG applications with PostgreSQL. Use with database-expert for query optimization and DBA-level tuning. Use when this capability is needed.4---56# PostgreSQL78## Quick Reference910### Schema Organization11- Use one database with multiple named schemas; avoid multiple databases12- Create application-specific schemas; avoid relying on `public`13- Use `GRANT` for schema-level permissions1415### Data Types16- Prefer `TEXT` over `VARCHAR(n)` unless length constraints matter17- Use `UUID` for primary keys when distributed generation is needed18- Use `JSONB` for semi-structured data; `JSON` only when you need exact whitespace19- Use `TIMESTAMPTZ` for timestamps (stores UTC, displays in session timezone)2021### Transactions22- Wrap related operations in `BEGIN` / `COMMIT`; use `SAVEPOINT` for nested logic23- Keep transactions short; avoid long-running work inside a transaction24- Use `SET TRANSACTION ISOLATION LEVEL` when needed (e.g. `REPEATABLE READ` for consistency)2526---2728## Index Types and When to Use2930| Type | Use Case | Syntax |31|------|----------|--------|32| **B-tree** (default) | Equality, range, sort, `LIKE 'prefix%'` | `CREATE INDEX ON t (col)` |33| **GIN** | `@>`, `?`, `?&`, `?|` on arrays/JSONB; full-text search | `CREATE INDEX ON t USING GIN (col)` |34| **GiST** | Geometric types, full-text, `tsvector`; extensible | `CREATE INDEX ON t USING GiST (col)` |35| **BRIN** | Very large tables with natural order (time, sequence) | `CREATE INDEX ON t USING BRIN (col)` |36| **Hash** | Equality only; rarely needed over B-tree | `CREATE INDEX ON t USING HASH (col)` |3738### Index Rules39401. **Index columns used in WHERE, JOIN, ORDER BY**—avoid indexing rarely filtered columns412. **Composite indexes**: order matters; put equality columns before range columns42 ```sql43 -- Good for WHERE a = ? AND b > ?44 CREATE INDEX ON t (a, b);45 ```463. **Partial indexes** for filtered subsets:47 ```sql48 CREATE INDEX ON orders (user_id) WHERE status = 'pending';49 ```504. **Expression indexes** when querying transformed values:51 ```sql52 CREATE INDEX ON users (LOWER(email));53 ```545. **Avoid over-indexing**: each index adds write cost; measure before adding5556### GIN vs GiST for Full-Text57- **GIN**: better query speed, slower updates; prefer for read-heavy58- **GiST**: faster updates, smaller index; prefer for write-heavy or when index size matters5960---6162## Vector Search (pgvector)6364Enable: `CREATE EXTENSION vector;`6566### Distance Operators67| Operator | Distance | Operator Class | Typical Use |68|----------|----------|----------------|--------------|69| `<->` | L2 (Euclidean) | `vector_l2_ops` | Raw embeddings |70| `<=>` | Cosine | `vector_cosine_ops` | Normalized embeddings (common) |71| `<#>` | Negative inner product | `vector_ip_ops` | When similarity = dot product |7273### Index Types7475**HNSW** (preferred for most cases):76- Better recall and robustness to data changes77- Tune `m` (connections per layer) and `ef_construction` (build quality)7879```sql80CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops)81 WITH (m = 16, ef_construction = 64);82```8384**IVFFlat**:85- Faster build, smaller index; good for static or append-heavy data86- Requires `lists` ≥ rows/1000; tune `lists` and `probes` at query time8788```sql89CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops)90 WITH (lists = 100);91-- At query time: SET ivfflat.probes = 10;92```9394### Query Pattern95```sql96SELECT id, content, embedding <=> $1 AS distance97FROM embeddings98ORDER BY embedding <=> $199LIMIT 10;100```101102For detailed vector search patterns and hybrid search, see [references/vector-search.md](references/vector-search.md).103104---105106## RAG with PostgreSQL107108### Table Schema109```sql110CREATE TABLE document_chunks (111 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),112 document_id UUID NOT NULL,113 chunk_index INT NOT NULL,114 content TEXT NOT NULL,115 embedding vector(1536), -- match embedding model dimensions116 metadata JSONB,117 created_at TIMESTAMPTZ DEFAULT now(),118 UNIQUE (document_id, chunk_index)119);120121CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops);122CREATE INDEX ON document_chunks (document_id);123```124125### Chunking Guidelines126- Chunk size: 256–512 tokens typical; tune for recall vs context127- Overlap: 10–20% between chunks to preserve context at boundaries128- Store `chunk_index` and `document_id` for ordering and deduplication129130### RAG Query Flow1311. Embed the user query with the same model used for chunks1322. Run vector similarity search (e.g. top-k with `<=>` or `<->`)1333. Optionally combine with keyword/full-text (hybrid search)1344. Pass retrieved chunks to the LLM as context135136For chunking strategies, hybrid search, and RRF, see [references/rag-patterns.md](references/rag-patterns.md).137138---139140## Security and Performance141142### Security143- Use parameterized queries or prepared statements; never concatenate user input into SQL144- Principle of least privilege: create roles with minimal `GRANT`s145- Use `SECURITY DEFINER` functions sparingly; audit carefully146147### Performance148- Use `EXPLAIN (ANALYZE, BUFFERS)` to diagnose slow queries149- Prefer `EXISTS` over `IN` for subqueries when checking existence150- Use `UNION ALL` instead of `UNION` when duplicates are impossible151- Consider connection pooling (PgBouncer, pgpool) for high concurrency152153### Migrations154- Add indexes `CONCURRENTLY` in production to avoid locking:155 ```sql156 CREATE INDEX CONCURRENTLY idx_name ON table (column);157 ```158- Test rollback paths; keep migrations reversible when possible159160---161> Source: [iamjcabalejo/payoys-cursor-sub-agents](https://github.com/iamjcabalejo/payoys-cursor-sub-agents) — distributed by [TomeVault](https://tomevault.io).162<!-- tomevault:4.0:skill_md:2026-06-04 -->