Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 SQL Server Development (2000)
 UDF selecting exchangerates

Author  Topic 

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-02-23 : 10:00:49
Expert yaks;

I run a website which deals with transactions in 6 different currencies. I use euro as the base currency and have a table that gets updated with the correct exchangerates every day. This has been working fine for over 6 months now, however when I write reports I need to have the currency in euro and that means that I have to multiply each and every transaction-amount with the ex-rate for the day the transaction happened. We are not having that many transactions quite yet, but when I run the report queries the conversion just totally blows the performance. This is my UDF:
CREATE FUNCTION dbo.Currency (@UserID int, @Currency varchar(3), @CurDate datetime)
RETURNS decimal(19, 10)
AS

BEGIN
DECLARE
@Rate decimal(19, 10)

SET @Rate = (SELECT b.rate/a.Rate
FROM currency a WITH (NOLOCK), currency b WITH (NOLOCK), user WITH (NOLOCK)
WHERE user.UserID = @UserID
AND user.Currency = a.Currency
AND b.Currency = @Currency
AND a.Updated =
(SELECT MAX(Updated)
FROM currency
WHERE Currency = user.Currency AND Updated <= @CurDate)
AND b.Updated =
(SELECT MAX(Updated)
FROM currency
WHERE Currency = @Currency AND Updated <= @CurDate))

RETURN @Rate
END
A typical (simple) report would be something like this:

SELECT MONTH(LastUpdate), SUM(Volume * dbo.Currency(UserID, 'EUR', LastUpdate))
FROM transactions
WHERE DATEPART(yy, LastUpdate) = 2003
GROUP BY MONTH(LastUpdate)

Any idea how I can make this run any faster...?

--
Lumbago
"Real programmers don't document, if it was hard to write it should be hard to understand"

nr
SQLTeam MVY

12543 Posts

Posted - 2004-02-23 : 11:13:38
This will potentially call the function a lot of times - and udf's tend to be slow.
It might be better to get all the distinct results into a temp table and join to that.

Check that the subqueries are using indexes.
Also try

select @Rate =
(select top 1 rate from currency, user where user.Currency = currency.Currency and Updated <= @CurDate order by Updated desc)
/
(select top 1 rate from currency where currency.Currency = @Currency Updated <= @CurDate order by Updated desc)


==========================================
Cursors are useful if you don't know sql.
DTS can be used in a similar way.
Beer is not cold and it isn't fizzy.
Go to Top of Page

raymondpeacock
Constraint Violating Yak Guru

367 Posts

Posted - 2004-02-23 : 11:19:21
A couple ideas, which may be way off the mark!
If using SQL2000, take the TWO SELECT Max calls into one and use a table variable to hold the last updated for all currencies. This should reduce the number of scans on the currency table.
Secondly, try and build in logic that reduces lookups by checking if the userid currency is the same as the requested currency.
Finally, the currency aliased as b is included in your table list without joining onto the other two tables therefore it will be used to form a cartesian product of all possible combinations (I think). Try to get the answer you want by using a variable instead and then using that in your select.


Raymond
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-02-23 : 12:43:33
Wow, excellent feedback fellas! I did some checks and I found that a combination of your suggestions was actually the fastest:
ALTER FUNCTION dbo.Currency_2 (@UserID int, @Currency varchar(3), @CurDate datetime)
RETURNS decimal(19, 10)
AS

BEGIN
DECLARE
@Rate decimal(19, 10),
@UserCurrency varchar(3)

SET @UserCurrency = (SELECT Currency FROM bob_user WITH (NOLOCK) WHERE UserID = @UserID)

IF @UserCurrency = @Currency
SET @Rate = 1
ELSE
SET @Rate =
(SELECT TOP 1 rate
FROM bob_currency WITH (NOLOCK)
WHERE bob_currency.Currency = @Currency
AND Updated <= @CurDate
ORDER BY Updated DESC)
/
(SELECT TOP 1 Rate FROM bob_currency WITH (NOLOCK) WHERE Currency = @UserCurrency
AND Updated <= @CurDate ORDER BY Updated DESC)

