Phát triển cơ sở dữ liệu cho một doanh nghiệp chuyển tiền trong đó (a) người và tổ chức có thể (b) gửi và nhận tiền


7

Trong bối cảnh kinh doanh phù hợp, cả thành viêntổ chức cần phải có tài khoản cho các quỹ . Tiền có thể được chuyển

  • từ thành viên để thành viên ,
  • từ thành viên đến tổ chức ,
  • từ tổ chức đến tổ chức , và
  • từ tổ chức đến thành viên .

Cân nhắc

Để xây dựng cơ sở dữ liệu cho kịch bản như vậy, tôi đã tạo ba bảng sau:

CREATE TABLE Members ( 
  memberid serial primary key, 
  name varchar(50) unique, 
  passwd varchar(32), 
  account integer 
);

CREATE TABLE Organizations (  
  organizationid serial primary key, 
  name varchar(150) unique, 
  administrator integer references Members(memberid), 
  account integer 
);

CREATE TABLE TransferHistory  
  "from" integer, -- foreign key?
  "to" integer, -- foreign key?
  quantity integer 
);

Tôi nghĩ rằng TransferHistorybảng là cần thiết để hiển thị ai / cái gì đã gửi tiền cho ai / cái gì .

Vấn đề là, vì MembersOrganizationslà các bảng khác nhau, làm thế nào tôi có thể tham chiếu chúng từ TransferHistorybảng?

Chẳng hạn, dữ liệu liên quan có thể hiển thị như sau:

Account      Account      Quantity
-----------  -----------  --------
 1072561733  38574637847       500
38574637847   1072561733       281

Điều đó sẽ gợi ý rằng các tài khoản cần phải được ghi lại trong cùng một bảng, nhưng các tài khoản dành cho hai loại chủ sở hữu khác nhau ( thành viêntổ chức ), mỗi tài khoản được giữ lại trong bảng tương ứng.

Tôi có thể tạo một bảng được gọi Accounts, vì vậy bây giờ tôi sẽ có bốn bảng:

CREATE TABLE Members (
  memberid serial primary key, 
  name varchar(50) unique, 
  passwd varchar(32), 
  accountid integer references Accounts(accountid) 
);

CREATE TABLE Organizations (
  organizationid serial primary key, 
  name varchar(150) unique, 
  administrator integer references Members(memberid), 
  accountid integer references Accounts(accountid)
);

CREATE TABLE Accounts ( 
  accountid serial primary key, 
  state integer 
);

CREATE TABLE TransferHistory ( 
  "from" integer references Accounts(accountid), 
  "to" integer references Accounts(accountid), 
  quantity integer 
);

... nhưng bây giờ tôi phải đảm bảo rằng mỗi khóa ngoại MembersOrganizationsbảng không trỏ đến cùng một Accounthàng trong Accountsbảng ...

... Hoặc tôi có thể có một Accountsbảng có hai khóa ngoại, một khóa trỏ Membersvà một bảng khác Organizations(và một cột khóa ngoại sẽ phải chứa dấu NULL mọi lúc). Nhưng bây giờ mọi thứ có một chút khó hiểu đối với các truy vấn. Thiết kế cơ sở dữ liệu nói chung sẽ như sau:

CREATE TABLE Members ( 
  memberid serial primary key, 
  name varchar(50) unique, 
  passwd varchar(32) 
);

CREATE TABLE Organizations (
  organizationid serial primary key, 
  name varchar(150) unique, 
  administrator integer references Members(memberid) 
);

CREATE TABLE Accounts ( 
  accountid serial primary key, 
  member integer references Members(memberid), 
  organization integer references Organizations(organizationid),
  state integer 
);

CREATE TABLE TransferHistory ( 
  "from" integer references Accounts(accountid), 
  "to" integer references Accounts(accountid), 
  quantity integer 
);

Vì vậy, có ai có một đề nghị về cách giải quyết vấn đề này?

Câu trả lời:


11

Nếu ý định là xây dựng một cơ sở dữ liệu quan hệ , thì thực sự hữu ích trước tiên khi thực hiện (a) phân tích bối cảnh kinh doanh của mối quan tâm để đặt ra một lược đồ khái niệm về các loại thực thể , kiểm tra các thuộc tínhliên kết của chúng trước đó (b) suy nghĩ về các bảng, cột và các ràng buộc. Các phần tử tương ứng với mức logic logic . Theo tiến trình hành động này, việc nắm bắt ý nghĩa của miền doanh nghiệp với độ chính xác sẽ đơn giản hơn nhiều và sau đó phản ánh nó trong một thiết kế SQL-DDL thực tế, bị ràng buộc tốt.

