PostgreSQL:如何实现最小基数?

时间:2012-02-12 14:46:11

标签: sql postgresql cardinality

在这个问题中回答:Cardinality in PostgreSQL,使用constraints来加强基数。

基数规则定义了允许的关系计数 - 一对多,多对多等。使用连接表和一对多使用FOREIGN KEY实现多对多。

但是如何实现one-to-one_or_many(一对一+)关系。哪个是相同的问题:如何在PostgreSQL中强制执行最小基数?

实际情况是需要存储说明地址(或电话号码)的地址(或电话号码)必须由人(例如用户或客户)提供(但可以更多)。

编辑:

上述情况是一个普遍问题的特殊情况(基数为1)。一般问题是:如何强制执行任意数的基数?

作为answered by jug,如果最小基数为1,则非null FOREIGN KEY引用可用作解决方法。它还将提供一个额外的功能来选择许多默认值。

但请考虑 Cricket 团队与其玩家之间关系的另一种情况。每支球队必须拥有最少11名球员,才有资格成为一支球队。这里的最小基数是十一(11)。

类似地,学校中课程学生之间的关系,每个学生必须参加至少5门课程,每门课程必须最少10名学生。

4 个答案:

答案 0 :(得分:3)

没有办法使用CHECK约束来指定它,所以我认为最好的方法是触发器:

http://www.postgresql.org/docs/9.1/static/sql-createtrigger.html
http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html

你最终会得到类似的东西(我没有测试过它或其他东西):

CREATE TRIGGER at_least_one before INSERT, UPDATE, DELETE ON the_one_table  FOR EACH ROW EXECUTE PROCEDURE check_at_least_one();

CREATE OR REPLACE FUNCTION check_at_least_one() RETURNS trigger AS $$
    BEGIN
    nmany := select count(*) from the_many_table where the_many_table.the_one_id=NEW.id;   
    IF nmany > 0 THEN 
        RETURN NEW;
    END IF;
    RETURN NULL;
END;

答案 1 :(得分:3)

无法仅使用FOREIGN KEY约束来强制实施此类规则。

1)一种方法是允许表之间的循环引用(“默认”列,由jug建议)。这导致难以管理的鸡和蛋问题,您将不得不使用可延迟的约束。此外,这个选项在某些DBMS中根本不可用。另一个缺点是,对于足球队来说,你必须添加11个“默认”栏目(你必须处理鸡蛋和11个鸡蛋的问题)!

2)另一种选择是使用触发器。

3)另一种选择是在2个表之间使用数据库级约束。不确定是否有任何具有此类功能的DBMS。除了典型的UNIQUEPRIMARYFOREIGN KEY约束之外,大多数DBMS只有行级约束和限制(没有子查询等)。

4)另一种选择是通过创建适当的INSERT,UPDATE和DELETE过程来强制执行这些规则,这些过程只能访问两个相关表并根据这些规则强制执行完整性。这是更好的方法(在我看来)。

5)另一个选项,更简单的实现是使用标准的外键约束,强制执行1对多关系,并有一个View,显示那些实际上有11个或更多玩家的团队。这个过程意味着您实际上并没有执行您要求的规则。但是有可能(也许我可以说可能)你也买不起。例如,如果足球运动员在一次事故中丧生,那么球队就不能再参加锦标赛,但它仍然是一支球队。因此,您可以定义两个实体,即Team(基本表)和ProperTeam(View),它们可以玩游戏。例如:

CREATE VIEW AS ProperTeam
( SELECT *
  FROM Team
  WHERE ( SELECT COUNT(*)
          FROM Player
          WHERE Player.TeamId = Team.TeamId
        ) >= 11
) 

选项1和2看起来相当“混乱”,但这只是个人观点,很多人喜欢触发器。

我会选择选项4,除非我能(“作弊”和)实际上没有强制执行选项5的约束。

答案 2 :(得分:2)

如果您在一个表地址中有地址,则可以定义一个非null的列“default_address”(在表 customers 中) 必须提供的地址的外键引用。

如果你有一个表 shippings ,通过引用一个人,一个地址和一个订单(-item)来提供可选的多对多关系,那么你可以使用coalesce来覆盖您在(客户内部加入订单)和 shipping_addresses 之间的外部联接中获得的地址为NULL(加入发送的视图 地址)。但是为了防止可能有不同数量的非空组成地址的问题, Stephane Faroult 建议在他的(强烈推荐!)书籍 SQL的艺术中使用“隐藏”排序关键“技术(假设 customers_with_default_address 是使用”default_address“加入客户地址的视图:

select *
  from (select 1 as sortkey,
               line_1,
               line_2,
               city,
               state,
               postal_code,
               country
        from shipping_addresses
          where customer_id = ?
        union
        select 2 as sortkey,
               line_1,
               line_2,
               city,
               state,
               postal_code,
               country
        from customers_with_default_address
          where customer_id = ?
        order by 1) actual_shipping_address
      limit 1

答案 3 :(得分:1)

对我有用并且需要合理数量编码的方法(翻译成你的团队/球员问题):

  • 创建一个可延迟的外键约束来强制执行“每个玩家一个团队”的关系。
  • 在桌面小组上创建一个触发器,检查至少n个玩家是否附加到团队。如AdamKG所指出的,如果不遵守基数,则触发器应抛出异常。

这会强制执行约束,但作为副作用,这也只允许一种方法来编码新的玩家团队(也就是说,不会将其视为关键违规)

start transaction;
set constraints all deferred;
insert player_1 with teamPK --teamPK is not yet in table team, will be below
...
insert player_n with teamPK
insert teamPK --this fires the trigger which is successful.
commit transaction; --this fires the foreign key deferred check, successful.

请注意,我是通过使用自定义主键(对于teamPK)来实现的,例如:一个独特的团队名称,这样我就可以在实际在表团队中插入行之前了解teamPK(而不是使用自动增加的id)。