Écrire un schéma bancaire simple: comment dois-je synchroniser mes soldes avec l'historique de leurs transactions?


57

J'écris le schéma d'une base de données bancaire simple. Voici les spécifications de base:

  • La base de données stockera les transactions contre un utilisateur et une devise.
  • Chaque utilisateur a un solde par devise. Chaque solde est donc simplement la somme de toutes les transactions effectuées avec un utilisateur et une devise donnés.
  • Un solde ne peut être négatif.

L'application bancaire communiquera avec sa base de données exclusivement par le biais de procédures stockées.

Je m'attends à ce que cette base de données accepte des centaines de milliers de nouvelles transactions par jour, ainsi que des requêtes de solde d'un ordre de grandeur supérieur. Pour servir les soldes très rapidement, je dois les pré-agréger. Dans le même temps, je dois garantir qu'un solde ne contredit jamais l'historique de ses transactions.

Mes options sont:

  1. Ayez une balancestable séparée et effectuez l'une des opérations suivantes:

    1. Appliquez des transactions aux tables transactionset balances. Utilisez la TRANSACTIONlogique dans la couche de procédures stockées pour vous assurer que les soldes et les transactions sont toujours synchronisés. (Soutenu par Jack .)

    2. Appliquer des transactions à la transactionstable et avoir un déclencheur qui met à jour la balancestable pour moi avec le montant de la transaction.

    3. Appliquez des transactions à la balancestable et créez un déclencheur qui ajoute une nouvelle entrée dans la transactionstable avec le montant de la transaction.

    Je dois m'appuyer sur des approches basées sur la sécurité pour m'assurer qu'aucune modification ne peut être apportée en dehors des procédures stockées. Sinon, par exemple, certains processus pourraient directement insérer une transaction dans la transactionstable et, dans ce schéma, 1.3le solde correspondant serait désynchronisé.

  2. Avoir une balancesvue indexée qui agrège les transactions de manière appropriée. Les soldes sont garantis par le moteur de stockage pour rester synchronisés avec leurs transactions. Je n'ai donc pas besoin de recourir à des approches basées sur la sécurité pour le garantir. Par contre, je ne peux plus imposer que les balances soient non négatives car les vues - même les vues indexées - ne peuvent pas avoir de CHECKcontraintes. (Soutenu par Denny .)

  3. Ayez juste une transactionstable mais avec une colonne supplémentaire pour stocker le solde en vigueur juste après l'exécution de la transaction. Ainsi, le dernier enregistrement de transaction pour un utilisateur et une devise contient également leur solde actuel. (Suggéré ci-dessous par Andrew ; variante proposée par garik .)

Lorsque j'ai abordé ce problème pour la première fois, j'ai lu ces deux discussions et choisi l'option 2. Pour référence, vous pouvez voir une implémentation de base ici .

  • Avez-vous conçu ou géré une telle base de données avec un profil de charge élevé? Quelle a été votre solution à ce problème?

  • Pensez-vous que j'ai fait le bon choix de design? Y a-t-il quelque chose que je devrais garder à l'esprit?

    Par exemple, je sais que les modifications de schéma dans la transactionstable nécessiteront la reconstruction de la balancesvue. Même si j'archive des transactions pour garder la base de données petite (par exemple en les déplaçant ailleurs et en les remplaçant par des transactions récapitulatives), devoir reconstruire la vue de dizaines de millions de transactions à chaque mise à jour de schéma signifiera probablement beaucoup plus de temps mort par déploiement.

  • Si la vue indexée est la voie à suivre, comment puis-je garantir qu'aucun solde n'est négatif?


Archivage des transactions:

Permettez-moi de développer un peu les transactions d'archivage et les "transactions récapitulatives" que j'ai mentionnées ci-dessus. Premièrement, un archivage régulier sera une nécessité dans un système à forte charge comme celui-ci. Je souhaite maintenir la cohérence entre les soldes et l'historique de leurs transactions tout en permettant de transférer les anciennes transactions ailleurs. Pour ce faire, je remplacerai chaque lot de transactions archivées par un récapitulatif de leurs montants par utilisateur et par devise.

