How to Show a Running Balance in a Report in Oracle APEX

Calculate the balance after every row with an analytic SUM, show it in an Interactive Report, highlight negative values and keep users from sorting it out of order.

A running balance shows the amount left after each row of a list: the bank balance after every transaction, the stock after every movement or the hours left after every booking. Users read it from top to bottom, so every number depends on the rows above it.

This guide builds an account statement page in Oracle APEX. One analytic SUM in the SQL query calculates the balance, an Interactive Report shows it with negative balances highlighted, and the report settings keep the rows in the one order where the balance makes sense.

The example uses bank accounts and their transactions. The same steps work for any table with a date and a signed amount. The SQL side of running totals is covered in Oracle SQL Query to Calculate Running Totals; this article is about the APEX page and the problems that appear when users sort, search and filter the report.

Step 1: Create the Tables

Two tables hold the data:

  • BANK_ACCOUNTS has one row per account with a unique account number and the opening balance.
  • ACCOUNT_TRANSACTIONS has one row per transaction: the account, the date, a description, the type CREDIT or DEBIT and a positive amount.

SIGNED_AMOUNT is a virtual column. It returns the amount for a credit and the negative amount for a debit, so no trigger or process has to keep a sign in sync. Create the tables with 2 accounts and 15 transactions:

Example:

set pages 100 lines 200 feedback on
alter session set nls_date_format = 'YYYY-MM-DD';
create table bank_accounts (
  account_id       number generated by default on null as identity,
  account_no       varchar2(20) not null,
  account_name     varchar2(100) not null,
  opening_date     date not null,
  opening_balance  number(12,2) default on null 0 not null,
  constraint bank_accounts_pk primary key (account_id),
  constraint bank_accounts_no_uk unique (account_no)
);

create table account_transactions (
  txn_id         number generated by default on null as identity,
  account_id     number not null,
  txn_date       date not null,
  description    varchar2(200) not null,
  txn_type       varchar2(6) not null,
  amount         number(12,2) not null,
  signed_amount  number(12,2) as (case txn_type when 'CREDIT' then amount else -amount end) virtual,
  constraint account_transactions_pk primary key (txn_id),
  constraint account_txn_account_fk foreign key (account_id) references bank_accounts (account_id),
  constraint account_txn_type_ck check (txn_type in ('CREDIT', 'DEBIT')),
  constraint account_txn_amount_ck check (amount > 0)
);

create index account_txn_account_ix on account_transactions (account_id, txn_date, txn_id);

insert into bank_accounts (account_no, account_name, opening_date, opening_balance)
select account_no, account_name, to_date(opening_date, 'YYYY-MM-DD'), opening_balance
  from (values ('ACC-100', 'Operating Account', '2026-09-01', 2000),
               ('ACC-200', 'Savings Account',   '2026-09-01', 10000)) v (account_no, account_name, opening_date, opening_balance);

insert into account_transactions (account_id, txn_date, description, txn_type, amount)
select a.account_id, to_date(v.txn_date, 'YYYY-MM-DD'), v.description, v.txn_type, v.amount
  from (values ('ACC-100', '2026-09-02', 'Customer payment INV-501',  'CREDIT', 1500),
               ('ACC-100', '2026-09-05', 'Office rent September',     'DEBIT',  2800),
               ('ACC-100', '2026-09-05', 'Internet and phone',        'DEBIT',   250),
               ('ACC-100', '2026-09-12', 'Customer payment INV-502',  'CREDIT',  900),
               ('ACC-100', '2026-09-20', 'Supplier invoice 7741',     'DEBIT',  1900),
               ('ACC-100', '2026-09-28', 'Customer payment INV-503',  'CREDIT', 3200),
               ('ACC-100', '2026-09-30', 'Salaries September',        'DEBIT',  2400),
               ('ACC-100', '2026-10-01', 'Transfer from savings',     'CREDIT', 2000),
               ('ACC-100', '2026-10-05', 'Office rent October',       'DEBIT',  2800),
               ('ACC-100', '2026-10-09', 'Customer payment INV-504',  'CREDIT', 1750),
               ('ACC-100', '2026-10-09', 'Bank charges',              'DEBIT',    35),
               ('ACC-100', '2026-10-15', 'Supplier invoice 7802',     'DEBIT',  1200),
               ('ACC-200', '2026-09-30', 'Interest September',        'CREDIT',   42),
               ('ACC-200', '2026-10-01', 'Transfer to operating',     'DEBIT',  2000),
               ('ACC-200', '2026-10-15', 'Deposit',                   'CREDIT', 1500)
       ) v (account_no, txn_date, description, txn_type, amount)
  join bank_accounts a on a.account_no = v.account_no
 order by v.account_no, v.txn_date;

