如何通过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,通过事务保证原子性,避免并发场景下的序列号重复问题:
- 先确保
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'
- 编写信号处理函数:
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端无需额外逻辑:
- 执行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();
- Django端只需正常保存Item实例,
cl_itemid由数据库自动生成。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