Ainsi, par exemple, cette liste de transactions:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1       10.60             0
      3              1      -55.00             0
      3              1      -12.12             0

est archivé et remplacé par ceci:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1      -56.52             1

De cette manière, un solde avec des transactions archivées conserve un historique complet et cohérent des transactions.


1
Si vous choisissez l'option 2 (qui, à mon avis, est plus propre), consultez pgcon.org/2008/schedule/attachments/… pour savoir comment mettre en œuvre efficacement les "vues matérialisées". Pour l'option 1, le chapitre 11 de Mathématiques appliquées pour professionnels de la base de données de Haan's et Koppelaars (ne vous inquiétez pas du titre) serait utile pour avoir une idée de la manière de mettre en œuvre efficacement les "contraintes de transition". Le premier lien concerne PostgreSQL et le second Oracle, mais les techniques devraient fonctionner pour tout système de base de données raisonnable.
jp

En théorie, vous voulez faire # 3. La bonne façon de faire un "solde courant" consiste à affecter un solde à chaque transaction. Assurez-vous de pouvoir commander définitivement les transactions avec un ID de série (préféré) ou un horodatage. Vous n'êtes vraiment pas censé "calculer" un solde courant.
pbreitenbach

Réponses:


15

Je ne suis pas familier avec la comptabilité, mais j'ai résolu certains problèmes similaires dans des environnements de type inventaire. Je stocke les totaux cumulés sur la même ligne que la transaction. J'utilise des contraintes pour que mes données ne soient jamais fausses, même en cas de simultanéité élevée. J'ai écrit la solution suivante à l'époque en 2009 :

Le calcul des totaux cumulés est notoirement lent, que vous le fassiez avec un curseur ou avec une jointure triangulaire. Il est très tentant de dénormaliser, de stocker les totaux cumulés dans une colonne, surtout si vous la sélectionnez fréquemment. Cependant, comme d'habitude lorsque vous dénormalisez, vous devez garantir l'intégrité de vos données dénormalisées. Heureusement, vous pouvez garantir l'intégrité des totaux cumulés avec des contraintes - tant que toutes vos contraintes sont approuvées, tous vos totaux cumulés sont corrects. De même, vous pouvez facilement vous assurer que le solde actuel (les totaux cumulés) n’est jamais négatif - l’application par d’autres méthodes peut également être très lente. Le script suivant illustre la technique.

CREATE TABLE Data.Inventory(InventoryID INT NOT NULL IDENTITY,
  ItemID INT NOT NULL,
  ChangeDate DATETIME NOT NULL,
  ChangeQty INT NOT NULL,
  TotalQty INT NOT NULL,
  PreviousChangeDate DATETIME NULL,
  PreviousTotalQty INT NULL,
  CONSTRAINT PK_Inventory PRIMARY KEY(ItemID, ChangeDate),
  CONSTRAINT UNQ_Inventory UNIQUE(ItemID, ChangeDate, TotalQty),
  CONSTRAINT UNQ_Inventory_Previous_Columns 
     UNIQUE(ItemID, PreviousChangeDate, PreviousTotalQty),
  CONSTRAINT FK_Inventory_Self FOREIGN KEY(ItemID, PreviousChangeDate, PreviousTotalQty)
    REFERENCES Data.Inventory(ItemID, ChangeDate, TotalQty),
  CONSTRAINT CHK_Inventory_Valid_TotalQty CHECK(
         TotalQty >= 0 
     AND (TotalQty = COALESCE(PreviousTotalQty, 0) + ChangeQty)
  ),
  CONSTRAINT CHK_Inventory_Valid_Dates_Sequence CHECK(PreviousChangeDate < ChangeDate),
  CONSTRAINT CHK_Inventory_Valid_Previous_Columns CHECK(
        (PreviousChangeDate IS NULL AND PreviousTotalQty IS NULL)
     OR (PreviousChangeDate IS NOT NULL AND PreviousTotalQty IS NOT NULL)
  )
);

