2009-04-09 6 views
0

J'ai besoin de conseils pour résoudre une requête. Je peux gérer cela dans une application frontale, cependant, en raison de la conception, je dois mettre cela en place dans le back-end. J'ai les suivantesCONSEIL sur la requête de facturation dans SQL Server 2000

 

CREATE TABLE [dbo].[openitems](
    [id] [varchar](8) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, 
    [type] [char](5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, 
    [date] [smalldatetime] NULL, 
    [amount] [decimal](9, 2) NULL, 
    [daysOpen] [smallint] NULL, 
    [balance] [decimal](9, 2) NULL 
) ON [PRIMARY] 




insert into openitems values('A12399','INV','2008-12-05',491.96,123) 

insert into openitems values('A12399','INV','2008-12-12',4911.37,116) 

insert into openitems values('A12399','INV','2008-12-05',3457.69,109) 

Le tableau ci-dessus ont toutes les factures ouvertes pour un client. Je dois appliquer un paiement à ces factures à partir de la facture la plus ancienne (colonne daysOpen dans le tableau). Donc, si j'ai un paiement de 550,00 $, je vais d'abord l'appliquer à la facture avec 123 jours, soit 491,96 $ - 500 $ (ce qui laisse 8,04 $ à appliquer à la prochaine facture ... et ainsi de suite), puis mettre à jour colonne dans le tableau) à 0.00 et passer à la suivante et appliquer le reste. Ce serait 4911,37 $ - 8,04 $, ce qui laisserait 4903,33 $. Comme il n'y a plus de solde à appliquer, la boucle se termine.

La colonne équilibre doit maintenant

 
0 
4903.33 
3457.69 

Note: Je dois faire cela pour tous les clients dans un tableau (environ 10 000). Un client a en moyenne une vingtaine de factures ouvertes.

Merci

Répondre

2

Considérez ce qui suit:

un paiement soit applique intégralement à un équilibre, applique en partie à un équilibre ou surpaye un équilibre. Maintenant, imaginons que nous puissions trouver, pour tout solde, le solde cumulé des factures à ce jour. Plutôt que d'imaginer que, nous allons le faire:

create view cumulative_balance as 
select a.*, 
    (select sum(balance) 
    from openitems b 
    where b.id = a.id and b.type = a.type and a.daysOpen >= a.daysOpen) 
    as cumulative_balance 
from openitems a; 

Maintenant, nous pouvons trouver le premier solde cumulé inférieur ou égal au paiement, pour tout id et le type, et le magasin qui, et daysOpen et solde cumulatif serveur variables

Ensuite, nous mettons à jour tous les éléments openItems avec cet ID et ce type, où daysOpen < = la valeur obtenue, en mettant tous ces soldes à zéro.

Ensuite, nous trouvons le premier solde non-nul de cet ID et type, et définissons son solde à son solde - (paiement - le solde cumulé nous avons stocké). S'il y a un trop-payé, cet équilibre sera correctement négatif.

Avec la requête correcte, vous serez en mesure d'effectuer la recherche et la première mise à jour en une seule instruction.

Il y a deux problèmes. L'un est que vous ne pouvez pas déterminer, de deux ou plusieurs allées avec le même id et type et daysOpen, qui devrait être payé en premier. Ajouter un unique id à votre table servirait de tie-breaker pour ces cas.

Deuxièmement est le besoin de sauvegarder la balance cumulative pour l'utiliser dans la requête pour la deuxième mise à jour. Si vous avez conçu votre table correctement, avec une colonne pour invoice_amount qui n'a pas été mise à jour par les paiements, et une colonne de paiement qui était, cela résoudrait votre problème. Un refactoring encore meilleur serait d'avoir deux tables, une pour les factures et une pour le paiement: alors une vue pourrait faire tout le travail, en comparant les soldes cumulatifs aux paiements cumulatifs, produisant une liste des soldes impayés ou des trop-payés.

En fait, j'ai conçu un tel système pour une grande société de garantie hypothécaire avec les initiales FM. C'était un peu plus compliqué que ce que vous avez, car les soldes étaient calculés à partir d'un certain nombre de formules de montants et de pourcentages, et les payeurs multiples (les assureurs, en fait, pour les hypothèques en défaut) devaient être facturés dans un ordre prescrit selon d'autres règles, par hypothèque defauted. Tout cela a été fait dans les vues, avec une procédure courte (100 lignes ou plus) qui a essentiellement fait ce que j'ai décrit ci-dessus: utilisé une vue qui a ordonné la facturation des factures par ces règles, les paiements appliqués (en la vue), en calculant quels paiements supplémentaires à facturer à quelle date à quel assureur. La procédure stockée générait alors simplement des factures pour la date actuelle (la date actuelle pouvait être définie, à nouveau à l'aide d'une vue, à n'importe quelle date à des fins de test). L'ironie est que j'avais pris l'engagement d'écrire C++, le seul C++ que j'ai écrit utilisait les API Oracle et Sybase C pour transférer des données du système Oracle vers le système Sybase.

