在金融应用程序的数据库中,有如下表格:
account(account_id PK, account_name, …)
,transaction(transaction_id PK, transaction_timestamp, account_debit_id FK, account_credit_id FK)
,和balance_event(account_balance, account_id FK, transaction_id FK)
.SQL 定义如下。仅列出相关字段。
后者用于跟踪每个账户的余额;命名可能很糟糕,但该表记录了由于某些交易导致帐户余额发生变化时的事件。这种方式获取账户余额不需要遍历和汇总所有的数千笔交易,而且账户余额的历史记录也被保留以备将来参考。
我想描述一个查询1 ,它给出了
account
表中的所有行,以及从 account_balance
中获取的相应
balance_event
,——一个经典问题。我尝试了很多不同的方法,但由于不同的原因,没有一种方法有效(另外,ChatGPT 有很多变体,但我不在这里包括它们)。有些查询不起作用,因为 ONLY_FULL_GROUP_BY
已打开;如果可能的话,我想保持这种状态。
这大概就是我想要做的:
-- I know this query doesn't make any sense, don't bite me
-- This is only a gist, a suggestion
SELECT
account.*,
balance_event.account_balance
FROM
account
JOIN
balance_event ON account.account_id = balance_event.account_id
WHERE
balance_event.transaction_id = (
SELECT
`transaction`.transaction_id
FROM
`transaction`
JOIN
balance_event ON balance_event.transaction_id = `transaction`.transaction_id
WHERE
balance_event.account_id = account.account_id
AND
`transaction`.transaction_timestamp = MAX(`transaction`.transaction_timestamp)
GROUP BY
balance_event.account_id
)
GROUP BY
balance_event.account_id
;
如何正确地做,以便它真正起作用?
1 — 我正在随机在线 SQL 编辑器中测试代码,该编辑器恰好使用 MySQL,但我寻求有一个通用查询或至少一个 PostgreSQL 支持的查询。
供参考,这是字段的完整定义:
CREATE TABLE account (
account_id VARCHAR(36) NOT NULL PRIMARY KEY, -- uuid
account_name VARCHAR(255) NOT NULL
);
CREATE TABLE `transaction` (
transaction_id VARCHAR(36) NOT NULL PRIMARY KEY, -- uuid
transaction_timestamp BIGINT NOT NULL,
account_debit_id VARCHAR(36) NOT NULL,
account_credit_id VARCHAR(36) NOT NULL,
transaction_amount DECIMAL(10,2) NOT NULL,
FOREIGN KEY (account_debit_id) REFERENCES account(account_id),
FOREIGN KEY (account_credit_id) REFERENCES account(account_id)
);
CREATE TABLE balance_event (
balance_event_id VARCHAR(36) NOT NULL PRIMARY KEY, -- uuid
transaction_id VARCHAR(36) NOT NULL,
account_id VARCHAR(36) NOT NULL,
account_balance DECIMAL(10,2) NOT NULL,
FOREIGN KEY (transaction_id) REFERENCES `transaction`(transaction_id),
FOREIGN KEY (account_id) REFERENCES account(account_id)
);
这是一个使用 Window Functions 的解决方案,PostgreSQL 和 MySQL 8.0 以及大多数其他流行的 SQL 数据库,包括商业和开源。
SELECT *
FROM (
SELECT
a.*,
b.account_balance,
ROW_NUMBER() OVER (PARTITION BY a.account_id ORDER BY t.transaction_timestamp DESC) AS rownum
FROM account AS a
JOIN balance_event AS b USING (account_id)
JOIN transaction AS t USING (transaction_id)
) t
WHERE rownum = 1;
有了这个方案,你根本不需要GROUP BY,所以MySQL的ONLY_FULL_GROUP_BY SQL模式不会影响它。