T-SQL Comparison of Min and Max Values Over Time - sql

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

Related

SQL - Return count of consecutive days where value was unchanged

I have a table like
date
ticker
Action
'2022-03-01'
AAPL
BUY
'2022-03-02'
AAPL
SELL.
'2022-03-03'
AAPL
BUY.
'2022-03-01'
CMG
SELL.
'2022-03-02'
CMG
HOLD.
'2022-03-03'
CMG
HOLD.
'2022-03-01'
GPS
SELL.
'2022-03-02'
GPS
SELL.
'2022-03-03'
GPS
SELL.
I want to do a group by ticker then count all the times that Actions have sequentially been the value that they are as of the last date, here it's 2022-03-03. ie for this example table it'd be like;
ticker
NumSequentialDaysAction
AAPL
0
CMG
1
GPS
2
Fine to pass in 2022-03-03 as a value, don't need to figure that out on the fly.
Tried something like this
---Table Creation---
CREATE TABLE UserTable
([Date] DATETIME2, [Ticker] varchar(5), [Action] varchar(5))
;
INSERT INTO UserTable
([Date], [Ticker], [Action])
VALUES
('2022-03-01' , 'AAPL' , 'BUY'),
('2022-03-02' , 'AAPL' , 'SELL'),
('2022-03-03' , 'AAPL' , 'BUY'),
('2022-03-01' , 'CMG' , 'SELL'),
('2022-03-02' , 'CMG' , 'HOLD'),
('2022-03-03' , 'CMG' , 'HOLD'),
('2022-03-01' , 'GPS' , 'SELL'),
('2022-03-02' , 'GPS' , 'SELL'),
('2022-03-03' , 'GPS' , 'SELL')
;
---Attempted Solution---
I'm thinking that I need to do a sub query to get the last value and join on itself to get the matching values. Then apply a window function, ordered by date to see that the proceeding value is sequential.
WITH CTE AS (SELECT Date, Ticker, Action,
ROW_NUMBER() OVER (PARTITION BY Ticker, Action ORDER BY Date) as row_num
FROM UserTable)
SELECT Ticker, COUNT(DISTINCT Date) as count_of_days
FROM CTE
WHERE row_num = 1
GROUP BY Ticker;
WITH CTE AS (SELECT Date, Ticker, Action,
DENSE_RANK() OVER (PARTITION BY Ticker ORDER BY Action,Date) as rank
FROM table)
SELECT Ticker, COUNT(DISTINCT Date) as count_of_days
FROM CTE
WHERE rank = 1
GROUP BY Ticker;
You can do this with the help of the LEAD function like so. You didn't specify which RDBMS you're using. This solution works in PostgreSQL:
WITH "withSequential" AS (
SELECT
ticker,
(LEAD("Action") OVER (PARTITION BY ticker ORDER BY date ASC) = "Action") AS "nextDayIsSameAction"
FROM UserTable
)
SELECT
ticker,
SUM(
CASE
WHEN "nextDayIsSameAction" IS TRUE THEN 1
ELSE 0
END
) AS "NumSequentialDaysAction"
FROM "withSequential"
GROUP BY ticker
Here is a way to do this using gaps and islands solution.
Thanks for sharing the create and insert scripts, which helps to build the solution quickly.
dbfiddle link.
https://dbfiddle.uk/rZLDTrNR
with data
as (
select date
,ticker
,action
,case when lag(action) over(partition by ticker order by date) <> action then
1
else 0
end as marker
from usertable
)
,interim_data
as (
select *
,sum(marker) over(partition by ticker order by date) as grp_val
from data
)
,interim_data2
as (
select *
,count(*) over(partition by ticker,grp_val) as NumSequentialDaysAction
from interim_data
)
select ticker,NumSequentialDaysAction
from interim_data2
where date='2022-03-03'
Another option, you could use the difference between two row_numbers approach as the following:
select [Ticker], count(*)-1 NumSequentialDaysAction -- you could use (distinct) to remove duplicate rows
from
(
select *,
row_number() over (partition by [Ticker] order by [Date]) -
row_number() over (partition by [Ticker], [Action] order by [Date]) grp
from UserTable
where [date] <= '2022-03-03'
) RN_Groups
/* get only rows where [Action] = last date [Action] */
where [Action] = (select top 1 [Action] from UserTable T
where T.[Ticker] = RN_Groups.[Ticker] and [date] <= '2022-03-03'
order by [Date] desc)
group by [Ticker], [Action], grp
See demo

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)

Joining sub-queries to get data for start and end provided dates optimization

