You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Django模型实现PostgreSQL自定义ID生成逻辑?

Django实现自定义自增ID生成逻辑方案

现有PostgreSQL逻辑说明

函数代码

SELECT CONCAT(f_office_id,f_sy,f_sem,'-',office_serial) INTO office_lastnumber from curriculum.clearing_office
    where office_id=f_office_id;
    
UPDATE curriculum.clearing_office SET office_serial =office_serial+1 where office_id=f_office_id;

函数作用

保存条目时生成形如test2021-20221-1的cl_itemid,同时将clearing_office表中的office_serial字段自增1,下一次保存的条目ID会变为test2021-20221-2。

传入参数示例

{
    "studid": "4321-4321",
    "office": "test",
    "sem": "1",
    "sy": "2021-2022"
}

现有Django Item模型

class Item(models.Model):
    cl_itemid = models.CharField(primary_key=True, max_length=20)
    studid = models.CharField(max_length=9, blank=True, null=True)
    office = models.ForeignKey('ClearingOffice', models.DO_NOTHING, blank=True, null=True)
    sem = models.CharField(max_length=1, blank=True, null=True)
    sy = models.CharField(max_length=9, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'item'

解决方案

方案一:Django信号+事务实现

利用pre_save信号在保存前生成ID,通过事务保证原子性,避免并发场景下的序列号重复问题:

  1. 先确保ClearingOffice模型定义完成(示例结构):
class ClearingOffice(models.Model):
    office_id = models.CharField(primary_key=True, max_length=50)
    f_sy = models.CharField(max_length=9)
    f_sem = models.CharField(max_length=1)
    office_serial = models.IntegerField(default=1)

    class Meta:
        managed = False
        db_table = 'curriculum.clearing_office'
  1. 编写信号处理函数:
from django.db import transaction
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Item, ClearingOffice

@receiver(pre_save, sender=Item)
def generate_cl_itemid(sender, instance, **kwargs):
    if not instance.cl_itemid:
        with transaction.atomic():
            # 锁定目标行,防止并发更新冲突
            office = ClearingOffice.objects.select_for_update().get(office_id=instance.office.office_id)
            # 拼接生成ID
            instance.cl_itemid = f"{office.office_id}{office.f_sy}{office.f_sem}-{office.office_serial}"
            # 自增序列号并保存
            office.office_serial += 1
            office.save()

方案二:调用现有PostgreSQL函数

如果想保留原数据库函数,可通过Django的数据库游标直接调用:

from django.db import connection

class Item(models.Model):
    # 字段定义同原模型

    def save(self, *args, **kwargs):
        if not self.cl_itemid:
            with connection.cursor() as cursor:
                # 调用数据库中定义的生成函数(需提前在PostgreSQL中创建完整函数)
                cursor.execute("SELECT generate_cl_itemid(%s, %s, %s)", [self.office.office_id, self.sy, self.sem])
                self.cl_itemid = cursor.fetchone()[0]
        super().save(*args, **kwargs)

方案三:数据库序列+触发器(高并发场景推荐)

在PostgreSQL中为每个办公点创建独立序列,配合触发器自动生成ID,Django端无需额外逻辑:

  1. 执行SQL创建序列与触发器:
-- 为指定办公点创建序列(可批量生成所有办公点的序列)
CREATE SEQUENCE curriculum.office_test_serial_seq START WITH 1;

-- 定义触发器函数
CREATE OR REPLACE FUNCTION curriculum.generate_cl_itemid_trigger()
RETURNS TRIGGER AS $$
BEGIN
    NEW.cl_itemid = CONCAT(NEW.office_id, NEW.sy, NEW.sem, '-', nextval('curriculum.office_' || NEW.office_id || '_serial_seq'));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 绑定触发器到item表
CREATE TRIGGER trigger_generate_cl_itemid
BEFORE INSERT ON curriculum.item
FOR EACH ROW EXECUTE FUNCTION curriculum.generate_cl_itemid_trigger();
  1. Django端只需正常保存Item实例,cl_itemid由数据库自动生成。

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 03:31:22