0

我需要一些建议来处理查询。我可以在前端应用程序中处理这个,但是,由于设计,我必须在后端实现它。我有以下


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)

上表包含客户的所有未结发票。我需要从最早的发票(表中的 daysOpen 列)开始对这些发票进行付款。因此,如果我有 550.00 美元的付款,我将首先将其应用于 123 天的发票,即 491.96 美元 - 500 美元(剩下 8.04 美元用于下一张发票......等等),然后更新该记录(余额表中的列)到 0.00 并移动到下一个并应用剩余的。那将是 4911.37 美元 - 8.04 美元,剩下 4903.33 美元。由于没有余额可供应用,因此循环退出。

余额栏现在应该是

0
4903.33
3457.69

注意:我需要为表中的所有客户(大约 10,000 个)执行此操作。一个客户平均有大约 20 张未结发票。

谢谢

4

4 回答 4

2

考虑以下:

付款要么全部适用于余额,要么部分适用于余额,要么多付余额。

现在,想象一下,对于任何余额,我们都可以找到迄今为止发票的累计余额。与其想象,不如让我们这样做:

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;

现在我们可以找到小于或等于付款的第一个累积余额,对于任何 id 和类型,并将其、daysOpen 和累积余额存储在服务器变量中。

然后我们使用该 id 和类型更新所有 openItems,其中 daysOpen <= 我们得到的值,将所有这些余额设置为零。

然后我们找到该 id 和类型的第一个非零余额,并将其余额设置为余额 - (付款 - 我们存储的累积余额)。如果多付,则此余额将正确地为负数。

使用正确的查询,您将能够在一个语句中进行查找和第一次更新。

有两个问题。一个是你无法确定,两个或多个具有相同 id 和 type 以及 daysOpen 的alances,应该先付款。为您的表添加一个唯一的id 将作为这些情况的决胜局。

其次是需要保存累积余额以在查询中使用它进行第二次更新。如果您正确设计了表格,其中有一个未由付款更新的 invoice_amount 列,以及一个付款列,这将解决您的问题。

更好的重构是有两张表,一张用于发票,一张用于付款:然后视图可以完成所有工作,通过比较累积余额和累积付款,生成未付余额或多付的列表。

事实上,我正是为一家首字母为 FM 的大型抵押担保公司设计了这样一个系统。它比您所拥有的要复杂一些,因为余额是根据许多金额和百分比的公式计算得出的,并且多个付款人(实际上是保险公司,这是针对已违约的抵押贷款)必须开具发票根据其他规则规定的顺序,按违约抵押。

所有这些都是在视图中完成的,一个简短的(100 行左右)存储过程基本上完成了我上面概述的操作:使用按这些规则订购发票开票的视图,应用付款(在视图中),计算在什么日期向哪个保险公司开具发票的额外付款。然后,存储过程只生成当前日期的发票(可以再次使用视图将当前日期设置为任何日期以用于测试目的)。

(具有讽刺意味的是,我接受了这份工作,并承诺我会编写 C++;我编写的唯一 C++ 使用 Oracle 和 Sybase C API 将数据从 Oracle 系统传输到 Sybase 系统。)

于 2009-04-09T20:39:33.327 回答
1

这应该这样做。我已经声明了一个局部变量,但您可以将它作为存储过程的参数。我还在表中添加了 invoice_id 以唯一标识发票,因为 id 和 date 似乎不是唯一的。

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
于 2009-04-09T22:21:20.540 回答
0

除非这是一次性的努力...

听起来这是业务逻辑,属于应用程序的业务层。

于 2009-04-09T20:19:07.837 回答
-2

您将不得不使用几个游标和两个嵌套循环。(这可能有点慢)

一个阅读所有付款 - 我假设客户,金额

然后为每个客户创建另一个未清项目的光标。

第一个循环将读取付款直到完成

在该循环中,为客户的未清项目打开一个新光标,按最旧的顺序排序。

遍历每个未清项目并按照您的描述应用付款

然后获得下一笔付款。

重复直到不再付款。

于 2009-04-09T19:31:45.893 回答