数据库 创建触发器实现以下功能
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库 创建触发器实现以下功能相关的知识,希望对你有一定的参考价值。
建立部门的delete触发器,当删除一条部门记录,1)如果这个部门有负责的项目则将它所负责的项目转到除该部门以外负责项目最少的部门名下。2)将这个部门的员工转到2号部门下。(假设删除的不是2号部门)
SQL server
员工(编号,姓名,性别,年龄,部门编号,年薪)
部门(编号,名称,人数,负责人)
项目(编号,名称,负责部门编号)
-- 此触发器 认为你一个 SQL 语句, 只删除 一个部门的情况。
-- 如果存在 一条 SQL 语句, 删除多个部门的,那么还需要修改.
CREATE TRIGGER AfterDeleteTest
ON 部门
FOR DELETE
AS
-- 这里假设部门编号的数据类型为 int 类型.
DECALRE
@DelCode INT,
@MinPrjCode INT;
BEGIN
-- 获取被删除的部门编号.
SELECT @DelCode = 编号 FROM deleted;
-- 获取除该部门以外负责项目最少的部门代码。
SELECT
top 1
@MinPrjCode = 负责部门编号
FROM
项目
WHERE
负责部门编号 @DelCode
GROUP BY
负责部门编号
ORDER BY
COUNT(*);
-- 1)如果这个部门有负责的项目则将它所负责的项目转到除该部门以外负责项目最少的部门名下。
UPDATE
项目
SET
负责部门编号 = @MinPrjCode
WHERE
负责部门编号 = @DelCode;
-- 2)将这个部门的员工转到2号部门下。
UPDATE
员工
SET
部门编号 = 2
WHERE
部门编号 = @DelCode;
END
使用数据库触发器生成无间隙数字
【中文标题】使用数据库触发器生成无间隙数字【英文标题】:Generate gap free numbers with database trigger 【发布时间】:2015-12-31 16:57:46 【问题描述】:我正在与我的团队一起开发生成发票编号的功能。要求说:
发票编号之间不应有间隔 数字应每年从 0 开始(与年份一起我们将拥有唯一的密钥) 发票编号应根据发票创建时间增长我们正在使用 php 和 postgres。我们通过以下方式实现这一点:
每次在数据库中保存新发票时,我们都会使用 BEFORE INSERT 触发器 触发器执行一个函数,该函数从 postgres 序列中检索新值并将其作为编号写入发票考虑到在同一笔交易中可能会创建多张发票,我的问题是:这是一种足够安全的方法吗?它的缺陷是什么?您建议如何改进它?
【问题讨论】:
为什么不使用 auto_increment 字段呢?您可以每年创建一个新表...该表将只有 auto_invrement PK 和关联发票的 id。每次您坚持并开具发票时,您都会将其 id 添加到他们的年表中......使用您的发票 id 查询该表并获得您的发票编号 @JulioSoares 您的建议似乎与我正在做的事情几乎相同。在 postgres 中,一个 auto_increment PK 正在使用一个序列,所以你只是建议将该序列存储在一个表中 你需要一个触发器吗?一个序列应该可以完成这项工作。将 nextval(⋅) 分配为该字段的默认值,您就完成了。设置一个每年初始化序列的批次。 @greg 其实我把情况简化了一点。我有多个序列可以完成这项工作,因为我有多个发票组,每个组都需要有其自主的无间隙编号。此外,仅使用序列会产生问题,因为序列不能很好地处理事务(即,我可以生成一个新数字,然后事务失败,但序列不会返回,因此丢失了一个数字) 查看***.com/q/9984196/398670了解详细的文章 【参考方案1】:简介
我认为这里最关键的一点是:
发票编号之间不应有间隔
在这种情况下,您不能使用序列和自动增量字段(正如其他人在 cmets 中建议的那样)。无论事务成功还是失败,自动递增字段使用序列和nextval(regclass)
函数都会递增序列的计数器(您自己指出)。
更新:
我的意思是你根本不应该使用序列,尤其是你提出的解决方案并没有消除间隙的可能性。您的触发器获得了新的序列值,但 INSERT
仍然可能失败。
序列之所以这样工作,是因为它们主要用于PRIMARY KEYs
和OIDs
值的生成,其中唯一性和非阻塞机制是最终目标,值之间的差距真的没什么大不了的。
但是,在您的情况下,优先级可能会有所不同,但有几件事需要考虑。
简单的解决方案
您的问题的第一个可能解决方案可能是将新数字作为当前现有数字的最大值返回。可以在您的触发器中完成:
NEW.invoice_number =
(SELECT foo.invoice_number
FROM invoices foo
WHERE foo._year = NEW._year
ORDER BY foo.invoice_number DESC NULLS LAST LIMIT 1
); /*query 1*/
这个查询可以使用你的复合UNIQUE INDEX
,如果它是用“正确的”语法和列顺序创建的,首先是“年”列,例如:
CREATE UNIQUE INDEX invoice_number_unique
ON invoices (_year, invoice_number DESC NULLS LAST);
在 PostgreSQL 中,UNIQUE CONSTRAINTs
被简单地实现为UNIQUE INDEXes
,所以大多数时候你将使用哪个命令没有区别。但是,使用上面介绍的特定语法,可以在该索引上定义顺序。如果发票表变大,这确实是一个很好的技巧,它使/*query 1*/
比简单的SELECT max(invoice_number) FROM invoices WHERE _year = NEW.year
更快。
这是一种简单的解决方案,但有一个很大的缺点。当两个事务尝试同时插入发票时,可能会出现竞争条件。两者都可以获得相同的最大值,UNIQUE CONSTRAINT
将阻止第二个提交。尽管在某些具有特殊插入策略的小型系统中它可能就足够了。
更好的解决方案
你可以创建表
CREATE TABLE invoice_numbers(
_year INTEGER NOT NULL PRIMARY KEY,
next_number_within_year INTEGER
);
存储特定年份的下一个可能数字。然后,在AFTER INSERT
触发器中,您可以:
-
锁定 invoice_numbers,其他交易甚至无法读取该号码
LOCK TABLE invoice_numbers IN ACCESS EXCLUSIVE;
获取新发票号new_invoice_number = (SELECT foo.next_number_within_year FROM invoice_numbers foo where foo._year = NEW.year);
更新新增发票行的数值
递增UPDATE invoice_numbers SET next_number_within_year = next_number_within_year + 1 WHERE _year = NEW._year;
由于事务在提交之前持有表锁,这可能应该是最后一次触发的触发器 (read more about trigger execution order here)
更新:
用Craig Ringer提供的link命令检查link而不是用LOCK
锁定整个表
这种情况下的缺点是INSERT
操作性能下降---当时只有一个事务可以执行插入。
【讨论】:
使用update ... returning
而不是单独的select
然后update
。或者至少使用select ... for update
。事实上,您上面的大部分问题都已由select ... for update
解决。以上是关于数据库 创建触发器实现以下功能的主要内容,如果未能解决你的问题,请参考以下文章
具有实时数据库触发器的 Firebase 云功能:如何更新源节点?