Scrivere un semplice schema bancario: come devo sincronizzare i miei saldi con la cronologia delle loro transazioni?


57

Sto scrivendo lo schema per un semplice database bancario. Ecco le specifiche di base:

  • Il database memorizzerà le transazioni contro un utente e una valuta.
  • Ogni utente ha un saldo per valuta, quindi ogni saldo è semplicemente la somma di tutte le transazioni rispetto a un determinato utente e valuta.
  • Un saldo non può essere negativo.

L'applicazione bancaria comunicherà con il proprio database esclusivamente tramite procedure memorizzate.

Mi aspetto che questo database accetti centinaia di migliaia di nuove transazioni al giorno, oltre a bilanciare le query su un ordine di grandezza superiore. Per servire rapidamente i saldi, devo pre-aggregarli. Allo stesso tempo, devo garantire che un saldo non contraddica mai la sua cronologia delle transazioni.

Le mie opzioni sono:

  1. Avere una balancestabella separata ed effettuare una delle seguenti operazioni:

    1. Applicare le operazioni sia alle transactionse balancestabelle. Usa la TRANSACTIONlogica nel mio livello di procedura memorizzata per assicurarti che i saldi e le transazioni siano sempre sincronizzati. (Supportato da Jack .)

    2. Applicare le transazioni alla transactionstabella e disporre di un trigger che aggiorna la balancestabella per me con l'importo della transazione.

    3. Applicare le transazioni alla balancestabella e disporre di un trigger che aggiunge una nuova voce nella transactionstabella con l'importo della transazione.

    Devo fare affidamento su approcci basati sulla sicurezza per assicurarmi che non possano essere apportate modifiche al di fuori delle procedure memorizzate. In caso contrario, ad esempio, alcuni processi potrebbero inserire direttamente una transazione nella transactionstabella e, in base allo schema, 1.3il saldo pertinente non sarebbe sincronizzato.

  2. Avere una balancesvista indicizzata che aggrega le transazioni in modo appropriato. I saldi sono garantiti dal motore di archiviazione per rimanere sincronizzati con le loro transazioni, quindi non ho bisogno di fare affidamento su approcci basati sulla sicurezza per garantirlo. D'altra parte, non posso più far valere i saldi in modo non negativo poiché le viste - anche le viste indicizzate - non possono avere CHECKvincoli. (Supportato da Denny .)

  3. Avere solo una transactionstabella ma con una colonna aggiuntiva per archiviare il saldo effettivo subito dopo l'esecuzione della transazione. Pertanto, l'ultimo record di transazione per un utente e una valuta contiene anche il saldo corrente. (Suggerito di seguito da Andrew ; variante proposta da Garik .)

Quando ho affrontato per la prima volta questo problema, ho letto queste due discussioni e ho deciso l'opzione 2. Per riferimento, puoi vedere un'implementazione semplice di questo qui .

  • Hai progettato o gestito un database come questo con un profilo di carico elevato? Qual è stata la tua soluzione a questo problema?

  • Pensi che ho fatto la scelta giusta per il design? C'è qualcosa che dovrei tenere a mente?

    Ad esempio, so che le modifiche dello schema alla transactionstabella richiederanno la ricostruzione della balancesvista. Anche se sto archiviando le transazioni per mantenere piccolo il database (ad es. Spostandoli altrove e sostituendoli con transazioni di riepilogo), dover ricostruire la vista su decine di milioni di transazioni con ogni aggiornamento dello schema significherà probabilmente un tempo di inattività significativamente maggiore per distribuzione.

  • Se la vista indicizzata è la strada da percorrere, come posso garantire che nessun saldo sia negativo?


Transazioni di archiviazione:

Consentitemi di approfondire un po 'l'archiviazione delle transazioni e le "transazioni di riepilogo" che ho menzionato sopra. Innanzitutto, l'archiviazione regolare sarà necessaria in un sistema ad alto carico come questo. Voglio mantenere la coerenza tra i saldi e la cronologia delle loro transazioni, consentendo nel contempo che le vecchie transazioni vengano spostate altrove. Per fare ciò sostituirò ogni lotto di transazioni archiviate con un riepilogo degli importi per utente e valuta.

Quindi, ad esempio, questo elenco di transazioni:

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

viene archiviato e sostituito con questo:

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

In questo modo, un saldo con le transazioni archiviate mantiene una cronologia delle transazioni completa e coerente.


