Do you really need the extra table?

You can get that data you need with a simple query, which you can obviously create as a view if you want it to appear like a table.

This will get you the data you are looking for:

select 
    account, bookdate, amount, 
    sum(amount) over (partition by account order by bookdate) running_total
from t
/

This will create a view to show you the data as if it were a table:

create or replace view t2
as
select 
    account, bookdate, amount, 
    sum(amount) over (partition by account order by bookdate) running_total 
from t
/

If you really need the table, do you mean that you need it constantly updated? or just a one off? Obviously if it's a one off you can just "create table as select" using the above query.

Test data I used is:

create table t(account number, bookdate date, amount number);

insert into t(account, bookdate, amount) values (1, to_date('20080101', 'yyyymmdd'), 100);

insert into t(account, bookdate, amount) values (1, to_date('20080102', 'yyyymmdd'), 101);

insert into t(account, bookdate, amount) values (1, to_date('20080103', 'yyyymmdd'), -200);

insert into t(account, bookdate, amount) values (2, to_date('20080102', 'yyyymmdd'), 200);

commit;

edit:

forgot to add; you specified that you wanted the table to be ordered - this doesn't really make sense, and makes me think that you really mean that you wanted the query/view - ordering is a result of the query you execute, not something that's inherant in the table (ignoring Index Organised Tables and the like).

Answer from William on Stack Overflow
Top answer
1 of 3
24

Do you really need the extra table?

You can get that data you need with a simple query, which you can obviously create as a view if you want it to appear like a table.

This will get you the data you are looking for:

select 
    account, bookdate, amount, 
    sum(amount) over (partition by account order by bookdate) running_total
from t
/

This will create a view to show you the data as if it were a table:

create or replace view t2
as
select 
    account, bookdate, amount, 
    sum(amount) over (partition by account order by bookdate) running_total 
from t
/

If you really need the table, do you mean that you need it constantly updated? or just a one off? Obviously if it's a one off you can just "create table as select" using the above query.

Test data I used is:

create table t(account number, bookdate date, amount number);

insert into t(account, bookdate, amount) values (1, to_date('20080101', 'yyyymmdd'), 100);

insert into t(account, bookdate, amount) values (1, to_date('20080102', 'yyyymmdd'), 101);

insert into t(account, bookdate, amount) values (1, to_date('20080103', 'yyyymmdd'), -200);

insert into t(account, bookdate, amount) values (2, to_date('20080102', 'yyyymmdd'), 200);

commit;

edit:

forgot to add; you specified that you wanted the table to be ordered - this doesn't really make sense, and makes me think that you really mean that you wanted the query/view - ordering is a result of the query you execute, not something that's inherant in the table (ignoring Index Organised Tables and the like).

2 of 3
6

I'll start with this very important caveate: do NOT create a table to hold this data. When you do you will find that you need to maintain it which will become a never ending headache. Write a view to return the extra column if you want to do that. If you're working with a data warehouse then maybe you would do something like this, but even then err on the side of a view unless you simply can't get the performance that you need with indexes,decent hardware, etc.

Here's a query that will return the rows the way that you need them.

SELECT
    Account,
    Bookdate,
    Amount,
    (
        SELECT SUM(Amount)
        FROM My_Table T2
        WHERE T2.Account = T1.Account
          AND T2.Bookdate <= T1.Bookdate
    ) AS Running_Total
FROM
    My_Table T1

Another possible solution is:

SELECT
    T1.Account,
    T1.Bookdate,
    T1.Amount,
    SUM(T2.Amount)
FROM
    My_Table T1
LEFT OUTER JOIN My_Table T2 ON
    T2.Account = T1.Account AND
    T2.Bookdate <= T1.Bookdate
GROUP BY
    T1.Account,
    T1.Bookdate,
    T1.Amount

Test them both for performance and see which works better for you. Also, I haven't thoroughly tested them beyond the example which you gave, so be sure to test some edge cases.

