我有一张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

按月汇总值并生成日历,然后使用PARTITIONedOUTER 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 获得的数据。


    –