所以我在数据库中有一个表,其中包含列" SELECTED"。此列中的值只能是" CHECKED"或者" UNCHECKED"。我想强制执行" CHECKED"只能通过PL / SQL触发器使用一次(如无线电按钮),但我想不出怎么做。
首先,这个想法(如果它没有变得清晰):
初始表"虚拟":
ID | SELECTED
--------------
1 | 'UNCHECKED'
2 | 'CHECKED'
3 | 'UNCHECKED'
然后,我执行此查询:
UPDATE dummy
SET SELECTED = 'CHECKED'
WHERE ID = 3;
通过PL / SQL触发器,我想要我的桌子" dummy"在执行后看起来像这样:
ID | SELECTED
--------------
1 | 'UNCHECKED'
2 | 'UNCHECKED'
3 | 'CHECKED'
我希望你明白这个主意。我自己试图解决这个问题,但没有成功。我想出了以下代码:
CREATE OR REPLACE TRIGGER DUMMY_ONE_CHECKED
AFTER INSERT OR UPDATE ON DUMMY
FOR EACH ROW
DECLARE
v_checked_is_present DUMMY.SELECTED%TYPE;
BEGIN
SELECT SELECTED
INTO v_checked_is_present
FROM DUMMY
WHERE SELECTED = 'CHECKED';
IF v_checked_is_present IS NOT NULL THEN
UPDATE DUMMY
SET SELECTED = 'UNCHECKED'
WHERE SELECTED = 'CHECKED';
UPDATE DUMMY
SET SELECTED = 'CHECKED'
WHERE ID = :NEW.ID;
END IF;
END;
但是,我收到错误ORA-04091,ORA-06512和ORA-04088,并带有以下消息:
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
显然,这不是正确的解决方案。我想知道如何完成我想做的事情(如果可能的话)?
提前谢谢!
答案 0 :(得分:2)
我不确定触发器是解决此问题的最佳方法。触发器需要更新每次更新的所有记录 - 更糟糕的是,这些行位于同一个表中,导致可怕的变异表错误。
完全不同的表结构怎么样?这个想法只是为了跟踪最后一次检查"""然后使用最大时间戳:
create table t_dummy (
id int,
checkedtime timestamp(6)
);
create view dummy as
select t_dummy.id,
(case when checkedtime = maxct then 'CHECKED' else 'UNCHECKED') as selected
from t_dummy cross join
(select max(checktime) as maxct from t_dummy) x;
这应该比触发器更容易实现。
答案 1 :(得分:2)
我不会那样设计。数据库应该强制执行规则,而不是自动尝试修复违反规则的行为。
因此,我强制执行一次只能检查一行,如下所示:
CREATE UNIQUE INDEX dummy_enforce_only_one ON dummy ( NULLIF(selected,'UNCHECKED') );
然后,我决定在选择新行之前调用代码来取消选择其他行(而不是尝试触发它)。
我知道这不会回答您问题的内容,但它会回答您问题的标题:"如何仅执行一个值......&# 34;
答案 2 :(得分:1)
实现此目的的一种方法是使用COMPOUND TRIGGER
。复合触发器是具有在每个可能的触发点(BEFORE STATEMENT
,BEFORE ROW
,AFTER ROW
和AFTER STATEMENT
)处触发的代码的触发器。让我们来看看如何处理您的要求:
CREATE OR REPLACE TRIGGER DUMMY_CHECKED_TRG
FOR INSERT OR UPDATE ON DUMMY
COMPOUND TRIGGER
TYPE NUMBER_TABLE IS TABLE OF NUMBER;
tblDUMMY_IDS NUMBER_TABLE;
BEFORE STATEMENT IS
BEGIN
tblDUMMY_IDS := NUMBER_TABLE();
END BEFORE STATEMENT;
AFTER STATEMENT IS
BEGIN
IF tblDUMMY_IDS.COUNT > 0 THEN
UPDATE DUMMY d
SET d.SELECTED = 'UNCHECKED'
WHERE d.ID <> tblDUMMY_IDS(tblDUMMY_IDS.LAST) AND
d.SELECTED = 'CHECKED';
END IF;
END AFTER STATEMENT;
AFTER EACH ROW IS
BEGIN
-- If the new value of `SELECTED` on this row is 'CHECKED'
-- save the ID of the row in tblDUMMY_IDS
IF :NEW.SELECTED = 'CHECKED' THEN
tblDUMMY_IDS.EXTEND;
tblDUMMY_IDS(tblDUMMY_IDS.LAST) := :NEW.ID;
END IF;
END AFTER EACH ROW;
END TABLE1_NUM_TRG;
在触发器的BEFORE STATEMENT
部分,我们只需分配一个表(可变长度集合对象)来保存ID值。在触发器处理任何行之前,触发器的这一部分执行一次。
在触发器的AFTER EACH ROW
部分,我们会查看该行的SELECTED
字段,如果它是'CHECKED'
,我们会将其ID值保存在我们分配的表中早。
触发器的AFTER STATEMENT
部分是实际工作完成的地方 - 它只是一个SQL语句。我们将触发器的实际工作推迟到AFTER STATEMENT
部分的原因是因为在此执行的代码不会引发可怕的&#34; MUTATING TABLE&#34;例外。我们采取的是最后 ID值,我们发现该值与SELECTED = 'CHECKED'
的行相关联。这是我们要保留的行CHECKED
- 表中的每一行都应为UNCHECKED
。所以我们执行一个UPDATE语句,说实际上#34;将SELECTED设置为&#39; UNCHECKED&#39;在表格中不的表格中的每一行上,我们所拥有的那一行,其SELECTED的当前值为CHECKED
&#34;。通常这只会更新一行 - 但它会处理在单个SQL语句中将一堆行设置为CHECKED
的情况。
我认为复合触发器在10g中可用,所以如果您使用的是Oracle版本或更高版本,那么您应该是好的。
希望这有帮助。
祝你好运。
答案 3 :(得分:0)
据我了解,您希望整个表中只有一行,可能包含CHECKED
值。但是你的方式是行不通的
我刚刚发明了一种如何做到这一点的新方法。也许,这有点复杂。将您的selected
列类型更改为number
,并将其填入后续数字(例如,使用序列)。然后考虑具有最大值的列为&#34;选择&#34;。这给您带来了很多好处:要更改选定的行,您只需要从序列中获取下一个值并将其放在所需的行中(您不需要更新所有行),您只需要一个查询,你永远不会遇到突变问题。缺点 - 很难获得选定的行和不可能(很难)&#34;取消选择所有&#34;。
答案 4 :(得分:0)
如果您知道需要取消选中的内容会更容易。但如果你做不到,那就解开一切。
UPDATE dummy
SET SELECTED = 'UNCHECKED';
然后检查你应该检查的那个。
UPDATE dummy
SET SELECTED = 'CHECKED'
WHERE ID = 3;
为什么不为此使用布尔值?
- EDIT--(布尔示例)
UPDATE dummy
SET SELECTED = 0;
然后检查你应该检查的那个。
UPDATE dummy
SET SELECTED = 1
WHERE ID = 3;
答案 5 :(得分:0)
使用仅包含一行的表的替代方法(使用PK强制执行)。 `BUTTON_ID包含所选按钮的ID(如果未选中所有按钮,则为1-3或NULL)。每行按钮结果显示在视图中。
create table button
(
id number primary key check (ID in (1)),
button_id number check (button_id in (1,2,3))
);
create view v_button as
with r3 as (select rownum button_id from dual connect by level <= 3)
select
case when button.button_id = r3.button_id then 'SELECTED' else 'UNSELECTED' end as button_code
from r3 cross join button
;
使用
初始化 insert into button values(1,1);
给出
select * from v_button;
BUTTON_CODE
-----------
SELECTED
UNSELECTED
UNSELECTED
使用更新语句切换简单:
update button set button_id = 3;
给出
BUTTON_CODE
-----------
UNSELECTED
UNSELECTED
SELECTED
取消选择所有简单设置为NULL
update button set button_id = NULL;
BUTTON_CODE
-----------
UNSELECTED
UNSELECTED
UNSELECTED