SQL设置了可以引用PK次数的限制

时间:2019-05-12 06:40:14

标签: sql database oracle

我正在为我的学校项目建立一个动物园的演示数据库,遇到了以下问题:我有一个表Pavilion,它具有一些主键id_pavilion和列容量(这是有关最大数量的信息的信息)。可以在这个亭子里生活的动物。

让我们说每个展馆最多可以容纳2只动物。

亭子

id_pavilion   capacity
-----------------------
    1             2
    2             2
    3             2
    4             2

动物

id_an-column2-column3    id_pavilion
---------------------------------------
   1                          2   
   2                          2   
   3                          2   
   4                          2   

(这显示了我要防止的事情)

然后我有一个餐桌动物,其中包含有关动物的一些信息,主要是来自Pavilion的id_pavilion作为外键。

我的问题是:我如何添加这样一个约束,使得id_pavilion中的PK Pavilion在表Animal中只能被引用多次。容量允许吗?

4 个答案:

答案 0 :(得分:2)

看看您的示例数据,您可能会争辩说,每个PAVILION都可以容纳2只动物,对吗?人们还可以说,在以适当的方式饲养动物之前,必须在适当的位置放置“住宿”。因此,我们可以创建一个名为ACCOMMODATION的表,列出所有可用空间。

create table pavilion( id primary key, capacity )
as
select level, 2 from dual connect by level <= 4 ;

create table accommodation(
  id number generated always as identity start with 1000 primary key 
, pavilionid number references pavilion( id )
) ;

生成所有住宿

-- No "human intervention" here.  
-- Only the available spaces will be INSERTed.
insert into accommodation ( pavilionid )
select id
from pavilion P1, lateral ( 
  select 1 
  from dual 
  connect by level <= ( select capacity from pavilion where id = P1.id )
) ;

-- we can accommodate 8 animals ...
select count(*) from accommodation ; 

  COUNT(*)
----------
         8

-- accommodations and pavilions
SQL> select * from accommodation ;

        ID PAVILIONID
---------- ----------
      1000          1
      1001          1
      1002          2
      1003          2
      1004          3
      1005          3
      1006          4
      1007          4

8 rows selected.

每只动物应位于单个(定义的)位置。当将动物“添加”到动物园时,它只能(物理上)位于单个位置/住宿中。我们可以使用UNIQUE键和FOREIGN键(参考ACCOMMODATION)来强制执行此操作。

-- the ANIMAL table will have more columns eg GENUS, SPECIES, NAME etc
create table animal( 
  id number generated always as identity start with 2000
-- , name varchar2( 64 ) 
, accommodation number 
) ;

alter table animal
add (
  constraint animal_pk primary key( id )
, constraint accommodation_unique unique( accommodation )
, constraint accommodation_fk 
    foreign key( accommodation ) references accommodation( id )
);

测试

-- INSERTs will also affect the columns GENUS, SPECIES, NAME etc
-- when the final version of the ANIMAL table is in place. 
insert into animal( accommodation ) values ( 1001 )  ;

SQL> insert into animal( accommodation ) values ( 1000 )  ;

1 row inserted.

SQL> insert into animal( accommodation ) values ( 1001 )  ;

1 row inserted.

-- trying to INSERT into the same location again 
-- MUST fail (due to the unique constraint)
SQL> insert into animal( accommodation ) values ( 1000 );
Error starting at line : 1 in command -
insert into animal( accommodation ) values ( 1000 )
Error report -
ORA-00001: unique constraint (...ACCOMMODATION_UNIQUE) violated


SQL> insert into animal( accommodation ) values ( 1001 );
Error starting at line : 1 in command -
insert into animal( accommodation ) values ( 1001 )
Error report -
ORA-00001: unique constraint (...ACCOMMODATION_UNIQUE) violated

-- trying to INSERT into a location that does not exist
-- MUST fail (due to the foreign key constraint)
SQL> insert into animal( accommodation ) values ( 9999 )  ;
Error starting at line : 1 in command -
insert into animal( accommodation ) values ( 9999 )
Error report -
ORA-02291: integrity constraint (...ACCOMMODATION_FK) violated - parent key not found

动物和住宿

select 
  A.id as animal
, P.id as pavilion
, AC.id as location --(accommodation)
from pavilion P
  join accommodation AC on P.id = AC.pavilionid
  join animal A on AC.id = A.accommodation
;

    ANIMAL   PAVILION   LOCATION
---------- ---------- ----------
      2000          1       1000
      2001          1       1001

DBfiddle here。经过Oracle 12c和18c测试。 (您需要使用12c +版才能横向连接才能正常工作。)

答案 1 :(得分:0)

您要在数据库级别强制实施的更多是“业务逻辑”规则,而不是硬数据约束。您不能在表设计中直接实现它;即使您可以(如@serg在评论中提到),也需要对表进行非常昂贵的锁定(就CPU /资源而言)以执行计数。

可以实现您的目标并使业务逻辑与数据设计分离的另一种选择是使用SQL Trigger

可以在将数据插入表之前运行触发器;在这里,您可以检查已为该“亭子实体”插入了多少行,然后中止或允许插入。

对“学校项目”方面的评论: 话虽这么说,但您正在谈论的那种逻辑在使用的应用程序中而不是数据库中得到了更好的服务(我认为,其他人可能会不同意)。还可能考虑定义数据中的大小限制,以便您可以拥有不同大小的展馆。

注释: 对于以后访问此问题的任何人,以上链接都是针对oracle触发器的(因为OP已将问题标记为oracle)。 T his link is for Microsoft SQL Server Triggers

答案 2 :(得分:0)

答案是“不容易”。尽管在展馆中将“住宿”作为单独的桌子摆放的想法很聪明,但动物却被放进了展馆,而不是住宿。对住宿进行建模可以使动物四处移动变得更加棘手。

也许最简单的方法是使用触发器。这从animal_count中的pavilions列开始。该列从零开始,随着动物的移入或移出而递增或递减。您可以使用check约束来验证展馆的容量是否过多。

不幸的是,要维持此列,需要在animals表上使用触发器,分别是insertupdatedelete的触发器。

最后,触发器将保持计数,如果您尝试将动物放入一个完整的展馆,则会违反check约束。

答案 3 :(得分:0)

您需要一栏(例如“ NrOccupants”),该栏会在将动物放入或从每个亭子中取出时进行更新。然后,向该列添加一个检查约束,以防止应用程序代码向亭中添加的动物数量超出该检查约束所强制执行的规则所允许的数量。

这里是可以执行此操作的SQL DDL的示例。

CREATE SCHEMA Pavilion
GO

CREATE TABLE Pavilion.Pavilion
(
    pavilionNr int NOT NULL,
    capacity tinyint CHECK (capacity IN (2)) NOT NULL,
    nrOccupants tinyint CHECK (nrOccupants IN (0, 2)) NOT NULL,
    CONSTRAINT Pavilion_PK PRIMARY KEY(pavilionNr)
)
GO


CREATE TABLE Pavilion.Animal
(
    animalNr int NOT NULL,
    name nchar(50) NOT NULL,
    pavilionNr int NOT NULL,
    type nchar(50) NOT NULL,
    weight smallint NOT NULL,
    CONSTRAINT Animal_PK PRIMARY KEY(animalNr)
)
GO


ALTER TABLE Pavilion.Animal ADD CONSTRAINT Animal_FK FOREIGN KEY (pavilionNr) REFERENCES Pavilion.Pavilion (pavilionNr) ON DELETE NO ACTION ON UPDATE NO ACTION

GO