-- beginning of inventory for item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090101', 10, 10, NULL, NULL);

-- cannot begin the inventory for the second time for the same item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090102', 10, 10, NULL, NULL);


Msg 2627, Level 14, State 1, Line 10

Violation of UNIQUE KEY constraint 'UNQ_Inventory_Previous_Columns'. 
Cannot insert duplicate key in object 'Data.Inventory'.

The statement has been terminated.


-- add more
DECLARE @ChangeQty INT;
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090103', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = 3;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090104', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = -4;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090105', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

-- try to violate chronological order
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20081231', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

Msg 547, Level 16, State 0, Line 4

The INSERT statement conflicted with the CHECK constraint 
"CHK_Inventory_Valid_Dates_Sequence". 
The conflict occurred in database "Test", table "Data.Inventory".

The statement has been terminated.

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- -----
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 5           15          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           18          2009-01-03 00:00:00.000 15
2009-01-05 00:00:00.000 -4          14          2009-01-04 00:00:00.000 18


-- try to change a single row, all updates must fail
UPDATE Data.Inventory SET ChangeQty = ChangeQty + 2 WHERE InventoryID = 3;
UPDATE Data.Inventory SET TotalQty = TotalQty + 2 WHERE InventoryID = 3;

-- try to delete not the last row, all deletes must fail
DELETE FROM Data.Inventory WHERE InventoryID = 1;
DELETE FROM Data.Inventory WHERE InventoryID = 3;

-- the right way to update
DECLARE @IncreaseQty INT;

SET @IncreaseQty = 2;

UPDATE Data.Inventory 
SET 
     ChangeQty = ChangeQty 
   + CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN @IncreaseQty 
        ELSE 0 
     END,
  TotalQty = TotalQty + @IncreaseQty,
  PreviousTotalQty = PreviousTotalQty + 
     CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN 0 
        ELSE @IncreaseQty 
     END
WHERE ItemID = 1 AND ChangeDate >= '20090103';

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- ----------------
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 7           17          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           20          2009-01-03 00:00:00.000 17
2009-01-05 00:00:00.000 -4          16          2009-01-04 00:00:00.000 20

14

Ne pas autoriser les clients à avoir un solde inférieur à 0 est une règle de gestion (ce qui changerait rapidement étant donné que les frais pour des éléments tels que les traites supplémentaires sont le moyen par lequel les banques utilisent la plus grande partie de leur argent). Vous voudrez gérer cela dans le traitement de l'application lorsque des lignes sont insérées dans l'historique des transactions. Surtout que certains clients peuvent se retrouver avec une protection contre les découverts et se faire facturer des frais et ne pas permettre la saisie de montants négatifs.

Jusqu'ici, j'aime bien ce que vous faites avec cela, mais si c'est pour un projet réel (pas pour une école), il faut beaucoup de réflexion dans les règles de gestion, etc. Une fois que vous avez un système bancaire en place et en cours d'exécution, il n'y a pas beaucoup de place pour une nouvelle conception car il existe des lois très spécifiques concernant l'accès des personnes à leur argent.


1
Je peux voir pourquoi la contrainte des soldes devrait être en réalité une règle de gestion. La base de données ne fait que fournir un service de transaction et il appartient à l'utilisateur de décider quoi faire avec.
Nick Chammas le

Que pensez-vous des commentaires de Jack sur le fait que l'utilisation des deux tableaux offre aux développeurs plus de flexibilité pour modifier ou implémenter une logique métier? De plus, avez-vous une expérience directe avec les vues indexées qui valide ou conteste ces préoccupations ?
Nick Chammas

