【问题标题】:Oracle: constraint preventing insert more than N (variable) rowsOracle:约束防止插入超过 N(变量)行
【发布时间】:2020-04-15 11:16:12
【问题描述】:

我有一个表格,定义了每个客户的最大对象数。

  • 表_1

    • id_table_1 数字主键
    • cd_object varchar2(20)
    • max_number 个数

另一个表存储分配给每个客户的对象

  • 表_2
    • id_table_2 数字主键
    • cd_customer varchar2(20)
    • cd_object varchar2(20)

如何在 table_2 中设置约束以防止每个“客户 - 对象”对的记录超过“max_number”条记录?

例如:

表_1

cd_object / max_number

xxx / 1

yyy / 2

表_2

插入“customer_1”、“xxx” -> 好的!

插入“customer_1”、“xxx” -> 成功!

插入“customer_1”、“yyy” -> 好的!

插入“customer_1”、“yyy” -> 好的!

插入“customer_1”、“yyy”-> 成功!

提前感谢您的回复。

【问题讨论】:

  • 您可以为此创建触发器

标签: oracle


【解决方案1】:

此约束比 CHECK 约束可以处理的更复杂。有一天,我们希望 Oracle 能够支持SQL ASSERTIONS,这是任意复杂性的约束。

同时,这可以使用物化视图 (MV) 和约束来完成(注意性能)。我blogged about this 可能多年前:您的要求与我的示例 3 非常相似。应用到你的情况下,它会是这样的:

create materialized view table_2_mv
build immediate
refresh complete on commit as
  select t2.cd_customer, t2.cd_object, t1.max_number, count(*) cnt
  from table_2 t2
  join table_1 t1 on t1.cd_object = t2.cd_object
  group by t2.cd_customer, t2.cd_object, t1.max_number;

alter table table_2_mv
add constraint table_2_mv_chk
check (cnt <= max_number)
deferrable;

纯基于触发器的解决方案在现实世界中往往会失败,因为当 2 个用户同时添加一条仅将计数达到最大值的记录时,无论成功还是提交时,都会使表中的行数超过最大值!

但是,考虑到您在 table_2 中有 2M 行的评论,这可能使上述 MV 方法无法使用,可能还有另一种涉及触发器的方法:

  1. 创建一个表,对来自 table_1 和 table_2 的信息进行非规范化处理,如下所示:
    create table denorm as
          select t2.cd_customer, t2.cd_object, t1.max_number, count(*) cnt
          from table_2 t2
          join table_1 t1 on t1.cd_object = t2.cd_object
          group by t2.cd_customer, t2.cd_object, t1.max_number;
  1. 在 table_1 上使用一个或多个触发器以确保 denorm.max_number 始终正确 - 例如,当 table_1.max_number 更新为新值时,更新相应的 denorm 表行。

  2. 在 table_2 上使用一个或多个触发器来更新 denorm.cnt 值 - 例如添加一行时,增加denorm.cnt,删除一行时,减少它。

  3. 为 denorm 添加检查约束

    alter table denorm
    add constraint denorm_chk
    check (cnt <= max_number);

这本质上与 MV 解决方案相同,但通过使用触发器在您进行时维护 denorm 表来避免完全刷新。它适用于多用户系统,因为对 denorm 表的更新会序列化对 table_2 的更改,因此 2 个用户无法同时修改它并破坏规则。

【讨论】:

  • 是的。这正是我们所需要的,因为正如我在之前的评论中所说的,我们处于 Web 环境中,并且每个插入语句都在单独的 sql 会话中执行。但我认为你的解决方案中的问题是性能,因为“table_2”有很多记录(现在2M记录并且快速增加!)。我几乎正在考虑编写一个程序来清理重复记录,并每分钟执行一次......
  • 是的,我同意 2M 行完全刷新物化视图可能是不明智的!
  • @AlessandroAlberani 我已经更新了您可以考虑的替代解决方案 - 具有讽刺意味的是使用触发器!
【解决方案2】:

您可以在TABLE_2 上使用trigger,如下所示:

-- 创建表格

SQL> CREATE TABLE TABLE_1 (
  2      ID_TABLE_1   NUMBER PRIMARY KEY,
  3      CD_OBJECT    VARCHAR2(20),
  4      MAX_NUMBER   NUMBER
  5  );

Table created.

SQL> CREATE TABLE TABLE_2 (
  2      ID_TABLE_2    NUMBER PRIMARY KEY,
  3      CD_CUSTOMER   VARCHAR2(20),
  4      CD_OBJECT     VARCHAR2(20)
  5  );

Table created.

--创建触发器

SQL> CREATE OR REPLACE TRIGGER TRG_TABLE_2_MAX_OBJECT BEFORE
  2      INSERT OR UPDATE ON TABLE_2
  3      FOR EACH ROW
  4  DECLARE
  5      LV_MAX_NUMBER   TABLE_1.MAX_NUMBER%TYPE;
  6      LV_COUNT        NUMBER;
  7  BEGIN
  8      BEGIN
  9          SELECT
 10              MAX_NUMBER
 11          INTO LV_MAX_NUMBER
 12          FROM
 13              TABLE_1
 14          WHERE
 15              CD_OBJECT = :NEW.CD_OBJECT;
 16
 17      EXCEPTION
 18          WHEN OTHERS THEN
 19              LV_MAX_NUMBER := -1;
 20      END;
 21
 22      SELECT
 23          COUNT(1)
 24      INTO LV_COUNT
 25      FROM
 26          TABLE_2
 27      WHERE
 28          CD_OBJECT = :NEW.CD_OBJECT;
 29
 30      IF LV_MAX_NUMBER = LV_COUNT AND LV_MAX_NUMBER >= 0 THEN
 31          RAISE_APPLICATION_ERROR(-20000, 'Not allowed - KO');
 32      END IF;
 33
 34  END;
 35  /

Trigger created.

--测试代码

SQL> INSERT INTO TABLE_1 VALUES (1,'xxx',1);

1 row created.

SQL> INSERT INTO TABLE_1 VALUES (2,'yyy',2);

1 row created.

SQL> INSERT INTO TABLE_2 VALUES (1,'customer_1','xxx');

1 row created.

SQL> INSERT INTO TABLE_2 VALUES (2,'customer_1','xxx');
INSERT INTO TABLE_2 VALUES (2,'customer_1','xxx')
            *
ERROR at line 1:
ORA-20000: Not allowed - KO
ORA-06512: at "TEJASH.TRG_TABLE_2_MAX_OBJECT", line 28
ORA-04088: error during execution of trigger 'TEJASH.TRG_TABLE_2_MAX_OBJECT'


SQL> INSERT INTO TABLE_2 VALUES (3,'customer_1','yyy');

1 row created.

SQL> INSERT INTO TABLE_2 VALUES (4,'customer_1','yyy');

1 row created.

SQL> INSERT INTO TABLE_2 VALUES (5,'customer_1','yyy');
INSERT INTO TABLE_2 VALUES (5,'customer_1','yyy')
            *
ERROR at line 1:
ORA-20000: Not allowed - KO
ORA-06512: at "TEJASH.TRG_TABLE_2_MAX_OBJECT", line 28
ORA-04088: error during execution of trigger 'TEJASH.TRG_TABLE_2_MAX_OBJECT'


SQL>

--检查TABLE_2中的数据

SQL> SELECT * FROM TABLE_2;

ID_TABLE_2 CD_CUSTOMER          CD_OBJECT
---------- -------------------- --------------------
         1 customer_1           xxx
         3 customer_1           yyy
         4 customer_1           yyy

SQL>

干杯!!

【讨论】:

  • 这在大多数情况下都可以工作,但当 2 个用户同时插入行时会失败。这可能是可以接受的,但请注意!
  • 我忘了说我在网络环境中。每个插入都在不同的会话中执行。所以我认为您的解决方案不适用于我的情况。
猜你喜欢
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
  • 2014-04-08
  • 2023-03-09
  • 1970-01-01
相关资源
最近更新 更多