【问题标题】:Database design - keep record of chained entries数据库设计 - 记录链式条目
【发布时间】:2011-11-08 21:11:41
【问题描述】:

有3种情况

1) Basic
Sender ---> Receiver

2) Parallel
Sender ----> Receiver1
       ----> ReceiverN

3) Chained
Sender ----> Primary Receiver  -----> Secondary Receiver1
                               -----> Secondary ReceiverN

对于 1) Basic 和 2) Parallels,您可能会像这样设计您的表格

Account
-Id (PK)
-UserId (FK)
-Name
-Description
-etc

Entry
-Id (PK)
-SenderAccountId (FK)
-ReceiverAccountId (FK)

现在您将如何设计数据库来记录“链式”条目?

【问题讨论】:

  • 这条链最多可以有多少个“链接”?
  • 您好 nulluserexception,已链接,发送者只能发送到 1 个主要接收者,然后这个主要接收者最多可以发送到 6 个辅助接收者。辅助接收器无法发送,因此最多 2 个链。

标签: sql-server database oracle database-design relational-database


【解决方案1】:

您可以在 Account(Id-PK,UserId-FK,Name,Description,...) 和 Entry(Id-PK) 表之间添加多对多关系: EntryAccount(EntryId & AccountId-PK,EntryAccountType) 其中 EntryAccountType 字段可以具有以下值之一 {S=Sender,R=Receiver, P=Primary receiver,N=secoNdary receiver}。

EntryAccount 表的 INSERT 语句将是:

--Basic
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'S')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'R')

--Parallel
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'S')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'R') 
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'R')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'R')

--Chained
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'S')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'P')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'N') 
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'N')
INSERT EntryAccount (EntryId,AccountId,EntryAccountType)
VALUES (...,...,'N')

然后,为了执行一些业务规则(一个发送者-S、一个主要接收者-P 和许多 [次要] 接收者-R/N),您可以在 EntryAccount 表上创建一个唯一的过滤索引(SQL Server 2008):IUF_EntryAccount_EntryId_EntryAccountType (key > EntryId & EntryAccountType, filter > EntryAccountType IN ('S','P'))。此外,此索引对于查询优化很有用。 但是,这个索引是不够的,因为你可以有这样的“不一致”的条目业务对象:

Entry(1001)
EntryAccoount(1001,...,'S') without EntryAccoount(1001,...,'R') 
or
EntryAccoount(1001,...,'R') without EntryAccoount(1001,...,'S')
, etc.

要纠正这个问题,您需要在 EntryAccount 表上的 AFTER INSERT、UPDATE、DELETE 触发器:

    CREATE TRIGGER ...
    AFTER INSERT, UPDATE, DELETE
    ...
    DECLARE @Results TABLE
    (
    EntryId INT PRIMARY KEY
    ,SendersCount INT NOT NULL DEFAULT O
    ,ReceiversCount INT NOT NULL DEFAULT O
    ,PrimaryReceiversCount INT NOT NULL DEFAULT O
    ,SecondaryReceiversCount INT NOT NULL DEFAULT O
    );
    INSERT @Results(EntryId)
    SELECT EntryId
    FROM inserted
    UNION --no duplicates
    SELECT EntryId
    FROM deleted;

    --Count senders
    UPDATE @Results
    SET SendersCount = q.Num
    FROM @Results r
    JOIN
    (
    SELECT ea.EntryId, COUNT(*) Num
    FROM EntryAccount ea
    JOIN @Results i ON ea.EntryId = i.EntryId
    WHERE ea.EntryAccountType = 'S'
    GROUP BY ea.EntryId
    ) q ON r.EntryId = q.EntryId;

    -Count [standard-R] receivers
    UPDATE @Results
    SET ReceiversCount = q.Num
    FROM @Results r
    JOIN
    (
    SELECT ea.EntryId, COUNT(*) Num
    FROM EntryAccount ea
    JOIN @Results i ON ea.EntryId = i.EntryId
    WHERE ea.EntryAccountType = 'R'
    GROUP BY ea.EntryId
    ) q ON r.EntryId = q.EntryId;

    --Count primary-P receivers
    UPDATE @Results
    SET PrimaryReceiversCount = q.Num
    FROM @Results r
    JOIN
    (
    SELECT ea.EntryId, COUNT(*) Num
    FROM EntryAccount ea
    JOIN @Results i ON ea.EntryId = i.EntryId
    WHERE ea.EntryAccountType = 'P'
    GROUP BY ea.EntryId
    ) q ON r.EntryId = q.EntryId;


    --Count secondary-N receivers
    UPDATE @Results
    SET SecondaryReceiversCount = q.Num
    FROM @Results r
    JOIN
    (
    SELECT ea.EntryId, COUNT(*) Num
    FROM EntryAccount ea
    JOIN @Results i ON ea.EntryId = i.EntryId
    WHERE ea.EntryAccountType = 'N'
    GROUP BY ea.EntryId
    ) q ON r.EntryId = q.EntryId;

    --Final validation
    IF EXISTS
    (
    SELECT *
    FROM @Results r
    WHERE NOT(r.SendersCount=1 AND r.ReceiversCount>=1 AND r.PrimaryReceiver=0 AND r.SecondaryReceiversCount=0 
    OR r.SenderCount=1 AND r.ReceiversCount=0 AND r.PrimaryReceiver=1 AND r.SecondaryReceiversCount >=1
    OR r.SenderCount=0 AND r.ReceiversCount=0 AND r.PrimaryReceiver=0 AND r.SecondaryReceiversCount=0
)
    )
    ROLLBACK;

如果您没有 SQL Server 2008 (R1/R2),则无法创建筛选索引,但只能依赖触发器。

PS:我没有测试过这个解决方案。

【讨论】:

    【解决方案2】:

    将 parent_record_id 添加到 Entry 表。对于链中的第一步,它将为空。对于第二个,它将包含原始记录的 ID。第三步它将包含相关第二步的ID,依此类推。

    我在 cmets 中看到你说它最多可以有 2 个链接深度,所以你可以想出用另一个表解决这个问题的不同方法,但这种方法是通用的,适用于许多场景,并在您的要求更改为 3 级或更多级别的链接时继续工作。

    然后,您可以查找 CONNECT BY SQL 语法,以对此类数据结构执行有效的层次结构查询。

    【讨论】:

    • 可能是 Id、parent_id、sender_account_id、receiver_account_id
    猜你喜欢
    • 1970-01-01
    • 2012-12-20
    • 1970-01-01
    • 1970-01-01
    • 2018-12-02
    • 2017-03-24
    • 1970-01-01
    • 2011-04-02
    • 1970-01-01
    相关资源
    最近更新 更多