write query without using cursors - sql

I wrote this query to display single client's account transactions:
select *
from (
select top 1
[id]
,[client_id]
,[transactionDate]
,N'revolving balance' [details]
,NULL [amount]
,NULL [debit]
,NULL [credit]
,[balance]
FROM [dbo].[bsitems]
where [client_id]=#client_id and
[transactionDate] < #transactionDateFrom
order by id desc) t1
union
SELECT [id]
,[client_id]
,[transactionDate]
,[details]
,[amount]
,[debit]
,[credit]
,[balance]
FROM [dbo].[bsitems]
where [client_id]=#client_id and
[transactionDate] between #transactionDateFrom and #transactionDateTo
How to display the transactions for all clients that exists in "client" table? Assume client table structure is (id, name)

For the second part (which is really what the transactions are), just use join:
SELECT b.id, b.client_id, b.transactionDate, b.details,
b.amount, b.debit, b.credit, b.balance
FROM [dbo].[bsitems] b join
clients c
on b.client_id = c.client_id
WHERE transactionDate between #transactionDateFrom and #transactionDateTo;
This assumes that the client_id is stored in the clients table.
The first part of the query is returning the most recent id before the from date. It returns no rows if there are no previous transactions. You can approach this in a similar way.
select id, client_id, transactiondate, details, amount, debit, credit, balance
from (SELECT b.id, b.client_id, b.transactionDate,
N'revolving balance' as b.details,
NULL as b.amount, NULL as b.debit, NULL as b.credit,
b.balance,
row_number() over (partition by client_id order by TransactionDate desc) as seqnum
FROM [dbo].[bsitems] b join
clients c
on b.client_id = c.client_id
WHERE transactionDate < #transactionDateFrom
) t
where seqnum = 1;
Instead of top 1 this is using row_number() to assign a sequential value to the transactions before the cutoff date. Then the most recent of these is chosen.
The final result is just the union all of these two queries:
select id, client_id, transactiondate, details, amount, debit, credit, balance
from (SELECT b.id, b.client_id, b.transactionDate,
N'revolving balance' as b.details,
NULL as b.amount, NULL as b.debit, NULL as b.credit,
b.balance,
row_number() over (partition by client_id order by TransactionDate desc) as seqnum
FROM [dbo].[bsitems] b join
clients c
on b.client_id = c.client_id
WHERE transactionDate < #transactionDateFrom
) t
where seqnum = 1
union all
SELECT b.id, b.client_id, b.transactionDate, b.details,
b.amount, b.debit, b.credit, b.balance
FROM [dbo].[bsitems] b join
clients c
on b.client_id = c.client_id
WHERE transactionDate between #transactionDateFrom and #transactionDateTo;

SELECT *
FROM (
SELECT TOP 1
[id]
,[client_id]
,[transactionDate]
,N'revolving balance' [details]
,NULL [amount]
,NULL [debit]
,NULL [credit]
,[balance]
FROM [dbo].[bsitems]
INNER JOIN [dbo].[client] ON [dbo].[bsitems].[client_id] = [dbo].[client].[id]
AND [transactionDate] < #transactionDateFrom
ORDER BY id DESC
) t1
UNION
SELECT
[id]
,[client_id]
,[transactionDate]
,[details]
,[amount]
,[debit]
,[credit]
,[balance]
FROM [dbo].[bsitems]
INNER JOIN [dbo].[client] ON [dbo].[bsitems].[client_id] = [dbo].[client].[id]
AND [transactionDate] between #transactionDateFrom and #transactionDateTo

Related

Bring next value after condition