commit;

select a.account_no, a.account_name, a.opening_balance,
       count(t.txn_id) txns,
       a.opening_balance + nvl(sum(t.signed_amount), 0) closing_balance
  from bank_accounts a
  left join account_transactions t on t.account_id = a.account_id
 group by a.account_no, a.account_name, a.opening_balance
 order by a.account_no;

Output:

Session altered.

Table BANK_ACCOUNTS created.

Table ACCOUNT_TRANSACTIONS created.

Index ACCOUNT_TXN_ACCOUNT_IX created.

2 rows inserted.

15 rows inserted.

Commit complete.

ACCOUNT_NO    ACCOUNT_NAME            OPENING_BALANCE    TXNS    CLOSING_BALANCE
_____________ ____________________ __________________ _______ __________________
ACC-100       Operating Account                  2000      12                -35
ACC-200       Savings Account                   10000       3               9542

2 rows selected.
  • ACCOUNT_TXN_ACCOUNT_FK links each transaction to its account. ACCOUNT_TXN_TYPE_CK allows only CREDIT and DEBIT, and ACCOUNT_TXN_AMOUNT_CK allows only positive amounts.
  • OPENING_BALANCE has DEFAULT ON NULL 0, so a new account without a value starts at zero.
  • The index ACCOUNT_TXN_ACCOUNT_IX is on account, date and ID: the same columns the balance is calculated by.

The balance itself is not stored. A stored balance would be wrong after every back-dated or corrected transaction. The query calculates it each time.

Step 2: Write the Running Balance Query

The running balance is the opening balance plus the sum of all signed amounts up to and including the current row. The analytic SUM function does this in one pass:

Example:

set pages 100 lines 200 feedback on
alter session set nls_date_format = 'YYYY-MM-DD';
select t.txn_id,
       t.txn_date,
       t.description,
       t.signed_amount,
       a.opening_balance
         + sum(t.signed_amount) over (
             partition by t.account_id
             order by t.txn_date, t.txn_id
             rows between unbounded preceding and current row
           ) running_balance
  from account_transactions t
  join bank_accounts a on a.account_id = t.account_id
 where a.account_no = 'ACC-100'
 order by t.txn_date, t.txn_id;

Output:

Session altered.

   TXN_ID TXN_DATE      DESCRIPTION                    SIGNED_AMOUNT    RUNNING_BALANCE
_________ _____________ ___________________________ ________________ __________________
        1 2026-09-02    Customer payment INV-501                1500               3500
        2 2026-09-05    Office rent September                  -2800                700
        3 2026-09-05    Internet and phone                      -250                450
        4 2026-09-12    Customer payment INV-502                 900               1350
        5 2026-09-20    Supplier invoice 7741                  -1900               -550
        6 2026-09-28    Customer payment INV-503                3200               2650
        7 2026-09-30    Salaries September                     -2400                250
        8 2026-10-01    Transfer from savings                   2000               2250
        9 2026-10-05    Office rent October                    -2800               -550
       10 2026-10-09    Customer payment INV-504                1750               1200
       11 2026-10-09    Bank charges                             -35               1165
       12 2026-10-15    Supplier invoice 7802                  -1200                -35

