Files
medical-mall/docs/sql/04_triggers_and_functions.md

241 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 触发器与函数(数据库承载的业务规则)
本节汇总数据库内实现的关键触发器与函数,说明:
- 它们解决什么业务问题
- 触发时机是什么
- 对数据一致性与性能的影响
- 典型使用/触发示例
---
## 1. 通用触发器:自动维护 `updated_at`
### 1.1 `public.update_updated_at_column()`complete 脚本)
- **目的**:统一把 `updated_at` 设置为当前时间,避免应用层漏写。
- **触发时机**`BEFORE UPDATE`
典型触发表complete 脚本中出现):
- `ml_user_profiles`
- `ml_user_addresses`
- `ml_products`
- `ml_product_skus`
- `ml_shops`
- `ml_orders`
- `ml_shopping_cart`
触发效果示例:
```sql
update public.ml_products
set name = '新标题'
where id = '...product_uuid...'::uuid;
-- updated_at 会自动变为 now()
```
### 1.2 `public.set_updated_at()`(订阅脚本)
订阅模块在 `doc_mall/create_mall_subscription_tables.sql` 里定义了一个更轻量的 `set_updated_at()`,并对:
- `ml_subscription_plans`
- `ml_user_subscriptions`
设置 `BEFORE UPDATE` 触发器。
---
## 2. 地址一致性:默认地址唯一
### `public.ensure_single_default_address()`
- **目的**:保证同一个用户最多只有一个 `is_default = true` 的地址。
- **触发时机**`BEFORE INSERT OR UPDATE ON ml_user_addresses`
- **核心逻辑**:当新行/更新行被设为默认时,把该用户其他地址全部置为非默认。
示例:
```sql
update public.ml_user_addresses
set is_default = true
where id = '...address_uuid...'::uuid;
-- 触发器会把同 user_id 的其他地址 is_default 设为 false
```
注意事项:
- 这是“业务规则下沉 DB”的典型。
- 若存在并发更新两条地址为默认,最终仍会收敛为“最后提交事务的那条为默认”。
---
## 3. 库存汇总SKU 维护SPU 汇总
### `public.update_product_stock()`
- **目的**:当 SKU 改变时,自动汇总刷新商品表的库存字段,避免每次展示都 `join + group by`
- **触发时机**`AFTER INSERT OR UPDATE OR DELETE ON ml_product_skus`
- **影响字段**
- `ml_products.total_stock`
- `ml_products.available_stock`
示例:
```sql
update public.ml_product_skus
set stock = 8
where id = '...sku_uuid...'::uuid;
-- 触发器会汇总该 product_id 下所有 status=1 的 SKU stock
-- 并更新到 ml_products.total_stock / available_stock
```
注意事项:
- 这种“汇总字段”设计利于读性能,但写入 SKU 会产生额外更新 SPU 的成本。
- 若 SKU 更新频率很高,需要评估热点商品写放大。
---
## 4. 订单状态副作用complete 脚本)
### `public.handle_order_status_change()`
- **目的**:订单状态变化时,自动写入关键时间点,并在完成时累计销量。
- **触发时机**`BEFORE UPDATE ON ml_orders`
核心行为(按脚本逻辑):
-`order_status: 1 -> 2`:写入 `paid_at = now()`
-`order_status: 2 -> 3`:写入 `shipped_at = now()`
-`order_status: 3 -> 4`:写入 `delivered_at/completed_at = now()`,并累计 `ml_products.sale_count`
示例:
```sql
update public.ml_orders
set order_status = 2
where id = '...order_uuid...'::uuid;
-- paid_at 自动写入
```
销量累计示例(订单完成):
```sql
update public.ml_orders
set order_status = 4
where id = '...order_uuid...'::uuid;
-- 会对订单明细涉及的商品 sale_count 做累加
```
注意事项:
- 若业务上“支付状态”与“订单状态”不是严格 1->2 的映射,可能需要调整触发条件。
- 累计销量属于“统计字段”,适合下沉 DB但需要考虑退款/取消是否回滚销量(脚本未体现)。
---
## 5. 生成类函数
### 5.1 `public.generate_order_no()` + `public.ml_order_seq`
- **目的**:生成业务订单号(形如 `MLYYYYMMDD000001`)。
- **依赖**:序列 `ml_order_seq`
示例:
```sql
select public.generate_order_no();
```
建议:
- 若订单号生成需要“并发唯一 + 分库分表友好”,可以进一步引入节点号/随机段。
### 5.2 `public.generate_coupon_code()`
- **目的**:生成券码(脚本中为 `CP` + 8 位随机字符)。
示例:
```sql
select public.generate_coupon_code();
```
注意事项:
- 随机生成+唯一约束在极端高并发下可能出现冲突重试需求(一般可接受)。
---
## 6. 查询辅助函数
### 6.1 `public.get_user_default_address(p_user_id uuid)`
- **目的**:快速获取用户默认地址,并拼接 `full_address`
示例:
```sql
select *
from public.get_user_default_address('...user_uuid...'::uuid);
```
### 6.2 `public.calculate_cart_total(p_user_id uuid)`
- **目的**:计算用户购物车选中商品的总金额(按 SKU 价 * 数量)。
示例:
```sql
select public.calculate_cart_total('...user_uuid...'::uuid);
```
注意事项:
- 该函数读取多表cart + sku + product并包含状态过滤。
- 若购物车行数很多或频繁调用,需要关注执行计划与索引。
### 6.3 `public.get_product_available_stock(p_product_id uuid, p_sku_id uuid default null)`
- **目的**:查询商品或指定 SKU 的可用库存。
示例:
```sql
-- 查 SKU
select public.get_product_available_stock('...product_uuid...'::uuid, '...sku_uuid...'::uuid);
-- 查商品汇总
select public.get_product_available_stock('...product_uuid...'::uuid, null);
```
---
## 7. SEO 相关函数complete 脚本)
- `get_product_by_cid(p_cid int)`
- `get_category_by_cid(p_cid int)`
- `get_brand_by_cid(p_cid int)`
- `get_shop_by_cid(p_cid int)`
- `generate_seo_url(p_type, p_cid, p_slug)`
- `update_seo_slugs()`
示例:
```sql
select * from public.get_category_by_cid(1001);
select public.generate_seo_url('shop', 88, 'my-shop');
```
注意事项:
- `update_seo_slugs()` 使用正则把名称转为 slug
- 适合初始化/批处理
- 需要注意多语言、重复 slug、空字符等边界