3

背景

我的数据库我有一个充满项目的表。每个项目都可以有一系列付款。
一旦用户进行了支付,用户就可以通过兑换来使用这笔钱。钱可以部分赎回。

问题

对于每次赎回,我想计算剩余的钱。这等于做SUM(paid_amount) - SUM(previous_redemptions) - this_redemption

问题是由于 SQL 连接的性质(一行是连接的组合),如果有多次先前的兑换,每次付款将被计算多次。

我实际上不确定我想要什么可以仅使用 MySQL 来计算(并且仍然相当快)。

如果我能以某种方式使每个 SQL 组只包含一行,并且每列有多个值,那么问题就会得到解决,我认为 SQL 不支持这一点。

桌子

mysql> DESCRIBE items;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | NULL    |       |
+-------+---------+------+-----+---------+-------+

mysql> DESCRIBE payments;
+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id          | int(11) | NO   | PRI | NULL    |       |
| item_id     | int(11) | NO   |     | NULL    |       |
| paid_amount | int(11) | NO   |     | NULL    |       |
+-------------+---------+------+-----+---------+-------+

mysql> DESCRIBE redemptions;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| id          | int(11)  | NO   | PRI | NULL    |       |
| item_id     | int(11)  | NO   |     | NULL    |       |
| amount      | int(11)  | YES  |     | NULL    |       |
| create_time | datetime | YES  |     | NULL    |       |
+-------------+----------+------+-----+---------+-------+

数据

mysql> SELECT * FROM items;
+----+
| id |
+----+
|  1 |
+----+

mysql> SELECT * FROM payments;
+----+---------+-------------+
| id | item_id | paid_amount |
+----+---------+-------------+
|  1 |       1 |          50 |
|  2 |       1 |          50 |
+----+---------+-------------+

mysql> SELECT * FROM redemptions;
+----+---------+--------+---------------------+
| id | item_id | amount | create_time         |
+----+---------+--------+---------------------+
|  1 |       1 |     10 | 2013-01-01 00:00:00 |
|  2 |       1 |     10 | 2013-01-01 00:01:00 |
|  3 |       1 |     10 | 2013-01-01 00:02:00 |
+----+---------+--------+---------------------+

查询

mysql> SELECT
    ->     redemptions.id AS redemption_id,
    ->     SUM(payments.paid_amount) - COALESCE(SUM(previous_redemptions.amount), 0) - redemptions.amount AS remaining_balance
    -> FROM redemptions
    -> JOIN payments ON payments.item_id = redemptions.item_id
    -> LEFT JOIN redemptions AS previous_redemptions
    -> ON
    ->     previous_redemptions.item_id = redemptions.item_id AND
    ->     previous_redemptions.create_time < redemptions.create_time
    -> GROUP BY redemptions.id;
+---------------+-------------------+
| redemption_id | remaining_balance |
+---------------+-------------------+
|             1 |                90 |
|             2 |                70 |
|             3 |               150 |
+---------------+-------------------+

如您所见,这并没有真正按照我的意愿行事。我希望remaining_balancefor redemption 3 是 70。

桌子的大小

  • 项目 - 几百万行
  • 付款 - 几百万行
  • 赎回 - 数百万行

这意味着创建子查询首先计算每次赎回的所有已使用金额(先前赎回的总和)是不可能的。

适合那些想在家学习的人的 MySQL 命令

CREATE TABLE items (
    id int(11) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE payments (
    id int(11) NOT NULL,
    item_id int(11) NOT NULL,
    paid_amount int(11) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE redemptions (
    id int(11) NOT NULL,
    item_id int(11) NOT NULL,
    amount int(11),
    create_time datetime,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO items VALUES (1);

INSERT INTO payments VALUES
    (1, 1, 50),
    (2, 1, 50);

INSERT INTO redemptions VALUES
    (1, 1, 10, '2013-01-01 00:00:00'),
    (2, 1, 10, '2013-01-01 00:01:00'),
    (3, 1, 10, '2013-01-01 00:02:00');

SELECT 
    redemptions.id AS redemption_id,
    SUM(payments.paid_amount) - COALESCE(SUM(previous_redemptions.amount), 0) - redemptions.amount AS remaining_balance
FROM redemptions
JOIN payments ON payments.item_id = redemptions.item_id
LEFT JOIN redemptions AS previous_redemptions
ON
    previous_redemptions.item_id = redemptions.item_id AND 
    previous_redemptions.create_time < redemptions.create_time
GROUP BY redemptions.id;
4

3 回答 3

2

给你!

SELECT 
    redemptions.id AS redemption_id,
    (payments.paid_amount - SUM(previous_redemptions.amount)) AS remaining_balance
FROM redemptions
JOIN payments ON payments.id = redemptions.payment_id
LEFT JOIN redemptions AS previous_redemptions
ON
    previous_redemptions.payment_id = redemptions.payment_id AND 
    previous_redemptions.id <= redemptions.id
GROUP BY redemptions.id;

SQL小提琴

于 2013-03-30T02:12:02.443 回答
1

这使用用户定义的变量来保持运行总计:

  SELECT 
    r.id AS redemption_id,
    @totAmt:=SUM(p.paid_amount)-COALESCE(@sumAmt,r.amount) totAmt,
    @sumAmt:=COALESCE(@sumAmt,r.amount)+r.amount
  FROM redemptions r
    JOIN payments p ON p.id = r.payment_id
    JOIN (SELECT @sumAmt:=NULL, @totAmt:=0) s
  GROUP BY r.id
  ORDER BY r.create_time

SQL 小提琴演示

编辑:给定评论,您可以使用相关子查询:

  SELECT 
    r.id AS redemption_id,
    SUM(p.paid_amount)-
      (SELECT SUM(r2.amount)
       FROM redemptions r2
       WHERE r.item_id = r2.item_id AND 
          r2.create_time <= r.create_time )
  FROM redemptions r
    JOIN payments p ON p.item_id = r.item_id
  GROUP BY r.id
  ORDER BY r.create_time

更新的 SQL Fiddle

于 2013-03-30T02:13:35.410 回答
1

鉴于多次付款和多次兑换问题,我能想到的最好的办法是:

SELECT 
    redemptions.id AS redemption_id,
    payment_totals.paid_amount - SUM(cumulative_redemptions.amount) AS remaining_balance
FROM redemptions
INNER JOIN
    (SELECT item_id, SUM(paid_amount) paid_amount FROM payments GROUP BY item_id) payment_totals
ON
    redemptions.item_id = payment_totals.item_id
INNER JOIN redemptions AS cumulative_redemptions
ON
    cumulative_redemptions.item_id = redemptions.item_id AND 
    cumulative_redemptions.create_time <= redemptions.create_time
GROUP BY redemptions.id, payment_totals.paid_amount;

SQL小提琴

这对每个项目的总付款使用子查询,这在您的问题中没有明确禁止,但我不保证性能。由于您在对 sgeddes 的评论中提到了批量更新,因此另一种选择是创建一个payment_totals完整的带有索引的实际表并在运行其余批次之前重新填充它。

于 2013-03-30T05:31:22.413 回答