12 rows selected.

How the window works:

  • PARTITION BY account_id starts a new sum for every account.
  • ORDER BY txn_date, txn_id sets the order in which rows are added up.
  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW adds every row from the first one up to the current row.

The operating account goes below zero twice: after the supplier invoice on 2026-09-20 and after the October rent. The last balance, -35, is the closing balance from Step 1.

Why TXN_ID Is in the ORDER BY

Two transactions on 2026-09-05 and two on 2026-10-09 share a date. Here is the same query ordered by date only:

Example:

set pages 100 lines 200 feedback on
alter session set nls_date_format = 'YYYY-MM-DD';
select t.txn_id,
       t.txn_date,
       t.description,
       t.signed_amount,
       a.opening_balance
         + sum(t.signed_amount) over (
             partition by t.account_id
             order by t.txn_date
           ) balance_by_date_only
  from account_transactions t
  join bank_accounts a on a.account_id = t.account_id
 where a.account_no = 'ACC-100'
 order by t.txn_date, t.txn_id;

Output:

Session altered.

   TXN_ID TXN_DATE      DESCRIPTION                    SIGNED_AMOUNT    BALANCE_BY_DATE_ONLY
_________ _____________ ___________________________ ________________ _______________________
        1 2026-09-02    Customer payment INV-501                1500                    3500
        2 2026-09-05    Office rent September                  -2800                     450
        3 2026-09-05    Internet and phone                      -250                     450
        4 2026-09-12    Customer payment INV-502                 900                    1350
        5 2026-09-20    Supplier invoice 7741                  -1900                    -550
        6 2026-09-28    Customer payment INV-503                3200                    2650
        7 2026-09-30    Salaries September                     -2400                     250
        8 2026-10-01    Transfer from savings                   2000                    2250
        9 2026-10-05    Office rent October                    -2800                    -550
       10 2026-10-09    Customer payment INV-504                1750                    1165
       11 2026-10-09    Bank charges                             -35                    1165
       12 2026-10-15    Supplier invoice 7802                  -1200                     -35

12 rows selected.

Both rows of 2026-09-05 show 450, and both rows of 2026-10-09 show 1,165. An ORDER BY without a frame clause uses RANGE, which adds all rows with the same date at once. The balance after the rent payment, 700, never appears.

Adding TXN_ID gives every row a unique position. ROWS then adds one row at a time, and each row shows its own balance.

Filter After the Balance Is Calculated

A statement for October only must still start from the balance at the end of September. If the date filter is in the WHERE clause of the same query, the earlier rows are removed before the SUM sees them:

Example:

set pages 100 lines 200 feedback on
alter session set nls_date_format = 'YYYY-MM-DD';
select t.txn_id,
       t.txn_date,
       t.description,
       t.signed_amount,
       a.opening_balance
         + sum(t.signed_amount) over (
             partition by t.account_id
             order by t.txn_date, t.txn_id
             rows between unbounded preceding and current row
           ) running_balance
  from account_transactions t
  join bank_accounts a on a.account_id = t.account_id
 where a.account_no = 'ACC-100'
   and t.txn_date >= date '2026-10-01'
 order by t.txn_date, t.txn_id;

Output:

Session altered.

   TXN_ID TXN_DATE      DESCRIPTION                    SIGNED_AMOUNT    RUNNING_BALANCE
_________ _____________ ___________________________ ________________ __________________
        8 2026-10-01    Transfer from savings                   2000               4000
        9 2026-10-05    Office rent October                    -2800               1200
       10 2026-10-09    Customer payment INV-504                1750               2950
       11 2026-10-09    Bank charges                             -35               2915
       12 2026-10-15    Supplier invoice 7802                  -1200               1715

5 rows selected.

The October rows now start from the opening balance of 2,000, and every balance is wrong. Calculate the balance in an inline view first and filter outside of it:

Example:

set pages 100 lines 200 feedback on
alter session set nls_date_format = 'YYYY-MM-DD';
select *
  from (select t.txn_id,
               t.txn_date,
               t.description,
               t.signed_amount,
               a.opening_balance
                 + sum(t.signed_amount) over (
                     partition by t.account_id
                     order by t.txn_date, t.txn_id
                     rows between unbounded preceding and current row
                   ) running_balance
          from account_transactions t
          join bank_accounts a on a.account_id = t.account_id
         where a.account_no = 'ACC-100')
 where txn_date >= date '2026-10-01'
 order by txn_date, txn_id;

Output:

Session altered.

   TXN_ID TXN_DATE      DESCRIPTION                    SIGNED_AMOUNT    RUNNING_BALANCE
_________ _____________ ___________________________ ________________ __________________
        8 2026-10-01    Transfer from savings                   2000               2250
        9 2026-10-05    Office rent October                    -2800               -550
       10 2026-10-09    Customer payment INV-504                1750               1200
       11 2026-10-09    Bank charges                             -35               1165
       12 2026-10-15    Supplier invoice 7802                  -1200                -35

5 rows selected.

The balances match the full statement from Step 2. An Interactive Report works the same way: it applies its search outside of the region query, as the test in Step 9 shows.

Step 3: Create the Interactive Report Page

The report query reads the account from a page item, P850_ACCOUNT_ID, which is created in Step 4. It returns separate Debit and Credit columns, as bank statements usually do:

Example:

select t.txn_id,
       t.txn_date,
       t.description,
       case t.txn_type when 'DEBIT' then t.amount end debit,
       case t.txn_type when 'CREDIT' then t.amount end credit,
       a.opening_balance
         + sum(t.signed_amount) over (
             partition by t.account_id
             order by t.txn_date, t.txn_id
             rows between unbounded preceding and current row
           ) running_balance
  from account_transactions t
  join bank_accounts a on a.account_id = t.account_id
 where t.account_id = :P850_ACCOUNT_ID

In Page Designer, click Create (the plus icon) and choose Page. Select the Interactive Report tile:

Create a Page wizard with the Interactive Report tile marked with a red box
Choose the Interactive Report page type.

Enter Page Number 850 and Name Account Statement. Leave Page Mode Normal and Include Form Page off:

Create Interactive Report screen with Page Number 850 and Name Account Statement marked with a red box
Page number and name.

Under Data Source, keep Local Database, set Source Type to SQL Query and paste the query above:

Data Source section with Source Type SQL Query and the running balance query marked with a red box
The report source is the running balance query.

Under Navigation, keep the defaults: a breadcrumb and a new navigation menu entry with the icon fa-table. Click Create Page:

Navigation section of the Create Interactive Report wizard with the default breadcrumb and navigation menu settings
Keep the navigation defaults and create the page.

The wizard creates page 850 with the Interactive Report region Account Statement and one column per query column.

Step 4: Add the Account Select List

Right-click Body in the Rendering tree and choose Create Region. Set Name to Account and keep Type Static Content. Set Sequence to 5, so the region appears above the report:

New region with Name Account, Type Static Content and Sequence 5 marked with red boxes
A small region for the account field.

Right-click the Account region, choose Create Page Item and set:

  • Name: P850_ACCOUNT_ID, Type: Select List, Label: Account
  • List of Values Type: SQL Query, with the query below
  • Display Null Value: off, because a statement always belongs to one account

Example:

select account_no || ' - ' || account_name as display_value,
       account_id as return_value
  from bank_accounts
 order by account_no
P850_ACCOUNT_ID with Name, Type Select List, Label Account, List of Values SQL Query and Display Null Value off marked with red boxes
The account select list.

Under Layout, check that Region is Account and set Column Span to 4:

Layout section with Region Account and Column Span 4 marked with red boxes
The select list uses a third of the width.