+0

Parfois, cela me rend fou d'essayer d'implémenter Business Logic dans le magasin de données. –

+0

SQL est sur les ensembles; Si vous pensez que les termes des ensembles, cela devient évident. Tout comme, disons, Haskell semble bizarre et alambiqué aux programmeurs avec un arrière-plan uniquement dans les langages impératifs, mais plus propre et plus doux pour les programmeurs Haskell. – tpdi

-2

Vous devez utiliser deux curseurs et deux boucles imbriquées. (Cela peut être un peu lent)

Un pour lire tous les paiements - Je suppose que la clientèle, Montant

Ensuite, pour chaque client de créer un autre curseur pour les éléments ouverts.

La première boucle lire les paiements jusqu'à ce que fait

Dans cette boucle ouvrir un nouveau curseur pour les éléments ouverts du client triées par la plus ancienne.

boucle à travers chaque élément ouvert et appliquer les paiements que vous avez décrit

ensuite obtenir le paiement suivant.

Répétez jusqu'à ce que plus de paiements.

+0

Je ne suggère pas à l'aide des curseurs pour des raisons de performace ... si vous trouvez votre auto exécuter la logique métier dans SQL il devrait aller dans votre couche métier de votre application ... –

+0

à moins que cela est une chose de temps. .. –

+0

Je sais - je ne le recommande pas non plus - mais l'OP veut le faire dans le DB –

0

moins que ce soit un effort d'une fois ...

On dirait que cela est logique buisness et appartient dans votre couche métier de votre application.

+0

Je sais. D'une certaine façon, je dois exécuter ce processus tous les soirs, ce qui ne fait pas partie de l'application. –

+0

Ahhhh, la couche business mythique! –

1

Cela devrait le faire. J'ai déclaré une variable locale, mais vous pouvez en faire un paramètre à partir d'une procédure stockée. J'ai également ajouté un ID facture sur la table pour identifier les factures de manière unique, car l'identifiant et la date ne semblent pas uniques.

DECLARE 
    @paid_amount DECIMAL(9, 2) 

SET @paid_amount = 500 

UPDATE 
    OI 
SET 
    balance = 
      CASE 
       WHEN @paid_amount - SQ.running_total > balance THEN 0 
       ELSE balance - (@paid_amount - SQ.running_total) 
      END 
FROM 
    dbo.OpenItems OI 
INNER JOIN (
    SELECT 
     I1.id, 
     I1.invoice_id, 
     I1.date, 
     ISNULL(SUM(I2.amount), 0) AS running_total 
    FROM 
     OpenItems I1 
    LEFT OUTER JOIN OpenItems I2 ON 
     I2.id = I1.id AND 
     I2.type = 'INV' AND 
     I2.daysopen > I1.daysopen 
    GROUP BY 
     I1.id, 
     I1.invoice_id, 
     I1.date 
) AS SQ ON 
    SQ.id = OI.id AND 
    SQ.invoice_id = OI.invoice_id 
WHERE 
    @paid_amount > SQ.running_total 
+0

Cela n'a pas fonctionné. Le solde @paid_amount - SQ.running_total> ne peut pas fonctionner. Le calcul doit avoir lieu et la différence doit ensuite passer à la ligne suivante. Je crois qu'un CURSEUR pourrait être la meilleure solution. J'essayais d'éviter ça. –

+0

L'avez-vous testé?Je l'ai juste couru encore sur une base de données locale et il a donné les résultats exacts que vous cherchiez. Êtes-vous sûr de comprendre comment cela fonctionne? Ma conjecture n'est pas, puisque vous avez dit que cela "ne peut pas fonctionner". Sinon, je peux l'expliquer. Vous devez penser en ensembles lorsque vous travaillez avec SQL, pas de code de procédure. –

Questions connexes