1
Je ne dirais pas que le fait d’avoir deux tables vous donne la possibilité de changer de position, c’est la mise en œuvre d’une logique d’entreprise Cela vous donne plus de flexibilité pour l'archivage des données. Cependant, en tant que banque (du moins aux États-Unis), vous disposez de lois indiquant le volume de données à conserver. Vous voudrez tester à quoi ressemblent les performances avec la vue affichée en haut, et tenir compte du fait que si vous avez une vue indexée, vous ne pouvez pas modifier le schéma des tables sous-jacentes. Juste une autre chose à penser.
mrdenny

Tous les éléments mentionnés dans l'article sont des préoccupations valables à propos de l'utilisation d'une vue indexée.
mrdenny

1
Pour clarifier: une API transactionnelle IMO offre plus de flexibilité lors de la mise en œuvre de la logique métier (ne pas avoir deux tables) Dans ce cas, je serais également en faveur de deux tableaux (du moins compte tenu des informations dont nous disposons à ce jour) en raison des compromis proposés avec l'approche de la vue indexée (par exemple, vous ne pouvez pas utiliser DRI pour imposer un solde> 0 entreprise règle)
Jack Douglas

13

Une approche légèrement différente (similaire à votre deuxième option) à considérer consiste à ne prendre que la table de transaction, avec une définition de:

CREATE TABLE Transaction (
      UserID              INT
    , CurrencyID          INT 
    , TransactionDate     DATETIME  
    , OpeningBalance      MONEY
    , TransactionAmount   MONEY
);

Vous voudrez peut-être également un identifiant de transaction / une commande afin de pouvoir traiter deux transactions avec la même date et améliorer votre requête de récupération.

Pour obtenir le solde actuel, tout ce dont vous avez besoin est le dernier enregistrement.

Méthodes pour obtenir le dernier enregistrement :

/* For a single User/Currency */
Select TOP 1 *
FROM dbo.Transaction
WHERE UserID = 3 and CurrencyID = 1
ORDER By TransactionDate desc

/* For multiple records ie: to put into a view (which you might want to index) */
SELECT
    C.*
FROM
    (SELECT 
        *, 
        ROW_NUMBER() OVER (
           PARTITION BY UserID, CurrencyID 
           ORDER BY TransactionDate DESC
        ) AS rnBalance 
    FROM Transaction) C
WHERE
    C.rnBalance = 1
ORDER BY
    C.UserID, C.CurrencyID

Les inconvénients:

  • Lorsque vous insérez une transaction hors séquence (par exemple, pour corriger un problème / un solde initial incorrect), vous devrez peut-être mettre en cascade les mises à jour pour toutes les transactions suivantes.
  • Les transactions pour l'utilisateur / la devise doivent être sérialisées pour maintenir un solde exact.

    -- Example of getting the current balance and locking the 
    -- last record for that User/Currency.
    -- This lock will be freed after the Stored Procedure completes.
    SELECT TOP 1 @OldBalance = OpeningBalance + TransactionAmount  
    FROM dbo.Transaction with (rowlock, xlock)   
    WHERE UserID = 3 and CurrencyID = 1  
    ORDER By TransactionDate DESC;

Avantages:

  • Vous n'avez plus besoin de gérer deux tables séparées ...
  • Vous pouvez facilement valider le solde et lorsque celui-ci se désynchronise, vous pouvez identifier le moment exact où il s'est échappé lorsque l'historique des transactions s'auto-documente.

Edit: Quelques exemples de requêtes sur la récupération du solde en cours et pour mettre en évidence les inconvénients (Merci @Jack Douglas)


3
Le SELECT TOP (1) ... ORDER BY TransactionDate DESCsera très difficile à mettre en œuvre de telle manière que SQL Server ne scanne pas en permanence la table des transactions. Alex Kuznetsov a publié ici une solution à un problème de conception similaire, qui complète parfaitement cette réponse.
Nick Chammas

2
+1 J'utilise une approche similaire. BTW, nous devons faire très attention et nous assurer que notre code fonctionne correctement sous une charge de travail simultanée.
AK

12

Après avoir lu ces deux discussions, j'ai choisi l'option 2.