Một trong nhiều lợi thế được cung cấp bởi mô hình quan hệ là nó cho phép quản lý dữ liệu theo cấu trúc tự nhiên của nó; do đó người ta phải tìm kiếm cấu trúc như vậy trước khi sử dụng các công cụ quan hệ để quản lý nó. Không có vấn đề gì nếu kịch bản có vấn đề liên quan đến một dự án cá nhân (như bạn đã chỉ ra qua các bình luận): bạn càng xác định nó thực tế hơn, bạn sẽ càng học được nhiều hơn từ sự phát triển của nó (nếu đó là mục đích của nỗ lực này). Tất nhiên, một dự án cá nhân thực tế có thể phát triển thành một dự án thương mại với những điều chỉnh tương đối nhỏ.

Quy tắc kinh doanh

Để trình bày một tiến trình đầu tiên mà bạn có thể muốn sử dụng làm tài liệu tham khảo, tôi đã xây dựng một số quy tắc kinh doanh ở cấp độ khái niệm nằm trong số những quy tắc quan trọng nhất và chúng được liệt kê như sau:

  • Một người sở hữu tài khoản không có một hoặc nhiều
  • Một người chủ yếu được phân biệt bởi Id của nó
  • Một người được luân phiên phân biệt bởi anh / cô FirstName , LastName , ngày sinhgiới tính
  • Một tổ chức sở hữu các tài khoản không có một hoặc nhiều
  • Một tổ chức chủ yếu được phân biệt bởi Id của nó
  • Một tổ chức được phân biệt xen kẽ bởi Tên của nó
  • Một tổ chức bắt đầu hoạt động tại FoundingDate
  • Một tài khoảnGiao trong zero-một-hoặc-nhiều Transfers
  • Một tài khoảnnhận chuyển giao trong zero-một-hoặc-nhiều Transfers
  • Một tài khoản chủ yếu được xác định bởi nó Số
  • Một tài khoản được cấp tại một chính xác CreatedDate
  • Trong một chuyển , các Bên Giao phải khác với những nhận chuyển giao
  • Một người có thể đăng nhập thông qua UserProfile 0 hoặc 1

Kể từ khi các hiệp hội -Hoặc relationships- (1) giữa ngườitài khoản và (2) giữa tổ chứctài khoản là rất giống nhau, thực tế này cho thấy Ngườitài khoản là tổ chức phân nhóm của Đảng (về cơ bản, hoặc là một cá nhân hoặc một nhóm cá nhân) , đó là lần lượt siêu thực thể của họ . Đây là một cấu trúc thông tin cổ điển phát sinh rất thường xuyên trong nhiều mô hình khái niệm thuộc nhiều loại khác nhau. Theo cách này, hai quy tắc mới có thể được khẳng định:

  • Một PartyType phân loại zero-one-hoặc-nhiều Bên
  • Một bên là một cá nhân hoặc một tổ chức

Và hai trong số các quy tắc kinh doanh trước đây có thể được hợp nhất thành một quy tắc duy nhất:

  • Một Bên sở hữu Tài khoản không có một hoặc nhiều

Điều này cũng có thể được nêu theo quan điểm của loại thực thể Tài khoản :

  • Một tài khoản được sở hữu bởi chính xác-one Đảng

Sơ đồ IDEF1X tiếp xúc

Do đó, tôi đã tạo ra một bình luận (giản thể) IDEF1X sơ đồ tổng hợp các quy tắc xây dựng ở trên, và nó được hiển thị trong Hình 1 :

Hình 1 - Mô hình IDEF1X chuyển tiền

Đảng, Người và Tổ chức: Cấu trúc siêu kiểu phụ

Như đã trình bày, PersonOrganizationđược mô tả như loại trừ lẫn nhau phân nhóm của Party.

Các Partysiêu kiểu giữ một phân biệt (ví dụ PartyTypeCode) và tất cả các thuộc tính (hoặc các thuộc tính) mà là chung cho các phân nhóm của mình, trong đó, đến lượt nó, có các tính chất áp dụng cho mỗi người trong số họ.

Tài khoản

Các Accountloại thực thể được kết nối trực tiếp với Party, cung cấp một kết nối tiếp theo giữa (i) AccountPerson, và giữa (ii) AccountOrganization.

