BEL CRM Schema Write DB
CRITICAL: Read SQL Rules First!
Before writing ANY SQL, consult the bel-crm-sql-rules skill!
Key limitations of the PostgreSQL MCP server:
- NO
RETURNING clause - will cause syntax error
- NO
ON CONFLICT - will fail (also: no UNIQUE constraints on name columns!)
- Use
read_query to get IDs after INSERT
Overview
This skill provides comprehensive knowledge of the CRM PostgreSQL database schema and generates correct, PostgreSQL-specific SQL queries for managing companies, contacts, sales opportunities, and activity events.
The CRM database uses a relational model with JSONB fields for flexible tagging and metadata, temporal tracking with timestamps, and foreign key relationships between companies, people, opportunities, and events.
When to Use This Skill
Use this skill when:
- Creating INSERT statements for new CRM records
- Writing UPDATE queries to modify existing data
- Constructing SELECT queries with JOINs across CRM entities
- Working with JSONB fields (tags, metadata)
- Querying relationships between companies, contacts, and opportunities
- Building reports from sales pipeline data
- Tracking activities and events
- The user mentions database operations on: company_site, person, sales_opportunity, or event tables
The user may SHORTEN that by: company_site (c:), person (p:), sales_opportunity (so:), event (e:)
Core Capabilities
1. Schema Knowledge
Load references/schema.md to understand:
- Complete table structures with all columns and data types
- Foreign key relationships between entities
- JSONB field usage for tags and metadata
- Timestamp fields for created_at/updated_at tracking
- Entity relationship diagram
Use schema knowledge to:
- Validate column names and data types before generating SQL
- Understand which tables to JOIN for specific queries
- Handle optional vs. required fields correctly
- Use appropriate PostgreSQL data types (JSONB, NUMERIC, TIMESTAMP)
2. SQL Query Generation
Load references/sql_examples.md for PostgreSQL-specific patterns:
INSERT Operations:
- Insert new companies with full address and metadata
- Create person records linked to companies
- Add sales opportunities with probability tracking
- Record events with JSONB metadata
UPDATE Operations:
- Update company information and annual revenue
- Change person job titles and departments
- Update opportunity status and close dates
- Add/remove JSONB tags
- Modify nested JSONB metadata
SELECT Queries:
- Get companies with contact counts
- Fetch person details with company information
- Query open opportunities with details
- Calculate pipeline value by status
- Search by JSONB tags using
?, ?|, ?& operators
- Get activity timelines for opportunities
- Complex multi-table JOINs
3. PostgreSQL-Specific Features
JSONB Operations:
-- Check if tag exists
WHERE tags ? 'enterprise'
-- Check if any tag exists
WHERE tags ?| ARRAY['prospect', 'customer']
-- Add a tag
SET tags = tags || '["new-tag"]'::jsonb
-- Remove a tag
SET tags = tags - 'old-tag'
-- Update nested metadata
SET metadata = jsonb_set(metadata, '{outcome}', '"positive"')
Timestamp Handling:
-- Current timestamp
created_at = CURRENT_TIMESTAMP
-- Date truncation
DATE_TRUNC('quarter', CURRENT_DATE)
-- Interval calculations
event_date >= CURRENT_DATE - INTERVAL '30 days'
Get ID After Insert (Two-Step Pattern):
-- Step 1: INSERT (write_query) - NO RETURNING!
INSERT INTO person (name, email, created_at, updated_at)
VALUES ('John Doe', 'john@example.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-- Step 2: GET ID (read_query) - separate call
SELECT id FROM person WHERE email = 'john@example.com' ORDER BY created_at DESC LIMIT 1;
Workflow
Step 1: Understand the Request
Identify what the user wants to accomplish:
- Which table(s) are involved?
- Is this an INSERT, UPDATE, SELECT, or DELETE?
- What relationships need to be considered?
- Are JSONB fields involved?
Step 2: Load Schema Reference
If unfamiliar with the table structure, read references/schema.md to understand:
- Column names and data types
- Required vs. optional fields
- Foreign key relationships
- JSONB field structures
Step 3: Load SQL Examples
Read references/sql_examples.md to find similar query patterns:
- Look for analogous operations in the examples
- Adapt the pattern to the specific request
- Use PostgreSQL-specific syntax
Step 4: Generate the SQL
Create the SQL query using:
- Correct PostgreSQL syntax (see
bel-crm-sql-rules for limitations!)
- Proper data types (especially JSONB casting with
::jsonb)
- NO RETURNING clauses - use separate read_query to get IDs
- NO ON CONFLICT - check existence first, then INSERT or UPDATE
- Appropriate JOINs when querying multiple tables
- JSONB operators for tag/metadata queries
Step 5: Explain the Query
Provide context for the user:
- What the query does
- Which tables are involved
- Any assumptions made
- Expected output format
Common Patterns
Creating a Complete Company Record
When adding a new company, include:
- Required:
name
- Optional but recommended: address fields, industry, website
- Tags as JSONB array:
'["prospect", "technology"]'::jsonb
- Do NOT use RETURNING - query for ID separately if needed
Linking People to Companies
When adding a person:
- Set
company_site_id to link to company
- Include job_title and department for context
- Add tags like "decision-maker", "technical", "champion"
- Query for ID after INSERT if you need to confirm
Tracking Sales Opportunities
When creating opportunities:
- Link to both person_id and company_site_id
- Set realistic probability (0-100)
- Use common status values: 'open', 'qualified', 'proposal', 'won', 'lost'
- Include expected_close_date
- Add descriptive tags for filtering
Recording Events
When logging activities:
- Set appropriate type: 'email', 'call', 'meeting', 'note'
- Link to person_id, company_site_id, or opportunity_id as appropriate
- Use metadata JSONB for structured details (duration, outcome, next_action)
- Set event_date to when activity occurred, not when recorded
Querying with JOINs
When fetching related data:
- Use LEFT JOIN to include records without relationships
- Use INNER JOIN to filter to only records with relationships
- Include relevant columns from joined tables
- Consider using subqueries for aggregations (COUNT, SUM)
Tips
Always Cast JSONB: When inserting JSONB data, cast with ::jsonb:
'["tag1", "tag2"]'::jsonb
NEVER Use RETURNING: The PostgreSQL MCP server does not support RETURNING clauses. To get the ID after insert:
-- Step 1: INSERT (write_query)
INSERT INTO person (name, email, created_at, updated_at) VALUES ('John', 'john@example.com', now(), now());
-- Step 2: GET ID (read_query)
SELECT id FROM person WHERE email = 'john@example.com' ORDER BY created_at DESC LIMIT 1;
NEVER Use ON CONFLICT: The MCP server doesn't support upserts. Check existence first:
-- Step 1: Check (read_query)
SELECT id FROM company_site WHERE name ILIKE '%Acme%' LIMIT 1;
-- Step 2: INSERT if not found, UPDATE if found (write_query)
Handle NULL Values: Use IS NULL / IS NOT NULL for checking null values, not = NULL
JSONB Tag Searches: Use ? for single tag, ?| for any of multiple tags, ?& for all tags
Update Timestamps: Always set updated_at = CURRENT_TIMESTAMP when updating records
Pipeline Calculations: Calculate weighted pipeline value:
SUM(value_eur * probability / 100.0) as weighted_value
See bel-crm-sql-rules: For complete list of SQL limitations and correct patterns.
Resources
references/schema.md
Complete database schema documentation including:
- All table structures
- Column definitions and data types
- Foreign key relationships
- JSONB field examples
- Index recommendations
references/sql_examples.md
Comprehensive PostgreSQL query examples:
- INSERT operations for all tables
- UPDATE patterns including JSONB manipulation
- SELECT queries with JOINs and aggregations
- Date/time queries
- Complex reporting queries
- Common patterns (upsert, bulk insert, conditional updates)
1---2name: bel-crm-schema-write-db3description: This skill should be used when working with the CRM PostgreSQL database for sales, contacts, companies, and opportunities. Use this skill when the user asks to insert, update, query, or analyze data in the CRM database (may be called crm or db), or when SQL queries need to be created for company_site, person, sales_opportunity, or event tables. This skill provides comprehensive schema knowledge and PostgreSQL-specific SQL examples tailored to the CRM data model.4---5
6# BEL CRM Schema Write DB
7
8## CRITICAL: Read SQL Rules First!
9
10**Before writing ANY SQL, consult the `bel-crm-sql-rules` skill!**
11
12Key limitations of the PostgreSQL MCP server:
13- **NO `RETURNING` clause** - will cause syntax error
14- **NO `ON CONFLICT`** - will fail (also: no UNIQUE constraints on name columns!)
15- Use `read_query` to get IDs after INSERT
16
17## Overview
18
19This skill provides comprehensive knowledge of the CRM PostgreSQL database schema and generates correct, PostgreSQL-specific SQL queries for managing companies, contacts, sales opportunities, and activity events.
20
21The CRM database uses a relational model with JSONB fields for flexible tagging and metadata, temporal tracking with timestamps, and foreign key relationships between companies, people, opportunities, and events.
22
23## When to Use This Skill
24
25Use this skill when:
26- Creating INSERT statements for new CRM records
27- Writing UPDATE queries to modify existing data
28- Constructing SELECT queries with JOINs across CRM entities
29- Working with JSONB fields (tags, metadata)
30- Querying relationships between companies, contacts, and opportunities
31- Building reports from sales pipeline data
32- Tracking activities and events
33- The user mentions database operations on: company_site, person, sales_opportunity, or event tables
34 The user may SHORTEN that by: company_site (c:), person (p:), sales_opportunity (so:), event (e:)
35
36## Core Capabilities
37
38### 1. Schema Knowledge
39
40Load `references/schema.md` to understand:
41- Complete table structures with all columns and data types
42- Foreign key relationships between entities
43- JSONB field usage for tags and metadata
44- Timestamp fields for created_at/updated_at tracking
45- Entity relationship diagram
46
47Use schema knowledge to:
48- Validate column names and data types before generating SQL
49- Understand which tables to JOIN for specific queries
50- Handle optional vs. required fields correctly
51- Use appropriate PostgreSQL data types (JSONB, NUMERIC, TIMESTAMP)
52
53### 2. SQL Query Generation
54
55Load `references/sql_examples.md` for PostgreSQL-specific patterns:
56
57**INSERT Operations:**
58- Insert new companies with full address and metadata
59- Create person records linked to companies
60- Add sales opportunities with probability tracking
61- Record events with JSONB metadata
62
63**UPDATE Operations:**
64- Update company information and annual revenue
65- Change person job titles and departments
66- Update opportunity status and close dates
67- Add/remove JSONB tags
68- Modify nested JSONB metadata
69
70**SELECT Queries:**
71- Get companies with contact counts
72- Fetch person details with company information
73- Query open opportunities with details
74- Calculate pipeline value by status
75- Search by JSONB tags using `?`, `?|`, `?&` operators
76- Get activity timelines for opportunities
77- Complex multi-table JOINs
78
79### 3. PostgreSQL-Specific Features
80
81**JSONB Operations:**
82```sql
83-- Check if tag exists
84WHERE tags ? 'enterprise'
85
86-- Check if any tag exists
87WHERE tags ?| ARRAY['prospect', 'customer']
88
89-- Add a tag
90SET tags = tags || '["new-tag"]'::jsonb
91
92-- Remove a tag
93SET tags = tags - 'old-tag'
94
95-- Update nested metadata
96SET metadata = jsonb_set(metadata, '{outcome}', '"positive"')
97```
98
99**Timestamp Handling:**
100```sql
101-- Current timestamp
102created_at = CURRENT_TIMESTAMP
103
104-- Date truncation
105DATE_TRUNC('quarter', CURRENT_DATE)
106
107-- Interval calculations
108event_date >= CURRENT_DATE - INTERVAL '30 days'
109```
110
111**Get ID After Insert (Two-Step Pattern):**
112```sql
113-- Step 1: INSERT (write_query) - NO RETURNING!
114INSERT INTO person (name, email, created_at, updated_at)
115VALUES ('John Doe', 'john@example.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
116
117-- Step 2: GET ID (read_query) - separate call
118SELECT id FROM person WHERE email = 'john@example.com' ORDER BY created_at DESC LIMIT 1;
119```
120
121## Workflow
122
123### Step 1: Understand the Request
124
125Identify what the user wants to accomplish:
126- Which table(s) are involved?
127- Is this an INSERT, UPDATE, SELECT, or DELETE?
128- What relationships need to be considered?
129- Are JSONB fields involved?
130
131### Step 2: Load Schema Reference
132
133If unfamiliar with the table structure, read `references/schema.md` to understand:
134- Column names and data types
135- Required vs. optional fields
136- Foreign key relationships
137- JSONB field structures
138
139### Step 3: Load SQL Examples
140
141Read `references/sql_examples.md` to find similar query patterns:
142- Look for analogous operations in the examples
143- Adapt the pattern to the specific request
144- Use PostgreSQL-specific syntax
145
146### Step 4: Generate the SQL
147
148Create the SQL query using:
149- Correct PostgreSQL syntax (see `bel-crm-sql-rules` for limitations!)
150- Proper data types (especially JSONB casting with `::jsonb`)
151- **NO RETURNING clauses** - use separate read_query to get IDs
152- **NO ON CONFLICT** - check existence first, then INSERT or UPDATE
153- Appropriate JOINs when querying multiple tables
154- JSONB operators for tag/metadata queries
155
156### Step 5: Explain the Query
157
158Provide context for the user:
159- What the query does
160- Which tables are involved
161- Any assumptions made
162- Expected output format
163
164## Common Patterns
165
166### Creating a Complete Company Record
167
168When adding a new company, include:
169- Required: `name`
170- Optional but recommended: address fields, industry, website
171- Tags as JSONB array: `'["prospect", "technology"]'::jsonb`
172- **Do NOT use RETURNING** - query for ID separately if needed
173
174### Linking People to Companies
175
176When adding a person:
177- Set `company_site_id` to link to company
178- Include job_title and department for context
179- Add tags like "decision-maker", "technical", "champion"
180- Query for ID after INSERT if you need to confirm
181
182### Tracking Sales Opportunities
183
184When creating opportunities:
185- Link to both person_id and company_site_id
186- Set realistic probability (0-100)
187- Use common status values: 'open', 'qualified', 'proposal', 'won', 'lost'
188- Include expected_close_date
189- Add descriptive tags for filtering
190
191### Recording Events
192
193When logging activities:
194- Set appropriate type: 'email', 'call', 'meeting', 'note'
195- Link to person_id, company_site_id, or opportunity_id as appropriate
196- Use metadata JSONB for structured details (duration, outcome, next_action)
197- Set event_date to when activity occurred, not when recorded
198
199### Querying with JOINs
200
201When fetching related data:
202- Use LEFT JOIN to include records without relationships
203- Use INNER JOIN to filter to only records with relationships
204- Include relevant columns from joined tables
205- Consider using subqueries for aggregations (COUNT, SUM)
206
207## Tips
208
2091. **Always Cast JSONB**: When inserting JSONB data, cast with `::jsonb`:
210 ```sql
211 '["tag1", "tag2"]'::jsonb
212 ```
213
2142. **NEVER Use RETURNING**: The PostgreSQL MCP server does not support RETURNING clauses. To get the ID after insert:
215 ```sql
216 -- Step 1: INSERT (write_query)
217 INSERT INTO person (name, email, created_at, updated_at) VALUES ('John', 'john@example.com', now(), now());
218
219 -- Step 2: GET ID (read_query)
220 SELECT id FROM person WHERE email = 'john@example.com' ORDER BY created_at DESC LIMIT 1;
221 ```
222
2233. **NEVER Use ON CONFLICT**: The MCP server doesn't support upserts. Check existence first:
224 ```sql
225 -- Step 1: Check (read_query)
226 SELECT id FROM company_site WHERE name ILIKE '%Acme%' LIMIT 1;
227
228 -- Step 2: INSERT if not found, UPDATE if found (write_query)
229 ```
230
2314. **Handle NULL Values**: Use IS NULL / IS NOT NULL for checking null values, not = NULL
232
2335. **JSONB Tag Searches**: Use `?` for single tag, `?|` for any of multiple tags, `?&` for all tags
234
2356. **Update Timestamps**: Always set `updated_at = CURRENT_TIMESTAMP` when updating records
236
2377. **Pipeline Calculations**: Calculate weighted pipeline value:
238 ```sql
239 SUM(value_eur * probability / 100.0) as weighted_value
240 ```
241
2428. **See `bel-crm-sql-rules`**: For complete list of SQL limitations and correct patterns.
243
244## Resources
245
246### references/schema.md
247Complete database schema documentation including:
248- All table structures
249- Column definitions and data types
250- Foreign key relationships
251- JSONB field examples
252- Index recommendations
253
254### references/sql_examples.md
255Comprehensive PostgreSQL query examples:
256- INSERT operations for all tables
257- UPDATE patterns including JSONB manipulation
258- SELECT queries with JOINs and aggregations
259- Date/time queries
260- Complex reporting queries
261- Common patterns (upsert, bulk insert, conditional updates)