Après avoir lu ces discussions également, je ne vois pas trop pourquoi vous avez choisi la solution DRI plutôt que la plus sensée des autres options que vous décrivez:

Appliquez des transactions aux tables de transactions et de soldes. Utilisez la logique TRANSACTION dans la couche de procédure stockée pour vous assurer que les soldes et les transactions sont toujours synchronisés.

Ce type de solution présente d’énormes avantages pratiques si vous avez le luxe de limiter tout accès aux données via votre API transactionnelle. Vous perdez le très important avantage de DRI, à savoir que l'intégrité est garantie par la base de données, mais dans tout modèle suffisamment complexe , certaines règles métier ne peuvent pas être appliquées par DRI .

Dans la mesure du possible, je vous conseillerais d'utiliser DRI pour appliquer des règles métier sans trop plier votre modèle pour rendre cela possible:

Même si j'archive des transactions (par exemple en les déplaçant ailleurs et en les remplaçant par des transactions récapitulatives)

Dès que vous envisagez de polluer votre modèle de la sorte, je pense que vous vous dirigez maintenant dans la région où les avantages que présente le DRI sont contrebalancés par les difficultés que vous présentez. Imaginons par exemple qu’un bogue dans votre processus d’archivage puisse en théorie faire en sorte que votre règle d’or (équilibrer toujours la somme des transactions) se brise en silence avec une solution DRI .

Voici un résumé des avantages de l'approche transactionnelle telle que je la vois:

  • Nous devrions le faire de toute façon si possible. Quelle que soit la solution choisie pour ce problème particulier, elle vous offre davantage de flexibilité de conception et de contrôle sur vos données. Tout accès devient alors "transactionnel" en termes de logique métier, plutôt qu'en termes de logique de base de données.
  • Vous pouvez garder votre modèle soigné
  • Vous pouvez "appliquer" une gamme beaucoup plus large et complexe de règles métier (en notant que le concept de "appliquer" est plus vague que celui de DRI)
  • Vous pouvez toujours utiliser DRI dans la mesure du possible pour donner au modèle une intégrité sous-jacente plus robuste, ce qui peut servir de contrôle de votre logique transactionnelle.
  • La plupart des problèmes de performances qui vous troublent vont vous dissiper
  • Introduire de nouvelles exigences peut être beaucoup plus facile - par exemple: des règles complexes pour des transactions litigieuses peuvent vous forcer à abandonner une approche DRI pure plus loin, ce qui signifie beaucoup d'efforts inutiles
  • La partition ou l'archivage des données historiques devient beaucoup moins risqué et douloureux

--modifier

Pour permettre l'archivage sans ajouter de complexité ni de risque, vous pouvez choisir de conserver les lignes de résumé dans un tableau de synthèse séparé, généré en continu (emprunté à @Andrew et @Garik).

Par exemple, si les résumés sont mensuels:

  • chaque fois qu'il y a une transaction (via votre API), une mise à jour ou une insertion correspondante est insérée dans le tableau récapitulatif
  • le tableau récapitulatif n'est jamais archivé, mais l'archivage des transactions devient aussi simple qu'un effacement (ou une partition?)
  • chaque ligne du tableau récapitulatif comprend le «solde d'ouverture» et le «montant»
  • les contraintes de contrôle telles que 'solde d'ouverture' + 'montant'> 0 et 'solde d'ouverture'> 0 peuvent être appliquées au tableau récapitulatif
  • les lignes de résumé peuvent être insérées dans un lot mensuel pour faciliter le verrouillage de la dernière ligne de résumé (il y aura toujours une ligne pour le mois en cours)

Concernant votre édition: Vous proposez donc d’avoir ce tableau récapitulatif à côté du tableau des soldes principaux? Le tableau des soldes devient-il alors effectivement un tableau récapitulatif contenant uniquement les enregistrements du mois en cours (puisque les deux stockeront le même type de données)? Si j'ai bien compris, pourquoi ne pas simplement remplacer le tableau des soldes par la partition appropriée du tableau récapitulatif?
Nick Chammas le

