Entwicklung einer Datenbank für ein Geldtransfergeschäft, in dem (a) Personen und Organisationen (b) Geld senden und empfangen können


7

Im relevanten Geschäftskontext müssen sowohl Mitglieder als auch Organisationen über ein Konto für Mittel verfügen . Fonds können werden übertragen

  • von Mitglied zu Mitglied ,
  • vom Mitglied zur Organisation ,
  • von Organisation zu Organisation und
  • von der Organisation zum Mitglied .

Überlegungen

Um eine Datenbank für ein solches Szenario zu erstellen, habe ich die folgenden drei Tabellen erstellt:

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 
);

Ich denke , dass die TransferHistoryTabelle notwendig ist , um zu zeigen , wer / was geschickt Mittel zu wem / was .

Das Problem ist, da Membersund Organizationssind verschiedene Tabellen, wie kann ich sie aus der TransferHistoryTabelle referenzieren ?

Beispielsweise können die beteiligten Daten wie folgt angezeigt werden:

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

Dies würde bedeuten, dass Konten in derselben Tabelle erfasst werden müssen, Konten jedoch für zwei verschiedene Arten von Eigentümern ( Mitglieder und Organisationen ) gelten, die jeweils in ihrer jeweiligen Tabelle gespeichert sind.

Ich könnte eine Tabelle mit dem Namen erstellen Accounts, also hätte ich jetzt vier Tabellen:

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 
);

... aber jetzt muss ich sicherstellen, dass nicht jeder Fremdschlüssel aus Membersund OrganizationsTabellen auf dieselbe AccountZeile in der AccountsTabelle verweist ...

... oder ich könnte eine AccountsTabelle mit zwei Fremdschlüsseln haben, von denen einer auf Membersund einer auf zeigt Organizations(und eine Fremdschlüsselspalte müsste immer eine NULL-Markierung enthalten). Aber jetzt wird es etwas verwirrend in Bezug auf Anfragen. Das Datenbankdesign wäre im Allgemeinen wie folgt:

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 
);

Hat jemand einen Vorschlag, wie dieses Problem gelöst werden kann?

Antworten:


11

Wenn eine relationale Datenbank erstellt werden soll, ist es sehr hilfreich, zunächst (a) eine Analyse des interessierenden Geschäftskontexts durchzuführen, um ein konzeptionelles Schema in Bezug auf Entitätstypen abzugrenzen und deren Eigenschaften und Zuordnungen zuvor zu überprüfen (b) Denken in Tabellen, Spalten und Einschränkungen - Aspekte, die der logischen Ebene entsprechen -. Nach dieser Vorgehensweise ist es viel simplier die erfassen Bedeutung des Business - Bereichs mit Genauigkeit und dann reflektieren sie in einem tatsächlichen, auch gezwungen, SQL-DDL - Design.

Einer der zahlreichen Vorteile des relationalen Paradigmas besteht darin, dass es die Verwaltung der Daten in ihrer natürlichen Struktur ermöglicht. Daher muss man eine solche Struktur „finden“, bevor man relationale Instrumente einsetzt, um sie zu verwalten. Es spielt keine Rolle, ob sich das fragliche Szenario auf ein persönliches Projekt bezieht (wie Sie in Kommentaren hervorgehoben haben): Je realistischer Sie es definieren, desto mehr lernen Sie aus seiner Entwicklung (wenn dies der Zweck dieser Bemühungen ist). Natürlich kann sich aus einem realistischen persönlichen Projekt ein kommerzielles Projekt mit relativ geringen Anpassungen entwickeln.

Geschäftsregeln

Um einen ersten Fortschritt zu präsentieren, den Sie möglicherweise als Referenz verwenden möchten, habe ich einige der Geschäftsregeln auf konzeptioneller Ebene formuliert, die zu den wichtigsten gehören, und sie werden wie folgt aufgelistet:

  • Eine Person besitzt null, eins oder viele Konten
  • Eine Person unterscheidet sich hauptsächlich durch ihre ID
  • Eine Person wird abwechselnd zeichnet sich durch sein / ihr Vorname , Nachname , Geburtsdatum und Geschlecht
  • Eine Organisation besitzt null, eins oder viele Konten
  • Eine Organisation unterscheidet sich hauptsächlich durch ihre ID
  • Eine Organisation wird abwechselnd durch ihren Namen unterschieden
  • Eine Organisation beginnt mit einem Gründungsdatum
  • Ein Konto ist der Übertragende bei Null-Eins-oder-Viele- Überweisungen
  • Ein Konto ist der Transferee bei Null-Eins-oder-Viele- Überweisungen
  • Ein Konto wird hauptsächlich durch seine Nummer identifiziert
  • Ein Konto wird zu einem genauen Erstellungsdatum ausgestellt
  • Bei einer Übertragung der Übertragenden muss von dem verschieden sein Transferee
  • Eine Person kann sich über das Benutzerprofil null oder eins anmelden