Discussions

Running Total by Group SQL (Oracle) - Stack Overflow
I have a table in an Oracle db that has the following fields of interest: Location, Product, Date, Amount. I would like to write a query that would get a running total of amount by Location, Produ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
sql - Multiple Running Totals with Group By - Stack Overflow
I am struggling to find a good way to run running totals with a group by in it, or the equivalent. The below cursor based running total works on a complete table, but I would like to expand this t... More on stackoverflow.com
๐ŸŒ stackoverflow.com
sap ase 16 - SQL: Running total with group by - Database Administrators Stack Exchange
I have a dataset that has two date columns for each transaction. An invoice date and a charge date. I am trying to do a cumulative or running total of the table in sql. Data table looks like this: More on dba.stackexchange.com
๐ŸŒ dba.stackexchange.com
July 26, 2021
sql server - Comparing running total with group by - Database Administrators Stack Exchange
If you are using an "over" clause to get your running total, then just make sure you order the data by business unit ... Thanks for your response Dominique. I thought the same, but thought this can be achieved as running total based on group by BusinessUnit, and doing order by linenumber, but ... More on dba.stackexchange.com
๐ŸŒ dba.stackexchange.com
July 8, 2020
๐ŸŒ
LearnSQL.com
learnsql.com โ€บ blog โ€บ what-is-a-running-total-and-how-to-compute-it-in-sql
What Is a Running Total and How Do You Compute It in SQL? | LearnSQL.com
In game 1, we have the gamers 4 and 5; in game 2, we have the gamers 6, 7, and 8. Among each group (a given gamer plays in a given game), rows are sorted by competition_date and the score from each day is summed. In each group, we can observe each gamerโ€™s changing score in a given game. Using a running total value in SQL reports can be very handy, especially for financial specialists.
๐ŸŒ
PopSQL
popsql.com โ€บ learn-sql โ€บ sql-server โ€บ how-to-calculate-cumulative-sum-running-total-in-sql-server
SQL Server: Cumulative Sum/Running Total by Day or Group - PopSQL
select convert(varchar(10), start_date, 105) as day, count(1) from sessions group by convert(varchar(10), start_date, 105); day | count ------------+------- 02-02-2020 | 3 03-02-2020 | 3 04-02-2020 | 4 ยท Next, we'll write a SQL Server common table expression (CTE) and use a window function to keep track of the cumulative sum/running total: with data as ( select convert(varchar(10), start_date, 105) as day, count(1) as number_of_sessions from sessions group by convert(varchar(10), start_date, 105) ) select day, sum(number_of_sessions) over (order by day asc rows between unbounded preceding and current row) from data; day | sum ------------+------- 02-02-2020 | 3 03-02-2020 | 6 04-02-2020 | 10 ยท
๐ŸŒ
Sqlperformance
sqlperformance.com โ€บ home โ€บ best approaches for grouped running totals
Best approaches for grouped running totals - SQLPerformance.com
June 30, 2014 - If you have a better method for arbitrary data population, by all means, don't use my mumblings as an example โ€“ they're peripheral to the point of this post. There are various ways to solve this problem in T-SQL. Here are seven approaches, along with their associated plans. I've left out techniques like cursors (because they will be undeniably slower) and date-based recursive CTEs (because they depend on contiguous days). SELECT LicenseNumber, IncidentDate, TicketAmount, RunningTotal = TicketAmount + COALESCE( ( SELECT SUM(TicketAmount) FROM dbo.SpeedingTickets AS s WHERE s.LicenseNumber = o.LicenseNumber AND s.IncidentDate < o.IncidentDate ), 0) FROM dbo.SpeedingTickets AS o ORDER BY LicenseNumber, IncidentDate;
๐ŸŒ
Silota
silota.com โ€บ sql recipes โ€บ smoothing data โ€บ calculating running total
Calculating Running Total ยท Advanced SQL ยท SILOTA
select date, count(user_id) as count, sum(count(user_id)) over (order by date) as running_total from users_joined group by date order by date; ๐Ÿ‘‹ No fuss, just SQL We are open sourcing everything from the experience working with our agency clients. They spend thousands of dollars to get this ...
Find elsewhere
Top answer
1 of 3
5

