我有一张balance_history
具有以下结构和数据的表(简化)
帐户 | 平衡 | 交易日期 |
---|---|---|
一个 | 100 | 2024 年 9 月 29 日 |
一个 | 75 | 2024 年 8 月 20 日 |
一个 | 65 | 2024 年 8 月 18 日 |
b | 50 | 2024 年 8 月 15 日 |
丙 | 200 | 2024 年 10 月 7 日 |
仅当发生交易时,帐户余额才会出现在该表上。我可以通过rank over partition
对整个表运行语句来获取每个帐户的当前/最新余额。
但是,我需要的是显示每个帐户的月度余额。这意味着,如果某个特定月份没有交易,我希望查询从前几个月进行搜索并找到具有最新余额的最后一笔交易(有 id 列)。
我想展示这个结果:
月 | 帐户 | 平衡 |
---|---|---|
07/2024 | 一个 | 0 |
07/2024 | b | 0 |
07/2024 | 丙 | 200 |
08/2024 | 一个 | 75 |
08/2024 | b | 50 |
08/2024 | 丙 | 200 |
09/2024 | 一个 | 100 |
09/2024 | b | 50 |
09/2024 | 丙 | 200 |
如您所见,即使 9 月份 c 账户没有交易,我仍然希望显示余额。我该怎么做?
最佳答案
3
按月汇总值并生成日历,然后使用PARTITION
edOUTER JOIN
将每月值加入日历:
WITH month_data (account, month, balance) AS (
SELECT account,
TRUNC(transaction_date, 'MM'),
MAX(balance) KEEP (DENSE_RANK LAST ORDER BY Transaction_date)
FROM table_name t
GROUP BY
account,
TRUNC(transaction_date, 'MM')
),
calendar (month) AS (
SELECT ADD_MONTHS(min_month, LEVEL - 1)
FROM (
SELECT MIN(month) AS min_month,
MAX(month) AS max_month
FROM month_data
)
CONNECT BY ADD_MONTHS(min_month, LEVEL - 1) <= max_month
)
SELECT m.account,
c.month,
COALESCE(
LAST_VALUE(m.balance) IGNORE NULLS
OVER (PARTITION BY m.account ORDER BY c.month),
0
) AS balance
FROM calendar c
LEFT OUTER JOIN month_data m
PARTITION BY (m.account)
ON c.month = m.month
对于样本数据来说:
CREATE TABLE table_name (Account, Balance, Transaction_date) AS
SELECT 'a', 100, DATE '2024-09-29' FROM DUAL UNION ALL
SELECT 'a', 75, DATE '2024-08-20' FROM DUAL UNION ALL
SELECT 'a', 65, DATE '2024-08-18' FROM DUAL UNION ALL
SELECT 'b', 50, DATE '2024-08-15' FROM DUAL UNION ALL
SELECT 'c', 200, DATE '2024-07-10' FROM DUAL;
输出:
帐户 | 月 | 平衡 |
---|---|---|
一个 | 2024-07-01 00:00:00 | 0 |
一个 | 2024-08-01 00:00:00 | 75 |
一个 | 2024-09-01 00:00:00 | 100 |
b | 2024-07-01 00:00:00 | 0 |
b | 2024-08-01 00:00:00 | 50 |
b | 2024-09-01 00:00:00 | 50 |
丙 | 2024-07-01 00:00:00 | 200 |
丙 | 2024-08-01 00:00:00 | 200 |
丙 | 2024-09-01 00:00:00 | 200 |
3
-
为什么每个账户、每个月都有 MAX() ?不应该是最新的吗?没有理由余额一直在增长
– -
@p3consulting 这是最新的,就是这样的
KEEP (DENSE_RANK LAST ORDER BY Transaction_date)
。
–
-
@MT0,谢谢!这对我有用。但是哇,从这个查询中可以解开和学到很多东西。
–
|
一种选择是使用递归 cte 来准备数据,为您需要的所有月份创建行,获取月份的最后日期并在每个特定月份结束时记录最后的交易日期。
WITH -- R e c u r s i v e C T E :
months ( mnth, account, balance, tx_date, last_date_mnth, last_tx_date ) AS
( Select 1, account, balance, tx_date,
LAST_DAY(To_Date( '01.' || Lpad('1', 2, '0') || '.' || EXTRACT(Year From Sysdate), 'dd.mm.yyyy' ) ),
Max(Case When tx_date <= LAST_DAY(To_Date( '01.' || Lpad('1', 2, '0') || '.' || EXTRACT(Year From Sysdate), 'dd.mm.yyyy' ) )
Then tx_date
End) Over(Partition By account, 1
Order By tx_date
Rows Between Unbounded Preceding and Current Row)
From tbl
UNION ALL
Select mnth + 1, account, balance, tx_date,
LAST_DAY( ADD_MONTHS(last_date_mnth, 1) ),
Max(Case When tx_date <= LAST_DAY( ADD_MONTHS(last_date_mnth, 1) )
Then tx_date
End) Over(Partition By account, mnth
Order By tx_date
Rows Between Unbounded Preceding and Current Row)
From months
Where mnth < 12
)
…通过收集所有月份的数据,您可以获取每月最后一笔交易的余额,包括上个月的余额(如果那是最后一笔交易)…
-- M a i n S Q L :
Select mnth_str "MONTH", account "ACCOUNT", Max(balance) "BALANCE"
FRom ( Select To_Char(last_date_mnth, 'mm/yyyy') mnth_str, account,
Case When Max(last_tx_date) Over(Partition By account, last_date_mnth) = tx_date
Then balance
Else 0
End "BALANCE"
From months
Where mnth Between 7 and 9
)
Group By mnth_str, account
Order By mnth_str, account
/* R e s u l t :
MONTH ACCOUNT BALANCE
------- ------- -------
07/2024 a 0
07/2024 b 0
07/2024 c 200
08/2024 a 75
08/2024 b 50
08/2024 c 200
09/2024 a 100
09/2024 b 50
09/2024 c 200 */
注意:
要获取从第一笔交易的月份到当前月份的所有数据,请检查代码和结果。
如果实际数据中的余额小于零,则(在主 SQL 的 Select 子句中)“BALANCE”列的代码应如下所示:
Nvl(Max(Case When balance != 0 Then balance End), 0) "BALANCE"
2
-
您如何显示多年的结果?这似乎只显示当前年份的结果。
–
-
@MT0 这取决于利息期。如果是从第一笔交易的月份到当前月份,那么就像这里的小提琴一样 ->
–
|
您好,这是我在 Oracle 21c 中的解决方案:
首先,我生成月份和帐户的唯一组合,然后对月份-帐户组合对与原始数据进行左连接,使用每月和帐户的dense_rank选择最新的余额。
最后的选择包括案例语句,根据交易日期的月份是否是该帐户的最早月份来计算总余额。
CREATE TABLE balance_history (
account VARCHAR2(10),
balance NUMBER,
transaction_date DATE
);
INSERT INTO balance_history (account, balance, transaction_date) VALUES ('a', 100, TO_DATE('2024-09-29', 'YYYY-MM-DD'));
INSERT INTO balance_history (account, balance, transaction_date) VALUES ('a', 75, TO_DATE('2024-08-20', 'YYYY-MM-DD'));
INSERT INTO balance_history (account, balance, transaction_date) VALUES ('a', 65, TO_DATE('2024-08-18', 'YYYY-MM-DD'));
INSERT INTO balance_history (account, balance, transaction_date) VALUES ('b', 50, TO_DATE('2024-08-18', 'YYYY-MM-DD'));
INSERT INTO balance_history (account, balance, transaction_date) VALUES ('c', 200, TO_DATE('2024-07-10', 'YYYY-MM-DD'));
WITH monthly_account_pairs AS (
select month, a.account from (
SELECT unique TO_CHAR(ADD_MONTHS((select MIN(transaction_date) from balance_history), LEVEL - 1), 'YYYY-MM') AS month
FROM balance_history
CONNECT BY LEVEL <= MONTHS_BETWEEN((select MAX(transaction_date) from balance_history),
(select MIN(transaction_date)from balance_history) )
+ 1)
CROSS JOIN (SELECT DISTINCT account FROM balance_history) a
) , monthly_account_balances AS (
SELECT
map.month,
map.account,
COALESCE(bh.balance, 0) AS balance,
DENSE_RANK() OVER (
PARTITION BY TO_CHAR(bh.transaction_date, 'YYYY-MM'), bh.account
ORDER BY bh.transaction_date DESC
) AS rank_order,
bh.transaction_date
FROM monthly_account_pairs map
LEFT JOIN balance_history bh
ON map.month = TO_CHAR(bh.transaction_date, 'YYYY-MM')
AND map.account = bh.account
)
SELECT
month,
account,
CASE
WHEN EXTRACT(MONTH FROM COALESCE(transaction_date, TO_DATE(month || '-01', 'YYYY-MM-DD'))) <>
(
SELECT MIN(EXTRACT(MONTH FROM COALESCE(transaction_date, TO_DATE(month || '-01', 'YYYY-MM-DD'))))
FROM monthly_account_balances
)
THEN MAX(balance) OVER (PARTITION BY account ORDER BY COALESCE(transaction_date, TO_DATE(month || '-01', 'YYYY-MM-DD')))
ELSE balance
END AS balance
FROM monthly_account_balances mab
WHERE rank_order = 1
ORDER BY month, account;
希望有帮助。
2
-
1如果某月没有活动,则不会给出该月的值 – 请参阅,其中有一行额外的输入数据,并且 2024-06 没有输出任何内容。
– -
@MT0 谢谢你的观察。你说得对,但我已更正了我的初始查询,即使该月没有活动,也会显示该月,在本例中为 6 月。基于你从 fiddle 获得的数据。
–
|
|