我有一个名为Transaction的表,其中包含一些列:[TransactionID,Type(credit或debit),Amount,Cashout,CreditPaid,EndTime]
客户可以获得大量信用,这些交易存储在交易表中。如果客户在月末支付了部分或全部信用交易的金额,我希望更新这些交易。如果总付款涵盖某些交易,则应更新交易。
例如,客户支付300.如果交易'金额'是300而'类型'是信用,那么'CreditPaid'金额应该是300.(这是一个简单的更新声明)但是......
如果有两个交易,即一个300和另一个400,并且都是信用,每月支付金额是600,那么最早的交易应该全额支付300,而下一个交易300剩下100个未支付。
任何想法如何做到这一点?
TrID Buyin Type Cashout CustID StartTime EndTime AddedBy CreditPaid
72 200 Credit 0 132 2013-05-21 NULL NULL NULL
73 300 Credit 0 132 2013-05-22 NULL NULL NULL
75 400 Credit 0 132 2013-05-23 NULL NULL NULL
客户支付600后所需的结果
TrID Buyin Type Cashout CustID StartTime EndTime AddedBy CreditPaid
72 200 Credit 0 132 2013-05-21 2013-05-24 NULL 200
73 300 Credit 0 132 2013-05-22 2013-05-24 NULL 300
75 400 Credit 0 132 2013-05-23 NULL NULL 100
答案 0 :(得分:3)
这是SQL 2008版本:
CREATE PROCEDURE dbo.PaymentApply
@CustID int,
@Amount decimal(11, 2),
@AsOfDate datetime
AS
WITH Totals AS (
SELECT
T.*,
RunningTotal =
Coalesce (
(SELECT Sum(S.Buyin - Coalesce(S.CreditPaid, 0))
FROM dbo.Trans S
WHERE
T.CustID = S.CustID
AND S.Type = 'Credit'
AND S.Buyin < Coalesce(S.CreditPaid, 0)
AND (
T.Starttime > S.Starttime
OR (
T.Starttime = S.Starttime
AND T.TrID > S.TrID
)
)
),
0)
FROM
dbo.Trans T
WHERE
CustID = @CustID
AND T.Type = 'Credit'
AND T.Buyin < Coalesce(T.CreditPaid, 0)
)
UPDATE T
SET
T.EndTime = P.EndTime,
T.CreditPaid = Coalesce(T.CreditPaid, 0) + P.CreditPaid
FROM
Totals T
CROSS APPLY (
SELECT TOP 1
V.*
FROM
(VALUES
(T.Buyin - Coalesce(T.CreditPaid, 0), @AsOfDate),
(@Amount - RunningTotal, NULL)
) V (CreditPaid, EndTime)
ORDER BY
V.CreditPaid,
V.EndTime DESC
) P
WHERE
T.RunningTotal <= @Amount
AND @Amount > 0;
;
或者,对于使用SQL 2012的任何人,您可以使用新的窗口函数将CTE的内容替换为性能更好且更简单的查询:
SELECT
*,
RunningTotal =
Sum(Buyin - Coalesce(CreditPaid, 0)) OVER(
ORDER BY StartTime
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) - Buyin
FROM dbo.Trans
WHERE
CustID = @CustID
AND Type = 'Credit'
AND Buyin - Coalesce(CreditPaid, 0) > 0
以下是它们的工作原理:
CreditPaid
金额小于Buyin
金额的所有先前行的运行总计。请注意,这不包括当前行。T.RunningTotal <= @Amount
。这是因为此时所有先前的行都将完全消耗付款,因此我们可以停止应用它。Buyin - CreditPaid
金额或2)全额@Amount - RunningTotalOfPriorRows
左边的较小者来实现此目的。我本可以将此作为CASE
表达式完成,但我喜欢使用Min
函数,特别是因为我们必须执行两个CASE
表达式来确定是否还要更新EndTime
{1}}列(根据您的要求)。Buyin - CreditPaid
之和,使用窗口函数而不是相关子查询。RunningTotal
小于要应用的金额的所有行执行更新(因为如果它等于金额,则当前行不会有任何付款)。 / LI>
醇>
现在,你应该考虑一些更大的考虑因素。
我喜欢你的一些计划 - 我不相信,正如一些评论者所说,你应该忽略个别交易。我认为处理单个交易可能非常重要。这很像医院如何为每位患者(MRN)提供一个医疗记录编号,但是打开一个新的帐户 / 文件 / 访问每次患者进行服务。每个帐户都是单独处理的,这有很多原因,包括 - 这对您来说似乎也很重要 - 客户需要了解究竟什么是总数。看到总数加起来可能会令人震惊,但当这个问题在个别日期被分解为个别交易时,这对人们来说更有意义,他们可以开始明白他们如何花费更多的钱而不是他们在时间。 &#34;你欠我600美元&#34;可能比#34更难以面对;您的100美元,300美元和200美元的交易仍未支付&#34;。 :)
所以,这里有一些重要的考虑因素。
如果你认为交易或基于余额的账户从0开始作为一种&#34;锚点#34;并且要找到当前余额,你只需要将所有交易加起来:好吧这确实满足了关系理论,但在实践中它完全不可行,因为它没有提供快速,准确的方法来获得当前的平衡。必须将当前余额保存为离散值。如果你是一家银行,你怎么知道你有多少钱,而不是每次累计数十年的交易历史?相反,最好将当前余额视为&#34;锚定&#34; (而不是0)并将交易视为及时向后。此外,记录定期余额没有坏处。银行通过将期间结束到报表中来实现此目的,并在每个报表截止日期之前定义余额。没有必要一直回到零,因为你不会过分关注历史上旧的,未经修复的末端的平衡。你知道,最终每个帐户都是从0开始的。大不了!
鉴于这些想法, 对于您来说非常重要,因为您可以使用表格来简单说明客户的总帐户余额。您还需要一个地方来记录他的付款,退款,取消等。这应该与账户(在您的情况下,交易)本身分开,因为支付交易和信用交易之间没有一对一的对应关系。在您当前的计划中,您已经部分支付了没有记录日期的交易 - 这是系统中的一个巨大差距,它将会回来咬你。如果客户每天支付10美元到20美元的200美元信用额怎么办?其中19笔付款将不显示支付日期。
然后,我建议您创建一个存储过程(SP),首先将付款应用于总计,然后创建另一个将重写&#34;以按需方式支付交易。想一想信用卡公司如果要重新评估&#34;你的帐户。也许他们采取了不正确的信息,并在某个特定日期提高了您的利率。 (这实际上发生在我身上。我向他们证明他们所回应的收藏活动不是我的错 - 在我向他们展示他们的一名工作人员错误地改变了我的邮寄地址后,它被原公司收回了。我从未收到过能够支付的账单。所以他们必须能够追溯重新运行我账户上的所有购买/借记/利率计算,根据正确的利率重新计算原始更改日期之后的所有内容。 。)考虑一下,只要你正确地设计你的系统,你就会发现很有可能以这种方式运行。您的SP会获得一个日期范围或一组允许其工作的交易,然后重写&#34;历史好像旧历史从未存在过。
但是,你实际上并不想破坏历史,所以在某个时间点,你对客户账户的最佳了解会让事情变得更加复杂一段时间内的余额与您当前对该时段的帐户余额的最佳了解不同 - 两者都是真实数据,需要保留。
让我们假设您发现您的系统偶尔错误地将信用交易加倍。当您修复客户数据时,您需要能够看到他们有问题的事实,即使他们现在没有。这是通过使用其他日期列EffectiveDate
和ExpirationDate
- 或任何您想要调用它们来完成的。然后,这些需要成为聚簇索引的一部分,并在每个查询上使用以始终获取当前值。我强烈建议您使用9999-12-31
而不是NULL
作为当前行的ExpirationDate
值 - 这会在查询当前数据时对性能产生巨大的积极影响。我还建议将ExpirationDate
作为聚集索引中的第一列(或者至少在EffectiveDate
列之前),因为历史记录总是可能有比未来更多的记录,所以它将是比EffectiveDate
首先更具选择性(想一想:所有过去的知识都有EffectiveDate =< GetDate()
,但只有当前或未来的数据会有ExpirationDate > GetDate()
)。要点回家:你不能删除。您可以通过将列设置为知识过时的日期来过期旧行,并插入表示新知识的新行,其中列显示您学习此信息的日期并且无限期地打开未来&#34; 34;另一个日期列中的值。
最后有几点:
CreditPaid
列应为NOT NULL
,默认值为0
。我不得不扔掉一堆Coalesce
来处理NULL
s。此时我不能继续下去,但我希望这些想法对你有所帮助。您的设计是一个良好的开端,但它需要一些工作才能使其成为一个企业级质量系统。
<强>更新强>
我在2008版本中纠正了一个小故障(将外部查询中的条件添加到子查询中)。
这是我的最后一次编辑(所有:请不要再次编辑此答案,否则它将转换为社区维基)。
如果你选择了一个方案,其中行标记为日期,它们被理解为真(EffectiveDate
和ExpirationDate
),你可以通过创建内联使系统中的编码更容易一些表函数,仅选择表WHERE EffectiveDate <= GetDate() AND GetDate() < ExpirationDate
中的活动行。请特别注意您使用的比较运算符(例如<=
vs <
),并在开头使用包含的日期范围独家最后。如果您不确定这意味着什么,请在继续之前查看这些条款并理解它们。您希望以后能够更改日期数据类型的分辨率,而不会破坏任何查询。如果您使用包含结束日期,则无法执行此操作。网上有很多帖子谈论如何在SQL中正确查询日期。
这样的事情:
CREATE FUNCTION dbo.TransCurrent
RETURNS TABLE
AS
RETURN (
SELECT *
FROM dbo.Trans
WHERE
EffectiveDate <= GetDate()
AND GetDate() < ExpirationDate --make clustered index have this first!
);
不要将它与多语句表值返回函数混淆。这样做不会很好。这里的函数类型将表现良好,因为它可以内联到查询中,其中引擎基本上采用函数正在执行的操作的逻辑意图,并完全配置函数调用。使用任何其他类型的功能都会打败这个,当你的桌子大小增加时,你的表现会进入底池。