RETURN @Rate
END
The UDF is properly indexed and all, and the execution-plan looks alot better with this function. Execution-time is cut in half almost and I'm really happy with that! However I have one more question; when I ran my initial query with the original udf it took some 20 seconds I belive, second time I ran it it took only 5 seconds...why is this? Is there some caching or something involved here?

--
Lumbago
"Real programmers don't document, if it was hard to write it should be hard to understand"
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-02-23 : 16:50:01
Just wanted to say that after even more checking you wouldn't belive the performance-gains I have after the change, running reports just got fun again :)

--
Lumbago
"Real programmers don't document, if it was hard to write it should be hard to understand"
Go to Top of Page

mohdowais
Sheikh of Yak Knowledge

1456 Posts

Posted - 2004-02-24 : 00:26:40
quote:
when I ran my initial query with the original udf it took some 20 seconds I belive, second time I ran it it took only 5 seconds...why is this? Is there some caching or something involved here?


Yup, the initial delay you see when you run it for the first time is when SQL Server is compiling the query execution plan. Subsequent runs will re-use the cached execution plan until it is flushed from the cache. While testing you sometimes need to force a cache clear, and run a "cold" query. Use DBCC DROPCLEANBUFFERS and DBCC FREEPROCCACHE to clear out any execution plans. You will need admin permissions on the server to run this query, and it is not recommended on a production server.

OS
Go to Top of Page

nr
SQLTeam MVY

12543 Posts

Posted - 2004-02-24 : 03:22:07
That gain was probably to the first run having to read all the datat from disk - the second run some of the data was already in memory.

The art of writing efficient SPs is mainly about reducing disk access - read data then do all the processing on it as possible before it gets flushed by reading more data.

==========================================
Cursors are useful if you don't know sql.
DTS can be used in a similar way.
Beer is not cold and it isn't fizzy.
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-02-24 : 07:12:00
Thanx mohdowais, I will treasure those commands for later use...

And nr, this makes sense I guess but my production database which I ran these queries on is only about 200MB and when I check in task-manager sqlserver.exe uses some 750MB's of RAM. I have a hard time understanding why it consumes so much of the memory, and when it does this, wouldn't the entire dataset reside in memory so data-access should be fast all the time...?

--
Lumbago
"Real programmers don't document, if it was hard to write it should be hard to understand"
Go to Top of Page

nr
SQLTeam MVY

12543 Posts

Posted - 2004-02-24 : 07:21:17
Nope.
Think of
select * from sysobjects, sysobjects, sysobjects

This will build a resultset of the cube of the number of recs in sysobjects (about 2 billion) each 3 times as wide as a sysobjects rec.
Even this will take up a fair amount of memory.

Lack of good indexes can cause a lot of memory to be used processing queries.

I was on a project where they couldn't manage to get reasonable performance even though they only had 100 mb of data.
Idea was to leave it to the dba team to look at queries to index all tables after the project was complete.
Dba teams conclusion was that the queries were too complex for them to decide on indexes.
My comment was that they should know something about the app so that they didn't optimise things that weren't important at the expense of things that were.
Most tables had a single index - the first field of which had 3 possible values.

Strange - got a similar situation here - no dba team though so the people doing it don't even claim to know about sql server or databases.

==========================================
Cursors are useful if you don't know sql.
DTS can be used in a similar way.
Beer is not cold and it isn't fizzy.
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-02-24 : 07:29:08
I have to admit that indexes are one of the major issues I need to look up on. I basically clueless in creating them and optimizing the execution-plan and we actually hired a fellow MVP of yours (Tony Rogerson if you know him) to do the indexing/tuning for us and we will be hireing him again. You wouldn't happen to have any sql-server-indexing-tutorials-for-total-morons laying around do you...?

--
Lumbago
"Real programmers don't document, if it was hard to write it should be hard to understand"
Go to Top of Page
   

- Advertisement -