Oracle SQL:如何强制执行只有一个值可能会被检查&#39 ;?

时间:2016-01-11 21:13:51

标签: sql oracle plsql

所以我在数据库中有一个表,其中包含列" 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.

显然,这不是正确的解决方案。我想知道如何完成我想做的事情(如果可能的话)?

提前谢谢!

6 个答案:

答案 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 STATEMENTBEFORE ROWAFTER ROWAFTER 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