Da die Verbände -oder relationships- (1) zwischen Person und Konto und (2) zwischen Organisation und Konto sehr ähnlich ist, diese Tatsache zeigt , dass Person und Konto Einheit sind Subtypen von Partei (grundsätzlich entweder eine Einzelperson oder eine Gruppe von Personen) , was wiederum ihr Entity- Supertyp ist . Dies ist eine klassische Informationsstruktur, die sehr häufig in mehreren konzeptionellen Modellen unterschiedlicher Art auftritt. Auf diese Weise können zwei neue Regeln geltend gemacht werden:

  • Ein PartyType klassifiziert null, eins oder viele Parteien
  • Eine Partei ist entweder eine Person oder eine Organisation

Und zwei der vorherigen Geschäftsregeln können zu einer einzigen zusammengefasst werden:

  • Eine Partei besitzt null, eins oder viele Konten

Was auch unter dem Gesichtspunkt des Entitätstyps Konto angegeben werden kann:

  • Ein Konto gehört genau einer Partei

Expository IDEF1X-Diagramm

Infolgedessen habe ich ein Expository- Diagramm (vereinfacht) IDEF1X † erstellt , das die oben formulierten Regeln zusammenfasst. Es ist in Abbildung 1 dargestellt :

Abbildung 1 - IDEF1X-Modell für Geldtransfer

Partei, Person und Organisation: Supertyp-Subtyp-Struktur

Wie gezeigt Personund Organizationals sich gegenseitig ausschließende Subtypen von dargestellt Party.

Der PartySupertyp enthält einen Diskriminator (dh PartyTypeCode) und alle Eigenschaften (oder Attribute), die seinen Untertypen gemeinsam sind, die wiederum die Eigenschaften haben, die für jeden von ihnen gelten.

Konto

Der AccountEntitätstyp ist direkt mit verbunden Party, wodurch eine nachfolgende Verbindung zwischen (i) Accountund Personund zwischen (ii) Accountund hergestellt wird Organization.

Da es möglich ist , dass in der realen Welt, (a) eine Bank ist Accountnicht übertragbar, dh sein Ownerkann und ändern (b) eine Accountkann nicht ohne eine, gegenwärtige oder aktiviert beginnen Owner, der Primärschlüssel dieses Entitätstyp kann bestehen aus die Eigenschaften PartyId und AccountNumber , daher sollten Sie das Szenario noch genauer analysieren, um diesen Punkt mit hoher Präzision zu definieren.

Transfer

Auf der anderen Seite, die Transferaus Entitätstyp stellt ein zusammengesetzter Primärschlüssel von drei Eigenschaften auf, dh TransferorAccountNumber, TransfereeAccountNumber( Rollennamen mir jeden von beiden zu unterscheiden zugeordnet AccountEigenschaften in jeder beteiligten TransferInstanz) und TransferDateTime(die die exaxct erzählen Sofort , wenn ein TransferAuftreten war durchgeführt).

Faktoren zu AccountNumbers

Beachten Sie auch, dass in tatsächlichen Bankensystemen das Format eines AccountNumberDatenpunkts normalerweise komplexer ist als ein „bloßer“ ganzzahliger Wert. Es gibt verschiedene Formatvereinbarungen, z. B. die, die der durch die Norm ISO 13616 definierten internationalen Bankkontonummer (IBAN) entspricht. Dieser Aspekt impliziert offensichtlich, dass die (1) konzeptionelle Analyse und die späteren (2) logischen Definitionen einen viel umfassenderen Ansatz erfordern.

Illustrative logische SQL-DDL-Deklarationen

Dann habe ich als Ableitung von der vorherigen Analyse ein logisches Design deklariert, in dem

  • Jede Tabelle repräsentiert einen Entitätstyp.
  • Jede Spalte steht für eine Eigenschaft des jeweiligen Entitätstyps und
  • Es werden (deklarativ) mehrere Einschränkungen eingerichtet, um sicherzustellen, dass die in allen Tabellen enthaltenen Zusicherungen in Form von Zeilen den auf der konzeptionellen Ebene festgelegten Geschäftsregeln entsprechen.

Ich habe Anmerkungen als Kommentare geliefert, die einige der Merkmale erläutern, die ich im Hinblick auf die oben erwähnte Struktur, die unten gezeigt wird, für besonders wichtig halte:

-- 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)
);

Wie bereits erwähnt, müssen keine mehrdeutigen und problematischen NULL-Markierungen in den Spalten einer der Basistabellen beibehalten werden.