A select list without a null value shows its first entry, but the item stays empty in session state until the user changes it. The report would then show no rows on the first visit. Give the item a default value: under Default, set Type to SQL Query (return single value) with this query:

Example:

select min(account_id) keep (dense_rank first order by account_no)
  from bank_accounts
Default Type SQL Query (return single value) and the default query marked with a red box
The page opens with the first account.

The query returns the ID of the account that is first by account number, the same account the select list shows first.

Step 5: Refresh the Report When the Account Changes

Select the Account Statement region. Under Source, set Page Items to Submit to P850_ACCOUNT_ID, so each refresh sends the selected account to the server.

In the same SQL Query, change the WHERE line to:

Example:

 where t.account_id = to_number(:P850_ACCOUNT_ID default null on conversion error)

In a first test with the plain bind variable, a value forced into the item from the browser console (abc) made the report show ORA-01722: unable to convert string value containing 'a' to a number.

With DEFAULT NULL ON CONVERSION ERROR, any value that is not a number becomes NULL and the report shows no rows instead of an error.

Account Statement region with the final SQL Query and Page Items to Submit P850_ACCOUNT_ID marked with a red box
The final report query and Page Items to Submit.

The full query of the region is now:

Example:

select t.txn_id,
       t.txn_date,
       t.description,
       case t.txn_type when 'DEBIT' then t.amount end debit,
       case t.txn_type when 'CREDIT' then t.amount end credit,
       a.opening_balance
         + sum(t.signed_amount) over (
             partition by t.account_id
             order by t.txn_date, t.txn_id
             rows between unbounded preceding and current row
           ) running_balance
  from account_transactions t
  join bank_accounts a on a.account_id = t.account_id
 where t.account_id = to_number(:P850_ACCOUNT_ID default null on conversion error)

On the Attributes tab of the region, under Messages, set When No Data Found to No transactions for this account.:

Messages section with When No Data Found set to No transactions for this account. marked with a red box
A clear message for an account without transactions.

Now create the dynamic action. Right-click P850_ACCOUNT_ID, choose Create Dynamic Action and set Name to Account Changed. Keep Event Change, Selection Type Item(s) and Item(s) P850_ACCOUNT_ID:

Dynamic action Account Changed with Event Change, Selection Type Item(s) and Item(s) P850_ACCOUNT_ID marked with red boxes
The dynamic action fires when the account changes.

Select its True action and set Action to Refresh, Selection Type to Region and Region to Account Statement. Turn Fire on Initialization off; the report already loads with the page:

True action with Action Refresh, Selection Type Region, Region Account Statement and Fire on Initialization off marked with red boxes
Refresh the report with the new account.

Step 6: Format the Columns

Expand Account Statement and Columns in the Rendering tree. Select TXN_DATE and set Heading to Date and Format Mask to YYYY-MM-DD:

TXN_DATE column with Heading Date and Format Mask YYYY-MM-DD marked with red boxes
The date column.

Select RUNNING_BALANCE and set Heading to Balance and Format Mask to FM999G999G990D00:

RUNNING_BALANCE column with Heading Balance and Format Mask FM999G999G990D00 marked with red boxes
The balance column.

Set the same Format Mask on DEBIT and CREDIT, and change the heading of TXN_ID to Txn ID. Save the page.

Step 7: Set the Default Sort and Highlight Negative Balances

The query has no ORDER BY. In a first run, the rows came in date order even without a sort, but only by chance: nothing in the query guarantees it. The report needs a fixed sort, saved as the default for all users.

Run the page while you are signed in to App Builder. Open Actions, Data, and click Sort:

Actions menu with Data expanded and Sort marked with a red box
Actions, Data, Sort.

Set Column 1 to Date and Column 2 to Txn ID, both Ascending, and click Apply. These are the same two columns as the ORDER BY of the window in the query:

Sort dialog with Date Ascending and Txn ID Ascending marked with a red box
The report sort matches the balance order.

