计算电子钱包系统中的可用余额(不包括过期信用)

时间:2018-11-22 00:28:43

标签: php mysql sql

我在信用表中

  

((id,user_id,进程,金额,date_add,date_exp,date_redeemed,备注)

SELECT * FROM credits WHERE user_id = 2;
+----+---------+---------+--------+------------+------------+---------------+----------+
| id | user_id | process | amount |  date_add  |  date_exp  | date_redeemed |  remark  |
+----+---------+---------+--------+------------+------------+---------------+----------+
| 22 |       2 | Add     | 200.00 | 2018-01-01 | 2019-01-01 |               | Credit1  |
| 23 |       2 | Add     | 200.00 | 2018-03-31 | 2019-03-31 |               | Credit2  |
| 24 |       2 | Deduct  | 200.00 |            |            | 2018-04-28    | Redeemed |
| 25 |       2 | Add     | 200.00 | 2018-07-11 | 2018-10-11 |               | Campaign |
| 26 |       2 | Deduct  | 50.00  |            |            | 2018-08-30    | Redeemed |
| 27 |       2 | Add     | 200.00 | 2018-10-01 | 2019-09-30 |               | Credit3  |
| 28 |       2 | Deduct  | 198.55 |            |            | 2018-10-20    | Redeemed |
+----+---------+---------+--------+------------+------------+---------------+----------+

我写的以下查询将仅计算余额,但我不知道信用额是否已到期并在到期前使​​用。

SELECT 
    u.id,
    email,
    CONCAT(first_name, ' ', last_name) AS name,
    type,
    (CASE
        WHEN (SUM(amount) IS NULL) THEN 0.00
        ELSE CASE
            WHEN
                (SUM(CASE
                    WHEN process = 'Add' THEN amount
                END) - SUM(CASE
                    WHEN process = 'Deduct' THEN amount
                END)) IS NULL
            THEN
                SUM(CASE
                    WHEN process = 'Add' THEN amount
                END)
            ELSE SUM(CASE
                WHEN process = 'Add' THEN amount
            END) - SUM(CASE
                WHEN process = 'Deduct' THEN amount
            END)
        END
    END) AS balance
FROM
    users u
        LEFT JOIN
    credits c ON u.id = c.user_id
GROUP BY u.id;

还是我做错了方法?也许我应该在后端而不是在SQL中进行计算?

编辑1:

我想计算每个用户的电子钱包的余额,但是信用会过期,

如果它已过期且未兑换,则从余额中排除

在到期前使​​用ELSE IF,并兑换金额<到期金额   然后(余额-(到期金额-赎回金额))

在到期前使​​用ELSE IF和赎回金额>到期金额   然后,将使用可用余额扣除,因为到期金额不足以扣除已兑换的金额

编辑2:

上面的查询将输出351.45,我的预期输出是201.45。由于赎回金额低于到期金额,因此无法计算2018-08-30的赎回金额

编辑3:

用户表:

+----+------------+-----------+----------+----------------+----------+
| id | first_name | last_name |   type   |     email      | password |
+----+------------+-----------+----------+----------------+----------+
|  2 | Test       | Oyster    | Employee | test@gmail.com | NULL     |
+----+------------+-----------+----------+----------------+----------+

我的输出:

+----+----------------+-------------+----------+---------+
| id |     email      |    name     |   type   | balance |
+----+----------------+-------------+----------+---------+
|  2 | test@gmail.com | Test Oyster | Employee |  351.45 |
+----+----------------+-------------+----------+---------+

预期输出:

总(200 + 200 + 200)600

兑现金额448.55(200 + 50 + 198.55)

剩余余额为151.45

+----+----------------+-------------+----------+---------+
| id |     email      |    name     |   type   | balance |
+----+----------------+-------------+----------+---------+
|  2 | test@gmail.com | Test Oyster | Employee |  151.45 |
+----+----------------+-------------+----------+---------+

2 个答案:

答案 0 :(得分:5)

当前表存在一些基本的结构性问题。因此,我将对表结构和随后的应用程序代码提出一些更改。电子钱包系统的表格结构非常详细;但我建议在此进行最小程度的更改。 我并不是说这是理想的方法。但应该可以。最初,我将介绍当前方法的一些问题。

问题:

  • 如果有多个可用信用尚未到期怎么办?
  • 在这些可用积分中,有些可能实际上已经被使用,但是尚未过期。我们如何忽略它们的可用余额?
  • 此外,有些可能已被部分利用。我们如何计算部分利用率?
  • 在某些情况下,赎回金额跨越多个未过期的信用额度。有些可能会被部分利用。有些可能会得到充分利用。

常规做法:

我们通常采用 FIFO 先进先出)方法,以使客户获得最大利益。因此,较早使用的信用额度(没有使用权的情况下更有可能过期)。

