如何禁止自定义列表包含来自不同基本列表的元素

how to disallow custom lists to have elements from different base lists

我有以下型号:

  1. BASE_LIST:在这个 table 中我们存储基本列表。基本列表可以是例如大陆的城市列表、商店中的汽车类型等

  2. ELEMENT:在这个 table 中,我们存储基本列表的元素。例如城市和汽车类型在这里(布达佩斯、伦敦、巴黎、欧宝、宝马、奥迪)

  3. CUSTOM_LIST:在这个 table 中,我们存储列表的自定义。定制意味着过滤。例如,可以有一个名为 'european cities' 的自定义列表,它是城市的一个子集。或作为汽车子集的昂贵汽车。自定义列表必须只有一个父列表 - 基本列表,并且只能包含来自该基本列表的元素。

目前的表示是这样的:

BASE_LIST和ELEMENT之间的关系是一对多关系(一个元素只能是一个基本列表的一部分,但一个基本列表可以有多个元素)。

BASE_LIST和CUSTOM_LIST之间的关系是一对多的关系,每个自定义列表必须只有一个“父列表”。

CUSTOM_LIST和ELEMENT之间的关系是多对多关系,因为

问题在于此结构允许使用来自不同基本列表的元素的自定义列表。

我们想禁止这样做。换句话说,包含来自“汽车”基本列表的元素的自定义列表是可以的,包含来自“城市”列表元素的自定义列表是可以的,但是混合了汽车和城市的自定义列表是不行的。

有没有办法用标准约束(没有存储过程等)来禁止这种混合列表?

我为此创建了一个fiddle:

http://sqlfiddle.com/#!4/40801/2


DDL:

CREATE TABLE BASE_LIST
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL
);
ALTER TABLE BASE_LIST ADD CONSTRAINT BASE_LIST_PK PRIMARY KEY ( ID ) ;

CREATE TABLE ELEMENT
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL,
  BASE_LIST_ID NUMBER (18) NOT NULL
);
ALTER TABLE ELEMENT ADD CONSTRAINT ELEMENT_PK PRIMARY KEY ( ID ) ;
ALTER TABLE ELEMENT ADD CONSTRAINT ELEMENT_FK_TO_BASE_LIST FOREIGN KEY ( BASE_LIST_ID ) REFERENCES BASE_LIST ( ID );

CREATE TABLE CUSTOM_LIST
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL ,
  BASE_LIST_ID NUMBER (18) NOT NULL
);
ALTER TABLE CUSTOM_LIST ADD CONSTRAINT CUSTOM_LIST_PK PRIMARY KEY ( ID ) ;
ALTER TABLE CUSTOM_LIST ADD CONSTRAINT CUSTOM_LIST_FK_TO_BASE_LIST FOREIGN KEY ( BASE_LIST_ID ) REFERENCES BASE_LIST ( ID );

CREATE TABLE CUSTOM_LISTS_ELEMENTS
(
  CUSTOM_LIST_ID NUMBER (18) NOT NULL,
  ELEMENT_ID NUMBER (18) NOT NULL
);
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT CUSTOM_LISTS_ELEMENTS_PK PRIMARY KEY ( CUSTOM_LIST_ID, ELEMENT_ID ) ;
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT FK_TO_CUSTOM_LIST FOREIGN KEY ( CUSTOM_LIST_ID ) REFERENCES CUSTOM_LIST ( ID );
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT FK_TO_ELEMENT FOREIGN KEY ( ELEMENT_ID ) REFERENCES ELEMENT ( ID );

问题:

insert into BASE_LIST values (1, 'cities');
insert into ELEMENT values (1, 'Budapest', 1);
insert into ELEMENT values (2, 'London', 1);
insert into ELEMENT values (3, 'Paris', 1);
insert into BASE_LIST values (2, 'cars');
insert into ELEMENT values (4, 'Opel', 2);
insert into ELEMENT values (5, 'Bmw', 2);
insert into ELEMENT values (6, 'Audi', 2);

insert into CUSTOM_LIST values (1, 'EuCities', 1);
insert into CUSTOM_LIST values (2, 'PriceyCars', 2);

-- the below two inserts are allowed, custom list 1 will have 
-- only two elements from base list 1: 1 and 3
insert into CUSTOM_LISTS_ELEMENTS values (1, 1);
insert into CUSTOM_LISTS_ELEMENTS values (1, 3);

-- this should be forbidden, because element 4 is in base list 2,
-- but custom list 1 is only for elements from base list 1.
insert into CUSTOM_LISTS_ELEMENTS values (1, 4);

为了防止来自不同列表的自定义元素,您需要使用复合键。这些将沿着两个关系分支传播,并将强制每个自定义元素属于一个基本列表。

