10

正如在这个问题中回答的那样:Cardinality in PostgreSQL,cardinality 是使用constraints强制执行的。

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

但是如何实现一对一或多(一对一+)关系。这与问:如何在 PostgreSQL 中强制执行最小基数相同?

一种实际情况是需要存储必须由个人(例如用户或客户)提供(但可能不止一个)的地址(或电话号码)。

编辑:

上述情况是一般问题的特例(具有基数)。一般问题是:如何强制任意数的基数?

正如jug 所回答的,如果 minimum-cardinality 为 1,则可以使用非空 FOREIGN KEY 引用作为解决方法它还将提供一个附加功能,可以在许多中选择默认值。

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

类似地,学校中的课程学生之间的关系,其中每个学生必须注册至少 5 门课程,并且每门课程必须至少有 10 名学生。

4

4 回答 4

3

FOREIGN KEY仅使用约束无法强制执行此类规则。

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

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

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

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

5) 另一个更易于实现的选项是使用标准外键约束,强制一对多关系,并有一个视图来显示那些实际上有 11 名或更多球员的球队。这当然意味着您实际上并没有执行您要求的规则。但有可能(我可以说很可能)你也买不起。例如,如果足球运动员在事故中丧生,该球队将无法再参加锦标赛,但它仍然是一支球队。因此,您可以定义两个可以玩游戏的实体,Team(基表)和 ProperTeam(视图)。例子:

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

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

我会选择选项 4,除非我可以(“作弊”并且)实际上不使用选项 5 强制执行约束。

于 2012-02-13T13:08:58.843 回答
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;
于 2012-02-12T15:25:41.260 回答
2

If you have the addresses in one table adresses, you can define a column "default_address" (in the table customers) that is a non-null foreign key reference to the address that must be provided.

If you have a table shippings that provides an optional many-to-many relationship by referencing a person, an address and maybe an order(-item), then you could use coalesce to overwrite the NULLs for addresses you get in an outer join between (customers inner join orders) and shipping_addresses (a view joining shippings with addresses). But to prevent problems with maybe different numbers of non-null components of addresses, Stephane Faroult recommends in his (strongly recommended!) book The Art of SQL to use the "hidden sort key" technique (assuming that customers_with_default_address is a view joining customers with addresses using "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
于 2012-02-12T18:42:44.220 回答
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)来做到这一点,例如唯一的团队名称,这样我就可以在实际将行插入 table team 之前知道 teamPK(而不是使用自动递增的 id)。

于 2012-09-07T13:32:48.763 回答