Désolé, vous avez raison, ce n'est pas clair. Je parlais de la distribution du tableau des soldes car il s'agirait toujours d'une recherche clé sur le tableau récapitulatif pour obtenir le solde actuel (ce qui n'est pas vrai avec la suggestion d'Andrews, autant que je sache). L'avantage est que le calcul des soldes à des moments antérieurs devient plus facile et qu'il existe une piste d'audit plus claire pour les soldes en cas de problème.
Jack Douglas

6

Pseudo.

L'idée principale est de stocker les enregistrements de solde et de transaction dans la même table. C'est arrivé historiquement je pensais. Donc, dans ce cas, nous pouvons obtenir un équilibre simplement en localisant le dernier compte rendu.

 id   user_id    currency_id      amount    is_summary (or record_type)
----------------------------------------------------
  1       3              1       10.60             0
  2       3              1       10.60             1    -- summary after transaction 1
  3       3              1      -55.00             0
  4       3              1      -44.40             1    -- summary after transactions 1 and 3
  5       3              1      -12.12             0
  6       3              1      -56.52             1    -- summary after transactions 1, 3 and 5 

Une meilleure variante est le nombre décroissant d’enregistrements récapitulatifs. Nous pouvons avoir un enregistrement de solde à la fin (et / ou au début) du jour. Comme vous le savez, chaque banque doit operational dayouvrir et fermer ses portes pour effectuer certaines opérations récapitulatives ce jour-là. Cela nous permet de calculer facilement les intérêts en utilisant chaque relevé de solde quotidien, par exemple:

user_id    currency_id      amount    is_summary    oper_date
--------------------------------------------------------------
      3              1       10.60             0    01/01/2011 
      3              1      -55.00             0    01/01/2011
      3              1      -44.40             1    01/01/2011 -- summary at the end of day (01/01/2011)
      3              1      -12.12             0    01/02/2011
      3              1      -56.52             1    01/02/2011 -- summary at the end of day (01/02/2011)

La chance.


4

En fonction de vos besoins, l'option 1 semble être la meilleure. Bien que ma conception permette uniquement les insertions dans la table des transactions. Et avoir le déclencheur sur la table de transaction, pour mettre à jour la table de solde en temps réel. Vous pouvez utiliser les autorisations de base de données pour contrôler l'accès à ces tables.

Dans cette approche, le solde en temps réel est garanti pour être synchronisé avec la table de transaction. Et peu importe si des procédures stockées ou psql ou jdbc sont utilisés. Vous pouvez avoir votre vérification de solde négatif si nécessaire. La performance ne sera pas un problème. Pour obtenir l'équilibre en temps réel, il s'agit d'une requête singleton.

L'archivage n'affectera pas cette approche. Vous pouvez avoir un tableau récapitulatif hebdomadaire, mensuel et annuel également si nécessaire pour des éléments tels que les rapports.


3

Dans Oracle, vous pouvez le faire en utilisant uniquement la table de transactions avec une vue matérialisée rapide et actualisable qui effectue l'agrégation pour former le solde. Vous définissez le déclencheur dans la vue matérialisée. Si la vue matérialisée est définie avec 'ON COMMIT', elle empêche efficacement l'ajout / la modification de données dans les tables de base. Le déclencheur détecte les données valides [in] et lève une exception, où il annule la transaction. Un bel exemple est ici http://www.sqlsnippets.com/en/topic-12896.html

Je ne sais pas sqlserver mais peut-être qu'il a une option similaire?


2
Les vues matérialisées dans Oracle ressemblent à la "vue indexée" de SQL Server, mais elles s'actualisent automatiquement plutôt que de manière explicitement gérée, comme le comportement "ON COMMIT" d'Oracle. Voir social.msdn.microsoft.com/Forums/fi-FI/transactsql/thread/… et techembassy.blogspot.com/2007/01/…
GregW
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.