例如你可以这样做:

create table base_list (
  id int primary key not null,
  name varchar(50)
);

create table element (
  id int not null,
  name varchar(50),
  base_list_id int references base_list (id),
  primary key (base_list_id, id)
);

create table custom_list (
  id int not null,
  name varchar(50),
  base_list_id int references base_list (id),
  primary key (base_list_id, id)
);

create table_custom_list_element (
  custom_list_id int not null,
  base_list_id int not null,
  element_id int not null,
  constraint fk_clist foreign key (base_list_id, custom_list_id) 
    references custom_list (base_list_id, id),
  constraint fk_celement foreign key (base_list_id, element_id) 
    references element (base_list_id, id)
);  

特别注意最后 table 中的两个外键共享同一列 base_list_id。这会强制执行您想要的规则。

解决方案应归功于“The Impaler”。

记录下来,这是使用复合外键的解决方案:

修改后的DDL:

CREATE TABLE BASE_LIST
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL
);
ALTER TABLE BASE_LIST ADD CONSTRAINT BASE_LIST_PK PRIMARY KEY ( ID ) ;

CREATE TABLE ELEMENT
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL,
  BASE_LIST_ID NUMBER (18) NOT NULL
);
ALTER TABLE ELEMENT ADD CONSTRAINT ELEMENT_PK PRIMARY KEY ( ID ) ;
ALTER TABLE ELEMENT ADD CONSTRAINT ELEMENT_UK_ID_BASE_LIST_ID UNIQUE (ID, BASE_LIST_ID) ;
ALTER TABLE ELEMENT ADD CONSTRAINT ELEMENT_FK_TO_BASE_LIST FOREIGN KEY ( BASE_LIST_ID ) REFERENCES BASE_LIST ( ID );

CREATE TABLE CUSTOM_LIST
(
  ID  NUMBER (18) NOT NULL ,
  NAME VARCHAR2 (50) NOT NULL ,
  BASE_LIST_ID NUMBER (18) NOT NULL
);
ALTER TABLE CUSTOM_LIST ADD CONSTRAINT CUSTOM_LIST_PK PRIMARY KEY ( ID ) ;
ALTER TABLE CUSTOM_LIST ADD CONSTRAINT CUSTOM_LIST_UK_ID_BASE_LIST_ID UNIQUE (ID, BASE_LIST_ID) ;
ALTER TABLE CUSTOM_LIST ADD CONSTRAINT CUSTOM_LIST_FK_TO_BASE_LIST FOREIGN KEY ( BASE_LIST_ID ) REFERENCES BASE_LIST ( ID );

CREATE TABLE CUSTOM_LISTS_ELEMENTS
(
  BASE_LIST_ID NUMBER (18) NOT NULL,
  CUSTOM_LIST_ID NUMBER (18) NOT NULL,
  ELEMENT_ID NUMBER (18) NOT NULL
);
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT CUSTOM_LISTS_ELEMENTS_PK PRIMARY KEY ( CUSTOM_LIST_ID, ELEMENT_ID ) ;
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT FK_TO_CUSTOM_LIST FOREIGN KEY ( CUSTOM_LIST_ID, BASE_LIST_ID ) REFERENCES CUSTOM_LIST ( ID, BASE_LIST_ID );
ALTER TABLE CUSTOM_LISTS_ELEMENTS ADD CONSTRAINT FK_TO_ELEMENT FOREIGN KEY ( ELEMENT_ID, BASE_LIST_ID ) REFERENCES ELEMENT ( ID, BASE_LIST_ID );

修改后的测试:

insert into BASE_LIST values (1, 'cities');
insert into ELEMENT values (1, 'Budapest', 1);
insert into ELEMENT values (2, 'London', 1);
insert into ELEMENT values (3, 'Paris', 1);
insert into BASE_LIST values (2, 'cars');
insert into ELEMENT values (4, 'Opel', 2);
insert into ELEMENT values (5, 'Bmw', 2);
insert into ELEMENT values (6, 'Audi', 2);

insert into CUSTOM_LIST values (1, 'EuCities', 1);
insert into CUSTOM_LIST values (2, 'PriceyCars', 2);

-- the below two inserts are allowed, custom list 1 will have 
-- only two elements from base list 1: 1 and 3
insert into CUSTOM_LISTS_ELEMENTS values (1, 1, 1);
insert into CUSTOM_LISTS_ELEMENTS values (1, 1, 3);

-- this should be forbidden, because element 4 is in base list 2,
-- but custom list 1 is only for elements from base list 1.
insert into CUSTOM_LISTS_ELEMENTS values (2, 1, 4);

sql fiddle:

http://sqlfiddle.com/#!4/3306c6/2