I am trying to fetch the next value after the condition is found. In this case, it is a row from 13/05/2021 the result I want to see is the row from 19/05/2021 Cte and CTE1 bring correct results.
I can't figure out what is wrong with my query.
<with cte as
(
select
customerid
,max(timestamp) as [Case Submitted]
,row_number() over (partition by [CustomerId] order by [CustomerId] ,max([timestamp]) desc) as rownum
from Table1
where substatus = 'Case Submitted'
and timestamp > '2021-01-01'
Group by
customerid
,timestamp
)
,CTE2 as
(
Select *
from cte
Where rownum = 1
),
CTE3 as
(
select
PS.customerid
,(PS.timestamp) as [Customer Support]
,row_number() over (partition by PS.customerid order by PS.customerid ) as rownum
from Table1 PS
left join CTE2 C2 on C2.customerid = PS.customerid and C2.[Case Submitted] > PS.timestamp and C2.rownum =1
where status = 'Customer Support'
and timestamp > '2021-01-01'
Group by
PS.customerid
,ps.timestamp
)
Select*
from CTE3>
untested notepad scribble
with CTE1 as
(
select
customerid
, [timestamp] as [Case Submitted]
, rownum = row_number() over (partition by CustomerId order by [timestamp] desc)
from Table1
where substatus = 'Case Submitted'
and [timestamp] > cast('2021-01-01' as date)
),
CTE2 AS
(
select
PS.customerid
, PS.timestamp as [Customer Support]
, rownum = row_number() over (partition by PS.customerid order by PS.timestamp)
from Table1 as PS
join CTE1 as C1
on C1.customerid = PS.customerid
and C1.[Case Submitted] > PS.[timestamp]
and C1.rownum = 1
where PS.status = 'Customer Support'
and [timestamp] > cast('2021-01-01' as date)
)
select *
from CTE2
where rownum = 1

T-SQL Comparison of Min and Max Values Over Time

