如何在一对多关系中强制执行“至少一个”?

huangapple go评论107阅读模式
英文:

How to enforce "at least one" in a one-to-many relationship?

问题

[![ERD][1]][1]

如何在PostgreSQL中实现这种关系
我需要确保每个会议至少有一个会话,但我不知道如何实现它。

这是我的尝试:

```sql
CREATE TABLE Meeting(
    MeetingId INT PRIMARY KEY NOT NULL,
    Cost INT NOT NULL,
    StartDate DATE NOT NULL,
    EndDate DATE NOT NULL
);

CREATE TABLE Session(
    SessId INT PRIMARY KEY NOT NULL,
    StartDate DATE NOT NULL,
    EndDate DATE NOT NULL,
    MeetingId INT NOT NULL REFERENCES Meeting(MeetingId) ON UPDATE CASCADE ON DELETE CASCADE
);

我应该添加一些新表或约束吗?


<details>
<summary>英文:</summary>

[![ERD][1]][1]

How to implement this relationship in PostgreSQL?  
I need every meeting to have at least one session, but I do not how to realize it.

This is my try:

```sql
CREATE TABLE Meeting(
    MeetingId INT PRIMARY KEY NOT NULL,
    Cost INT NOT NULL,
    StartDate DATE NOT NULL,
    EndDate DATE NOT NULL
);

CREATE TABLE Session(
    SessId INT PRIMARY KEY NOT NULL,
    StartDate DATE NOT NULL,
    EndDate DATE NOT NULL,
    MeetingId INT NOT NULL REFERENCES Meeting(MeetingId) ON UPDATE CASCADE ON DELETE CASCADE
);

Should I add some new tables or constraints?

答案1

得分: 3

以下是翻译好的内容:

这似乎是一个鸡生蛋的问题,但有一个(大部分)简单的解决方案,可以完美解决:

CREATE TABLE meeting (
  meeting_id       int  GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, first_session_id int  NOT NULL
, cost             int NOT NULL
, startdate        date NOT NULL
, enddate          date NOT NULL
);

CREATE TABLE session (
  session_id       int  GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, meeting_id       int  NOT NULL
, startdate        date NOT NULL
, enddate          date NOT NULL
, CONSTRAINT session_meeting_id_fkey
  FOREIGN KEY (meeting_id) REFERENCES meeting ON UPDATE CASCADE ON DELETE CASCADE   
, UNIQUE (meeting_id, session_id)  -- needed for FK meeting_first_session_id_fkey
);

ALTER TABLE meeting ADD CONSTRAINT meeting_first_session_id_fkey
FOREIGN KEY (meeting_id, first_session_id) REFERENCES session(meeting_id, session_id);  -- no cascading

现在,不可以插入会议而不插入会话。并且一直强制执行引用完整性。为了避免另一个潜在的鸡生蛋问题,使用数据修改的 CTE 来插入会议 其第一个会话:

WITH ins_meeting AS (
   INSERT INTO meeting
          (first_session_id                                        , cost, startdate   , enddate)
   VALUES (nextval(pg_get_serial_sequence('session', 'session_id')), 1   , '2023-03-08', '2023-03-09')
   RETURNING meeting_id, first_session_id
   )
INSERT INTO session (session_id, meeting_id, startdate, enddate)
SELECT first_session_id, meeting_id, '2023-03-08', '2023-03-09'
FROM   ins_meeting;

fiddle

这是基于以前的答案进行优化的:

关于演示的 IDENTITY 列:

请注意使用 nextval(pg_get_serial_sequence('session', 'session_id'))。请参考:

PRIMARY KEY 列在隐式情况下是 NOT NULL。拼写出来是可选的噪音。请参考:

英文:

This may seem like a chicken-egg problem, but there is a (mostly) simple solution that works perfectly:

CREATE TABLE meeting (
  meeting_id       int  GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, first_session_id int  NOT NULL
, cost             int  NOT NULL
, startdate        date NOT NULL
, enddate          date NOT NULL
);

CREATE TABLE session (
  session_id       int  GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, meeting_id       int  NOT NULL
, startdate        date NOT NULL
, enddate          date NOT NULL
, CONSTRAINT session_meeting_id_fkey
  FOREIGN KEY (meeting_id) REFERENCES meeting ON UPDATE CASCADE ON DELETE CASCADE   
, UNIQUE (meeting_id, session_id)  -- needed for FK meeting_first_session_id_fkey
);

ALTER TABLE meeting ADD CONSTRAINT meeting_first_session_id_fkey
FOREIGN KEY (meeting_id, first_session_id) REFERENCES session(meeting_id, session_id);  -- no cascading

Now, a meeting cannot be inserted without also inserting a session. And referential integrity is enforced at all times.
To avoid another lurking chicken-egg problem, use a data-modifying CTE to insert a meeting and its first session:

WITH ins_meeting AS (
   INSERT INTO meeting
          (first_session_id                                        , cost, startdate   , enddate)
   VALUES (nextval(pg_get_serial_sequence(&#39;session&#39;, &#39;session_id&#39;)), 1   , &#39;2023-03-08&#39;, &#39;2023-03-09&#39;)
   RETURNING meeting_id, first_session_id
   )
INSERT INTO session (session_id, meeting_id, startdate, enddate)
SELECT first_session_id, meeting_id, &#39;2023-03-08&#39;, &#39;2023-03-09&#39;
FROM   ins_meeting;

fiddle

This is optimized based on previous answers:

About the demonstrated IDENTITY columns:

Note the use of nextval(pg_get_serial_sequence(&#39;session&#39;, &#39;session_id&#39;)). See:

A PRIMARY KEY column is NOT NULL implicitly. Spelling that out is optional noise. See:

huangapple
  • 本文由 发表于 2023年3月8日 17:31:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/75671346.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定