Vì có thể, trong thế giới thực, (a) một ngân hàng Accountkhông thể chuyển nhượng được, nghĩa là, ngân hàng không Ownerthể thay đổi và (b) Accountkhông thể bắt đầu hiện tại hoặc được kích hoạt mà không có Owner, PRIMary KEY của loại thực thể này thể bao gồm các thuộc tính PartyId AccountNumber , vì vậy bạn nên phân tích kịch bản kỹ lưỡng hơn nữa để xác định điểm này với độ chính xác cao.

chuyển khoản

Mặt khác, Transfernhững món quà loại thực thể một PRIMARY KEY tổng hợp tạo thành từ ba tài sản, ví dụ TransferorAccountNumber, TransfereeAccountNumber( tên vai trò tôi được giao để phân biệt mỗi một trong hai Accountthuộc tính liên quan đến mỗi Transferchẳng hạn) và TransferDateTime(mà kể exaxct tức thì khi Transferxảy ra là thực hiện).

Các yếu tố về AccountNumbers

Cũng cần lưu ý rằng, trong các hệ thống ngân hàng thực tế, định dạng của một AccountNumberđiểm dữ liệu thường phức tạp hơn một giá trị số nguyên đơn giản. Có nhiều cách sắp xếp định dạng khác nhau, ví dụ: cách sắp xếp tương ứng với Số tài khoản ngân hàng quốc tế (IBAN), được xác định theo tiêu chuẩn ISO 13616 . Rõ ràng khía cạnh này ngụ ý rằng phân tích khái niệm (1) và các định nghĩa logic sau (2) đòi hỏi một cách tiếp cận toàn diện hơn nhiều.

Các khai báo SQL-DDL logic minh họa

Sau đó, như một dẫn xuất từ ​​phân tích trước đó, tôi đã tuyên bố một thiết kế logic trong đó

  • mỗi bảng đại diện cho một loại thực thể,
  • mỗi cột là viết tắt của một thuộc tính của loại thực thể tương ứng và
  • nhiều ràng buộc được thiết lập (khai báo) để đảm bảo rằng các xác nhận dưới dạng hàng được giữ lại trong tất cả các bảng tuân thủ các quy tắc kinh doanh được xác định ở tầng khái niệm.

Tôi đã cung cấp các ghi chú dưới dạng các nhận xét giải thích một số tính năng mà tôi đánh giá cao đặc biệt quan trọng đối với cấu trúc nói trên, được hiển thị bên dưới:

-- You have to determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on the business context characteristics.

-- Also, you should make accurate tests to define the
-- most convenient physical implementation settings; e.g.,
-- a good INDEXing strategy based on query tendencies.

-- As one would expect, you are free to make use of 
-- your preferred (or required) naming conventions. 

CREATE TABLE PartyType (
    PartyTypeCode CHAR(1)  NOT NULL, -- This one is meant to contain the meaningful values 'P', for 'Person', and 'O' for 'Organization'.
    Name          CHAR(30) NOT NULL,
    --
    CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode)
);

CREATE TABLE Party ( -- Represents the supertype.
    PartyId         INT       NOT NULL,
    PartyTypeCode   CHAR(1)   NOT NULL, -- Denotes the subtype discriminator.
    CreatedDateTime TIMESTAMP NOT NULL,  
    Etcetera        CHAR(30)  NOT NULL,  
    --
    CONSTRAINT Party_PK            PRIMARY KEY (PartyId),
    CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
        REFERENCES PartyType (PartyTypeCode)
);

CREATE TABLE Person ( -- Stands for a subtype.
    PersonId        INT      NOT NULL, -- To be CONSTRAINed as PRIMARY KEY and FOREIGN KEY at the same time, enforcing an association cardinality of one-to-zero-or-one from Party to Person.
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    BirthDate       DATE     NOT NULL,
    Etcetera        CHAR(30) NOT NULL,  
    --
    CONSTRAINT Person_PK        PRIMARY KEY (PersonId),
    CONSTRAINT Person_AK        UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT PersonToParty_FK FOREIGN KEY (PersonId)
        REFERENCES Party (PartyId)
);

CREATE TABLE Organization ( -- Represents the other subtype.
    OrganizationId  INT      NOT NULL, -- To be CONSTRAINed as PRIMARY KEY and FOREIGN KEY simultaneously, enforcing a association cardinality of one-to-zero-or-one from Party to Organization.
    Name            CHAR(30) NOT NULL,
    FoundingDate    DATE     NOT NULL,
    Etcetera        CHAR(30) NOT NULL,  
    --
    CONSTRAINT Organization_PK        PRIMARY KEY (OrganizationId),
    CONSTRAINT Organization_AK        UNIQUE      (Name), -- ALTERNATE KEY.
    CONSTRAINT OrganizationToParty_FK FOREIGN KEY (OrganizationId)
        REFERENCES Party (PartyId)
);