Next, open Actions, Format, and click Highlight:

Actions menu with Format expanded and Highlight marked with a red box
Actions, Format, Highlight.

Enter Name Negative balance, set Highlight Type to Cell and Text Color to #d0021b, and keep the default Background Color. Under Highlight Condition, set Column to Balance, Operator to < and Expression to 0. Click Apply:

Highlight dialog with Name Negative balance, Highlight Type Cell, Text Color #d0021b and the condition Balance less than 0 marked with a red box
Negative balances get red text.

Now save both settings for everyone. Open Actions, Report, and click Save Report:

Actions menu with Report expanded and Save Report marked with a red box
Actions, Report, Save Report.

In the Save list, choose As Default Report Settings. This list appears only for developers who run the page from App Builder:

Save Report dialog with the Save list marked with a red box
Choose As Default Report Settings in the Save list.

Keep Default Report Type Primary and click Apply:

Save Default Report dialog with Default Report Type Primary marked with a red box
Save the settings as the primary default report.

Every user now opens the report sorted by date and ID, with negative balances in red.

Step 8: Test the Report and Sort by Another Column

Sign in as an ordinary user (SALES_USER in this example) and open Account Statement. The balances match the SQL output of Step 2, and the three negative balances are red:

Account Statement page with the running balance of ACC-100 and three negative balances highlighted in red
The statement as every user sees it.

Now click the Debit heading and choose Sort Descending. The rows change their order, but the balances stay the balances of the date order:

Report sorted by Debit descending: the Balance column jumps between 3,500.00, 1,350.00, -550.00 and 1,165.00 without any visible logic
Sorted by Debit, the Balance column no longer adds up from row to row.

Nothing is calculated wrong here. Each balance still belongs to its transaction. But read from top to bottom, the column does not add up: the second row adds a 900.00 credit to 3,500.00 and shows 1,350.00. Users will report this as a bug.

Step 9: Keep the Rows in Balance Order

Back in Page Designer, select each column of the report (TXN_ID, TXN_DATE, DESCRIPTION, DEBIT, CREDIT and RUNNING_BALANCE). Under Enable Users To, turn Sort and Control Break off:

Enable Users To section of a report column with Sort off and Control Break off marked with red boxes
Users can no longer sort or break the report by a column.

Control Break is turned off as well because a break sorts the rows by the break column first. Filter, Highlight and Aggregate stay on.

Run the page as SALES_USER and click Actions, Report, Reset. The rows are back in date order. Click the Debit heading: the menu now has only the Hide Column button and the filter list. The sort and control break buttons are gone:

Column heading menu of Debit with only the Hide Column button and the filter list
The heading menu without sort buttons.

In this test, the Sort and Control Break dialogs under the Actions menu listed no columns at all, while the saved default sort by Date and Txn ID still worked. The back-dated test below confirms it.

Search and Filters Keep the Correct Balance

Type Office rent in the search field and press Enter. Only the two rent rows remain, and they show 700.00 and -550.00, the same balances as in the full statement:

Report filtered by the search Office rent with two rows and their balances 700.00 and -550.00
The search filters rows after the balance is calculated.

This is the same rule as the inline view in Step 2. The report applies the search outside the region query, so the balance of each remaining row still includes all earlier transactions.

Switch the Account

Reset the report and choose ACC-200 - Savings Account. The dynamic action refreshes the report, and the balance starts from the opening balance of 10,000:

Account Statement for ACC-200 with balances 10,042.00, 8,042.00 and 9,542.00
Each account has its own running balance.

Add a Back-Dated Transaction

A transaction entered today with an earlier date must appear at its date and change every balance after it. Insert one for 2026-09-15:

Example:

set pages 100 lines 200 feedback on
insert into account_transactions (account_id, txn_date, description, txn_type, amount)
select account_id, date '2026-09-15', 'Supplier refund 7741', 'CREDIT', 100
  from bank_accounts
 where account_no = 'ACC-100';

