Why is this select so slow (SqlServer)? - sql

We have 3 nested selects that are each creating temporary table. The outer two go very fast. But the inner one (below takes about 1/4 second sometimes to execute. It's creating a table with 7 rows, each holding a date:
declare #StartDate datetime
declare #EndDate datetime
select #StartDate = cast(#Weeks_Loop_TheDate as date), #EndDate = cast((#Weeks_Loop_TheDate + 6) as date)
declare #temp3 table
(
TheDate datetime
)
while (#StartDate<=#EndDate)
begin
insert into #temp3
values (#StartDate )
select #StartDate=DATEADD(dd,1,#StartDate)
end
select * from #temp3
The params are set with a DateTime variable so the cast shouldn't be significant. And the populating should be trivial and fast. So any ideas why it's slow?
And is there a better way to do this? We need to get back a result set that is 7 dates in this range.
thanks - dave

Wouldn't this work? Loops/cursors are slow in SQL Server compared to set operations.
DECLARE #StartDate DATE = '2017-05-03';
SELECT DATEADD(DAY, RowNr, #StartDate)
FROM (SELECT ROW_NUMBER () OVER (ORDER BY object_id) - 1 AS RowNr FROM sys.objects) AS T
WHERE T.RowNr < 7;
Subquery will generate a sequence of numbers from 0 to n (amount of objects you have in database, it's always going to be more than 7, and if not, you can just CROSS JOIN inside).
Then just use DATEADD to add these generated numbers.
And finally limit amount of days you want to add in your WHERE clause.
And if you're going to use this quite often, you can wrap it up in a Inline Table-Valued Function.
CREATE FUNCTION dbo.DateTable (
#p1 DATE,
#p2 INT)
RETURNS TABLE
AS RETURN
SELECT DATEADD(DAY, RowNr, #p1) AS TheDate
FROM (SELECT ROW_NUMBER () OVER (ORDER BY object_id) - 1 AS RowNr FROM sys.objects) AS T
WHERE T.RowNr < #p2;
GO
And then query it like that:
SELECT *
FROM dbo.DateTable ('2017-05-03', 7);
Result in both cases:
+------------+
| TheDate |
+------------+
| 2017-05-03 |
| 2017-05-04 |
| 2017-05-05 |
| 2017-05-06 |
| 2017-05-07 |
| 2017-05-08 |
| 2017-05-09 |
+------------+
Yet another useful tool is a Numbers table. It can be created just like that (source: http://dataeducation.com/you-require-a-numbers-table/) :
CREATE TABLE Numbers
(
Number INT NOT NULL,
CONSTRAINT PK_Numbers
PRIMARY KEY CLUSTERED (Number)
WITH FILLFACTOR = 100
)
INSERT INTO Numbers
SELECT
(a.Number * 256) + b.Number AS Number
FROM
(
SELECT number
FROM master..spt_values
WHERE
type = 'P'
AND number <= 255
) a (Number),
(
SELECT number
FROM master..spt_values
WHERE
type = 'P'
AND number <= 255
) b (Number)
GO
Then you would not have to use ROW_NUMBER() and your function could be as follows:
ALTER FUNCTION dbo.DateTable (
#p1 DATE,
#p2 INT)
RETURNS TABLE
AS RETURN
SELECT DATEADD(DAY, Number, #p1) AS TheDate
FROM Numbers
WHERE Number < #p2;
GO
This is going to work like a charm and Numbers table could be reused in many other scenarios where you need a sequence of numbers to do some sort of calculations.

It shouldn't take more than a millisecond to run your script. There must be a server issue that requires investigation.
That said, this operation can be done as a more efficient set-based operation instead of looping. The example below uses a CTE to generate the number sequence. A utility numbers table facilitates set-based processing like this so I suggest you create a permanent table with a sequence of numbers (with number as the primary key) to improve performance further.
DECLARE #StartDate date = #Weeks_Loop_TheDate;
WITH numbers(n) AS (
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) - 1 FROM (VALUES(0),(0),(0),(0),(0),(0),(0)) AS a(b)
)
SELECT DATEADD(day, n, #StartDate)
FROM numbers
ORDER BY n;

Rather than using a variable such as #temp use a temp table (#) instead. The query analyser doesn't do optimizing well when using #temp.

Related

How to calculate average in date column

I don't know how to calculate the average age of a column of type date in SQL Server.
You can use datediff() and aggregation. Assuming that your date column is called dt in table mytable, and that you want the average age in years over the whole table, then you would do:
select avg(datediff(year, dt, getdate())) avg_age
from mytable
You can change the first argument to datediff() (which is called the date part), to any other supported value depending on what you actually mean by age; for example datediff(day, dt, getdate()) gives you the difference in days.
First, lets calculate the age in years correctly. See the comments in the code with the understanding that DATEDIFF does NOT calculate age. It only calculates the number of temporal boundaries that it crosses.
--===== Local obviously named variables defined and assigned
DECLARE #StartDT DATETIME = '2019-12-31 23:59:59.997'
,#EndDT DATETIME = '2020-01-01 00:00:00.000'
;
--===== Show the difference in milliseconds between the two date/times
-- Because of the rounding that DATETIME does on 3.3ms resolution, this will return 4ms,
-- which certainly does NOT depict an age of 1 year.
SELECT DATEDIFF(ms,#StartDT,#EndDT)
;
--===== This solution will mistakenly return an age of 1 year for the dates given,
-- which are only about 4ms apart according the SELECT above.
SELECT IncorrectAgeInYears = DATEDIFF(YEAR, #StartDT, #EndDT)
;
--===== This calulates the age in years correctly in T-SQL.
-- If the anniversary data has not yet occurred, 1 year is substracted.
SELECT CorrectAgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Now, lets turn that correct calculation into a Table Valued Function that returns a single scalar value producing a really high speed "Inline Scalar Function".
CREATE FUNCTION [dbo].[AgeInYears]
(
#StartDT DATETIME, --Date of birth or date of manufacture or start date.
#EndDT DATETIME --Usually, GETDATE() or CURRENT_TIMESTAMP but
--can be any date source like a column that has an end date.
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT AgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Then, to Dale's point, let's create a test table and populate it. This one is a little overkill for this problem but it's also useful for a lot of different examples. Don't let the million rows scare you... this runs in just over 2 seconds on my laptop including the Clustered Index creation.
--===== Create and populate a large test table on-the-fly.
-- "SomeInt" has a range of 1 to 50,000 numbers
-- "SomeLetters2" has a range of "AA" to "ZZ"
-- "SomeDecimal has a range of 10.00 to 100.00 numbers
-- "SomeDate" has a range of >=01/01/2000 & <01/01/2020 whole dates
-- "SomeDateTime" has a range of >=01/01/2000 & <01/01/2020 Date/Times
-- "SomeRand" contains the value of RAND just to show it can be done without a loop.
-- "SomeHex9" contains 9 hex digits from NEWID()
-- "SomeFluff" is a fixed width CHAR column just to give the table a little bulk.
SELECT TOP 1000000
SomeInt = ABS(CHECKSUM(NEWID())%50000) + 1
,SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
+ CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
,SomeDecimal = CAST(RAND(CHECKSUM(NEWID())) * 90 + 10 AS DECIMAL(9,2))
,SomeDate = DATEADD(dd, ABS(CHECKSUM(NEWID())%DATEDIFF(dd,'2000','2020')), '2000')
,SomeDateTime = DATEADD(dd, DATEDIFF(dd,0,'2000'), RAND(CHECKSUM(NEWID())) * DATEDIFF(dd,'2000','2020'))
,SomeRand = RAND(CHECKSUM(NEWID())) --CHECKSUM produces an INT and is MUCH faster than conversion to VARBINARY.
,SomeHex9 = RIGHT(NEWID(),9)
,SomeFluff = CONVERT(CHAR(170),'170 CHARACTERS RESERVED') --Just to add a little bulk to the table.
INTO dbo.JBMTest
FROM sys.all_columns ac1 --Cross Join forms up to a 16 million rows
CROSS JOIN sys.all_columns ac2 --Pseudo Cursor
;
GO
--===== Add a non-unique Clustered Index to SomeDateTime for this demo.
CREATE CLUSTERED INDEX IXC_Test ON dbo.JBMTest (SomeDateTime ASC)
;
Now, lets find the average age of those million represented by the SomeDateTime column.
SELECT AvgAgeInYears = AVG(age.AgeInYears )
,RowsCounted = COUNT(*)
FROM dbo.JBMTest tst
CROSS APPLY dbo.AgeInYears(SomeDateTime,GETDATE()) age
;
Results:

Autofill SQL table field with Datetime value based on input parameter

I have a datetime field in my SQL table. I have created a procedure that takes count as a variable and generates records for the table. If the count is 5 it will generate 5 records.The logic i want is that when i provide 5 as an input parameter the datetime field in the table should be autofilled with values
12/20/2015 9:00
12/20/2015 11:00
12/20/2015 13:00
12/20/2015 15:00
12/20/2015 17:00
So every time a record is inserted into a table,the 2 hours of time should be added.
Recursive CTEs are one way to create records on the fly. The key here is to create an anchor (this is the first SELECT inside the CTE, which is your starting point). And an exit check (which is the WHERE clause).
Read up on MAXRECURSION if you want to create more than 100 records at a time.
Example
DECLARE #RecordsRequired INT = 5;
DECLARE #BaseDateTime SMALLDATETIME = GETDATE();
WITH [Sample] AS
(
/* This CTE uses recursion to create the required number of
* records.
*/
SELECT
1 AS RowNumber,
#BaseDateTime AS [DateTime]
UNION ALL
SELECT
RowNumber + 1 AS RowNumber,
DATEADD(HOUR, 2, [DateTime]) AS [DateTime]
FROM
[Sample]
WHERE
RowNumber < #RecordsRequired
)
SELECT
RowNumber,
[DateTime]
FROM
[Sample]
;
You could also look into WHILE blocks.
Use this code:
------------------ INPUT ------------------------
declare #start_date datetime = '01/01/2000 14:00'
declare #loops int = 5
-------------------------------------------------
declare #i int = 0
while (#i < #loops) begin
select dateadd(hour, #i * 2, #start_date)
set #i = #i + 1
end
Try this without LOOP
Declare #count int = 5,
#incrementer int =2 -- in case if you want to change the incrementer
SELECT Dateadd(hh, num * #incrementer, dates)
FROM (SELECT Cast(CONVERT(VARCHAR(20), Dateadd(dd, 1, Getdate()), 111)
+ ' 9:00 AM' AS DATETIME) AS Dates,
num
FROM (VALUES(0),(1),(2),(3),(4),(5)) TC (num)) A
WHERE num <= #count - 1
SQL FIDDLE DEMO
Please find the sample code below, it contains the logic that you needed. Hope it helps!!
--Create a temp table for sample output
CREATE TABLE #temp
(
CreatedDate datetime
)
--Declaring variables
DECLARE #Count int
DECLARE #TimeCounter int
--intializing values
SET #Count=5
SET #TimeCounter=0
WHILE(#Count>0)
BEGIN
--SELECT getdate()+1
insert into #temp(#temp.CreatedDate) Select DATEADD(hour,#TimeCounter,getdate())
SET #TimeCounter=#TimeCounter+2
SET #Count=#Count-1
END
--Final values
SELECT * FROM #temp tmp
--Dropping table
DROP TABLE #temp
This is one of those problems that's best solved with a numbers table / function. Much less code than recursion or loops, usually faster for anything non-trivial and more reusable too.
The core code you want is
CREATE PROCEDURE usp_PopulateAppointments
(
#StartDateTime datetime2(3),
#Records int,
#Interval int = 120 --Time between appointment slots in minutes. Default to 2h if not manually specified.
)
INSERT INTO Appointments
SELECT
DATEADD(m, #Interval * Number, #StartDateTime)
FROM dbo.udfNumbers(0, #Recs)
I've assumed in this a numbers function that takes #StartAt and #NumberResults. I use one derived from Adam's final code in the comments on http://dataeducation.com/you-require-a-numbers-table/ - in my experience it's faster than a real table, and takes less space too.
Create Table dates
(
datetimefield datetime not null
)
go
Create Procedure FillDateTimeField
#insertxrows int
AS
begin
Declare #LastDateTimeInserted as datetime
set #LastDateTimeInserted = (select isnull(max(datetimefield),getdate()) from Dates)
;WITH norows AS (
SELECT 1 as num, Dateadd(hour,2,#LastDateTimeInserted) as FirstRecord
UNION ALL
SELECT num + 1, dateadd(hour,2,firstrecord) FROM
norows
WHERE num < #insertxrows
)
insert into dates
select firstrecord from norows
end

Multiple Results of Scalar to select

I have a table of dates that is passed into a scalar function:
select getVal('1/1/1') returns a float.
I'm looping through my table of dates and setting #date,
then saving the result to #result and then performing a log on that value.
How can I get multiple results (with multiple scalar calls) without doing a loop? I've been told to try to stay away from doing loops inside of sql, and that there's a better way to perform this operation.
Example:
#DateTable
ID Dates
1 1/1/1
2 1/3/1
3 1/4/1
4 1/2/2
5 1/3/2
6 4/15/12
set #k = 1
while #k <= (select count(ID) from #DateTable)
begin
set #date = select Dates from #DateTable where ID = #k
set #result = (select mySchema.getVal(#date))
insert into #ResTable(result,logged) values (#result, LOG(#result))
set #k = #k + 1
end
You can simplify this to:
INSERT #ResTable (Result, Logged)
SELECT d.Result, LOG(Result)
FROM ( SELECT Result = mySchema.getVal(Dates)
FROM #DateTable
) AS d;
I suspect if you show what mySchema.getVal() does, it can probably be made more efficient still.
To answer the comment - If you are using SQL Server 2012 or newer you can use the LEAD/LAG functions, something like:
SELECT Dates,
PreviousDate = LAG(Dates) OVER (ORDER BY Dates),
NextDate = LEAD(Dates) OVER (ORDER BY Dates)
FROM #DateTable;
If you are using earlier versions you will need to use APPLY:
SELECT Dates,
PreviousDate = prev.Dates
FROM #DateTable AS dt
OUTER APPLY
( SELECT TOP 1 Dates
FROM #DateTable AS Prev
WHERE Prev.Dates < dt.Dates
ORDER BY Dates DESC
) AS prev;

What's an easy way to populate a database through an sql query from a single entry?

We're looking to populate a number of tables for testing. The data can all be the same except for a timestamp column. Other columns in the row contain complicated data, like objects in xml format and other messy stuff, so we don't want to have to remake it. What would be an easy way to expand a single entry to thousands, by incrementing a timestamp by a constant interval, through a sql query.
Our current idea (in a C-ish pseudocode) is to:
Get the latest (likely only) row and store it in a variable "thisRow"
While(thisRow->time < endTime)
{
Increment thisRow->time by a constant variable
Insert thisRow
}
We are using postgres
I like to keep a Numbers table in my databases for certain queries. If you had one then you could use the single statement below. If you don't have one, it's easy enough to generate one as a temporary table or permanent table.
INSERT INTO Test_Table
(
col1,
col2,
...,
my_timestamp
)
SELECT
ST.col1,
ST.col2,
...,
DATEADD(mi, N.number, ST.my_timestamp)
FROM
Source_Table ST
INNER JOIN Numbers N ON
N.number BETWEEN 1 AND 1000 -- Change this to what you want
This is for SQL Server. The DATEADD function may be different in your RDBMS. You can also add in a random function if you don't want the intervals to be exactly one minute and you could of course change the intervals to be hours, days, or whatever. You could also use a simple equation if you wanted say two hours per interval.
Using SQL SERVER 2005+ you can make use of CTE statements
Have a look at this
DECLARE #StartTime DATETIME,
#EndTime DATETIME
SELECT #StartTime = '01 Jan 2009',
#EndTime = '31 Jan 2009'
DECLARE #Table TABLE(
DateVal DATETIME,
Col1 INT
)
INSERT INTO #Table SELECT #StartTime, 1
SELECT *
FROM #Table
;WITH CTE AS (
SELECT #StartTime StartTime
UNION ALL
SELECT DATEADD(dd, 1, StartTime) StartTime
FROM CTE
WHERE StartTime < #EndTime
)
SELECT CTE.*,
t.Col1
FROM CTE,
#Table t
If you are using a database that supports recursion, for example Microsoft SQL Server, you can use something like:
WITH T AS (
SELECT 1 AS r
UNION ALL
SELECT r + 1 AS r
FROM T
WHERE r < 100 -- Or however many rows you need
)
INSERT INTO TestTable (Col1, Col2,...)
SELECT s.Col1, s.Col2,...
FROM SourceTable s
CROSS JOIN T

How to Determine Values for Missing Months based on Data of Previous Months in T-SQL

I have a set of transactions occurring at specific points in time:
CREATE TABLE Transactions (
TransactionDate Date NOT NULL,
TransactionValue Integer NOT NULL
)
The data might be:
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('1/1/2009', 1)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('3/1/2009', 2)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('6/1/2009', 3)
Assuming that the TransactionValue sets some kind of level, I need to know what the level was between the transactions. I need this in the context of a set of T-SQL queries, so it would be best if I could get a result set like this:
Month Value
1/2009 1
2/2009 1
3/2009 2
4/2009 2
5/2009 2
6/2009 3
Note how, for each month, we either get the value specified in the transaction, or we get the most recent non-null value.
My problem is that I have little idea how to do this! I'm only an "intermediate" level SQL Developer, and I don't remember ever seeing anything like this before. Naturally, I could create the data I want in a program, or using cursors, but I'd like to know if there's a better, set-oriented way to do this.
I'm using SQL Server 2008, so if any of the new features will help, I'd like to hear about it.
P.S. If anyone can think of a better way to state this question, or even a better subject line, I'd greatly appreciate it. It took me quite a while to decide that "spread", while lame, was the best I could come up with. "Smear" sounded worse.
I'd start by building a Numbers table holding sequential integers from 1 to a million or so. They come in really handy once you get the hang of it.
For example, here is how to get the 1st of every month in 2008:
select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
from Numbers
where n <= 12;
Now, you can put that together using OUTER APPLY to find the most recent transaction for each date like so:
with Dates as (
select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
from Numbers
where n <= 12
)
select d.firstOfMonth, t.TransactionValue
from Dates d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.firstOfMonth
order by TransactionDate desc
) t;
This should give you what you're looking for, but you might have to Google around a little to find the best way to create the Numbers table.
here's what i came up with
declare #Transactions table (TransactionDate datetime, TransactionValue int)
declare #MinDate datetime
declare #MaxDate datetime
declare #iDate datetime
declare #Month int
declare #count int
declare #i int
declare #PrevLvl int
insert into #Transactions (TransactionDate, TransactionValue)
select '1/1/09',1
insert into #Transactions (TransactionDate, TransactionValue)
select '3/1/09',2
insert into #Transactions (TransactionDate, TransactionValue)
select '5/1/09',3
select #MinDate = min(TransactionDate) from #Transactions
select #MaxDate = max(TransactionDate) from #Transactions
set #count=datediff(mm,#MinDate,#MaxDate)
set #i=1
set #iDate=#MinDate
while (#i<=#count)
begin
set #iDate=dateadd(mm,1,#iDate)
if (select count(*) from #Transactions where TransactionDate=#iDate) < 1
begin
select #PrevLvl = TransactionValue from #Transactions where TransactionDate=dateadd(mm,-1,#iDate)
insert into #Transactions (TransactionDate, TransactionValue)
select #iDate, #prevLvl
end
set #i=#i+1
end
select *
from #Transactions
order by TransactionDate
To do it in a set-based way, you need sets for all of your data or information. In this case there's the overlooked data of "What months are there?" It's very useful to have a "Calendar" table as well as a "Number" table in databases as utility tables.
Here's a solution using one of these methods. The first bit of code sets up your calendar table. You can fill it using a cursor or manually or whatever and you can limit it to whatever date range is needed for your business (back to 1900-01-01 or just back to 1970-01-01 and as far into the future as you want). You can also add any other columns that are useful for your business.
CREATE TABLE dbo.Calendar
(
date DATETIME NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (date)
)
INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-01', 1) -- New Year
INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-02', 1)
...
Now, using this table your question becomes trivial:
SELECT
CAST(MONTH(date) AS VARCHAR) + '/' + CAST(YEAR(date) AS VARCHAR) AS [Month],
T1.TransactionValue AS [Value]
FROM
dbo.Calendar C
LEFT OUTER JOIN dbo.Transactions T1 ON
T1.TransactionDate <= C.date
LEFT OUTER JOIN dbo.Transactions T2 ON
T2.TransactionDate > T1.TransactionDate AND
T2.TransactionDate <= C.date
WHERE
DAY(C.date) = 1 AND
T2.TransactionDate IS NULL AND
C.date BETWEEN '2009-01-01' AND '2009-12-31' -- You can use whatever range you want
John Gibb posted a fine answer, already accepted, but I wanted to expand on it a bit to:
eliminate the one year limitation,
expose the date range in a more
explicit manner, and
eliminate the need for a separate
numbers table.
This slight variation uses a recursive common table expression to establish the set of Dates representing the first of each month on or after from and to dates defined in DateRange. Note the use of the MAXRECURSION option to prevent a stack overflow (!); adjust as necessary to accommodate the maximum number of months expected. Also, consider adding alternative Dates assembly logic to support weeks, quarters, even day-to-day.
with
DateRange(FromDate, ToDate) as (
select
Cast('11/1/2008' as DateTime),
Cast('2/15/2010' as DateTime)
),
Dates(Date) as (
select
Case Day(FromDate)
When 1 Then FromDate
Else DateAdd(month, 1, DateAdd(month, ((Year(FromDate)-1900)*12)+Month(FromDate)-1, 0))
End
from DateRange
union all
select DateAdd(month, 1, Date)
from Dates
where Date < (select ToDate from DateRange)
)
select
d.Date, t.TransactionValue
from Dates d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.Date
order by TransactionDate desc
) t
option (maxrecursion 120);
If you do this type of analysis often, you might be interested in this SQL Server function I put together for exactly this purpose:
if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go
create function fn_daterange
(
#MinDate as datetime,
#MaxDate as datetime,
#intval as datetime
)
returns table
--**************************************************************************
-- Procedure: fn_daterange()
-- Author: Ron Savage
-- Date: 12/16/2008
--
-- Description:
-- This function takes a starting and ending date and an interval, then
-- returns a table of all the dates in that range at the specified interval.
--
-- Change History:
-- Date Init. Description
-- 12/16/2008 RS Created.
-- **************************************************************************
as
return
WITH times (startdate, enddate, intervl) AS
(
SELECT #MinDate as startdate, #MinDate + #intval - .0000001 as enddate, #intval as intervl
UNION ALL
SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
FROM times
WHERE startdate + intervl <= #MaxDate
)
select startdate, enddate from times;
go
it was an answer to this question, which also has some sample output from it.
I don't have access to BOL from my phone so this is a rough guide...
First, you need to generate the missing rows for the months you have no data. You can either use a OUTER join to a fixed table or temp table with the timespan you want or from a programmatically created dataset (stored proc or suchlike)
Second, you should look at the new SQL 2008 'analytic' functions, like MAX(value) OVER ( partition clause ) to get the previous value.
(I KNOW Oracle can do this 'cause I needed it to calculate compounded interest calcs between transaction dates - same problem really)
Hope this points you in the right direction...
(Avoid throwing it into a temp table and cursoring over it. Too crude!!!)
-----Alternative way------
select
d.firstOfMonth,
MONTH(d.firstOfMonth) as Mon,
YEAR(d.firstOfMonth) as Yr,
t.TransactionValue
from (
select
dateadd( month, inMonths - 1, '1/1/2009') as firstOfMonth
from (
values (1), (2), (3), (4), (5), (7), (8), (9), (10), (11), (12)
) Dates(inMonths)
) d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.firstOfMonth
order by TransactionDate desc
) t