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
 Transact-SQL (2000)
 SPROC vs. SQL performance

Author  Topic 

dairec
Starting Member

16 Posts

Posted - 2007-05-31 : 06:07:47
Hey there.

I have a .net application that queries a SQL Server 2k database and I have a problem where a call to one of my stored procedures is timing out. The SPROC involves a SELECT that joins a few tables through a chain of views.
When I EXEC the SPROC in Query analyzer it takes 20mins to run - little wonder it's timing out in the client! However, when I copy and paste the code from the SPROC and run the T-SQL in QA (with the same parameters) it only takes 2 seconds.

I've looked at the Execution Plans but I'm not great at analysing these and I'm not sure I can see the discrepancy.

Note also that if I remove all the WHERE clauses except the ImportBatchID check the query only takes 10s.

If anyone has a chance to have a look added the TSQL below and also the Execution plans for both the SPROC and just the SQL.

Thanks,
Daire

SPROC:
CREATE PROCEDURE dbo.usp_Market_View_Get_Headline_Data_By_User
(
@ImportBatchID int,
@CurrentUsername [udtCode],
@BrokerCode udtCode = NULL,
@ProviderCode udtCode = NULL
)
AS
-- Get the limit as a variable for improved performance
DECLARE @Limit udtMoney
SET @Limit = dbo.udf_Get_Market_View_Limit()

--Return holdings for Brokers only associated with @CurrentUsername
SELECT *
FROM [CFD_Risk].[dbo].vw_Account_Totals A_T INNER JOIN [CFD_Risk].[dbo].udf_Get_Accounts_By_User(@CurrentUserName) A
ON A_T.AccountNumber = A.AccountNumber
WHERE ImportBatchID = @ImportBatchID
AND (BrokerCode = @BrokerCode OR @BrokerCode IS NULL)
AND (ProviderCode = @ProviderCode OR @ProviderCode IS NULL)
AND (ProviderCode <> 'PSil' OR TradeValue < @Limit)
RETURN

GO

Execution in QA:
SET @ImportBatchID = 396
SET @CurrentUsername = 'sarah'
EXEC @RC = [CFD_Risk].[dbo].[usp_Market_View_Get_Headline_Data_By_User] @ImportBatchID, @CurrentUsername, @BrokerCode, @ProviderCode


The SPROC Exection plan:


The SQL only execution plan:

Kristen
Test

22859 Posts

Posted - 2007-05-31 : 07:53:03
Check that the statistics are updated regularly, if not update then (sp_UpdateStats)

Recompile the SProc - EXEC sp_Recompile 'usp_Market_View_Get_Headline_Data_By_User'

Try running the SProc again.

Maybe change:

AND (BrokerCode = @BrokerCode OR @BrokerCode IS NULL)

to

AND (@BrokerCode IS NULL OR BrokerCode = @BrokerCode)

so that the IS NULL test bypasses the Comparison when @BrokerCode IS NULL.