1
Se scegli l'opzione 2 (che ritengo più pulita), dai un'occhiata a pgcon.org/2008/schedule/attachments/… come implementare in modo efficiente le "viste materializzate". Per l'opzione 1, il capitolo 11 di Haan's e Koppelaars ' Applied Mathematics for Database Professionals (non preoccuparti del titolo) sarebbe utile per avere un'idea di come implementare i "vincoli di transizione" in modo efficiente. Il primo collegamento è per PostgreSQL e il secondo per Oracle, ma le tecniche dovrebbero funzionare per qualsiasi ragionevole sistema di database.
jp,

In teoria, vuoi fare # 3. Il modo corretto di fare un "saldo corrente" è assegnare un saldo a ciascuna transazione. Assicurati di poter ordinare le transazioni in modo definitivo con un ID seriale (preferito) o un timestamp. Non dovresti davvero "calcolare" un saldo corrente.
pbreitenbach,

Risposte:


15

Non ho familiarità con la contabilità, ma ho risolto alcuni problemi simili in ambienti di tipo inventario. Conservo i totali correnti nella stessa riga della transazione. Sto usando i vincoli, in modo che i miei dati non siano mai sbagliati anche in caso di concorrenza elevata. Nel 2009 ho scritto la seguente soluzione :

Il calcolo dei totali correnti è notoriamente lento, sia che lo si faccia con un cursore che con un join triangolare. È molto allettante denormalizzare, archiviare i totali in esecuzione in una colonna, specialmente se lo selezioni frequentemente. Tuttavia, come al solito quando denormalizzi, devi garantire l'integrità dei tuoi dati denormalizzati. Fortunatamente, è possibile garantire l'integrità dei totali in esecuzione con vincoli - purché tutti i vincoli siano attendibili, tutti i totali in esecuzione siano corretti. Inoltre, in questo modo puoi facilmente assicurarti che il saldo corrente (totali correnti) non sia mai negativo - l'applicazione con altri metodi può anche essere molto lenta. Il seguente script dimostra la tecnica.

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

Non consentire ai clienti di avere un saldo inferiore a 0 è una regola aziendale (che cambierebbe rapidamente in quanto le commissioni per cose come un over draft sono il modo in cui le banche fanno la maggior parte dei loro soldi). Ti consigliamo di gestirlo nell'elaborazione dell'applicazione quando le righe vengono inserite nella cronologia delle transazioni. Soprattutto perché si potrebbe finire con alcuni clienti che hanno una protezione da scoperto e alcuni che ricevono commissioni e altri che non consentono di immettere importi negativi.

Finora mi piace dove stai andando con questo, ma se questo è per un progetto reale (non a scuola) ci deve essere un sacco di pensieri messi nelle regole aziendali, ecc. Una volta che hai un sistema bancario attivo e in esecuzione non c'è molto spazio per la riprogettazione in quanto vi sono leggi molto specifiche sulle persone che hanno accesso al proprio denaro.


1
Posso capire perché il vincolo dei saldi dovrebbe effettivamente essere una regola aziendale. Il database fornisce solo un servizio di transazione e spetta all'utente decidere cosa farne.
Nick Chammas,

Cosa ne pensi dei commenti di Jack secondo cui l'uso delle due tabelle offre agli sviluppatori una maggiore flessibilità nel cambiare o implementare la logica aziendale? Inoltre, hai qualche esperienza diretta con le viste indicizzate che convalida o sfida queste preoccupazioni ?
Nick Chammas,

1
Non direi che avere due tabelle per darti flessibilità di spostamento stia implementando la logica di business. Ti dà più flessibilità nel fare l'archiviazione dei dati. Tuttavia, come banca (almeno negli Stati Uniti) hai delle leggi che stabiliscono quanti dati devi conservare. Ti consigliamo di testare l'aspetto delle prestazioni con la vista in alto, oltre a tenere presente che se hai una vista indicizzata non puoi modificare lo schema delle tabelle sottostanti. Solo un'altra cosa a cui pensare.
mrdenny,

Tutti gli elementi menzionati nell'articolo sono validi dubbi su quando usare una vista indicizzata.
mrdenny,

1
Per chiarire: IMO un'API transazionale offre maggiore flessibilità nell'implementazione della logica di business (senza due tabelle). In questo caso sarei anche a favore di due tabelle (almeno date le informazioni che abbiamo finora) a causa dei compromessi proposti con l'approccio con vista indicizzata (ad es. Non posso quindi utilizzare DRI per far rispettare l'equilibrio> 0 business regola)
Jack Douglas,

