繁体   English   中英

使用连接计算MySQL中的SUM

[英]Calculating SUMs in MySQL with joins

背景

我的数据库我有一个装满物品的桌子。 每件商品都可以进行一系列付款。
一旦用户付款,用户就可以通过兑换它来使用该钱。 钱可以部分兑换。

问题

对于每次兑换,我想计算剩余的钱。 这等于执行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 |
+---------------+-------------------+

正如你所看到的,这并没有像我想要的那样。 我希望兑换3的remaining_balance为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;

你去吧!

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小提琴

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

  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小提琴

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

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表,并在运行批处理的其余部分之前重新填充它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM