我正在使用触发器更新一个名为tb_sign_employees的表,其结构如下:
sign_id INT NOT NULL,
employee_id INT NOT NULL,
sign_term_in CHARACTER(3) NOT NULL,
sign_data_in TIMESTAMP NOT NULL,
sign_term_out CHARACTER(3),
sign_data_out TIMESTAMP,
sign_time_minutes INT,
主键是sign_id。我需要使用触发器tg_sign_inserted(),所以当我插入employee_id和sign_term_in时,它会自动插入一个新的sign_id,它是1加上最大的sign_id和sign_data_in,这是当前的时间戳,剩下的为null。然后,如果我插入employee_id和sign_term_out,它应该用我输入的那个更新sign_term_out,用当前时间戳更新sign_data_out,并将sign_time_minutes作为sign_data_out减去sign_data_in分钟。此外,如果您之前已经执行过但未更新值,则无法插入,如果您签入但未 checkout ,则无法再次签入。如果你已经 checkout 了,就不可能 checkout ,你不能在插入时使用sign_tern_in和sign_term_out。我试过:
CREATE OR REPLACE FUNCTION erp.fn_check_sign_inserted_values(new_sign erp.tb_sign_employees)
RETURNS BOOLEAN AS $$
BEGIN
IF new_sign.sign_term_in IS NULL AND new_sign.sign_term_out IS NULL THEN
RAISE EXCEPTION 'Cal informar de sign_term_in o sign_term_out';
END IF;
IF new_sign.sign_term_in IS NOT NULL AND new_sign.sign_term_out IS NOT NULL THEN
RAISE EXCEPTION 'Cal informar de sign_term_in o sign_term_out, no els dos';
END IF;
IF new_sign.sign_term_in IS NOT NULL THEN
IF new_sign.employee_id IS NULL THEN
RAISE EXCEPTION 'Cal informar del id del empleat';
END IF;
IF EXISTS (
SELECT 1
FROM erp.tb_sign_employees
WHERE employee_id = new_sign.employee_id
AND sign_term_out IS NULL
) THEN
RAISE EXCEPTION E'Ja hi ha un registre d''entrada sense sortida';
END IF;
END IF;
IF new_sign.sign_term_out IS NOT NULL THEN
IF new_sign.employee_id IS NULL THEN
RAISE EXCEPTION 'Cal informar del id del empleat';
END IF;
IF NOT EXISTS (
SELECT 1
FROM erp.tb_sign_employees
WHERE employee_id = new_sign.employee_id
AND sign_term_out IS NULL
) THEN
RAISE EXCEPTION E'Ja hi ha un registre d''entrada sense sortida';
END IF;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION erp.tg_sign_inserted()
RETURNS TRIGGER AS $$
BEGIN
IF NOT erp.fn_check_sign_inserted_values(NEW) THEN
RETURN NULL;
END IF;
IF NEW.sign_term_in IS NOT NULL THEN
INSERT INTO erp.tb_sign_employees (sign_id, employee_id, sign_term_in, sign_data_in)
SELECT COALESCE(MAX(sign_id), 0) + 1, NEW.employee_id, NEW.sign_term_in, current_timestamp
FROM erp.tb_sign_employees;
ELSIF NEW.sign_term_out IS NOT NULL THEN
UPDATE erp.tb_sign_employees
SET sign_term_out = NEW.sign_term_out,
sign_data_out = current_timestamp,
sign_time_minutes = extract(epoch from current_timestamp - sign_data_in) / 60
WHERE employee_id = NEW.employee_id
AND sign_term_out IS NULL
AND sign_term_in IS NOT NULL; -- Agregamos esta condición para actualizar solo las filas con sign_term_in ya definido.
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tg_insert_sign_employee
BEFORE INSERT ON erp.tb_sign_employees
FOR EACH ROW
EXECUTE FUNCTION erp.tg_sign_inserted()
当我用途:
INSERT INTO erp.tb_sign_employees(employee_id, sign_term_in)
VALUES(1, 'Terminal1')
它的工作原理只是文件,但如果然后我用途:
INSERT INTO erp.tb_sign_employees(employee_id, sign_term_out)
VALUES(1, 'Terminal1')
它并没有填充由前面的命令生成的行,而是尝试创建一个新行,如下所示:(null,1,null,null,EP,null,null)导致违反列sign_id中的非空约束。它不更新列的事实是一个错误,但不是唯一的错误,因为试图创建的列包含null,而不是在sing_data_out中插入当前时间戳。我看不到错误。为了给予你一个它应该如何表现的例子,假设最大的sign_id是1。然后使用日期2023-03-06在9点钟:
INSERT INTO erp.tb_sign_employees(employee_id, sign_term_in)
VALUES(1, 'Terminal1')
应创建以下行:
2, 1, Terminal1, 2023-03-06 09:14:00, null, null, null
如果我在同一天12点执行了以下操作:
INSERT INTO erp.tb_sign_employees(employee_id, sign_term_out)
VALUES(1, 'Terminal2')
2, 1, Terminal1, 2023-03-06 09:00:00, Terminal2, 2023-03-06 12:00:00, 180
其中180是12点和9点之间的分钟差。先谢谢你了。
1条答案
按热度按时间ktca8awb1#
你的扳机有几个大问题。如上所述,它不会像声明的那样插入初始行。触发器在
insert
上触发到表erp.tb_sign_employees
。然后在触发器中,它尝试执行相同的操作:结果,触发器再次触发,并且由于列
NEW.sign_term_in
仍然为空,因此触发器再次触发。它创建了一个无限循环,最终导致异常:SQL错误[54001]:错误:超出堆栈深度限制...;
不应在触发器中发出与最初触发触发器的语句相同的DML语句。在这种情况下,这是不必要的。您可以对列进行直接分配或选择进入,以便:
现在我们来谈第二个问题。以上是非常糟糕的实践,因为在多用户环境中,MAX+1分配实际上是获得副本的保证。当两个用户尝试插入一行,并且第二个用户在第一个用户提交之前执行时,就会发生这种情况,因为每个用户都将获得相同的MAX值。
现在来谈谈实际上引发你的问题的问题。您声明 * 它不更新列是一个错误... *。这是错误的,它正确地更新。问题是你实际上把一个
insert
变成了一个update
。但是,Postgres不会识别这种更改,因此当您在return new
之后执行时,它会继续处理插入。由于列sign_in
没有被赋值,也没有自动生成,所以它是NULL,因此您会得到空值异常,插入被中止。不幸的是,更新也被中止(回滚),所以它看起来还没有完成。正确的处理方法是只发出一个update
而不是第二个insert
。然而,周围有一个非常规的工作。更新后不发出return new
,而是发出返回null将终止对
insert
的进一步处理,但将保留更新的行。(行计数,如果taken将被关闭,则终止的插入不计为已处理的行。)考虑到所有这些,您的触发器函数变为:参见demo here。注意:对于演示,我已经硬编码了您提到的时间戳,并更正了以下内容。
最后,即使没有上述问题,您的初始插入也会失败,原因还有两个,可能只是错别字。1.列
sign_term_in
和sign_term_out
被定义为CHARACTER(3),但您希望插入值Terminal1
和Terminal2
。这些值将导致异常,因为它们不适合分配的字段限制。2.列sign_data_in
被定义为非空,但没有给出默认值,也没有在insert
上引用。因此,它将引发空值异常。您声明您的 * 命令是不修改结构 *。要插入指定的值,您必须**修改结构。当你和那些给你下命令的人讨论这个问题的时候,告诉他们MAX+1对于分配
sign_id
是多么的糟糕,并让他们也改变它。改变这两个,你最有可能可以消除触发所有在一起。