postgresql-table-design by wshobson/agents
npx skills add https://github.com/wshobson/agents --skill postgresql-table-designBIGINT GENERATED ALWAYS AS IDENTITY;仅当需要全局唯一性/不透明性时才使用 UUID。NUMERIC 进行精确的十进制算术运算)。snake_case。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
UNIQUE (...) NULLS NOT DISTINCT(PG15+)来限制只能有一个 NULL。NUMERIC(2,0) 会失败并报错,不像某些数据库会静默截断或舍入。CLUSTER 是一次性的重组操作,后续插入不会维持。磁盘上的行顺序是插入顺序,除非显式进行了聚集。BIGINT GENERATED ALWAYS AS IDENTITY(GENERATED BY DEFAULT 也可以);在合并/联合/分布式系统中使用或需要不透明 ID 时使用 UUID。使用 uuidv7() 生成(如果使用 PG18+ 则优先)或 gen_random_uuid()(如果使用较旧的 PG 版本)。BIGINT,除非存储空间至关重要;较小范围使用 INTEGER;除非受限制,否则避免使用 SMALLINT。DOUBLE PRECISION,除非存储空间至关重要。精确的十进制算术运算使用 NUMERIC。TEXT;如果需要长度限制,使用 CHECK (LENGTH(col) <= n) 而不是 VARCHAR(n);避免使用 CHAR(n)。二进制数据使用 BYTEA。大字符串/二进制数据(>2KB 默认阈值)会自动存储在 TOAST 中并进行压缩。TOAST 存储:PLAIN(无 TOAST)、EXTENDED(压缩 + 行外存储)、EXTERNAL(行外存储,无压缩)、MAIN(压缩,尽可能行内存储)。默认的 EXTENDED 通常是最优的。使用 ALTER TABLE tbl ALTER COLUMN col SET STORAGE strategy 和 来控制策略和阈值。不区分大小写:对于区域设置/重音处理,使用非确定性排序规则;对于纯 ASCII,在 上使用表达式索引(除非列需要不区分大小写的主键/外键/唯一约束,否则优先使用)或使用 。NUMERIC(p,s)(切勿使用浮点数)。TIMESTAMPTZ;仅日期使用 DATE;持续时间使用 INTERVAL。避免使用 TIMESTAMP(不带时区)。使用 now() 获取事务开始时间,clock_timestamp() 获取当前挂钟时间。BOOLEAN 并加上 NOT NULL 约束,除非需要三态值。CREATE TYPE ... AS ENUM。对于业务逻辑驱动且不断变化的值(例如订单状态)→ 使用 TEXT(或 INT)+ CHECK 或查找表。TEXT[]、INTEGER[] 等。用于需要查询元素的有序列表。使用 GIN 索引进行包含(@>、<@)和重叠(&&)查询。访问:arr[1](1 起始索引)、arr[1:3](切片)。适用于标签、类别;避免用于关系——改用连接表。字面量语法:'{val1,val2}' 或 ARRAY[val1,val2]。daterange、numrange、tstzrange 用于区间。支持重叠(&&)、包含(@>)等操作符。使用 GiST 索引。适用于调度、版本控制、数值范围。选择一个边界方案并始终如一地使用它;默认优先使用 [)(包含/排除)。INET,网络范围使用 CIDR,MAC 地址使用 MACADDR。支持网络操作符(<<、>>、&&)。POINT、LINE、POLYGON、CIRCLE。使用 GiST 索引。对于高级空间功能,考虑 PostGIS。TSVECTOR,搜索查询使用 TSQUERY。使用 GIN 索引 tsvector。始终指定语言:to_tsvector('english', col) 和 to_tsquery('english', 'query')。切勿使用单参数版本。这适用于索引表达式和查询。CREATE DOMAIN email AS TEXT CHECK (VALUE ~ '^[^@]+@[^@]+$') 创建具有验证的可重用自定义类型。跨表强制执行约束。CREATE TYPE address AS (street TEXT, city TEXT, zip TEXT) 用于列内的结构化数据。使用 (col).field 语法访问。pgvector 的 vector 类型进行嵌入向量的相似性搜索。timestamp(不带时区);请改用 timestamptz。char(n) 或 varchar(n);请改用 text。money 类型;请改用 numeric。timetz 类型;请改用 timestamptz。timestamptz(0) 或任何其他精度指定;请改用 timestamptzserial 类型;请改用 generated always as identity。使用 ALTER TABLE tbl ENABLE ROW LEVEL SECURITY 启用。创建策略:CREATE POLICY user_access ON orders FOR SELECT TO app_users USING (user_id = current_user_id())。内置的基于用户的行级访问控制。
ON DELETE/UPDATE 操作(CASCADE、RESTRICT、SET NULL、SET DEFAULT)。在引用列上添加显式索引——加速连接并防止父表删除/更新时的锁定问题。对于在事务结束时检查的循环外键依赖,使用 DEFERRABLE INITIALLY DEFERRED。NULLS NOT DISTINCT(PG15+)。标准行为:允许 (1, NULL) 和 (1, NULL)。使用 NULLS NOT DISTINCT:只允许一个 (1, NULL)。优先使用 NULLS NOT DISTINCT,除非你特别需要重复的 NULL 值。CHECK (price > 0) 允许 NULL 价格。与 NOT NULL 结合以强制执行:price NUMERIC NOT NULL CHECK (price > 0)。EXCLUDE USING gist (room_id WITH =, booking_period WITH &&) 防止房间重复预订。需要适当的索引类型(通常是 GiST)。=、<、>、BETWEEN、ORDER BY)WHERE a = ? AND b > ? 使用 (a,b) 上的索引,但 WHERE b = ? 不使用)。将选择性最高/最频繁筛选的列放在前面。CREATE INDEX ON tbl (id) INCLUDE (name, email) - 包含非键列以实现仅索引扫描,无需访问表。WHERE status = 'active' → CREATE INDEX ON tbl (user_id) WHERE status = 'active')。任何带有 status = 'active' 的查询都可以使用此索引。CREATE INDEX ON tbl (LOWER(email)))。WHERE 子句中的表达式必须完全匹配:WHERE LOWER(email) = 'user@example.com'。@>、?)、全文搜索(@@)CLUSTER 之后)。PARTITION BY RANGE (created_at))。创建分区:CREATE TABLE logs_2024_01 PARTITION OF logs FOR VALUES FROM ('2024-01-01') TO ('2024-02-01')。TimescaleDB 通过保留策略和压缩自动进行基于时间或 ID 的分区。PARTITION BY LIST (region))。示例:FOR VALUES IN ('us-east', 'us-west')。PARTITION BY HASH (user_id))。使用模数创建 N 个分区。CHECK 约束,以便查询规划器进行剪枝。声明式分区(PG10+)会自动创建。fillfactor=90 为 HOT 更新留出空间,避免索引维护。COPY 或多行 INSERT 而不是单行插入。BIGINT GENERATED ALWAYS AS IDENTITY 而不是 UUID。ON CONFLICT (col1, col2) 需要完全匹配的唯一索引(部分索引无效)。EXCLUDED.column 引用将要插入的值;仅更新实际发生变化的列以减少写入开销。DO NOTHING 比 DO UPDATE 更快。BEGIN; ALTER TABLE...; ROLLBACK; 用于安全测试。CREATE INDEX CONCURRENTLY 避免阻塞写入,但不能在事务中运行。now()、gen_random_uuid())的 NOT NULL 列会重写整个表。非易变默认值很快。ALTER TABLE DROP CONSTRAINT 然后 DROP COLUMN 以避免依赖问题。CREATE OR REPLACE 会创建重载,而不是替换。如果不希望重载,请删除旧版本。... GENERATED ALWAYS AS (<expr>) STORED 用于可计算、可索引的字段。PG18+ 增加了 VIRTUAL 列(读取时计算,不存储)。pgcrypto:用于密码哈希的 crypt()。uuid-ossp:替代的 UUID 函数;新项目优先使用 pgcrypto。pg_trgm:使用 % 操作符和 similarity() 函数进行模糊文本搜索。使用 GIN 索引加速 LIKE '%pattern%'。citext:不区分大小写的文本类型。优先在 LOWER(col) 上使用表达式索引,除非你需要不区分大小写的约束。btree_gin/btree_gist:启用混合类型索引(例如,在 JSONB 和文本列上都使用 GIN 索引)。hstore:键值对;大部分已被 JSONB 取代,但对于简单的字符串映射仍然有用。timescaledb:时序数据必备——自动分区、保留、压缩、连续聚合。postgis:超越基本几何类型的全面地理空间支持——基于位置的应用程序必备。pgvector:用于嵌入向量的向量相似性搜索。pgaudit:所有数据库活动的审计日志记录。JSONB。CREATE INDEX ON tbl USING GIN (jsonb_col); → 加速以下操作:
jsonb_col @> '{"k":"v"}'jsonb_col ? 'k',任意/所有键 ?\|,?&jsonb_col @> ANY(ARRAY['{"status":"active"}', '{"status":"pending"}'])@> 工作负载:考虑使用操作符类 jsonb_path_ops 以获得更小/更快的仅包含索引:
CREATE INDEX ON tbl USING GIN (jsonb_col jsonb_path_ops);?、?|、?&)查询的支持——仅支持包含(@>)ALTER TABLE tbl ADD COLUMN price INT GENERATED ALWAYS AS ((jsonb_col->>'price')::INT) STORED;CREATE INDEX ON tbl (price);WHERE price BETWEEN 100 AND 500(使用 B-tree)这样的查询,而不是没有索引的 WHERE (jsonb_col->>'price')::INT BETWEEN 100 AND 500。@> 进行包含(例如,标签)。如果只做包含查询,考虑 jsonb_path_ops。config JSONB NOT NULL CHECK(jsonb_typeof(config) = 'object')CREATE TABLE users (
user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ON users (LOWER(email));
CREATE INDEX ON users (created_at);
CREATE TABLE orders (
order_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(user_id),
status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','PAID','CANCELED')),
total NUMERIC(10,2) NOT NULL CHECK (total > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON orders (user_id);
CREATE INDEX ON orders (created_at);
CREATE TABLE profiles (
user_id BIGINT PRIMARY KEY REFERENCES users(user_id),
attrs JSONB NOT NULL DEFAULT '{}',
theme TEXT GENERATED ALWAYS AS (attrs->>'theme') STORED
);
CREATE INDEX profiles_attrs_gin ON profiles USING GIN (attrs);
每周安装量
9.6K
代码仓库
GitHub 星标数
32.4K
首次出现
2026 年 1 月 20 日
安全审计
安装于
claude-code6.9K
opencode6.2K
gemini-cli6.1K
codex5.9K
github-copilot5.5K
cursor5.3K
BIGINT GENERATED ALWAYS AS IDENTITY; use UUID only when global uniqueness/opacity is needed.NUMERIC for exact decimal arithmetic).snake_case for table/column names.UNIQUE (...) NULLS NOT DISTINCT (PG15+) to restrict to one NULL.NUMERIC(2,0) fails with error, unlike some databases that silently truncate or round.CLUSTER is one-off reorganization, not maintained on subsequent inserts. Row order on disk is insertion order unless explicitly clustered.BIGINT GENERATED ALWAYS AS IDENTITY preferred (GENERATED BY DEFAULT also fine); UUID when merging/federating/used in a distributed system or for opaque IDs. Generate with uuidv7() (preferred if using PG18+) or gen_random_uuid() (if using an older PG version).BIGINT unless storage space is critical; INTEGER for smaller ranges; avoid SMALLINT unless constrained.DOUBLE PRECISION over REAL unless storage space is critical. Use for exact decimal arithmetic.timestamp (without time zone); DO use timestamptz instead.char(n) or varchar(n); DO use text instead.money type; DO use numeric instead.timetz type; DO use timestamptz instead.timestamptz(0) or any other precision specification; DO use timestamptz insteadEnable with ALTER TABLE tbl ENABLE ROW LEVEL SECURITY. Create policies: CREATE POLICY user_access ON orders FOR SELECT TO app_users USING (user_id = current_user_id()). Built-in user-based access control at the row level.
ON DELETE/UPDATE action (CASCADE, RESTRICT, SET NULL, SET DEFAULT). Add explicit index on referencing column—speeds up joins and prevents locking issues on parent deletes/updates. Use DEFERRABLE INITIALLY DEFERRED for circular FK dependencies checked at transaction end.NULLS NOT DISTINCT (PG15+). Standard behavior: (1, NULL) and (1, NULL) are allowed. With : only one allowed. Prefer unless you specifically need duplicate NULLs.=, <, >, BETWEEN, ORDER BY)WHERE a = ? AND b > ? uses index on (a,b), but WHERE b = ? does not). Put most selective/frequently filtered columns first.CREATE INDEX ON tbl (id) INCLUDE (name, email) - includes non-key columns for index-only scans without visiting table.PARTITION BY RANGE (created_at)). Create partitions: CREATE TABLE logs_2024_01 PARTITION OF logs FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'). TimescaleDB automates time-based or ID-based partitioning with retention policies and compression.PARTITION BY LIST (region)). Example: FOR VALUES IN ('us-east', 'us-west').PARTITION BY HASH (user_id)). Creates N partitions with modulus.CHECK constraints on partitions for query planner to prune. Auto-created for declarative partitioning (PG10+).fillfactor=90 to leave space for HOT updates that avoid index maintenance.COPY or multi-row INSERT instead of single-row inserts.BIGINT GENERATED ALWAYS AS IDENTITY over UUID.ON CONFLICT (col1, col2) needs exact matching unique index (partial indexes don't work).EXCLUDED.column to reference would-be-inserted values; only update columns that actually changed to reduce write overhead.DO NOTHING faster than DO UPDATE when no actual update needed.BEGIN; ALTER TABLE...; ROLLBACK; for safe testing.CREATE INDEX CONCURRENTLY avoids blocking writes but can't run in transactions.NOT NULL columns with volatile defaults (e.g., now(), gen_random_uuid()) rewrites entire table. Non-volatile defaults are fast.ALTER TABLE DROP CONSTRAINT then DROP COLUMN to avoid dependency issues.CREATE OR REPLACE with different arguments creates overloads, not replacements. DROP old version if no overload desired.... GENERATED ALWAYS AS (<expr>) STORED for computed, indexable fields. PG18+ adds VIRTUAL columns (computed on read, not stored).pgcrypto : crypt() for password hashing.uuid-ossp : alternative UUID functions; prefer pgcrypto for new projects.pg_trgm : fuzzy text search with % operator, similarity() function. Index with GIN for LIKE '%pattern%' acceleration.citext : case-insensitive text type. Prefer expression indexes on LOWER(col) unless you need case-insensitive constraints.JSONB with GIN index.CREATE INDEX ON tbl USING GIN (jsonb_col); → accelerates:
jsonb_col @> '{"k":"v"}'jsonb_col ? 'k', any/all keys ?\|, ?&jsonb_col @> ANY(ARRAY['{"status":"active"}', '{"status":"pending"}'])@> workloads: consider opclass for smaller/faster containment-only indexes:
CREATE TABLE users (
user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ON users (LOWER(email));
CREATE INDEX ON users (created_at);
CREATE TABLE orders (
order_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(user_id),
status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','PAID','CANCELED')),
total NUMERIC(10,2) NOT NULL CHECK (total > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON orders (user_id);
CREATE INDEX ON orders (created_at);
CREATE TABLE profiles (
user_id BIGINT PRIMARY KEY REFERENCES users(user_id),
attrs JSONB NOT NULL DEFAULT '{}',
theme TEXT GENERATED ALWAYS AS (attrs->>'theme') STORED
);
CREATE INDEX profiles_attrs_gin ON profiles USING GIN (attrs);
Weekly Installs
9.6K
Repository
GitHub Stars
32.4K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code6.9K
opencode6.2K
gemini-cli6.1K
codex5.9K
github-copilot5.5K
cursor5.3K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
103,800 周安装
ALTER TABLE tbl SET (toast_tuple_target = 4096)LOWER(col)CITEXTNUMERICTEXT; if length limits needed, use CHECK (LENGTH(col) <= n) instead of VARCHAR(n); avoid CHAR(n). Use BYTEA for binary data. Large strings/binary (>2KB default threshold) automatically stored in TOAST with compression. TOAST storage: PLAIN (no TOAST), EXTENDED (compress + out-of-line), EXTERNAL (out-of-line, no compress), MAIN (compress, keep in-line if possible). Default EXTENDED usually optimal. Control with ALTER TABLE tbl ALTER COLUMN col SET STORAGE strategy and ALTER TABLE tbl SET (toast_tuple_target = 4096) for threshold. Case-insensitive: for locale/accent handling use non-deterministic collations; for plain ASCII use expression indexes on LOWER(col) (preferred unless column needs case-insensitive PK/FK/UNIQUE) or CITEXT.NUMERIC(p,s) (never float).TIMESTAMPTZ for timestamps; DATE for date-only; INTERVAL for durations. Avoid TIMESTAMP (without timezone). Use now() for transaction start time, clock_timestamp() for current wall-clock time.BOOLEAN with NOT NULL constraint unless tri-state values are required.CREATE TYPE ... AS ENUM for small, stable sets (e.g. US states, days of week). For business-logic-driven and evolving values (e.g. order statuses) → use TEXT (or INT) + CHECK or lookup table.TEXT[], INTEGER[], etc. Use for ordered lists where you query elements. Index with GIN for containment (@>, <@) and overlap (&&) queries. Access: arr[1] (1-indexed), arr[1:3] (slicing). Good for tags, categories; avoid for relations—use junction tables instead. Literal syntax: '{val1,val2}' or ARRAY[val1,val2].daterange, numrange, tstzrange for intervals. Support overlap (&&), containment (@>), operators. Index with GiST. Good for scheduling, versioning, numeric ranges. Pick a bounds scheme and use it consistently; prefer [) (inclusive/exclusive) by default.INET for IP addresses, CIDR for network ranges, MACADDR for MAC addresses. Support network operators (<<, >>, &&).POINT, LINE, POLYGON, CIRCLE for 2D spatial data. Index with GiST. Consider PostGIS for advanced spatial features.TSVECTOR for full-text search documents, TSQUERY for search queries. Index tsvector with GIN. Always specify language: to_tsvector('english', col) and to_tsquery('english', 'query'). Never use single-argument versions. This applies to both index expressions and queries.CREATE DOMAIN email AS TEXT CHECK (VALUE ~ '^[^@]+@[^@]+$') for reusable custom types with validation. Enforces constraints across tables.CREATE TYPE address AS (street TEXT, city TEXT, zip TEXT) for structured data within columns. Access with (col).field syntax.vector type by pgvector for vector similarity search for embeddings.serial type; DO use generated always as identity instead.NULLS NOT DISTINCT(1, NULL)NULLS NOT DISTINCTCHECK (price > 0) allows NULL prices. Combine with NOT NULL to enforce: price NUMERIC NOT NULL CHECK (price > 0).EXCLUDE USING gist (room_id WITH =, booking_period WITH &&) prevents double-booking rooms. Requires appropriate index type (often GiST).WHERE status = 'active'CREATE INDEX ON tbl (user_id) WHERE status = 'active'status = 'active'CREATE INDEX ON tbl (LOWER(email))). Expression must match exactly in WHERE clause: WHERE LOWER(email) = 'user@example.com'.@>, ?), full-text search (@@)CLUSTER).btree_gin/btree_gist: enable mixed-type indexes (e.g., GIN index on both JSONB and text columns).hstore : key-value pairs; mostly superseded by JSONB but useful for simple string mappings.timescaledb : essential for time-series—automated partitioning, retention, compression, continuous aggregates.postgis : comprehensive geospatial support beyond basic geometric types—essential for location-based applications.pgvector : vector similarity search for embeddings.pgaudit : audit logging for all database activity.jsonb_path_opsCREATE INDEX ON tbl USING GIN (jsonb_col jsonb_path_ops);?, ?|, ?&) queries—only supports containment (@>)ALTER TABLE tbl ADD COLUMN price INT GENERATED ALWAYS AS ((jsonb_col->>'price')::INT) STORED;CREATE INDEX ON tbl (price);WHERE price BETWEEN 100 AND 500 (uses B-tree) over WHERE (jsonb_col->>'price')::INT BETWEEN 100 AND 500 without index.@> for containment (e.g., tags). Consider jsonb_path_ops if only doing containment.config JSONB NOT NULL CHECK(jsonb_typeof(config) = 'object')