This is finally simple to do in SQL Server 2012, where SUM and COUNT support OVER clauses that contain ORDER BY. Using Cris's #Checks table definition:

SELECT
  CompanyID,
  count(*) over (
    partition by CompanyID
    order by Cleared, ID
  ) as cnt,
  str(100.0*sum(Amount) over (
    partition by CompanyID
    order by Cleared, ID
  )/
  sum(Amount) over (
    partition by CompanyID
  ),5,1)+'%' as RunningTotalForThisCompany
FROM #Checks;

SQL Fiddle here.

2 of 3
5

I originally started posting the SQL Server 2012 equivalent (since you didn't mention what version you were using). Steve has done a great job of showing the simplicity of this calculation in the newest version of SQL Server, so I'll focus on a few methods that work on earlier versions of SQL Server (back to 2005).

I'm going to take some liberties with your schema, since I can't figure out what all these #test and #test_3 and #test_4 temporary tables are supposed to represent. How about:

USE tempdb;
GO

CREATE TABLE dbo.Checks
(
  Client VARCHAR(32),
  CheckDate DATETIME,
  Amount DECIMAL(12,2)
);

INSERT dbo.Checks(Client, CheckDate, Amount)
          SELECT 'Company A', '20120101', 50
UNION ALL SELECT 'Company A', '20120102', 75
UNION ALL SELECT 'Company A', '20120103', 120
UNION ALL SELECT 'Company A', '20120104', 40
UNION ALL SELECT 'Company B', '20120101', 75
UNION ALL SELECT 'Company B', '20120105', 200
UNION ALL SELECT 'Company B', '20120107', 90;

Expected output in this case:

Client    Count Running Total
--------- ----- -------------
Company A 1     17.54
Company A 2     43.86
Company A 3     85.96
Company A 4     100.00
Company B 1     20.55
Company B 2     75.34
Company B 3     100.00

One way:

;WITH gt(Client, Totals) AS 
(
  SELECT Client, SUM(Amount) 
    FROM dbo.Checks AS c
    GROUP BY Client
), n (Client, Amount, rn) AS
(
  SELECT c.Client, c.Amount, 
    ROW_NUMBER() OVER  (PARTITION BY c.Client ORDER BY c.CheckDate)
    FROM dbo.Checks AS c
)
SELECT n.Client, [Count] = n.rn, 
  [Running Total] = CONVERT(DECIMAL(5,2), 100.0*(
    SELECT SUM(Amount) FROM n AS n2 
    WHERE Client = n.Client AND rn <= n.rn)/gt.Totals
 )
 FROM n INNER JOIN gt ON n.Client = gt.Client
 ORDER BY n.Client, n.rn;

A slightly faster alternative - more reads but shorter duration and simpler plan:

;WITH x(Client, CheckDate, rn, rt, gt) AS 
(
   SELECT Client, CheckDate, rn = ROW_NUMBER() OVER
   (PARTITION BY Client ORDER BY CheckDate),
    (SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client 
      AND CheckDate <= c.CheckDate),
    (SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client)
FROM dbo.Checks AS c
)
SELECT Client, [Count] = rn, 
  [Running Total] = CONVERT(DECIMAL(5,2), rt * 100.0/gt)
  FROM x
  ORDER BY Client, [Count];

While I've offered set-based alternatives here, in my experience I have observed that a cursor is often the fastest supported way to perform running totals. There are other methods such as the quirky update which perform about marginally faster but the result is not guaranteed. The set-based approach where you perform a self-join becomes more and more expensive as the source row counts go up - so what seems to perform okay in testing with a small table, as the table gets larger, the performance goes down.

I have a blog post almost fully prepared that goes through a slightly simpler performance comparison of various running totals approaches. It is simpler because it is not grouped and it only shows the totals, not the running total percentage. I hope to publish this post soon and will try to remember to update this space.

There is also another alternative to consider that doesn't require reading previous rows multiple times. It's a concept Hugo Kornelis describes as "set-based iteration." I don't recall where I first learned this technique, but it makes a lot of sense in some scenarios.

DECLARE @c TABLE
(
 Client VARCHAR(32), 
 CheckDate DATETIME,
 Amount DECIMAL(12,2),
 rn INT,
 rt DECIMAL(15,2)
);

INSERT @c SELECT Client, CheckDate, Amount,
  ROW_NUMBER() OVER (PARTITION BY Client
 ORDER BY CheckDate), 0
 FROM dbo.Checks;

DECLARE @i INT, @m INT;
SELECT @i = 2, @m = MAX(rn) FROM @c;

UPDATE @c SET rt = Amount WHERE rn = 1;

WHILE @i <= @m
BEGIN
    UPDATE c SET c.rt = c2.rt + c.Amount
      FROM @c AS c
      INNER JOIN @c AS c2
      ON c.rn = c2.rn + 1
      AND c.Client = c2.Client
      WHERE c.rn = @i;

    SET @i = @i + 1;
END

SELECT Client, [Count] = rn, [Running Total] = CONVERT(
  DECIMAL(5,2), rt*100.0 / (SELECT TOP 1 rt FROM @c
 WHERE Client = c.Client ORDER BY rn DESC)) FROM @c AS c;

While this does perform a loop, and everyone tells you that loops and cursors are bad, one gain with this method is that once the previous row's running total has been calculated, we only have to look at the previous row instead of summing all prior rows. The other gain is that in most cursor-based solutions you have to go through each client and then each check. In this case, you go through all clients' 1st checks once, then all clients' 2nd checks once. So instead of (client count * avg check count) iterations, we only do (max check count) iterations. This solution doesn't make much sense for the simple running totals example, but for the grouped running totals example it should be tested against the set-based solutions above. Not a chance it will beat Steve's approach, though, if you are on SQL Server 2012.

UPDATE

I've blogged about various running totals approaches here:

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

๐ŸŒ
Database Star
databasestar.com โ€บ sql-running-total
How to Generate a Running Total in SQL | Database Star: Home
September 11, 2021 - 1SELECT 2order_id, 3sales_date, ... new salesperson. So, to calculate a running total for groups, you can use the PARTITION BY clause of the SUM function....
๐ŸŒ
1Keydata
1keydata.com โ€บ sql โ€บ advanced sql โ€บ running totals
SQL Running Totals | Calculate Cumulative Sums in SQL
The modern approach uses SUM() as a window function: SUM(Sales) OVER (ORDER BY Sales DESC). Without window functions, you can use a self-join that sums all rows with values greater than or equal to the current row. What is the difference between a running total and a regular SUM?
๐ŸŒ
Essential SQL
essentialsql.com โ€บ home โ€บ use sql to calculate a running total
Use SQL to Calculate a Running Total - Essential SQL
March 4, 2023 - Doing so allows us to calculate the running totals. ... SELECT T1.InvoiceID ,T1.TransactionDate ,T1.TransactionAmount ,Sum(T2.TransactionAmount) RunningTotal FROM Sales.CustomerTransactions T1 INNER JOIN Sales.CustomerTransactions T2 ON T1.InvoiceID >= T2.InvoiceID AND T1.TransactionDate = T2.TransactionDate WHERE T1.TransactionTypeID = 1 GROUP BY T1.InvoiceID ,T1.TransactionDate ,T1.TransactionAmount ORDER BY T1.InvoiceID ,T1.TransactionAmount
๐ŸŒ
Interview Query
interviewquery.com โ€บ p โ€บ sql-cumulative-sum-guide
SQL Cumulative SUM: Window Functions, Rolling Totals & Best Practices
March 17, 2026 - WITH daily_sales AS ( SELECT ... a reset flag: mark restocks, build grp = SUM(is_restock) OVER (PARTITION BY product_id ORDER BY date), then run SUM(sales) OVER (PARTITION BY product_id, grp ORDER BY date)....
๐ŸŒ
Oracle
blogs.oracle.com โ€บ sql โ€บ cumulative-running-total-of-previous-rows-with-sql
How to get the cumulative running total of previous rows with SQL
March 2, 2023 - Besides the overall cumulative sum, you might want to get running totals within a group. For example, cumulative salaries per department. To do this add the partition by clause before the sort, like so:
Top answer
1 of 1
1

Answer based on clarifications in comments

The question, really, is giving two data scenarios and saying in the first case, all rows should be returned in a select because when you order the rows by lineID and keep track of the cumulative sum of the Amount value then any time the BusinessUnit value changes if the cumulative sum of the Amount value is not zero, return the rows. In the second scenario, the cumulative sum for Amount is zero every time the Business Unit value changes when ordered by LineID - so no row is returned.

There are two ways to do this.

You can write a cursor that will iterate through your rows one by one and use local variables to store values, track the cumulative sum and note which rows fail the check, then somehow store those rows in a temporary table and return the table contents at the end.

However, as someone noted in comments, the strength in a database lies in set operations - dealing with sets of data at a time. So...

The second way to do this is as a "single" statement. This is really an aggregation of multiple statements - but they are all selects. The approach below makes use of the T-SQL lag function, which can read values from prior rows in an ordered result set. This function requires a partition clause - which allows us to create "windows" on our data - but we don't actually need those windows - we are happy to treat the whole dataset as a single window. So I guess ultimately this also processes rows one by one, but using T-SQL's native function rather than writing our own cursor.

Last note before the solution - you have a column [Value] which contains the word "Apple" on every row. It seems irrelevant to the question, so I have ignored it. If this column affects the behviour you are seeking, you will have to adjust the below SQL suitably to deal with your [Value] column.

Here is the solution - including data setup and tear down for each of your two scenarios.

Important! - The lineID value for the failing rows is in the column priorLineID (and not lineID)

This is because we are "reading behind" with the lag function - so we don't know if the businessUnit has changed until we get to the next row and look back at the prior one. At that time we know the businessUnit changed so we test whether the prior cumulative total was zero and if not, return the current row and provide the priorLineID in its own column. You can expand the SQL to return whatever prior row data values you need.

delete from test;
go

-- TEST CASE 1 - all rows returned because the cumulative total for Amount is not zero 
--  when BusinessUnit changes, when ordered by LineID 

INSERT INTO dbo.Test
SELECT 1, 'ABC', 'Apple', 20.00 UNION
SELECT 2, 'DEF', 'Apple', 40.00 UNION
SELECT 3, 'ABC', 'Apple', -20.00 UNION
SELECT 4, 'DEF', 'Apple', -40.00 

select * from test;

with EXPANDED_DATA as (
select lineID, BusinessUnit, value, amount from test 
union select 999999999,'','',0
),
PARTITIONED_DATA as (
select 
lineID,
BusinessUnit,
AMount,
lag(BusinessUnit,1,'') over (partition by 1 order by lineID) as priorBusinessUnit,
lag(lineID) over (partition by 1 order by lineID) as priorLineID,
lag(amount) over (partition by 1 order by lineID) as priorAmount
 from EXPANDED_DATA
)
,
WITH_PRIOR_CUMULATIVE_AMOUNT as (
select *,
case when priorBusinessUnit = businessUnit then priorAmount + amount else amount end as CumulativeBusinessUnitTotal
from PARTITIONED_DATA
),
WITH_PRIOR_TOTALS as (
select *, 
lag(CumulativeBusinessUnitTotal) over (partition by 1 order by lineID) as priorCumulativeTotal
 from WITH_PRIOR_CUMULATIVE_AMOUNT 
)
select * from WITH_PRIOR_TOTALS
where BusinessUnit <> priorBusinessUnit and priorCumulativeTotal <> 0



delete from test;
go

-- TEST CASE 2 - no rows returned because the cumulative total for Amount is zero 
--  when BusinessUnit changes, when ordered by LineID

INSERT INTO dbo.Test
SELECT 1, 'ABC', 'Apple', 20.00 UNION
SELECT 2, 'ABC', 'Apple', -20.00 UNION
SELECT 3, 'DEF', 'Apple', 40.00 UNION
SELECT 4, 'DEF', 'Apple', -40.00 

select * from test;

with EXPANDED_DATA as (
select lineID, BusinessUnit, value, amount from test 
union select 999999999,'','',0
),
PARTITIONED_DATA as (
select 
lineID,
BusinessUnit,
AMount,
lag(BusinessUnit,1,'') over (partition by 1 order by lineID) as priorBusinessUnit,
lag(lineID) over (partition by 1 order by lineID) as priorLineID,
lag(amount) over (partition by 1 order by lineID) as priorAmount
 from EXPANDED_DATA

)
,
WITH_PRIOR_CUMULATIVE_AMOUNT as (
select *,
case when priorBusinessUnit = businessUnit then priorAmount + amount else amount end as CumulativeBusinessUnitTotal
from PARTITIONED_DATA
),
WITH_PRIOR_TOTALS as (
select *, 
lag(CumulativeBusinessUnitTotal) over (partition by 1 order by lineID) as priorCumulativeTotal
 from WITH_PRIOR_CUMULATIVE_AMOUNT 
)
select * from WITH_PRIOR_TOTALS
where BusinessUnit <> priorBusinessUnit and priorCumulativeTotal <> 0

Original answer below

(Originally the question seemed to be about validating the insert statements at the time of insertion and I basically made the comment that you really can't do that. I will leave this part of the answer below).

If I understand your question correctly, you are saying the first attempt at insert is invalid because of the sequence of the select statements fails a business rule, and that the second attempt at insert is valid because the sequence of select statements passes that business rule. Is this correct?

If so, note that your select statements have been "joined together" (effectively), using union statements. This means your selection of four rows of data is carried out as a single statement - and there are no guarantees about the order in which those select statements will be processed.

The only difference between your first and second insert - as far as the database is concerned - is that the lineID value varies between the two statements, for a given combination of data.

However the more important implication to you is that it seems you want the database to validate something that it is not designed to validate - re-read my comment that there is no real difference between those two inserts, as far as the database is concerned.

Reading between the lines, I am wondering if your list of select statements is being generated by application code? If so, I would suggest your application should be validating the values it is appending to the query. That said, regardless of the order of rows, the net result (as far as the database is concerned) will be the same (notwithstanding the lineID difference).

๐ŸŒ
Stack Overflow
stackoverflow.com โ€บ questions โ€บ 68520216 โ€บ sql-running-total-with-group-by
sql server - SQL: Running total with group by - Stack Overflow
select invoicedate, chargedate, count(*) as cnt_on_dates, sum(count(*)) over (partition by invoicedate order by chargedate) as cumulative_count from t group by invoicedate, chargedate;
๐ŸŒ
CodingSight
codingsight.com โ€บ home โ€บ calculating running total with over clause and partition by clause in sql server
Calculating Running Total with OVER Clause and PARTITION BY Clause in SQL Server
October 13, 2022 - The article provides an example and possible issues of calculating a running total using the OVER clause, partitioning a running total by column values in SQL Server.
๐ŸŒ
Milvus
milvus.io โ€บ home โ€บ ai reference โ€บ how do you calculate running totals in sql?
How do you calculate running totals in SQL?
To calculate running totals in SQL, you typically use window functions with the `SUM()` aggregation and an `OVER` clause