CREATE TABLE UserProfile (
    UserId          INT       NOT NULL, -- To be CONSTRAINed as PRIMARY KEY and FOREIGN KEY at the same time, enforcing an association cardinality of one-to-zero-or-one from Person to UserProfile.
    UserName        CHAR(30)  NOT NULL,
    CreatedDateTime TIMESTAMP NOT NULL,
    Etcetera        CHAR(30)  NOT NULL,  
    --
    CONSTRAINT UserProfile_PK         PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK         UNIQUE      (Username),
    CONSTRAINT UserProfileToPerson_FK FOREIGN KEY (UserId)
        REFERENCES Person (PersonId)  
);

CREATE TABLE Account (
    AccountNumber   INT       NOT NULL,
    OwnerPartyId    INT       NOT NULL, -- A role name assigned to PartyId in order to depict the meaning it carries in the context of an Account.
    CreatedDateTime TIMESTAMP NOT NULL,
    Etcetera        CHAR(30)  NOT NULL,  
    --
    CONSTRAINT Account_PK        PRIMARY KEY (AccountNumber),
    CONSTRAINT AccountToParty_FK FOREIGN KEY (OwnerPartyId)
        REFERENCES Party (PartyId)
);

CREATE TABLE Transfer (
    TransferorAccountNumber INT       NOT NULL, -- Role name assigned to AccountNumber.
    TransfereeAccountNumber INT       NOT NULL, -- Role name assigned to AccountNumber
    TransferDateTime        TIMESTAMP NOT NULL,  
    Amount                  INT       NOT NULL, -- Retains the Amount in Cents, but there are other possibilities.
    Etcetera                CHAR(30)  NOT NULL, 
    --
    CONSTRAINT Transfer_PK             PRIMARY KEY (TransferorAccountNumber, TransfereeAccountNumber, TransferDateTime), -- Composite PRIMARY KEY.
    CONSTRAINT TransferToTransferor_FK FOREIGN KEY (TransferorAccountNumber)
        REFERENCES Account (AccountNumber),
    CONSTRAINT TransferToTransferee_FK FOREIGN KEY (TransfereeAccountNumber)
        REFERENCES Account (AccountNumber),
    CONSTRAINT AccountsAreDistinct_CK  CHECK       (TransferorAccountNumber <> TransfereeAccountNumber),
    CONSTRAINT AmountIsValid_CK        CHECK       (Amount > 0)
);

Như đã lưu ý, không cần giữ lại các dấu NULL mơ hồ và có vấn đề trong các cột của bất kỳ bảng cơ sở nào.

Nếu bạn muốn biết nếu một tài khoản tham gia vào một số chuyển được sở hữu bởi một tổ chức hoặc một người , bạn có thể lấy được những thông tin đó trong một câu lệnh SELECT đơn qua, ví dụ như, các Transfer.TrasnferorAccountNumber, các Account.PartyId, và các Party.PartyTypeCodecột.

Để bạn có thể đảm bảo rằng một Bên có thể sở hữu tối đa một Tài khoản (như đã nêu trong các nhận xét), thì bạn nên sửa một ràng buộc KHÔNG GIỚI HẠN cho Account.PartyIdcột. Tuy nhiên, trong các tình huống trong thế giới thực, ví dụ: trong một ngân hàng, một Người có thể sở hữu các Tài khoản không có một hoặc nhiều , do đó tôi đánh giá cao rằng một hiệp hội một-không-hoặc-một-không có vẻ thực tế.

Như đã đề cập trước đây, cách tiếp cận được đề xuất trong câu trả lời này được cho là sẽ được sử dụng như một tài liệu tham khảo mà bạn có thể tự mình mở rộng và điều chỉnh. Đương nhiên, các phần mở rộng và thích ứng được thực hiện ở cấp độ khái niệm nên được phản ánh trong mô hình logic.

Tôi đã thử nghiệm khai báo cấu trúc này trong (i) db <> fiddle này và trong (ii) SQL Fiddle này , cả hai đều chạy trên PostgreQuery 9.6 (ban đầu bạn đã gắn thẻ của hệ thống quản lý cơ sở dữ liệu này).

Cân nhắc tính toàn vẹn và nhất quán đối với các bảng Đảng, Người và Tổ chức