(I've read that this helps, but it might be folklaw!)

Graphical images of query plan are not much help. The output from:

SET SHOWPLAN_TEXT ON
GO

-- ... put query here - e.g.:

SELECT * FROM Northwind.dbo.Products

SET SET SHOWPLAN_TEXT OFF
GO

would be more helpful in this forum

Kristen
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2007-05-31 : 07:59:03
Also, it will be interesting to see what is hidden inside that function by the name - udf_Get_Accounts_By_User - there.

Harsh Athalye
India.
"The IMPOSSIBLE is often UNTRIED"
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2007-05-31 : 08:09:04
Also you will have to make sure Parameter sniffing is not an issue here because I see that plans for both are much different.

Harsh Athalye
India.
"The IMPOSSIBLE is often UNTRIED"
Go to Top of Page

dairec
Starting Member

16 Posts

Posted - 2007-05-31 : 10:05:11
Harsh, Kristin,
thanks for your replies.

I recompiled the query and it now runs in 40s. Quite an improvement on 20mins but obviously still totally unacceptable.

Kristin, I tried changing the IS NULL short-circuit check but it had no affect on the SPROC. In fact interestingly, it drastically disimproved the performance of the SQL-only version - brining it in at 40s also (the same as the SPROC which is a bit suspicious).

(Sidenote: I originally used the syntax "@x IS NULL OR x = @x" but changed it to "x = @x OR @x IS NULL" after reading this article [url]http://www.sommarskog.se/dyn-search.html#OR_ISNULL[/url])

Harsh, the UDF is a table-valued function with the following syntax:
CREATE  FUNCTION dbo.udf_Get_Accounts_By_User
(
@Username udtCode
)
RETURNS TABLE
AS
RETURN (SELECT DISTINCT A_S.AccountNumber
FROM vw_User_Brokers_Active U_B_A INNER JOIN vw_Broker_Accounts_Current B_A_C
ON U_B_A.BrokerCode = B_A_C.BrokerCode INNER JOIN vw_Account_Siblings A_S
ON B_A_C.AccountNumber = A_S.AccountNumberKey
WHERE U_B_A.Username = @Username)


Finally the output from the SPROC with SHOWPLAN_TEXT on is:
StmtText                       
------------------------------
SET STATISTICS PROFILE ON

(1 row(s) affected)

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DECLARE @RC int
DECLARE @ImportBatchID int
DECLARE @CurrentUsername varchar(20)
DECLARE @BrokerCode varchar(20)
DECLARE @ProviderCode varchar(20)
-- Set parameter values
SET @ImportBatchID = 396

SET @CurrentUsername = 'sarah'


--EXEC sp_Recompile 'usp_Market_View_Get_Headline_Data_By_User'

EXEC @RC = [CFD_Risk].[dbo].[usp_Market_View_Get_Headline_Data_By_User] @ImportBatchID, @CurrentUsername, @BrokerCode, @ProviderCode




CREATE PROCEDURE dbo.usp_Market_View_Get_Headline_Data_By_User
(
@ImportBatchID int,
@CurrentUsername [udtCode],
@BrokerCode udtCode = NULL,
@ProviderCode udtCode = NULL
)
AS
-- Get the limit as a variable for improved performance
DEC
SELECT *
FROM [CFD_Risk].[dbo].vw_Account_Totals A_T INNER JOIN [CFD_Risk].[dbo].udf_Get_Accounts_By_User(@CurrentUserName) A
ON A_T.AccountNumber = A.AccountNumber
WHERE ImportBatchID = @ImportBatchID
AND (BrokerCode = @BrokerCode OR @Broker

(5 row(s) affected)

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|--Compute Scalar(DEFINE:([Expr1070]=If ([Expr1060]<>NULL) then [Expr1060] else [Trade].[ImportBatchID], [Expr1071]=isnull([Expr1064], 0.00), [Expr1072]=isnull([Expr1065], 0.00), [Expr1073]=isnull([Expr1066], 0.00), [Expr1074]=isnull([Expr1067], 0.0
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([Client].[ClientNumber]))
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([Broker].[TeamID]))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Broker_Account].[BrokerCode]))
| | |--Hash Match(Inner Join, HASH:([Account].[AccountNumber])=([Expr1141]), RESIDUAL:([Expr1141]=[Account].[AccountNumber] AND ([Account].[ProviderCode]<>'PSil' OR isnull([Expr1022], 0.00)<[@Limit])))
| | | |--Merge Join(Inner Join, MERGE:([Client].[ClientNumber])=([Account].[ClientNumber]), RESIDUAL:([Account].[ClientNumber]=[Client].[ClientNumber]))
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Client].[PK_ClientNumber]), ORDERED FORWARD)
| | | | |--Sort(ORDER BY:([Account].[ClientNumber] ASC))
| | | | |--Merge Join(Inner Join, MERGE:([Account].[AccountNumber])=([Account].[AccountNumber]), RESIDUAL:([Account].[AccountNumber]=[Account].[AccountNumber]))
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Account].[PK_AccountNumber]), WHERE:([Account].[ProviderCode]=[@ProviderCode] OR [@ProviderCode]=NULL) ORDERED FORWARD)
| | | | |--Sort(ORDER BY:([Broker_Account].[AccountNumber] ASC))
| | | | |--Sort(DISTINCT ORDER BY:([Rank1140] ASC))
| | | | |--Hash Match(Inner Join, HASH:([Broker_Account].[AccountNumber])=([Account].[AccountNumber]), RESIDUAL:([Broker_Account].[AccountNumber]=[Account].[AccountNumber]))
| | | | |--Rank
| | | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Broker_Account].[PK_Broker_Account]), WHERE:((([Broker_Account].[BrokerCode]=[@BrokerCode] OR [@BrokerCode]=NULL) AND getdate()>=Convert([B
| | | | |--Hash Match(Inner Join, HASH:([Account].[ClientNumber])=([Account].[ClientNumber]), RESIDUAL:([Account].[ClientNumber]=[Account].[ClientNumber]))
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Account].[PK_AccountNumber]))
| | | | |--Hash Match(Inner Join, HASH:([Account].[AccountNumber])=([Broker_Account].[AccountNumber]), RESIDUAL:([Broker_Account].[AccountNumber]=[Account].[AccountNumber]))
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Account].[PK_AccountNumber]))
| | | | |--Hash Match(Inner Join, HASH:([Union1050])=([Broker_Account].[BrokerCode]), RESIDUAL:([Broker_Account].[BrokerCode]=[Union1050]))
| | | | |--Merge Join(Union)
| | | | | |--Nested Loops(Inner Join, OUTER REFERENCES:([User_Brokers].[BrokerCode]))
| | | | | | |--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[User_Brokers].[PK_User_Brokers]), SEEK:([User_Brokers].[Username]=[@CurrentUsername]) ORDERED FORWARD)
| | | | | | |--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[Broker].[PK_Broker]), SEEK:([Broker].[BrokerCode]=[User_Brokers].[BrokerCode]) ORDERED FORWARD)
| | | | | |--Nested Loops(Inner Join, OUTER REFERENCES:([Broker].[TeamID]))
| | | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Broker].[PK_Broker]), ORDERED FORWARD)
| | | | | |--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[User_Teams].[PK_User_Teams]), SEEK:([User_Teams].[Username]=[@CurrentUsername] AND [User_Teams].[TeamID]=[Broker].[Team
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Broker_Account].[PK_Broker_Account]), WHERE:(getdate()>=Convert([Broker_Account].[EffectiveFromDate]) AND getdate()<=Convert([Bro
| | | |--Compute Scalar(DEFINE:([Expr1141]=If ([Expr1061]<>NULL) then [Expr1061] else [Trade].[AccountNumber]))
| | | |--Filter(WHERE:(If ([Expr1060]<>NULL) then [Expr1060] else [Trade].[ImportBatchID]=[@ImportBatchID]))
| | | |--Hash Match(Full Outer Join, HASH:([Trade].[ImportBatchID], [Trade].[AccountNumber])=([Expr1060], [Expr1061]), RESIDUAL:([Trade].[ImportBatchID]=[Expr1060] AND [Trade].[AccountNumber]=[Expr1061]))
| | | |--Filter(WHERE:([Expr1022]<>0.00))
| | | | |--Hash Match(Aggregate, HASH:([Trade].[ImportBatchID], [Trade].[AccountNumber]), RESIDUAL:([Trade].[ImportBatchID]=[Trade].[ImportBatchID] AND [Trade].[AccountNumber]=[Trade].[AccountNumber]) DEFINE:([Ex
| | | | |--Clustered Index Scan(OBJECT:([CFD_Risk].[dbo].[Trade].[PK_Trade]))
| | | |--Compute Scalar(DEFINE:([Expr1060]=If ([Expr1053]<>NULL) then [Expr1053] else [ProviderHoldings].[ImportBatchID], [Expr1061]=If ([Expr1054]<>NULL) then [Expr1054] else [ProviderHoldings].[AccountNumber], [Ex
| | | |--Hash Match(Full Outer Join, HASH:([Expr1053], [Expr1054])=([ProviderHoldings].[ImportBatchID], [ProviderHoldings].[AccountNumber]), RESIDUAL:([Expr1053]=[ProviderHoldings].[ImportBatchID] AND [Expr1054
| | | |--Compute Scalar(DEFINE:([Expr1053]=If ([PSil_StockHoldings].[ImportBatchID]<>NULL) then [PSil_StockHoldings].[ImportBatchID] else [PSil_CashHoldings].[ImportBatchID], [Expr1054]=If ([PSil_StockHold
| | | | |--Hash Match(Full Outer Join, HASH:([PSil_CashHoldings].[ImportBatchID], [PSil_CashHoldings].[AccountNumber])=([PSil_StockHoldings].[ImportBatchID], [PSil_StockHoldings].[AccountNumber]), RESID
| | | | |--Filter(WHERE:([Expr1013]<>0.00))
| | | | | |--Hash Match(Aggregate, HASH:([PSil_CashHoldings].[ImportBatchID], [PSil_CashHoldings].[AccountNumber]), RESIDUAL:([PSil_CashHoldings].[ImportBatchID]=[PSil_CashHoldings].[ImportBatch
| | | | | |--Table Scan(OBJECT:([CFD_Risk].[dbo].[PSil_CashHoldings]))
| | | | |--Filter(WHERE:([Expr1016]<>0.00 OR [Expr1017]<>0.00))
| | | | |--Hash Match(Aggregate, HASH:([PSil_StockHoldings].[AccountNumber], [PSil_StockHoldings].[ImportBatchID]), RESIDUAL:([PSil_StockHoldings].[AccountNumber]=[PSil_StockHoldings].[Account
| | | | |--Table Scan(OBJECT:([CFD_Risk].[dbo].[PSil_StockHoldings]))
| | | |--Compute Scalar(DEFINE:([Expr1059]=isnull([Expr1004], 0.000000000000000)))
| | | |--Hash Match(Right Outer Join, HASH:([Provider_Opening_Positions].[ImportBatchID], [Provider_Opening_Positions].[AccountNumber])=([ProviderHoldings].[ImportBatchID], [ProviderHoldings].[Account
| | | |--Filter(WHERE:([Expr1004]<>0.000000000000000))
| | | | |--Hash Match(Aggregate, HASH:([Provider_Opening_Positions].[ImportBatchID], [Provider_Opening_Positions].[AccountNumber]), RESIDUAL:([Provider_Opening_Positions].[ImportBatchID]=[Prov
| | | | |--Hash Match(Inner Join, HASH:([Equity].[EquityID])=([Provider_Opening_Positions].[EquityID]))
| | | | |--Index Scan(OBJECT:([CFD_Risk].[dbo].[Equity].[IX_Equity_UNIQUE_NAME]), WHERE:(NOT(like([Equity].[EquityName], '%spot%', NULL))))
| | | | |--Table Scan(OBJECT:([CFD_Risk].[dbo].[Provider_Opening_Positions]))
| | | |--Filter(WHERE:([Expr1008]<>0.00 OR [Expr1009]<>0.00))
| | | |--Hash Match(Aggregate, HASH:([ProviderHoldings].[ImportBatchID], [ProviderHoldings].[AccountNumber]), RESIDUAL:([ProviderHoldings].[ImportBatchID]=[ProviderHoldings].[ImportBatchID]
| | | |--Table Scan(OBJECT:([CFD_Risk].[dbo].[ProviderHoldings]))
| | |--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[Broker].[PK_Broker]), SEEK:([Broker].[BrokerCode]=[Broker_Account].[BrokerCode]) ORDERED FORWARD)
| |--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[Team].[PK_Team]), SEEK:([Team].[TeamID]=[Broker].[TeamID]) ORDERED FORWARD)
|--Table Spool
|--Nested Loops(Inner Join, OUTER REFERENCES:([ClientGroup].[ClientGroupID]))
|--Index Scan(OBJECT:([CFD_Risk].[dbo].[ClientGroup].[IX_Family_Unique_Name]))
|--Clustered Index Seek(OBJECT:([CFD_Risk].[dbo].[ClientGroup_Clients].[PK_ClientGroup_Clients]), SEEK:([ClientGroup_Clients].[ClientGroupID]=[ClientGroup].[ClientGroupID] AND [ClientGroup_Clients].[ClientNumber]=[Client].[Client

(60 row(s) affected)

StmtText
--------------
RETURN

(1 row(s) affected)

StmtText
-----------------------------
SET STATISTICS PROFILE OFF

(1 row(s) affected)

Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2007-05-31 : 10:44:44
"it drastically disimproved the performance of the SQL-only version "

That's the "total time" for the job then, including creating the Query Plan. previously your "10 second run" has been relying on the fact that a Query Plan was already cached.

To check this you could run it twice, and the second time it should be 10 seconds instead of 40 seconds.

The Query Plan has been cut off after 256 characters - presumably because that is your max Column Width setting in QA.

Better not to post it as [ ... CODE ... ] as the width makes the follow-on posts very hard to read (we can cut&paste it to an editor to sort out the LineWrap)

But in principle there are far too many Index SCAN here, rather than Index SEEK, and the TABLE SCANs are probably lousy performers - might be very small tables though.

I doubt that you are going to get really good performance on this, whereas if you used a Parameterized Query and Dynamic SQL - e.g. using sp_ExecuteSQL - you would probably get very good performance (assuming indexes on appropriate columns).

(I'm meaning that your Dynamic SQL would leave out any WHERE tests that were not actually required)

Kristen
Go to Top of Page

dairec
Starting Member

16 Posts

Posted - 2007-05-31 : 11:43:23
quote:

That's the "total time" for the job then, including creating the Query Plan. previously your "10 second run" has been relying on the fact that a Query Plan was already cached.

To check this you could run it twice, and the second time it should be 10 seconds instead of 40 seconds.



Hmm I ran it three times in QA (with just the SELECTs, not the EXEC SPROC) and it always came out around the 40s mark.

I was thinking about going down the dynamic SQL route but wait...

After further investigation I have noticed that commenting out the JOIN to the UDF results in execution time of a couple of seconds - within acceptable limits.
Furthermore if I fetch the resultset from the UDF and place the (2400) records in a table and use this in the JOIN, the performance is also fine.
So the join to the UDF is what seems to be causing the problem.

I'll investigate further and let you know if I come up with something else.

Daire
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2007-05-31 : 11:53:19
I think rather than using UDF, it will perform better with derived table:

SELECT *
FROM
[CFD_Risk].[dbo].vw_Account_Totals A_T INNER JOIN
(
SELECT DISTINCT A_S.AccountNumber
FROM vw_User_Brokers_Active U_B_A INNER JOIN vw_Broker_Accounts_Current B_A_C
ON U_B_A.BrokerCode = B_A_C.BrokerCode INNER JOIN vw_Account_Siblings A_S
ON B_A_C.AccountNumber = A_S.AccountNumberKey
Where U_B_A.Username = @Username
) A
ON A_T.AccountNumber = A.AccountNumber
WHERE ImportBatchID = @ImportBatchID
AND (BrokerCode = @BrokerCode OR @BrokerCode IS NULL)
AND (ProviderCode = @ProviderCode OR @ProviderCode IS NULL)
AND (ProviderCode <> 'PSil' OR TradeValue < @Limit)


Harsh Athalye
India.
"The IMPOSSIBLE is often UNTRIED"
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2007-05-31 : 12:02:09
I don't like the look of that DISTINCT [performance-wise] ... what I reckon that is trying to do is:

SELECT A_S.AccountNumber
FROM vw_Account_Siblings AS A_S
WHERE EXISTS
(
SELECT *
FROM vw_Broker_Accounts_Current AS B_A_C
WHERE B_A_C.AccountNumber = A_S.AccountNumberKey
AND EXISTS
(
SELECT *
FROM vw_User_Brokers_Active AS U_B_A
WHERE U_B_A.BrokerCode = B_A_C.BrokerCode
AND U_B_A.Username = @Username)
)
)

Kristen
Go to Top of Page

dairec
Starting Member

16 Posts

Posted - 2007-05-31 : 12:21:09
Tried the sub-query solution but same performance as the UDF.
I'd prefer to use the UDF since the functionality is used elsewhere in other SPROCs.

Will be out of the office for a few days but will investigate Kristin's suggestion about rewriting this part when I get back.

Thanks,
Daire
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2007-05-31 : 12:55:54
Looking at it again: I presume that A_S.AccountNumber is Unique within vw_Account_Siblings? If not you are no better off ... and given that vw_Account_Siblings is a view it probably JOINs multiple tables, and maybe returns multiple rows for a given A_S.AccountNumber. That would reduce performance ...
Go to Top of Page

blindman
Master Smack Fu Yak Hacker

2365 Posts

Posted - 2007-05-31 : 14:21:21
quote:
Originally posted by Kristen

"it drastically disimproved the performance of the SQL-only version "

That's the "total time" for the job then, including creating the Query Plan. previously your "10 second run" has been relying on the fact that a Query Plan was already cached.

To check this you could run it twice, and the second time it should be 10 seconds instead of 40 seconds.

Its not going to take the optimizer 30 seconds to come up with a query plan...
More likely it was using a bad existing query plan.
I agree that dynamic SQL might help here, which ironically would generate a new query plan each run!

e4 d5 xd5 Nf6
Go to Top of Page

blindman
Master Smack Fu Yak Hacker

2365 Posts

Posted - 2007-05-31 : 14:28:05
quote:
Originally posted by dairec

Tried the sub-query solution but same performance as the UDF.
I'd prefer to use the UDF since the functionality is used elsewhere in other SPROCs.
Maybe joins would work better than nested EXISTS functions:
SELECT	A_S.AccountNumber
FROM vw_Account_Siblings AS A_S
inner join vw_Broker_Accounts_Current AS B_A_C on B_A_C.AccountNumber = A_S.AccountNumberKey
inner join vw_User_Brokers_Active AS U_B_A
on U_B_A.BrokerCode = B_A_C.BrokerCode
AND U_B_A.Username = @Username

Make sure the joined columns are indexed, and you may need to throw a DISTINCT in there depending upon the cardinality.

e4 d5 xd5 Nf6
Go to Top of Page

Jeff Moden
Aged Yak Warrior

652 Posts

Posted - 2007-05-31 : 20:50:27
It would be really interesting to see just how bad the code in the views might be... lots o' tables in those views and, I'm sure, lots of aggragates... some of the really skinny lines in the graphical representation of the execution plan would seem to indicate a multitude of correlated subqueries which is probably not a good thing for performance. Other lines are really thick and would seem to indicate the some form of triangular join exists in the WHERE clauses of some of the queries...

--Jeff Moden
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2007-06-01 : 03:15:31
"Maybe joins would work better than nested EXISTS functions"

I was proposing EXISTS because the original had JOINs but used a DISTINCT to get rid of the DUPEs.

I reckon that if there are a significant number of DUPEs the EXISTS would be quicker, but I haven't tested the theory!

Hmmm ... another part of the test is that the Grand Parent record has a specific value for U_B_A.Username, but other than that the presence of FKs would tell the Optimiser that parent/grand parent would be assured to exist!

"which ironically would generate a new query plan each run!"

I was rather hoping that a parameterized query would only cache the various combinations of the Criteria used in the WHERE clause. There are three separate tests, so I suppose that is a possibility of !3 combinations (haven't done that sort of maths for too many years!), so a maximum of 6 cached query plans, but probably in practice only some of those combinations will actually occur.

Or did I miscue on that assumption??

Kristen
Go to Top of Page

blindman
Master Smack Fu Yak Hacker

2365 Posts

Posted - 2007-06-01 : 10:31:11
I think only one query plan will be cached at a time, unless you use some of Adam Machanic's advanced techniques:
http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/controlling-stored-procedure-caching-with-dyanmic-sql.aspx

e4 d5 xd5 Nf6
Go to Top of Page

dairec
Starting Member

16 Posts

Posted - 2007-06-05 : 11:59:39
First thanks to all for having a look at this problem.

Perhaps I should take a little time to explain the relationships invloved here. The purpose of this part of the SPROC is to only retrieve data that @CurrentUsername has access to at the account level. A user has access via the following chain: User->Broker->Account(->Client->Account). A User can "see" one or more Brokers, a Broker can be associated with only one Account at any given time (the current relationships are given by vw_Broker_Accounts_Current). (There is a further relationship between Users and Brokers via a Team that I have not specified here but that is handled by vw_User_Brokers_Active).
The appended "(->Client->Account)" above indicates that Brokers can also "see" Accounts that share a common foreign key called ClientNumber and it is for this purpose that vw_Account_Siblings is used.
The full script for this view including explanation is:

/*
This view groups together Accounts with their sibling Accounts.
Two accounts are siblings if they share the same Client.
Even though Brokers are associated directly with an Account,
a given Broker is also allowed to view data for the other Accounts
for that Client, even if not directly associated with those Account.
This is why, this view is needed.
*/
CREATE VIEW dbo.vw_Account_Siblings
AS
SELECT A.AccountNumber AS AccountNumberKey, A2.AccountNumber
FROM dbo.Account A RIGHT OUTER JOIN
dbo.Account A2 ON A.ClientNumber = A2.ClientNumber


This is why A_S.AccountNumber is not unique and the DISTINCT operator is needed when joining to this table (or indeed when using sub-queries).

I experimented with the performance between using joins and sub-queries for this part and here were the results:
Changing the UDF and calling it from QA: both calls were in milliseconds, no noticable difference.
Substituting the SQL into the SPROC join: SPROC with sub-queries execution time: 2s, SPROC with joins execution time: 36s

So using the sub-queries makes a big difference but only when substituted into the SPROC.
Does this mean I cannot use a UDF to find the AccountNumbers visible to a given User?

Jeff, the code in the vw_Account_Totals does indeed refer to a chain of views a couple of which use aggregates and some of which are COLLATEd together but it seems to me it's the udf_Get_Accounts_By_User part of the original query and not the vw_Account_Totals or the WHERE clauses that is causing the big delay.

Daire
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2007-06-05 : 16:56:21
"RIGHT OUTER JOIN"

Aggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhh! Why do people use RIGHT outer joins?

"This is why A_S.AccountNumber is not unique and the DISTINCT operator is needed when joining to this table"

If you don't need both values from vw_Account_Siblings, create a different View that only gives the value you DO need, and uses an EXISTS rather than an OUTER JOIN. That will avoid the need for a DISTINCT.

(I guess its obvious, but in case not: DISTINCT has to pull all the matching rows, sort them, and throw away all the duplicates. For Mega Numbers Of Rows this is a BIG job!!)

Kristen
Go to Top of Page

dairec
Starting Member

16 Posts

Posted - 2007-06-07 : 10:07:54
quote:
Aggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhh! Why do people use RIGHT outer joins?

Your right in as much as I should have used an INNER JOIN here since the resultset is completely symmetrical (joining a table to a copy of itself).

quote:
If you don't need both values from vw_Account_Siblings, create a different View that only gives the value you DO need, and uses an EXISTS rather than an OUTER JOIN. That will avoid the need for a DISTINCT.

I don't need to return both values from vw_Account_Siblings but I do need to use one of the columns for the join (or sub query column-match). I can't just create a view with the values I want because ultimately they depend on @Username and therein lies the problem it seems.

In the end I found a solution by amending the SPROC to use a temp table:
...
--Return holdings for Brokers only associated with @CurrentUsername
SELECT AccountNumber INTO #UserAccounts FROM [CFD_Risk].[dbo].udf_Get_Accounts_By_User(@CurrentUserName)

SELECT *
FROM [CFD_Risk].[dbo].vw_Account_Totals A_T INNER JOIN #UserAccounts A
ON A_T.AccountNumber = A.AccountNumber
WHERE ImportBatchID = @ImportBatchID
AND (BrokerCode = @BrokerCode OR @BrokerCode IS NULL)
AND (ProviderCode = @ProviderCode OR @ProviderCode IS NULL)
AND (ProviderCode <> 'PSil' OR TradeValue < @Limit)

DROP TABLE #UserAccounts
RETURN

This works fine even if it is a little unwieldy. I'll consider rewriting it using dynamic SQL in the future.

Thanks,
Daire
Go to Top of Page
   

- Advertisement -