13

Un approccio leggermente diverso (simile alla tua seconda opzione) da considerare è quello di avere solo la tabella delle transazioni, con una definizione di:

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

È inoltre possibile che si desideri un ID transazione / Ordine, in modo da poter gestire due transazioni con la stessa data e migliorare la query di recupero.

Per ottenere il saldo corrente, tutto ciò che serve è l'ultimo record.

Metodi per ottenere l'ultimo record :

/* 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

Contro:

  • Quando si inserisce una transazione fuori sequenza (ad es .: per correggere un problema / saldo iniziale errato), potrebbe essere necessario collegare in cascata gli aggiornamenti per tutte le transazioni successive.
  • Le transazioni per l'utente / valuta dovrebbero essere serializzate per mantenere un saldo accurato.

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

Professionisti:

  • Non è più necessario mantenere due tabelle separate ...
  • È possibile convalidare facilmente il saldo e quando il saldo non è più sincronizzato, è possibile identificare esattamente quando è uscito di colpo quando la cronologia delle transazioni diventa autocompattante.

Modifica: alcune query di esempio sul recupero del saldo corrente e sull'evidenziazione con (Grazie a Jack Douglas)


3
Il SELECT TOP (1) ... ORDER BY TransactionDate DESCsarà molto difficile da implementare in modo che SQL Server non analizza costantemente la tabella delle transazioni. Alex Kuznetsov ha pubblicato qui una soluzione a un problema di progettazione simile che integra perfettamente questa risposta.
Nick Chammas,

2
+1 Sto usando un approccio simile. A proposito, dobbiamo stare molto attenti e assicurarci che il nostro codice funzioni correttamente sotto carico di lavoro simultaneo.
AK,

12

Dopo aver letto queste due discussioni, ho deciso sull'opzione 2

Dopo aver letto anche quelle discussioni, non sono sicuro del motivo per cui hai deciso la soluzione DRI sulla più sensata delle altre opzioni che descrivi:

Applicare le transazioni alle tabelle delle transazioni e dei saldi. Utilizzare la logica TRANSAZIONE nel mio livello di stored procedure per assicurarsi che i saldi e le transazioni siano sempre sincronizzati.

Questo tipo di soluzione ha immensi benefici pratici se avete il lusso di limitare tutto l'accesso ai dati attraverso il vostro API transazionale. Si perde l'importantissimo vantaggio di DRI, ovvero che l'integrità è garantita dal database, ma in qualsiasi modello di complessità sufficiente ci saranno alcune regole aziendali che non possono essere applicate dal DRI .

Consiglierei di utilizzare DRI ove possibile per far rispettare le regole aziendali senza piegare troppo il modello per renderlo possibile:

Anche se sto archiviando transazioni (ad es. Spostandole altrove e sostituendole con transazioni riepilogative)

Non appena inizi a considerare di inquinare il tuo modello in questo modo, penso che ti stai spostando nell'area in cui il beneficio di DRI è superato dalle difficoltà che stai introducendo. Si consideri, ad esempio, che in teoria un errore nel processo di archiviazione potrebbe causare la rottura della regola d'oro (che equilibra sempre la somma delle transazioni) con una soluzione DRI .

Ecco un riepilogo dei vantaggi dell'approccio transazionale come li vedo:

  • Dovremmo farlo comunque, se possibile. Qualunque sia la soluzione scelta per questo particolare problema, offre maggiore flessibilità di progettazione e controllo sui dati. Tutti gli accessi diventano quindi "transazionali" in termini di logica aziendale, piuttosto che in termini di logica del database.
  • Puoi mantenere pulito il tuo modello
  • Puoi "imporre" una gamma molto più ampia e una complessità delle regole aziendali (osservando che il concetto di "imporre" è più libero rispetto al DRI)
  • Puoi comunque usare il DRI ovunque sia pratico per dare al modello un'integrità sottostante più solida - e questo può agire come un controllo sulla tua logica transazionale
  • La maggior parte dei problemi di prestazioni che ti preoccupano si dissolveranno
  • Introdurre nuovi requisiti può essere molto più semplice - ad esempio: regole complesse per le transazioni controverse potrebbero costringerti ad allontanarti da un approccio DRI puro più in basso, il che significa un grande sforzo sprecato
  • Il partizionamento o l'archiviazione di dati storici diventa molto meno rischioso e doloroso

--modificare

Per consentire l'archiviazione senza aggiungere complessità o rischio, è possibile scegliere di mantenere le righe di riepilogo in una tabella di riepilogo separata, generata in modo continuo (prendendo in prestito da @Andrew e @Garik)

Ad esempio, se i riepiloghi sono mensili:

  • ogni volta che viene eseguita una transazione (tramite l'API), viene visualizzato un aggiornamento corrispondente o inserito nella tabella di riepilogo
  • la tabella di riepilogo non viene mai archiviata, ma le transazioni di archiviazione diventano semplici come eliminare (o eliminare la partizione?)
  • ogni riga della tabella di riepilogo include "saldo di apertura" e "importo"
  • controllare i vincoli come "saldo di apertura" + "importo"> 0 e "saldo di apertura"> 0 possono essere applicati alla tabella di riepilogo
  • le righe di riepilogo potrebbero essere inserite in un batch mensile per rendere più semplice il blocco dell'ultima riga di riepilogo (sarebbe sempre presente una riga per il mese corrente)

Per quanto riguarda la modifica: proponete di avere questa tabella di riepilogo a fianco della tabella dei saldi principali? La tabella dei saldi diventa effettivamente una tabella di riepilogo che contiene solo i record per il mese corrente (poiché entrambi memorizzeranno lo stesso tipo di dati)? Se ho capito bene, perché non sostituire semplicemente la tabella dei saldi con la partizione appropriata nella tabella di riepilogo?
Nick Chammas,

Mi dispiace che tu abbia ragione, non è chiaro - intendevo distribuire con la tabella dei saldi in quanto sarebbe sempre una ricerca chiave nella tabella di riepilogo per ottenere il saldo corrente (non vero con il suggerimento di Andrews AFAIK). Il vantaggio è che il calcolo dei saldi in periodi precedenti diventa più semplice e c'è una pista di controllo più chiara per i saldi se vanno male.
Jack Douglas,

6

Nick.

L'idea principale è l'archiviazione dei record di saldo e transazione nella stessa tabella. È successo storicamente, ho pensato. Quindi, in questo caso, possiamo ottenere l'equilibrio semplicemente individuando l'ultimo record di riepilogo.

 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 

Una variante migliore è la riduzione del numero di record di riepilogo. Possiamo avere un record di bilancio alla fine (e / o inizio) della giornata. Come sapete, ogni banca deve operational dayaprirla e chiuderla per fare alcune operazioni di riepilogo per questo giorno. Ci consente di calcolare facilmente gli interessi utilizzando il record di saldo giornaliero, ad esempio:

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)

Fortuna.


4

In base alle tue esigenze, l'opzione 1 sembrerebbe la migliore. Anche se avrei il mio design per consentire solo inserimenti nella tabella delle transazioni. E avere il trigger sulla tabella delle transazioni, per aggiornare la tabella dei bilanci in tempo reale. È possibile utilizzare le autorizzazioni del database per controllare l'accesso a queste tabelle.

In questo approccio, il saldo in tempo reale è garantito per essere sincronizzato con la tabella delle transazioni. E non importa se vengono utilizzate procedure memorizzate o psql o jdbc. Puoi fare il controllo del tuo saldo negativo, se necessario. Le prestazioni non saranno un problema. Per ottenere il saldo in tempo reale, è una query singleton.

L'archiviazione non influirà su questo approccio. Puoi avere una tabella di riepilogo settimanale, mensile, annuale anche se necessario per cose come i rapporti.


3

In Oracle è possibile farlo utilizzando solo la tabella delle transazioni con una vista materializzata rapidamente aggiornabile su di essa che esegue l'aggregazione per formare il saldo. È possibile definire il trigger nella vista materializzata. Se la vista materializzata è definita con 'ON COMMIT', impedisce efficacemente di aggiungere / modificare i dati nelle tabelle di base. Il trigger rileva i dati [in] validi e genera un'eccezione, dove esegue il rollback della transazione. Un bell'esempio è qui http://www.sqlsnippets.com/en/topic-12896.html

Non conosco sqlserver ma forse ha un'opzione simile?


2
Le viste materializzate in Oracle sono simili alla "vista indicizzata" di SQL Server, ma si aggiornano automaticamente anziché in modo esplicitamente gestito come il comportamento "ON COMMIT" di Oracle. Vedi social.msdn.microsoft.com/Forums/fi-FI/transactsql/thread/… e techembassy.blogspot.com/2007/01/…
GregW
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.