表示关系数据库中集合分区的集合

问题描述 投票:0回答:1

我有一组元素。从数学意义上来说,我想用多种方式来划分集合。分区被定义为子集的集合,使得每个元素恰好位于一个子集中。

我想在关系数据库中表示这种情况,并且我希望关系约束来强制执行分区的数学定义。

我得到的最接近的是这样的模式:

CREATE TABLE element (
    id integer PRIMARY KEY
);

CREATE TABLE partition (
    id integer PRIMARY KEY
);

CREATE TABLE subset (
    id integer PRIMARY KEY,
    partition_id integer REFERENCES partition (id)
);

CREATE TABLE element_subset_mapping (
    element_id integer REFERENCES element (id) NOT NULL,
    partition_id integer REFERENCES partition (id) NOT NULL,
    subset_id integer REFERENCES subset (id) NOT NULL,
    CONSTRAINT uq_element_partition UNIQUE (element_id, partition_id)
);

这正确地表示一个元素只能出现在给定分区的一个子集中。但至少存在几个问题:

  • 它并不强制要求在每个分区内,每个元素都属于某个子集。我想我可以采取这样的语义:缺少 (element_id,partition_id) 行指示该元素属于单个元素子集,但我更愿意明确表示。
  • 它不强制要求子集仅属于一个分区。也就是说,我可以有一个 (id,partition_id) = (22, 37) 的子集,但仍然有一个 (element_id,partition_id,subset_id) = (4005, 42, 22) 的 element_subset_mapping。这是不允许的,因为子集 22 属于分区 37,而不是分区 42。

感觉应该有一个规范的方法来做到这一点,但我的搜索没有得到任何答案。

sql postgresql rdbms set-theory
1个回答
0
投票

subset
上的主键约束已经强制执行了子集不属于多个分区的要求;但是,没有强制要求
subset_id
中的
partition_id
element_subset_mapping 
的组合引用
subset
中的对应对。以下 DDL 通过使子集和分区的组合在
subset
中唯一并将
subset_id
上的外键和
partition_id
中的
element_subset_mapping
组合成引用
subset(id, partition_id)
的单个外键来解决这些问题:

CREATE TABLE element (
    id integer PRIMARY KEY
);

CREATE TABLE partition (
    id integer PRIMARY KEY
);

CREATE TABLE subset (
    id integer PRIMARY KEY,
    partition_id integer NOT NULL REFERENCES partition (id),
    CONSTRAINT subset_partition_uq UNIQUE (id, partition_id) 
);

CREATE TABLE element_subset_mapping (
    element_id integer REFERENCES element (id) NOT NULL,
    partition_id integer NOT NULL,
    subset_id integer NOT NULL,
    CONSTRAINT element_subset_mapping_partition_uq UNIQUE (element_id, partition_id),
    CONSTRAINT element_subset_mapping_subset_fk FOREIGN KEY (subset_id, partition_id) REFERENCES subset(id, partition_id)
);

每个元素恰好属于每个分区中的一个子集的要求是一个更复杂的约束,因为它涉及来自多个表的行。让我们首先创建一个触发器函数,如果任何分区中缺少任何元素,该函数将引发异常:

CREATE OR REPLACE FUNCTION validate_all_elements_in_all_partitions() RETURNS trigger AS
$FUNC$
  DECLARE
    has_partition_with_missing_element BOOLEAN;
  BEGIN
    SELECT TRUE
      INTO has_partition_with_missing_element
      FROM element e
      CROSS JOIN partition p
      LEFT JOIN element_subset_mapping m
        ON e.id = m.element_id
          AND p.id = m.partition_id
      WHERE m.subset_id IS NULL
      LIMIT 1;

    IF has_partition_with_missing_element THEN
      RAISE EXCEPTION 'partition has missing element';
    END IF;
    RETURN NULL;
  END;
$FUNC$ LANGUAGE plpgsql;

此函数可用于在每个可能导致违反约束的表上定义约束触发器:

element
partition
element_subset_mapping

DROP TRIGGER IF EXISTS all_elements_in_all_partitions ON element;

CREATE CONSTRAINT TRIGGER all_elements_in_all_partitions
  AFTER DELETE OR INSERT OR UPDATE
  ON element
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  EXECUTE FUNCTION validate_all_elements_in_all_partitions();

DROP TRIGGER IF EXISTS all_elements_in_all_partitions ON partition;

CREATE CONSTRAINT TRIGGER all_elements_in_all_partitions
  AFTER DELETE OR INSERT OR UPDATE
  ON partition
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  EXECUTE FUNCTION validate_all_elements_in_all_partitions();

DROP TRIGGER IF EXISTS all_elements_in_all_partitions ON element_subset_mapping;

CREATE CONSTRAINT TRIGGER all_elements_in_all_partitions
  AFTER DELETE OR INSERT OR UPDATE
  ON element_subset_mapping
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  EXECUTE FUNCTION validate_all_elements_in_all_partitions();

这些触发器的一个重要方面是它们是

DEFERRED
,这意味着它们在提交当前事务时触发,而不是在执行每行或语句时触发。这允许在事务内执行一组 SQL 命令以达到与关系要求一致的状态。

© www.soinside.com 2019 - 2024. All rights reserved.