mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
108 lines
2.4 KiB
Markdown
108 lines
2.4 KiB
Markdown
---
|
|
title: Use Structured Columns Over JSONB When Possible
|
|
impact: MEDIUM
|
|
impactDescription: Improves query performance, type safety, and data integrity
|
|
tags: jsonb, json, schema-design, performance
|
|
---
|
|
|
|
## Use Structured Columns Over JSONB When Possible
|
|
|
|
JSONB is flexible but should not replace proper schema design. Use structured
|
|
columns for known fields, JSONB for truly dynamic data.
|
|
|
|
**Incorrect:**
|
|
|
|
```sql
|
|
-- Everything in JSONB - loses type safety and performance
|
|
create table users (
|
|
id uuid primary key,
|
|
data jsonb -- contains email, name, role, etc.
|
|
);
|
|
|
|
-- Querying is verbose and slow without indexes
|
|
select data ->> 'email' from users
|
|
where data ->> 'role' = 'admin';
|
|
```
|
|
|
|
**Correct:**
|
|
|
|
```sql
|
|
-- Structured columns for known fields
|
|
create table users (
|
|
id uuid primary key,
|
|
email text not null,
|
|
name text,
|
|
role text check (role in ('admin', 'user', 'guest')),
|
|
-- JSONB only for truly flexible data
|
|
preferences jsonb default '{}'
|
|
);
|
|
|
|
-- Fast, type-safe queries
|
|
select email from users where role = 'admin';
|
|
```
|
|
|
|
## When JSONB is Appropriate
|
|
|
|
- Webhook payloads
|
|
- User-defined fields
|
|
- API responses to cache
|
|
- Rapid prototyping (migrate to columns later)
|
|
|
|
## Indexing JSONB
|
|
|
|
```sql
|
|
-- GIN index for containment queries
|
|
create index idx_users_preferences on users using gin(preferences);
|
|
|
|
-- Query using containment operator
|
|
select * from users
|
|
where preferences @> '{"theme": "dark"}';
|
|
```
|
|
|
|
## Validate JSONB with pg_jsonschema
|
|
|
|
```sql
|
|
create extension if not exists pg_jsonschema with schema extensions;
|
|
|
|
alter table users
|
|
add constraint check_preferences check (
|
|
jsonb_matches_schema(
|
|
'{
|
|
"type": "object",
|
|
"properties": {
|
|
"theme": {"type": "string", "enum": ["light", "dark"]},
|
|
"notifications": {"type": "boolean"}
|
|
}
|
|
}',
|
|
preferences
|
|
)
|
|
);
|
|
```
|
|
|
|
## Querying JSONB
|
|
|
|
```javascript
|
|
// supabase-js
|
|
const { data } = await supabase
|
|
.from("users")
|
|
.select("email, preferences->theme")
|
|
.eq("preferences->>notifications", "true");
|
|
|
|
// containment queries
|
|
const { data: darkUsers } = await supabase
|
|
.from("users")
|
|
.select("*")
|
|
.contains("preferences", { theme: "dark" });
|
|
|
|
// contained-by query
|
|
const { data: subset } = await supabase
|
|
.from("users")
|
|
.select("*")
|
|
.containedBy("preferences", { theme: "dark", notifications: true });
|
|
```
|
|
|
|
## Related
|
|
|
|
- [perf-indexes.md](perf-indexes.md)
|
|
- [Docs](https://supabase.com/docs/guides/database/json)
|