热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
本提示词专为数据库管理员和开发人员设计,能够根据具体的数据库类型和业务需求,生成专业、准确的触发器代码。通过系统化的需求分析和代码生成流程,确保触发器逻辑严谨、性能优化,并提供详细的代码注释和使用说明,帮助用户快速实现数据库层面的业务规则自动化,提升数据一致性和系统可靠性。
| 变量 | 描述 | 示例 |
|---|---|---|
| 数据库类型 | 目标数据库管理系统类型 | mysql |
| 触发动作 | 触发器的触发时机和操作类型 | update |
| 业务场景 | 触发器的具体业务应用场景描述 | 用户注册后自动初始化账户信息 |
-- 假设表结构:
-- orders(id PK, status)
-- order_items(order_id, sku_id, quantity)
-- products(sku_id PK, stock)
-- inventory_ledger(sku_id, delta, order_id, ts)
DELIMITER //
DROP TRIGGER IF EXISTS trg_orders_after_update_shipped//
CREATE TRIGGER trg_orders_after_update_shipped
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
-- 仅当订单状态由 PAID -> SHIPPED 时执行
IF (OLD.status = 'PAID' AND NEW.status = 'SHIPPED') THEN
DECLARE v_missing INT DEFAULT 0; -- 未处理 SKU 中,有无在 products 不存在的
DECLARE v_insufficient INT DEFAULT 0; -- 未处理 SKU 中,有无库存不足的
-- 步骤1:锁定本次需处理的 products 行,降低并发超卖风险(按主键顺序加锁可减小死锁概率)
SELECT p.sku_id
FROM products p
JOIN (
SELECT sku_id, SUM(quantity) AS qty
FROM order_items
WHERE order_id = NEW.id
GROUP BY sku_id
HAVING SUM(quantity) > 0
) oi ON oi.sku_id = p.sku_id
LEFT JOIN inventory_ledger l
ON l.order_id = NEW.id AND l.sku_id = oi.sku_id
WHERE l.order_id IS NULL
ORDER BY p.sku_id
FOR UPDATE;
-- 步骤2:检查:未处理的 SKU 是否在 products 中缺失
SELECT COUNT(*) INTO v_missing
FROM (
SELECT oi.sku_id
FROM (
SELECT sku_id, SUM(quantity) AS qty
FROM order_items
WHERE order_id = NEW.id
GROUP BY sku_id
HAVING SUM(quantity) > 0
) oi
LEFT JOIN inventory_ledger l
ON l.order_id = NEW.id AND l.sku_id = oi.sku_id
LEFT JOIN products p
ON p.sku_id = oi.sku_id
WHERE l.order_id IS NULL
AND p.sku_id IS NULL
) AS x;
IF v_missing > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Stock deduction failed: one or more SKUs in the order do not exist (unprocessed).';
END IF;
-- 步骤3:检查:未处理的 SKU 是否库存不足
SELECT COUNT(*) INTO v_insufficient
FROM (
SELECT oi.sku_id
FROM (
SELECT sku_id, SUM(quantity) AS qty
FROM order_items
WHERE order_id = NEW.id
GROUP BY sku_id
HAVING SUM(quantity) > 0
) oi
JOIN products p
ON p.sku_id = oi.sku_id
LEFT JOIN inventory_ledger l
ON l.order_id = NEW.id AND l.sku_id = oi.sku_id
WHERE l.order_id IS NULL
AND p.stock < oi.qty
) AS y;
IF v_insufficient > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Stock deduction failed: insufficient stock for one or more SKUs (unprocessed).';
END IF;
-- 步骤4:扣减库存(仅对尚未入台账的 SKU 执行,保证幂等)
UPDATE products p
JOIN (
SELECT sku_id, SUM(quantity) AS qty
FROM order_items
WHERE order_id = NEW.id
GROUP BY sku_id
HAVING SUM(quantity) > 0
) oi
ON p.sku_id = oi.sku_id
LEFT JOIN inventory_ledger l
ON l.order_id = NEW.id AND l.sku_id = oi.sku_id
SET p.stock = p.stock - oi.qty
WHERE l.order_id IS NULL;
-- 步骤5:写入库存台账(幂等:仅对尚未入台账的 SKU 写入;建议配合唯一索引)
INSERT INTO inventory_ledger (sku_id, delta, order_id, ts)
SELECT oi.sku_id, -oi.qty, NEW.id, NOW()
FROM (
SELECT sku_id, SUM(quantity) AS qty
FROM order_items
WHERE order_id = NEW.id
GROUP BY sku_id
HAVING SUM(quantity) > 0
) oi
LEFT JOIN inventory_ledger l
ON l.order_id = NEW.id AND l.sku_id = oi.sku_id
WHERE l.order_id IS NULL;
END IF;
END//
DELIMITER ;
关键语法解释
业务逻辑说明
注意事项提醒
如需,我可以提供配套索引及约束的 DDL 建议(强烈推荐):
-- 要求:PostgreSQL 12+;需要扩展 pgcrypto 用于哈希与 UUID
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- 可选应用级配置项(可通过 ALTER DATABASE/ROLE SET 设置持久化)
-- app.base_currency:基础币种(默认 USD)
-- app.fx_table:汇率表名(默认 fx_rates)
-- app.period_table:会计期间表名(默认 accounting_periods)
-- 示例期望表结构(用于文档说明,不会被执行)
-- gl_entry(
-- id bigserial pk,
-- account_id bigint not null,
-- currency_code text not null,
-- debit_amount numeric not null default 0,
-- credit_amount numeric not null default 0,
-- base_amount numeric not null,
-- fx_rate numeric,
-- fx_rate_version integer,
-- base_currency_code text default current_setting('app.base_currency', true),
-- posted_at timestamptz,
-- period_id integer,
-- trace_id uuid,
-- description text,
-- source_system text,
-- signature text, -- 建议为 text 存放十六进制哈希
-- created_at timestamptz default now()
-- );
-- 辅助函数:获取开启的会计期间ID(动态SQL避免编译期对象依赖)
CREATE OR REPLACE FUNCTION fn_get_open_period_id(p_ts timestamptz)
RETURNS integer
LANGUAGE plpgsql
STABLE
SET search_path = pg_catalog, public
AS $$
DECLARE
v_tbl text := COALESCE(current_setting('app.period_table', true), 'accounting_periods');
v_sql text;
v_id integer;
BEGIN
IF to_regclass(v_tbl) IS NULL THEN
RAISE EXCEPTION '会计期间表 "%" 不存在,请通过 app.period_table 指定正确表名', v_tbl;
END IF;
v_sql := format(
'SELECT id FROM %I
WHERE start_at <= $1 AND $1 < end_at
AND is_open = true
ORDER BY id DESC
LIMIT 1
FOR SHARE',
v_tbl
);
EXECUTE v_sql INTO v_id USING p_ts;
IF v_id IS NULL THEN
RAISE EXCEPTION '未找到开启的会计期间覆盖时间点 %', p_ts;
END IF;
RETURN v_id;
END;
$$;
-- 辅助函数:查询并锁定汇率与版本
-- 期望 fx_rates 表具备字段:from_currency, to_currency, valid_from, valid_to, rate, version
CREATE OR REPLACE FUNCTION fn_get_fx(p_from text, p_to text, p_at timestamptz)
RETURNS TABLE(rate numeric, version integer)
LANGUAGE plpgsql
STABLE
SET search_path = pg_catalog, public
AS $$
DECLARE
v_tbl text := COALESCE(current_setting('app.fx_table', true), 'fx_rates');
v_sql text;
BEGIN
IF p_from = p_to THEN
rate := 1;
version := 0;
RETURN NEXT;
RETURN;
END IF;
IF to_regclass(v_tbl) IS NULL THEN
RAISE EXCEPTION '汇率表 "%" 不存在,请通过 app.fx_table 指定正确表名', v_tbl;
END IF;
v_sql := format($f$
SELECT r.rate, r.version
FROM %I r
WHERE r.from_currency = $1
AND r.to_currency = $2
AND r.valid_from <= $3
AND (r.valid_to IS NULL OR $3 < r.valid_to)
ORDER BY r.valid_from DESC, r.version DESC
LIMIT 1
FOR SHARE
$f$, v_tbl);
EXECUTE v_sql INTO rate, version USING p_from, p_to, p_at;
IF rate IS NULL THEN
RAISE EXCEPTION '未找到汇率:% -> % 在 %', p_from, p_to, p_at;
END IF;
RETURN NEXT;
END;
$$;
-- BEFORE INSERT 触发器函数:单行校验、自动补全、汇率折算、签名生成
CREATE OR REPLACE FUNCTION trg_bi_gl_entry()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
SET search_path = pg_catalog, public
AS $$
DECLARE
v_base_ccy text := COALESCE(current_setting('app.base_currency', true), 'USD');
v_rate numeric;
v_ver integer;
v_amt numeric;
BEGIN
-- 1) 借贷规范:仅一边为正,且不可同时为零或同时为正
IF COALESCE(NEW.debit_amount, 0) > 0 AND COALESCE(NEW.credit_amount, 0) > 0 THEN
RAISE EXCEPTION '借贷同时为正:debit=%, credit=%', NEW.debit_amount, NEW.credit_amount;
END IF;
IF COALESCE(NEW.debit_amount, 0) = 0 AND COALESCE(NEW.credit_amount, 0) = 0 THEN
RAISE EXCEPTION '借贷至少一边为正(另一边必须为0)';
END IF;
IF NEW.debit_amount < 0 OR NEW.credit_amount < 0 THEN
RAISE EXCEPTION '借贷金额不可为负';
END IF;
-- 2) 自动补全 posted_at / trace_id
IF NEW.posted_at IS NULL THEN
NEW.posted_at := clock_timestamp();
END IF;
IF NEW.trace_id IS NULL THEN
NEW.trace_id := gen_random_uuid();
END IF;
-- 3) 会计期开启校验(并回填 period_id,如存在该列)
-- 若表结构中不存在 period_id,可忽略赋值,但校验仍会执行
PERFORM 1;
BEGIN
NEW.period_id := fn_get_open_period_id(NEW.posted_at);
EXCEPTION
WHEN undefined_column THEN
-- period_id 列不存在,仅做校验不回填
PERFORM fn_get_open_period_id(NEW.posted_at);
END;
-- 4) 汇率折算与版本锁定
IF NEW.currency_code IS NULL THEN
RAISE EXCEPTION 'currency_code 不能为空';
END IF;
IF NEW.currency_code = v_base_ccy THEN
-- 基础币种:汇率视为1,版本可置0
NEW.fx_rate := 1;
IF NEW.fx_rate_version IS NULL THEN
NEW.fx_rate_version := 0;
END IF;
ELSE
-- 若外部已经传入 fx_rate 与版本,可复用并不重复查询;否则查询并锁定
IF NEW.fx_rate IS NULL OR NEW.fx_rate <= 0 OR NEW.fx_rate_version IS NULL THEN
SELECT rate, version INTO v_rate, v_ver
FROM fn_get_fx(NEW.currency_code, v_base_ccy, NEW.posted_at);
IF NEW.fx_rate IS NULL OR NEW.fx_rate <= 0 THEN
NEW.fx_rate := v_rate;
END IF;
IF NEW.fx_rate_version IS NULL THEN
NEW.fx_rate_version := v_ver;
END IF;
END IF;
IF NEW.fx_rate IS NULL OR NEW.fx_rate <= 0 THEN
RAISE EXCEPTION '无效汇率:% -> % at %', NEW.currency_code, v_base_ccy, NEW.posted_at;
END IF;
END IF;
-- 5) 计算 base_amount(以正额计量,方向由借/贷体现)
v_amt := CASE
WHEN COALESCE(NEW.debit_amount, 0) > 0 THEN NEW.debit_amount
ELSE NEW.credit_amount
END;
NEW.base_amount := v_amt * NEW.fx_rate;
-- 6) 生成幂等签名(sha256,十六进制),作为去重依据
-- 签名构成应涵盖:trace_id、账户、币种、借贷金额、base_amount、基础币种、汇率/版本、posted_at(秒级)
-- 注意:签名字段 signature 建议为 text
NEW.signature := encode(
digest(
concat_ws('|',
COALESCE(NEW.trace_id::text, ''),
COALESCE(NEW.account_id::text, ''),
COALESCE(NEW.currency_code, ''),
to_char(COALESCE(NEW.debit_amount, 0), 'FM9999999999990.999999'),
to_char(COALESCE(NEW.credit_amount, 0), 'FM9999999999990.999999'),
to_char(COALESCE(NEW.base_amount, 0), 'FM9999999999990.999999'),
v_base_ccy,
to_char(COALESCE(NEW.fx_rate, 0), 'FM9999999999990.999999'),
COALESCE(NEW.fx_rate_version::text, ''),
to_char(date_trunc('second', NEW.posted_at), 'YYYY-MM-DD"T"HH24:MI:SSOF')
)::bytea,
'sha256'
),
'hex'
);
RETURN NEW;
END;
$$;
-- BEFORE INSERT 触发器
DROP TRIGGER IF EXISTS bi_gl_entry ON gl_entry;
CREATE TRIGGER bi_gl_entry
BEFORE INSERT ON gl_entry
FOR EACH ROW
EXECUTE FUNCTION trg_bi_gl_entry();
-- 事务提交时的借贷平衡校验(按 trace_id 分组),使用约束触发器,初始化为延迟到提交时检查
CREATE OR REPLACE FUNCTION trg_balance_gl_entry()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
SET search_path = pg_catalog, public
AS $$
DECLARE
v_trace uuid;
v_debit numeric;
v_credit numeric;
BEGIN
v_trace := CASE
WHEN TG_OP IN ('INSERT', 'UPDATE') THEN NEW.trace_id
ELSE OLD.trace_id
END;
-- trace_id 为空则不校验(允许独立单条分录场景)
IF v_trace IS NULL THEN
RETURN NULL;
END IF;
SELECT COALESCE(SUM(debit_amount), 0), COALESCE(SUM(credit_amount), 0)
INTO v_debit, v_credit
FROM gl_entry
WHERE trace_id = v_trace;
IF v_debit <> v_credit THEN
RAISE EXCEPTION '分录不平衡(trace_id=%):debit=%,credit=%', v_trace, v_debit, v_credit;
END IF;
RETURN NULL;
END;
$$;
DROP TRIGGER IF EXISTS cs_gl_entry_balance ON gl_entry;
CREATE CONSTRAINT TRIGGER cs_gl_entry_balance
AFTER INSERT OR UPDATE OR DELETE ON gl_entry
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION trg_balance_gl_entry();
-- 去重建议:对签名建立唯一索引(需确保 signature 为 text 且非 NULL)
-- 注意:CONCURRENTLY 不能在事务中执行,如在迁移脚本中请拆到单独事务
-- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS ux_gl_entry_signature ON gl_entry(signature);
-- 性能建议:支撑触发器校验与查询的必要索引(如不存在)
-- 建议的非唯一索引(根据实际表结构调整)
-- CREATE INDEX IF NOT EXISTS ix_gl_entry_trace_id ON gl_entry(trace_id);
关键语法解释
业务逻辑说明
注意事项提醒
基本功能测试场景
边界条件测试建议
说明:以上代码与建议基于 PostgreSQL 12+ 并假定 gl_entry 具有文中提及字段类型。若实际表结构存在差异,请相应调整触发器函数中的列名与签名构成字段,并确保 app.fx_table、app.period_table 指向的对象及列名与查询一致。为避免运行时语义变化,建议在测试环境回归后再上线生产。
-- 先确保会话设置(对象创建时所需)
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
-- 触发器:客户地址删除后审计与归档,并写入 outbox
-- 兼容 SQL Server 2016+(使用 FOR JSON 生成 JSON)
CREATE OR ALTER TRIGGER dbo.trg_customer_address_after_delete
ON dbo.customer_address
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
-- 无行删除时快速返回(包含安全防御)
IF NOT EXISTS (SELECT 1 FROM deleted) RETURN;
BEGIN TRY
DECLARE @now datetime2(3) = SYSUTCDATETIME();
DECLARE @user nvarchar(128) = TRY_CONVERT(nvarchar(128), SESSION_CONTEXT(N'user'));
-- 若应用层未设置 SESSION_CONTEXT('user'),回退为原始登录名
IF @user IS NULL SET @user = ORIGINAL_LOGIN();
-------------------------------------------------------------------------
-- 1) 归档:插入 address_archive(最小需求:原主键、时间戳、删除人)
-- 注意:假定 address_archive 至少包含以下列:
-- (address_id <与主表相同类型>, ts datetime2(3), deleted_by nvarchar(128))
-------------------------------------------------------------------------
INSERT INTO dbo.address_archive (address_id, ts, deleted_by)
SELECT d.address_id, @now, @user
FROM deleted AS d;
-------------------------------------------------------------------------
-- 2) 审计日志:插入 audit_log(table, pk, op, ts)
-- 注意:[table] 为保留字,需加方括号
-- 假定 audit_log 列类型如下:
-- [table] sysname, [pk] nvarchar(256), [op] varchar(10), [ts] datetime2(3)
-------------------------------------------------------------------------
INSERT INTO dbo.audit_log ([table], [pk], [op], [ts])
SELECT
N'dbo.customer_address' AS [table],
CAST(d.address_id AS nvarchar(256)) AS [pk],
N'DELETE' AS [op],
@now AS [ts]
FROM deleted AS d;
-------------------------------------------------------------------------
-- 3) Outbox:写入主题为 address.deleted 的消息
-- 假定 outbox 至少包含:
-- topic sysname, payload nvarchar(max), ts datetime2(3)
-- payload 为每行一个 JSON(含最小必要字段)
-- 若需携带更多上下文,可在子查询中追加字段
-------------------------------------------------------------------------
INSERT INTO dbo.outbox (topic, payload, ts)
SELECT
N'address.deleted' AS topic,
(
SELECT
d.address_id,
d.customer_id
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) AS payload,
@now AS ts
FROM deleted AS d;
END TRY
BEGIN CATCH
-- 将错误冒泡给外层事务,确保原子性
DECLARE @errnum int = ERROR_NUMBER(),
@errmsg nvarchar(4000) = ERROR_MESSAGE(),
@errstate int = ERROR_STATE();
THROW @errnum, @errmsg, @errstate;
END CATCH
END
GO
备注与扩展
用一条提示词,把“写触发器”这件高风险、费时间的工作,变成标准化、可复用、可审计的交付流程。
快速制定统一的触发器规范,批量生成与校验,多库版本适配与迁移更省心;附带上线清单和监控建议,显著降低生产事故与告警噪声。
把业务需求一键转成触发逻辑,自动处理库存更新、余额变更、日志记录;自带注释与测试场景,减少后端代码复杂度与返工。
构建交易审计、异常留痕、额度管控等规则,自动生成边界测试与验证步骤,确保关键数据可追溯、可核验。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
半价获取高级提示词-优惠即将到期