Với cách bố trí mô tả ở trên, người ta phải đảm bảo rằng mỗi “siêu kiểu” hàng là bất cứ lúc nào bổ sung bởi “Loại” đối tác tương ứng của nó và, đến lượt nó, đảm bảo những gì đã nói “Loại” hàng là phù hợp với giá trị chứa trong các siêu kiểu “phân biệt " cột.

Sẽ rất tiện lợi và tao nhã khi thực thi các trường hợp như vậy một cách khai báo, nhưng thật không may, không có nền tảng SQL chính nào cung cấp các cơ chế phù hợp để làm như vậy (theo như tôi biết). Do đó, khá thuận tiện khi sử dụng GIAO DỊCH ACID để các điều kiện này luôn được đáp ứng một cách tự tin trong cơ sở dữ liệu.

Kịch bản tương tự

Trong trường hợp bạn quan tâm đến các lĩnh vực kinh doanh khác trong đó các cấu trúc siêu kiểu phụ xuất hiện, bạn có thể muốn xem câu trả lời của tôi về

Tài nguyên liên quan

  • Các bài đăng Stack Overflow này bao gồm các điểm rất phù hợp liên quan đến kiểu dữ liệu của một cột chứa dữ liệu tiền tệ , như Transfer.Amount, trong PostgreQuery.

Chú thích

Integration Definition Thông tin Modeling ( IDEF1X ) là một dữ liệu cao recommendable mô hình kỹ thuật mà đã được thành lập như là một tiêu chuẩn trong tháng 12 năm 1993 của Hoa Kỳ Viện Tiêu chuẩn và Công nghệ (NIST). Nó hoàn toàn dựa trên (a) một số tác phẩm lý thuyết ban đầu được tác giả bởi người khởi tạo mô hình quan hệ, tức là, Tiến sĩ EF Codd ; trên (b) quan điểm Thực thể-Mối quan hệ , được phát triển bởi Tiến sĩ PP Chen ; và cũng trên (c) Kỹ thuật thiết kế cơ sở dữ liệu logic, được tạo bởi Robert G. Brown.


1
Đó chính xác là tôi đang tìm kiếm! Giải pháp của bạn tốt hơn RDFozz vì bạn đã cung cấp câu trả lời ngắn gọn hơn và các tài khoản nằm trong một bảng riêng biệt và cho phép tách khỏi cơ sở dữ liệu trong tương lai nếu cần. Tôi sẽ đánh dấu câu trả lời này là chấp nhận. Cả hai bạn và RDFozz đều cảm ơn tôi.
aarnes

1

Thêm bảng tài khoản dường như không thay đổi vấn đề cơ bản.

Trong TransferHistory, bạn có thể có TransferFrom và TransferFromType, trong đó TransferFrom là Thành viên hoặc giá trị ID tổ chức và TransferFromType là "M" hoặc "O". Bạn không thể sử dụng khóa ngoại ở đây và sẽ phải duy trì tính toàn vẹn tham chiếu "thủ công" (ví dụ: có kích hoạt). Tương tự cho TransferTo.

Một truy vấn sẽ trông giống như thế này (thay thế bằng các trường thực tế khi cần thiết):

SELECT COALESCE(of.OrgName, mf.MemberName) as "From"
      ,COALESCE(ot.OrgName, mt.MemberName) as "To"
      ,th.*
  FROM TransferHistory th
         LEFT JOIN Organization ot ON (th.TransferTo = ot.ID AND th.TransferToType = 'O')
         LEFT JOIN Member mt ON (th.TransferTo = mt.ID AND th.TransferToType = 'M')
         LEFT JOIN Organization of ON (th.TransferFrom = of.ID AND th.TransferFromType = 'O')
         LEFT JOIN Member mf ON (th.TransferFrom = mf.ID AND th.TransferFromType = 'M')
;

Hoặc, bạn có 4 lĩnh vực, TransferFromMember, TransferFrom Org, TransferToMember andTransferToOrg`, tất cả các phím nước ngoài, và hạn chế sử dụng để đảm bảo chỉ có một từ và một Để được thiết lập.

Một truy vấn sẽ trông giống như thế này (thay thế bằng các trường thực tế khi cần thiết):

SELECT COALESCE(of.OrgName, mf.MemberName) as "From"
      ,COALESCE(ot.OrgName, mt.MemberName) as "To"
      ,th.*
  FROM TransferHistory th
         LEFT JOIN Organization ot ON (th.TransferToOrg = ot.ID)
         LEFT JOIN Member mt ON (th.TransferToMember = mt.ID)
         LEFT JOIN Organization of ON (th.TransferFromOrg = of.ID)
         LEFT JOIN Member mf ON (th.TransferFromMember = mf.ID)
;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.