I'm using SQL-Server 2008.
I have to select stock of items at provided start date and stock of items at provided end date from 2 warehouses.
This is how I'm selecting QuantityStock for #startDate:
DECLARE #startDate DATE = '20160111'
SELECT *
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN some_table st ON.....
As you see there are 2 similar queries, just selecting from different tables, for that I'm using UNION ALL
Also I'm using [DATE] <= #startDate that because not every day remaining stock is inserting, so for provided date '20160111' there can be no data, so need to select max date where remaining stock is inserted.
With query above a bit slowly, but working fine.
Problem is that I need to do the same with #endDate to get remaining stock for end date. Query is similar as above just instead of #startDate I need to use #endDate.
I've tried to use query above and LEFT JOIN similar query, just with #endDate instead of #startDate in following:
DECLARE #startDate DATE = '20160111',
#endDate DATE = '20165112'
SELECT stock_start.*, stock_end.QuantityStockEnd
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStock, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN (
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_end ON stock_start.Vendor = stock_end.Vendor AND stock_start.ItemNo = stock_end.ItemNo AND stock_start.Company = stock_end.Company
LEFT JOIN some_table st ON.....
In this way I got desired results, but Its execution time so high (about 10x longer than first query only with #startDate). Have you ideas how could I optimize It? It looks like there should be any other, simpler way, without repeating code...
So final results should be:
QuantityStockStart | Vendor | ItemNo | Company | [Date] | QuantityStockEnd
I suggest use of the analytic function ROW_NUMBER() to locate the wanted source table rows. While there is no sample data to test against it is something of a guess but I think you may be able to do this:
SELECT
whab.Vendor
, whab.ItemNo
, whab.Company
, MIN(CASE WHEN whab.start_rn = 1 THEN whab.[Date] END) start_dt
, SUM(CASE WHEN whab.start_rn = 1 THEN whab.QuantityStock END) qty_at_start
, MAX(CASE WHEN whab.end_rn = 1 THEN whab.[Date] END) end_dt
, SUM(CASE WHEN whab.end_rn = 1 THEN whab.QuantityStock END) qty_at_end
FROM (
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseA
UNION ALL
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseB
) whab
WHERE whab.start_rn = 1
OR whab.end_rn = 1
GROUP BY
whab.Vendor
, whab.ItemNo
, whab.Company
Try removing the subquery that follows WHERE [Date]= but keeping the where conditions from that subquery. Change [Date] to MAX([Date]) and remove [Date] from the GROUP BY in the query that used to use the removed subquery.

SQL Server 2008 calculating data difference when we have only one date column

I have a date column Order_date and I am looking for ways to calculate the date difference between customer last order date and his recent previous ( previous form last) order_date ....
Example
Customer : 1, 2 , 1 , 1
Order_date: 01/02/2007, 02/01/2015, 06/02/2014, 04/02/2015
As you can see customer # 1 has three orders.
I want to know the date difference between his recent order date (04/02/2015) and his recent previous (06/02/2014).
For SQL Server 2012 & 2014 you could use LAG with a DATEDIFF to see the number of days between them.
For older versions, a CTE would probably be your best bet:
;WITH CTE AS
(
SELECT CustomerID,
Order_Date,
rn = ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY Order_Date DESC)
)
SELECT c1.CustomerID,
DATEDIFF(d, c1.Order_Date, c2.Order_Date)
FROM CTE c1
INNER JOIN CTE c2 ON c2.rn = c1.rn + 1
In SQL Server 2012+, you can use lag() to get the difference between any two dates:
select t.*,
datediff(day, lag(order_date) over (partition by customer order by order_date),
order_date) as days_dff
from table t;
If you have an older version, you can do something similar with correlated subqueries or outer apply.
EDIT:
If you just want the difference between the two most recent dates, use conditional aggregation instead:
select customer,
datediff(day, max(case when seqnum = 2 then order_date end),
max(case when seqnum = 1 then order_date end)
) as MostRecentDiff
from (select t.*,
row_number() over (partition by customer order by order_date desc) as seqnum
from table t
) t
group by customer;
If you're using SQL Server 2008 or later, you can try CROSS APPLY.
SELECT [customers].[customer_id], DATEDIFF(DAY, MIN([recent_orders].[order_date]), MAX([recent_orders].[order_date])) AS [elapsed]
FROM [customers]
CROSS APPLY (
SELECT TOP 2 [order_date]
FROM [orders]
WHERE ([orders].[customer_id] = [customers].[customer_id])
) [recent_orders]
GROUP BY [customers].[customer_id]
SELECT DATEDIFF(DAY, Y.PrevLastOrderDate, Y.LastOrderDate) AS PreviousDays
FROM
(
SELECT X.LastOrderDate
, (SELECT MAX(OrderDate) FROM dbo.Orders SO WHERE SO.CustomerID=1 AND SO.OrderDate < X.LastOrderDate) AS PrevLastOrderDate
FROM
(
select MAX(OrderDate) AS LastOrderDate
FROM dbo.Orders O
WHERE O.CustomerID=1
)X
)Y
drop table #Invoices
create table #Invoices ( OrderId int , OrderDate datetime )
insert into #Invoices (OrderId , OrderDate )
select 101, '01/01/2001' UNION ALL Select 202, '02/02/2002' UNION ALL Select 303, '03/03/2003'
UNION ALL Select 808, '08/08/2008' UNION ALL Select 909, '09/09/2009'
;
WITH
MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
( OrderId,OrderDate,ROWID) AS
(
SELECT
OrderId,OrderDate
, ROW_NUMBER() OVER ( ORDER BY OrderDate ) as ROWID
FROM
#Invoices inv
)
SELECT
OrderId,OrderDate
,(Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) as PreviousOrderDate
,
[MyDiff] =
CASE
WHEN (Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) iS NULL then 0
ELSE DATEDIFF (mm, OrderDate , (Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) )
END
, ROWIDMINUSONE = (ROWID-1)
, ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDateASecondTimeForConvenience
FROM
MyCTE outerAlias
ORDER BY outerAlias.OrderDate Desc , OrderId

write query without using cursors

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