更新记录和设置值

时间:2013-05-24 21:54:34

标签: sql sql-server

我有一个名为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

1 个答案:

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

See a Live Demo at SQL Fiddle

或者,对于使用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

See a Live Demo at SQL Fiddle

以下是它们的工作原理:

  1. 我们计算CreditPaid金额小于Buyin金额的所有先前行的运行总计。请注意,这不包括当前行。
  2. 由此我们可以确定付款的哪一部分将适用于每一行以及哪些行将涉及付款。如果所有先前行的所有信用额的总和高于付款,则不会包含此行,因此T.RunningTotal <= @Amount。这是因为此时所有先前的行都将完全消耗付款,因此我们可以停止应用它。
  3. 对于我们要申请付款的每一行,我们都希望尽可能多地支付,但我们必须注意我们可能不会支付全额的最后一行(如第三个信用证的情况)在示例中)。因此,我们将支付以下两种金额中的一种:完整的信用额度(接收付款的行数更多)或仅剩下的部分可能小于该行的全部信用额度(这是最后一行) 。我们通过获取全部剩余Buyin - CreditPaid金额或2)全额@Amount - RunningTotalOfPriorRows左边的较小者来实现此目的。我本可以将此作为CASE表达式完成,但我喜欢使用Min函数,特别是因为我们必须执行两个CASE表达式来确定是否还要更新EndTime {1}}列(根据您的要求)。
  4. SQL 2012版本只是计算与2008版本相同的东西:所有先前行的Buyin - CreditPaid之和,使用窗口函数而不是相关子查询。
  5. 最后,我们对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;历史好像旧历史从未存在过。

    但是,你实际上并不想破坏历史,所以在某个时间点,你对客户账户的最佳了解会让事情变得更加复杂一段时间内的余额与您当前对该时段的帐户余额的最佳了解不同 - 两者都是真实数据,需要保留。

    让我们假设您发现您的系统偶尔错误地将信用交易加倍。当您修复客户数据时,您需要能够看到他们问题的事实,即使他们现在没有。这是通过使用其他日期列EffectiveDateExpirationDate - 或任何您想要调用它们来完成的。然后,这些需要成为聚簇索引的一部分,并在每个查询上使用以始终获取当前值。我强烈建议您使用9999-12-31而不是NULL作为当前行的ExpirationDate值 - 这会在查询当前数据时对性能产生巨大的积极影响。我还建议将ExpirationDate作为聚集索引中的第一列(或者至少在EffectiveDate列之前),因为历史记录总是可能有比未来更多的记录,所以它将是比EffectiveDate首先更具选择性(想一想:所有过去的知识都有EffectiveDate =< GetDate(),但只有当前或未来的数据会有ExpirationDate > GetDate())。要点回家:你不能删除。您可以通过将列设置为知识过时的日期来过期旧行,并插入表示新知识的新行,其中列显示您学习此信息的日期并且无限期地打开未来&#34; 34;另一个日期列中的值。

    最后有几点:

    • CreditPaid列应为NOT NULL,默认值为0。我不得不扔掉一堆Coalesce来处理NULL s。
    • 你需要以某种方式处理多付款项。通过阻止它们,或者通过存储多付的部分值并在以后应用它。您可以将UPDATE语句的结果输​​出到表中,然后从中选择Sum并使SP返回任何未使用的支付值。有很多方法可以解决这个问题。如果你建立&#34;重新率&#34;按照我的建议SP,这不会是一个太大的问题,因为你可以在收到新的交易后重新运行它(然后立即(重新)应用所有开放期间的所有付款)。

    此时我不能继续下去,但我希望这些想法对你有所帮助。您的设计是一个良好的开端,但它需要一些工作才能使其成为一个企业级质量系统。

    <强>更新

    我在2008版本中纠正了一个小故障(将外部查询中的条件添加到子查询中)。

    这是我的最后一次编辑(所有:请不要再次编辑此答案,否则它将转换为社区维基)。

    如果你选择了一个方案,其中行标记为日期,它们被理解为真(EffectiveDateExpirationDate),你可以通过创建内联使系统中的编码更容易一些表函数,仅选择表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!
    );
    

    不要将它与多语句表值返回函数混淆。这样做不会很好。这里的函数类型将表现良好,因为它可以内联到查询中,其中引擎基本上采用函数正在执行的操作的逻辑意图,并完全配置函数调用。使用任何其他类型的功能都会打败这个,当你的桌子大小增加时,你的表现会进入底池。