Wenn Sie wissen möchten, ob ein an einer bestimmten Übertragung beteiligtes Konto einer Organisation oder einer Person gehört , können Sie solche Informationen in einer einzelnen SELECT-Anweisung über z. B. die Spalten , die und die Spalten ableiten .Transfer.TrasnferorAccountNumberAccount.PartyIdParty.PartyTypeCode

Damit Sie sicherstellen können, dass eine Partei höchstens ein Konto besitzen kann (wie in den Kommentaren angegeben), sollten Sie eine EINZIGARTIGE Einschränkung für die Account.PartyIdSpalte festlegen . In realen Szenarien, z. B. in einer Bank, kann eine Person jedoch null, eins oder viele Konten besitzen. Daher schätze ich, dass eine Eins-zu-Null-oder-Eins-Zuordnung nicht realistisch erscheint.

Wie bereits erwähnt, soll der in dieser Antwort vorgeschlagene Ansatz als Referenz verwendet werden, die Sie selbst erweitern und anpassen können. Natürlich sollten Erweiterungen und Anpassungen auf konzeptioneller Ebene im logischen Modell berücksichtigt werden.

Ich habe die Deklaration dieser Struktur in (i) dieser Datenbank- Geige und in (ii) dieser SQL-Geige getestet , die beide unter PostgreSQL 9.6 ausgeführt werden (Sie haben ursprünglich das Tag dieses Datenbankverwaltungssystems angehängt).

Überlegungen zu Integrität und Konsistenz in Bezug auf die Tabellen Partei, Person und Organisation

Bei dem oben beschriebenen Layout muss sichergestellt werden, dass jede Zeile "Supertyp" jederzeit durch das entsprechende Gegenstück "Subtyp" ergänzt wird, und es muss wiederum sichergestellt werden, dass diese Zeile "Subtyp" mit dem im Supertyp "Diskriminator" enthaltenen Wert kompatibel ist " Säule.

Es wäre sehr praktisch und elegant, solche Umstände deklarativ durchzusetzen, aber leider hat keine der großen SQL-Plattformen die richtigen Mechanismen dafür bereitgestellt (soweit ich weiß). Daher ist es sehr praktisch, ACID TRANSACTIONS zu verwenden, damit diese Bedingungen in einer Datenbank immer sicher erfüllt werden.

Ähnliche Szenarien

Wenn Sie an anderen Geschäftsbereichen interessiert sind, in denen Supertyp-Subtyp-Strukturen entstehen, möchten Sie vielleicht meine Antworten auf sehen

Relevante Ressource

  • Diese Stapelüberlauf-Posts decken sehr relevante Punkte in Bezug auf den Datentyp einer Spalte ab, die ein Währungsdatum enthält , wie Transfer.Amountin PostgreSQL.

Endnote

Integration Definition für Informationsmodellierung ( IDEF1X ) ist eine sehr empfehlenswerte Datenmodellierungstechnik, die als etabliert wurde Standard im Dezember 1993 von den Vereinigten Staaten National Institute of Standards and Technology (NIST). Es basiert fest auf (a) einigen frühen theoretischen Arbeiten, die vom Urheber des relationalen Modells verfasst wurden, dh Dr. EF Codd ; zu (b) dervon Dr. PP Chen entwickelten Entity-Relationship-Sichtweise ; und auch auf (c) der Logical Database Design Technique, erstellt von Robert G. Brown.


1
Genau das habe ich gesucht! Ihre Lösung ist besser als die von RDFozz, da Sie präzisere Antworten gegeben haben und die Konten in einer separaten Tabelle aufgeführt sind und bei Bedarf eine zukünftige Trennung von der Datenbank ermöglichen. Ich werde diese Antwort als akzeptiert markieren. Sie und RDFozz haben meinen Dank.
Aarnes

1

Das Hinzufügen der Kontotabelle scheint das grundlegende Problem nicht zu ändern.

In TransferHistory können Sie TransferFrom und TransferFromType verwenden, wobei TransferFrom entweder ein Mitglied oder eine Organisations-ID ist und TransferFromType "M" oder "O" ist. Sie können hier überhaupt keine Fremdschlüssel verwenden und müssten die referenzielle Integrität "manuell" (z. B. mit Triggern) aufrechterhalten. Gleiches gilt für TransferTo.

Eine Abfrage würde ungefähr so ​​aussehen (bei Bedarf durch tatsächliche Felder ersetzen):

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')
;

Oder Sie haben 4 Felder TransferFromMember, TransferFrom Org, TransferToMember andTransferToOrg`, alle Fremdschlüssel und Nutzungsmöglichkeiten, um sicherzustellen , nur ein Von- und ein zu setzen.

Eine Abfrage würde ungefähr so ​​aussehen (bei Bedarf durch tatsächliche Felder ersetzen):

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)
;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.