commit;

select txn_id, to_char(txn_date, 'YYYY-MM-DD') txn_date, description, signed_amount
  from account_transactions
 where description = 'Supplier refund 7741';

Output:

1 row inserted.

Commit complete.

   TXN_ID TXN_DATE      DESCRIPTION                SIGNED_AMOUNT
_________ _____________ _______________________ ________________
       21 2026-09-15    Supplier refund 7741                 100

1 row selected.

The new row has ID 21, higher than all the others. Refresh the page:

Report with the back-dated row Supplier refund 7741 of 2026-09-15 marked with a red box between rows 4 and 5, and all later balances 100 higher
The back-dated row sits at its date, and all later balances moved up by 100.

The row is placed by its date, not by its ID. Every later balance is 100 higher, the supplier invoice of 2026-09-20 now leaves -450, and the closing balance turns positive at 65. A stored balance column would have needed an update of nine rows for this.

Check the last running balance against a plain SUM per account:

Example:

set pages 100 lines 200 feedback on
select a.account_no,
       a.opening_balance + sum(t.signed_amount) closing_balance,
       max(r.running_balance) keep (dense_rank last order by r.txn_date, r.txn_id) last_running_balance
  from bank_accounts a
  join account_transactions t on t.account_id = a.account_id
  join (select t2.txn_id, t2.txn_date,
               a2.opening_balance
                 + sum(t2.signed_amount) over (
                     partition by t2.account_id
                     order by t2.txn_date, t2.txn_id
                     rows between unbounded preceding and current row
                   ) running_balance
          from account_transactions t2
          join bank_accounts a2 on a2.account_id = t2.account_id) r on r.txn_id = t.txn_id
 group by a.account_no, a.opening_balance
 order by a.account_no;

Output:

ACCOUNT_NO       CLOSING_BALANCE    LAST_RUNNING_BALANCE
_____________ __________________ _______________________
ACC-100                       65                      65
ACC-200                     9542                    9542

2 rows selected.

Both ways give the same closing balance for both accounts. Remove the test row again:

Example:

set pages 100 lines 200 feedback on
delete from account_transactions
 where description = 'Supplier refund 7741';

commit;

Output:

1 row deleted.

Commit complete.

Force an Invalid Account

Finally, put values into P850_ACCOUNT_ID from the browser console that the select list does not offer, and refresh the report. In this test, 999, abc and 1 or 1=1 all showed the message No transactions for this account. and no rows:

Account Statement with the forced value 1 or 1=1 in the Account field and the message No transactions for this account.
A forced value shows no rows and no error.

The value is a bind variable, so it can never change the SQL. TO_NUMBER with DEFAULT NULL ON CONVERSION ERROR from Step 5 turns text into NULL, and NULL matches no account.

Things to Know

  • Always end the ORDER BY of the window with a unique column such as the ID, and use ROWS. Ordering by date only gives rows with the same date the same balance.
  • Filter in an outer query, never in the WHERE clause of the query that calculates the balance. The Interactive Report search already works this way.
  • The report sort must match the window order. Save it as the primary default report and turn off Sort and Control Break for the columns, or users get a balance column that does not add up.
  • Users can still hide columns, search, filter and add their own highlights. These do not change the order of the rows.
  • Saving As Default Report Settings needs a developer session: run the page from App Builder. The Save list does not appear for ordinary users.
  • For very long histories, show a date range in an outer query and keep the window over all rows, or store month-end balances and start the window from the last one.

Related Guides

Conclusion

A running balance in Oracle APEX needs one analytic SUM with a unique ORDER BY and a ROWS frame, an Interactive Report with the same order saved as the default, and column settings that stop users from sorting the rows into a different order.

In this example, the balances matched SQL for both accounts, a back-dated transaction moved every later balance correctly, searches kept the right numbers, and forced account values showed no rows instead of an error. The same steps work for stock movements, budgets or any other table with a date and a signed amount.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted