Проектирование базы данных - ведите учет связанных записей - PullRequest
0 голосов
/ 02 сентября 2011

Есть 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)

Теперь, как бы вы разработали базу данных для записи записей в цепочке?

Ответы [ 2 ]

1 голос
/ 02 сентября 2011

Вы можете добавить отношения многие ко многим между таблицами Account (Id-PK, UserId-FK, Name, Description, ...) и Entry (Id-PK): EntryAccount (EntryId & AccountId-PK, EntryAccountType) где поле EntryAccountType может иметь одно из следующих значений: {S = отправитель, R = получатель, P = основной получатель, N = вторичный получатель}.

Операторы INSERT для таблицы EntryAccount будут:

--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) вы можете создать уникальный отфильтрованный индекс (SQL Server 2008) для таблицы EntryAccount: IUF_EntryAccount_EntryId_EntryAccountType (ключ> EntryId & EntryAccountType, фильтр> EntryAccountType IN ('S', 'P')). Также этот индекс хорош для оптимизации запросов. Но этого индекса недостаточно, потому что вы можете иметь «несовместимые» бизнес-объекты Entry, подобные этим:

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

Для исправления этой проблемы необходим триггер AFTER INSERT, UPDATE, DELETE в таблице EntryAccount:

    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: я не проверял это решение.

0 голосов
/ 02 сентября 2011

Добавьте parent_record_id в таблицу Entry.Для первого шага в цепочке он будет нулевым.Во-вторых, он будет содержать идентификатор исходной записи.На третьем шаге он будет содержать идентификатор соответствующего второго шага и т. Д.

Я вижу в комментариях, что вы можете сказать, что он может содержать не более 2 ссылок, поэтому вы можете найтидругой способ решения этой проблемы с другой таблицей, но этот способ является общим и будет работать для многих сценариев, и будет продолжать работать, если ваши требования изменятся на 3 или более уровней сцепления.

Затем можно выполнить поиск по CONNECT BYСинтаксис SQL для выполнения эффективных запросов иерархии к этому типу структуры данных.

...