Working in SQL Server, I have a table with 4 columns
AccountId
AccountName
SubscriptionAmt
DateKey
It lists each company's SubscriptionAmt by month (DateKey, i.e 201801) as they change over time.
I want to write one select statement including
AccountId, AccountName, EarliestSubscriptionAmt, LatestSubscriptionAmt, Difference
I know the effort below is not correct. I know I have to do some kind of partition over in the sub queries but I cant put my finger on it
Any help is appreciated
SELECT
[Account ID],
[Account Name],
b.EarlySub,
c.LatestSub,
(c.LatestSub - b.EarlySub / b.EarlySub) * 100 as PercentageChange
FROM
SubTable AS
LEFT JOIN
(SELECT
[Account ID],
SubscriptionAmt AS EarlySub
FROM
SubTable
WHERE
DateKey = MIN(DateKey)) AS b ON a.[Account ID] = b.[Account ID]
LEFT JOIN
(SELECT
[Account ID],
SubscriptionAmt AS LatestSub
FROM
SubTable
WHERE
DateKey = MAX(DateKey)) AS c ON a.[Account ID] = c.[Account ID]
Sample Data:
AccountId AccountName SubscriptionAmt DateKey
----------------------------------------------------
1 Bob's Store 100 201701
1 Bob's Store 200 201702
1 Bob's Store 300 201703
1 Bob's Store 400 201704
Desired Results:
AccountId AccountName EarliestSubAmt LatestSubAmt PercentageChange
------------------------------------------------------------------------
1 Bob's Store 100 400 300%
FULL demonstration:
DECLARE #TABLE TABLE
(
AccountId VARCHAR(50),
AccountName VARCHAR(50),
SubscriptionAmt INT,
DateKey VARCHAR(50)
)
INSERT INTO #TABLE
VALUES('1','Bob''s Store','100','201701'),('1','Bob''s Store','200','201702'),('1','Bob''s Store','300','201703'),('1','Bob''s Store','400','201704')
;
WITH CTE
AS
(
SELECT AccountId,
AccountName,
SubscriptionAmt,
ROW_NUMBER()OVER(PARTITION BY AccountId,AccountName ORDER BY CAST(DateKey+'01' as DATE)) as ForMin,
ROW_NUMBER()OVER(PARTITION BY AccountId,AccountName ORDER BY CAST(DateKey+'01' as DATE) DESC) as ForMAX
FROM #TABLE
)
SELECT A.AccountId,
A.AccountName,
A.SubscriptionAmt as EarliestSubAmt,
B.SubscriptionAmt as LatestSubAmt,
CAST(((B.SubscriptionAmt - A.SubscriptionAmt)/A.SubScriptionAmt ) as varchar(50)) + '%' as PercentageChange
FROM CTE as A
INNER JOIN CTE as B
ON A.AccountId = B.AccountId
AND A.AccountName = B.AccountName
WHERE A.ForMin = 1 AND B.ForMAX = 1
you can get the Min and Max amounts by first getting the min date and the max date per account id using outer apply, then using a case expression to get the min or max amounts
select [AccountId], [AccountName],
MIN(CASE WHEN DateKey = MinDateKey THEN SubscriptionAmt END) as EarliestSubAmt,
MAX(CASE WHEN DateKey = MaxDateKey THEN SubscriptionAmt END) as LatestSubAmt
from SubTable s
outer apply (
select Min(DateKey) MinDateKey, Max(DateKey) MaxDateKey from SubTable t where s.AccountId = t.AccountId
) t
group by [AccountId], [AccountName]
you can wrap all of this to get the percent change.
select *,
((LatestSubAmt-EarliestSubAmt)/EarliestSubAmt) * 100 as PercentageChange
from (
select [AccountId], [AccountName],
MIN(CASE WHEN DateKey = MinDateKey THEN SubscriptionAmt END) as EarliestSubAmt,
MAX(CASE WHEN DateKey = MaxDateKey THEN SubscriptionAmt END) as LatestSubAmt
from SubTable s
outer apply (
select Min(DateKey) MinDateKey, Max(DateKey) MaxDateKey from SubTable t where s.AccountId = t.AccountId
) t
group by [AccountId], [AccountName]
) s
Please use below query . I have considered Account ID as key value for each store and it will not be duplicated. Please test before implementing into any system.
--Data Prep
create table #Test (
AccountId int,
AccountName varchar(max),
SubscriptionAmt int,
DateKey int
)
insert into #Test
Select 1,'Bobs Store',100,201701
union
select 1,'Bobs Store',200,201702
union
select 1,'Bobs Store',300,201703
union
select 1,'Bobs Store',400,201704
--Actual code ****************************************************
select *,
ROW_NUMBER() over(Partition by Accountid order by datekey asc) MinAm,
ROW_NUMBER() over(Partition by Accountid order by datekey desc) MaxAm into #Final
from #Test
Select *,
((LatestSubAmt-EarliestSubAmt)/EarliestSubAmt)*100 as PercentageChange
From (
select
AccountId,
AccountName,
(select SubscriptionAmt from #Final f2 where f1.AccountId=f2.AccountId and f2.MinAm=1) EarliestSubAmt,
(select SubscriptionAmt from #Final f2 where f1.AccountId=f2.AccountId and f2.MaxAm=1) LatestSubAmt
from #Final f1
Where MinAm=1) A
--********************************************************
If you don't want to use sub query
Select Distinct Accountid,
AccountName,
sum(case when minAm=1 then SubscriptionAmt else 0 END) over (Partition by Accountid) EarliestSubAmt ,
sum(case when maxam=1 then SubscriptionAmt else 0 END) over (Partition by Accountid) LatestSubAmt,
((sum(case when maxam=1 then SubscriptionAmt else 0 END) over (Partition by Accountid)-sum(case when minAm=1 then SubscriptionAmt else 0 END) over (Partition by Accountid))
/sum(case when minAm=1 then SubscriptionAmt else 0 END) over (Partition by Accountid))*100 PercentageChange
FRom (
select *,
ROW_NUMBER() over(Partition by Accountid order by datekey asc) MinAm,
ROW_NUMBER() over(Partition by Accountid order by datekey desc) MaxAm
from #Test
)A

How to find the highest value in a year and in all months

I want to return a year in which was the most contracts made and a month throughout all years - in which month the highest number of contracts is made.
I've tried:
SELECT
cal.CalendarYear
,cal.MonthOfYear
,COUNT(*) AS Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateId
--WHERE (CalendarYear IS NULL) OR (MonthOfYear IS NULL)
GROUP BY
cal.CalendarYear
,cal.MonthOfYear
WITH CUBE
ORDER BY COUNT(*) DESC
and...
--;WITH maxYear AS (
SELECT TOP 1
cal.CalendarYear AS [Year]
,0 AS [Month]
,COUNT(*) AS Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateId
GROUP BY cal.CalendarYear
-- ORDER BY COUNT(*) DESC
--)
UNION ALL
--,maxMonth AS (
SELECT TOP 1
0 AS [Year]
,cal.MonthOfYear AS [Month]
,COUNT(*) AS Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateID
GROUP BY cal.MonthOfYear
ORDER BY COUNT(*) DESC
--)
Any help would be appreciated. Thanks.
This will ORDER BY each portion of the UNION independently, and still have the results joined in one SELECT...
SELECT x.* FROM (
SELECT TOP 1
cal.CalendarYear AS [Year]
,0 AS [Month]
,COUNT(*) AS Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateId
GROUP BY cal.CalendarYear
ORDER BY COUNT(*) DESC
) x
UNION ALL
SELECT x.* FROM (
SELECT TOP 1
0 AS [Year]
,cal.MonthOfYear AS [Month]
,COUNT(*) AS Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateID
GROUP BY cal.MonthOfYear
ORDER BY COUNT(*) DESC
) x
Get the counts per year and month and use row_number to get the year and month with the highest contracts.
SELECT
MAX(CASE WHEN year_rank=1 then Year END) as Highest_contracts_year,
MAX(CASE WHEN year_rank=1 then Year_count END) as Year_Contracts_count
MAX(CASE WHEN month_year_rank=1 then Month END) as Highest_contracts_Month,
MAX(CASE WHEN month_year_rank=1 then Month_Year_count END) as MonthYear_Contracts_count
FROM (SELECT T.*
,ROW_NUMBER() OVER(ORDER BY Year_Cnt DESC) as Year_rank
,ROW_NUMBER() OVER(ORDER BY Month_Year_Cnt DESC) as Month_Year_rank
FROM (SELECT
cal.CalendarYear AS [Year]
,cal.MonthOfYear AS [Month]
,COUNT(*) OVER(PARTITION BY cal.CalendarYear) AS Year_Cnt
,COUNT(*) OVER(PARTITION BY cal.MonthOfYear) AS Month_Year_Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateId
) T
) T
You should specify what needs to be done when there are ties for highest counts. Assuming you need all highest count rows when there are ties, use
With ranks as
(SELECT T.*
,RANK() OVER(ORDER BY Year_Cnt DESC) as Year_rank
,RANK() OVER(ORDER BY Month_Year_Cnt DESC) as Month_Year_rank
FROM (SELECT
cal.CalendarYear AS [Year]
,cal.MonthOfYear AS [Month]
,COUNT(*) OVER(PARTITION BY cal.CalendarYear) AS Year_Cnt
,COUNT(*) OVER(PARTITION BY cal.MonthOfYear) AS Month_Year_Cnt
FROM dim.Application app
JOIN dim.Calendar cal ON app.ApplicationDateID = cal.DateId
) T
)
SELECT *
FROM (SELECT DISTINCT Year,Year_Cnt FROM RANKS WHERE Year_rank=1) ry
CROSS JOIN (SELECT DISTINCT Month,Month_Year_Cnt FROM RANKS WHERE Month_Year_rank=1) rm
EDIT: This might be what you want, unless you want it on single line:
select calendaryear AS 'year/month', cnt from (
SELECT TOP 1
cal.CalendarYear
,COUNT(*) AS Cnt
FROM dim.Application AS app
JOIN dim.Calendar AS cal ON app.ApplicationDateID = cal.DateId
GROUP BY
cal.CalendarYear
ORDER BY COUNT(*) DESC
) as year
UNION ALL
select MonthOfYear, Cnt FROM (
SELECT TOP 1
cal.CalendarYear
,cal.MonthOfYear
,COUNT(*) AS Cnt
FROM dim.Application AS app
JOIN dim.Calendar AS cal ON app.ApplicationDateID = cal.DateId
GROUP BY
cal.CalendarYear
,cal.MonthOfYear
ORDER BY COUNT(*) DESC
) AS month
It returns following result where month 3 is in fact 2016:
year/month cnt
2017 4
3 2
I have used following data as input
create table #calendar (DateId int, calendaryear int, monthofyear int)
create table #application (applicationdateId int)
insert into #calendar values (1,2017,01)
insert into #calendar values (2,2017,02)
insert into #calendar values (3,2017,03)
insert into #calendar values (4,2016,01)
insert into #calendar values (5,2016,03)
insert into #application values (1)
insert into #application values (1)
insert into #application values (2)
insert into #application values (3)
insert into #application values (4)
insert into #application values (5)
insert into #application values (5)

FIFO Balance at every month end with sql

I am trying to get the balance quantity at the end of every month i.e. running total at every month end with FIFO only but its not showing any result. Pls help
Here is my query.
declare #Stock table (Item char(3) not null,Date date not null,TxnType varchar(3) not null,Qty int not null,Price decimal(10,2) null)
insert into #Stock(Item , [Date] , TxnType, Qty, Price) values
('ABC','20120401','IN', 200, 750.00),
('ABC','20120402','OUT', 100 ,null ),
('ABC','20120403','IN', 50, 700.00),
('ABC','20120404','IN', 75, 800.00),
('ABC','20120405','OUT', 175, null ),
('XYZ','20120406','IN', 150, 350.00),
('XYZ','20120407','OUT', 120 ,null ),
('XYZ','20120408','OUT', 10 ,null ),
('XYZ','20120409','IN', 90, 340.00),
('ABC','20120510','IN', 200, 750.00),
('ABC','20120511','OUT', 100 ,null ),
('ABC','20120512','IN', 50, 700.00),
('ABC','20120513','IN', 75, 800.00),
('ABC','20120514','OUT', 175, null ),
('XYZ','20120515','IN', 150, 350.00),
('XYZ','20120516','OUT', 120 ,null ),
('XYZ','20120517','OUT', 10 ,null ),
('XYZ','20120518','IN', 90, 340.00);
;WITH OrderedIn as (
select *,ROW_NUMBER() OVER (PARTITION BY month(date) ORDER BY DATE) as rn
from #Stock
where TxnType = 'IN'
), RunningTotals as (
select Item,Qty,Price,Qty as Total,0 as PrevTotal,rn from OrderedIn where rn = 1
union all
select rt.Item,oi.Qty,oi.Price,rt.Total + oi.Qty,rt.Total,oi.rn
from
RunningTotals rt
inner join
OrderedIn oi
on
rt.Item = oi.Item and
rt.rn = oi.rn - 1
), TotalOut as (
select Item,SUM(Qty) as Qty from #Stock where TxnType='OUT' group by Item
)
select
rt.Item,SUM(CASE WHEN PrevTotal > out.Qty THEN rt.Qty ELSE rt.Total - out.Qty END * Price)
from
RunningTotals rt
inner join
TotalOut out
on
rt.Item = out.Item
where
rt.Total > out.Qty
group by rt.Item
Output
Month Item (No column name(qty*price))
4 ABC 40000
4 XYZ 37600
5 ABC 77500
5 XYZ 76100
With you sample data.Are you looking for this,else clearly sketch your desired output .
;WITH CTE AS
(
select Item ,[Date] ,TxnType ,s.Qty
,Price ,ROW_NUMBER() OVER (PARTITION BY month(date),TxnType ORDER BY DATE) as rn
from #Stock S
)
,
CTE1 AS
(
SELECT ITEM ,[DATE] ,TXNTYPE ,QTY,MONTH(DATE) MONTHDT,RN FROM CTE WHERE RN=1
UNION ALL
SELECT S.ITEM ,S.[DATE] ,S.TXNTYPE ,S.QTY+A.QTY,A.MONTHDT ,S.RN
FROM CTE S INNER JOIN CTE1 A
ON S.TXNTYPE=A.TXNTYPE AND MONTH(S.[DATE])=A.MONTHDT AND S.RN-A.RN=1
)
,CTE2 AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY month(date),TxnType ORDER BY QTY DESC )RN1
FROM CTE1 S
)
SELECT A.DATE,A.QTY-(SELECT B.Qty from CTE2 B
where a.MONTHDT=b.MONTHDT and b.RN1=1 AND b.TxnType='OUT')
from CTE2 A
WHERE A.RN1=1 AND a.TxnType='IN'
Try this.
;WITH cte
AS (SELECT Row_number()OVER(partition BY item, Datepart(mm, date) ORDER BY date DESC) rn,
item,
Month(date) AS [month],
TxnType,
Price,
date
FROM #Stock
WHERE TxnType = 'IN'),
cte1
AS (SELECT Row_number() OVER(partition BY item, Datepart(mm, date) ORDER BY date DESC) rn,
item,
Month(date) [month],
(SELECT Sum(CASE
WHEN TxnType = 'out' THEN -1 * qty
ELSE qty
END) AS qty
FROM #Stock b
WHERE a.item = b.item
AND a.Date >= b.Date) qty
FROM #Stock a)
SELECT a.[month],
a.Item,
( a.Price * b.qty ) running_tot
FROM cte a
JOIN cte1 b
ON a.Item = b.Item
AND a.[month] = b.[month]
WHERE a.rn = 1
AND b.rn = 1

Getting the value of a previous record using ROW_NUMBER() in SQL Server

Hopefully this is easy enough for those more experienced in SQL Server.
I have a table to customer loan activity data which is updated whenever an action happens on their account. For example if their limit is increased, a new record will be created with their new limit. I want to be able to create a listing of their activity where the activity amount is their new limit subtracting whatever their previous limit was.
At the moment I have the following but I'm struggling to work out how to access that previous record.
SELECT
CUSTOMER
,LEDGER
,ACCOUNT
,H.AMOUNT - COALESCE(X.AMOUNT, 0)
FROM
dbo.ACTIVITY H WITH (NOLOCK)
LEFT OUTER JOIN
(SELECT
CUSTOMER
,LEDGER
,ACCOUNT
,ACTIVITY_DATE
,AMOUNT
,ROW_NUMBER() OVER (PARTITION BY CUSTOMER, LEDGER, ACCOUNT ORDER BY ACTIVITY_DATE ASC) AS ROW_NUMBER
FROM
dbo.ACTIVITY WITH (NOLOCK)) X ON H.CUSTOMER = X.CUSTOMER
AND H.LEDGER = X.LEDGER
AND H.ACCOUNT = X.ACCOUNT
So basically I only want to subtract x.amount if it's the previous record but I'm not sure how to do this when I don't know what day it happened.
I thought Row_Number() would help me but I'm still a bit stumped.
Hope you hear from you all soon :)
Cheers
Here's a query that will only pass through dbo.Activity ONCE
SELECT H.CUSTOMER
,H.LEDGER
,H.ACCOUNT
,MAX(H.ACTIVITY_DATE) ACTIVITY_DATE
,SUM(CASE X.I WHEN 1 THEN AMOUNT ELSE -AMOUNT END) AMOUNT
FROM (SELECT CUSTOMER
,LEDGER
,ACCOUNT
,ACTIVITY_DATE
,AMOUNT
,ROW_NUMBER() OVER (PARTITION BY CUSTOMER, LEDGER, ACCOUNT ORDER BY ACTIVITY_DATE DESC) AS ROW_NUMBER
FROM dbo.ACTIVITY WITH (NOLOCK)
) H
CROSS JOIN (select 1 union all select 2) X(I)
WHERE ROW_NUMBER - X.I >= 0
GROUP BY H.CUSTOMER
,H.LEDGER
,H.ACCOUNT
,ROW_NUMBER - X.I;
And here's the DDL/DML for some data I used to test
CREATE TABLE dbo.ACTIVITY(CUSTOMER int, LEDGER int, ACCOUNT int, ACTIVITY_DATE datetime, AMOUNT int)
INSERT dbo.ACTIVITY select
1,2,3,GETDATE(),123 union all select
1,2,3,GETDATE()-1,16 union all select
1,2,3,GETDATE()-2,12 union all select
1,2,3,GETDATE()-3,1 union all select
4,5,6,GETDATE(),1000 union all select
4,5,6,GETDATE()-6,123 union all select
7,7,7,GETDATE(),99;
Alternatives
A more traditional approach using a subquery to get the previous row:
SELECT CUSTOMER, LEDGER, ACCOUNT, ACTIVITY_DATE,
AMOUNT - ISNULL((SELECT TOP(1) I.AMOUNT
FROM dbo.ACTIVITY I
WHERE I.CUSTOMER = O.CUSTOMER
AND I.LEDGER = O.LEDGER
AND I.ACCOUNT = O.ACCOUNT
AND I.ACTIVITY_DATE < O.ACTIVITY_DATE
ORDER BY I.ACTIVITY_DATE DESC), 0) AMOUNT
FROM dbo.ACTIVITY O
ORDER BY CUSTOMER, LEDGER, ACCOUNT, ACTIVITY_DATE;
Or ROW_NUMBER() the data twice and join between them
SELECT A.CUSTOMER, A.LEDGER, A.ACCOUNT, A.ACTIVITY_DATE,
A.AMOUNT - ISNULL(B.AMOUNT,0) AMOUNT
FROM (SELECT *, RN=ROW_NUMBER() OVER (partition by CUSTOMER, LEDGER, ACCOUNT
order by ACTIVITY_DATE ASC)
FROM dbo.ACTIVITY) A
LEFT JOIN (SELECT *, RN=ROW_NUMBER() OVER (partition by CUSTOMER, LEDGER, ACCOUNT
order by ACTIVITY_DATE ASC)
FROM dbo.ACTIVITY) B ON A.CUSTOMER = B.CUSTOMER
AND A.LEDGER = B.LEDGER
AND A.ACCOUNT = B.ACCOUNT
AND B.RN = A.RN-1 -- prior record
ORDER BY A.CUSTOMER, A.LEDGER, A.ACCOUNT, A.ACTIVITY_DATE;