为了遵循FIFO,我们必须每次都在查询/应用程序代码中有效使用循环技术,以计算基本内容,例如“可用的钱包余额”,“过期的和未充分利用的信用”等等。为此编写查询将很麻烦,并且在更大范围内可能效率低下

解决方案:

我们可以在当前表中再添加一列amount_redeemed。它基本上代表针对特定信用额的已兑现的金额

ALTER TABLE credits ADD COLUMN amount_redeemed DECIMAL (8,2);

因此,已填充的表格如下所示:

+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| id | user_id | process | amount | amount_redeemed |  date_add  |  date_exp     | date_redeemed |  remark  |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| 22 |       2 | Add     | 200.00 |      200.00     | 2018-01-01 | 2019-01-01    |               | Credit1  |
| 23 |       2 | Add     | 200.00 |      200.00     | 2018-03-31 | 2019-03-31    |               | Credit2  |
| 24 |       2 | Deduct  | 200.00 |                 |            |               | 2018-04-28    | Redeemed |
| 25 |       2 | Add     | 200.00 |      0.00       | 2018-07-11 | 2018-10-11    |               | Campaign |
| 26 |       2 | Deduct  | 50.00  |                 |            |               | 2018-08-30    | Redeemed |
| 27 |       2 | Add     | 200.00 |      48.55      | 2018-10-01 | 2019-09-30    |               | Credit3  |
| 28 |       2 | Deduct  | 198.55 |                 |            |               | 2018-10-20    | Redeemed |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+

请注意,使用FIFO方法,针对amount_redeemed的信用的id = 25 0.00 。它有机会在2018-10-20上进行兑换,但是到那时,它已经过期(date_exp = 2018-10-11

因此,现在完成此设置后,您就可以在应用程序代码中执行以下操作:

  1. 在表的现有行中填充amount_redeemed

这将是一次活动。为此,制定单个查询将很困难(这就是为什么我们首先出现在这里)。因此,我建议您使用循环和FIFO方法在应用程序代码(例如PHP)中执行一次。查看下面的第3点,以了解如何在应用程序代码中进行操作。

  1. 获取当前可用余额

现在查询变得无关紧要了,因为我们只需要为尚未到期的所有amount - amount_redeemed进程计算Add的总和即可。

SELECT SUM(amount - amount_redeemed) AS total_available_credit
FROM credits 
WHERE process = 'Add' AND 
      date_exp > CURDATE() AND 
      user_id = 2
  1. 在兑换时更新amount_redeemed

在这种情况下,您首先可以获取所有可用的点数,这些点数可以兑换,并且尚未过期。

SELECT id, (amount - amount_redeemed) AS available_credit 
FROM credits 
WHERE process = 'Add' AND 
      date_exp > CURDATE() AND 
      user_id = 2 AND 
      amount - amount_redeemed > 0
ORDER BY id

现在,我们可以遍历以上查询结果,并相应地利用金额

 // PHP code example

 // amount to redeem
 $amount_to_redeem = 100;

 // Map storing amount_redeemed against id
 $amount_redeemed_map = array();

 foreach ($rows as $row) {

     // Calculate the amount that can be used against a specific credit
     // It will be the minimum of available credit and amount left to redeem
     $amount_redeemed  = min($row['available_credit'], $amount_to_redeem);

     // Populate the map
     $amount_redeemed_map[$row['id']] = $amount_redeemed;

     // Adjust the amount_to_redeem
     $amount_to_redeem -= $amount_redeemed;

     // If no more amount_to_redeem, we can finish the loop
     if ($amount_to_redeem == 0) {
         break;
     } elseif ($amount_to_redeem < 0) {

        // This should never happen, still if it happens, throw error
        throw new Exception ("Something wrong with logic!");
        exit();
     }

     // if we are here, that means some more amount left to redeem
 }

现在,您可以使用两个更新查询。第一个将针对所有信用ID更新amount_redeemed值。第二个将使用所有单独的Insert值的总和来amount_redeemed扣除行。

答案 1 :(得分:3)

SELECT `id`, `email`, `NAME`, `type`,
    (
        ( SELECT SUM(amount) FROM credit_table AS ct1 WHERE u.id = ct1.id AND process = 'ADD' AND date_exp > CURDATE()) - 
        ( SELECT SUM(amount) FROM credit_table AS ct2 WHERE u.id = ct2.id AND process = 'Deduct' )
    ) AS balance
FROM
    `user_table` AS u
WHERE
    id = 2;

希望它如您所愿