显示给定用户的交易余额

Showing balances of transactions for a given user

我有一种情况需要显示每个用户的余额,相对于其他用户

Table 结构和虚拟数据脚本:

CREATE TABLE transactions (
    id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user1 INT NOT NULL,
    user2 INT NOT NULL,
    amount INT NOT NULL
);
INSERT INTO transactions VALUES(1, 1, 2, 10);
INSERT INTO transactions VALUES(2, 1, 3, 15);
INSERT INTO transactions VALUES(3, 4, 1, 25);
INSERT INTO transactions VALUES(4, 1, 5, 20);
INSERT INTO transactions VALUES(5, 5, 1, 18);
INSERT INTO transactions VALUES(6, 5, 1, 2);

结果:

现在我想汇总用户 = 1 的信息(余额)。我想看到的结果是这样的:

user    balance
2   10
3   15
4   -25
5   0

现在,我使用的是最新的 stable MySQL 版本 5.7.17-0ubuntu0.16.04.1。 我有 2 个问题:

此时我束手无策。我想为上述情况编写一个快速高效的查询。这是我的两次尝试(none 有效):

这个不起作用,因为我不能使用 FULL OUTER JOIN 子句

SELECT IFNULL(t3.user, t4.user), IFNULL(t3.amount, 0) - IFNULL(t4.amount, 0)
FROM (
    select t1.user2 user, sum(t1.amount) amount
    from transactions t1
    where 1=1
        and t1.user1 = 1
    group by t1.user2
) t3
FULL OUTER JOIN (
    select t2.user1 user, sum(t2.amount) amount
    from transactions t2
    where 1=1
        and t2.user2 = 1
    group by t2.user1
) t4 ON t3.user = t4.user

这个不起作用,因为我不能使用 WITH 子句

WITH t3 AS
 (
    select t1.user2 user, sum(t1.amount) amount
    from transactions t1
    where 1=1
        and t1.user1 = 1
    group by t1.user2
),
t4 AS
(
    select t2.user1 user, sum(t2.amount) amount
    from transactions t2
    where 1=1
        and t2.user2 = 1
    group by t2.user1
)
SELECT
    t1.user,
    IFNULL(t3.amount, 0) - IFNULL(t4.amount, 0) balance
FROM t1
LEFT JOIN t3 ON t1.user = t2.user
UNION
SELECT t2.user FROM t1
RIGHT JOIN t3 ON t1.user = t2.user

更新

使用 Gurwinder Singh 提供的解决方案,我能够在大约 500 万行测试数据上测试这两个查询的性能(尽管 user1 = 1 或 user2 = 1 - 远小于此)。

和(联合)

因此。查询 1 快 34% ((3.4-2.24)/3.4*100 = 34).

请注意,此 table 上没有索引。稍后我将尝试使用 MariaDB 进行相同类型的测试并比较结果。

更新 2

索引列后:user1user2amount情况发生了变化。

查询 1 运行 时间:

显示第 0 - 2 行(共 3 行,查询耗时 1.9857 秒。)

查询2运行时间:

显示第 0 - 2 行(共 3 行,查询耗时 1.5641 秒。)

但我仍然认为这是一个很糟糕的结果。也许我会把一些触发器更新到专用的 table 中。不过至此答案有了答案。

您可以使用基于 CASE 的条件聚合:

试试这个:

select case 
        when user1 = 1
            then user2
        else user1
        end as user,
    sum(case 
            when user1 = 1
                then amount
            else - amount
            end) as amount
from transactions
where 1 in (user1, user2)
group by case 
        when user1 = 1
            then user2
        else user1
        end;

Demo

或两步聚合:

select user, sum(amount) as amount
from (
    select user2 as user, sum(amount) as amount
    from transactions
    where user1 = 1
    group by user2

    union all

    select user1 as user, -sum(amount) as amount
    from transactions
    where user2 = 1
    group by user1
) t
group by user;

Demo