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.
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,DaireSPROC: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) RETURNGO Execution in QA:SET @ImportBatchID = 396SET @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)toAND (@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 ONGO-- ... put query here - e.g.:SELECT * FROM Northwind.dbo.ProductsSET SET SHOWPLAN_TEXT OFFGOwould be more helpful in this forumKristen |
 |
|
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 AthalyeIndia."The IMPOSSIBLE is often UNTRIED" |
 |
|
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 AthalyeIndia."The IMPOSSIBLE is often UNTRIED" |
 |
|
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 TABLEAS 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 intDECLARE @ImportBatchID intDECLARE @CurrentUsername varchar(20)DECLARE @BrokerCode varchar(20)DECLARE @ProviderCode varchar(20)-- Set parameter valuesSET @ImportBatchID = 396SET @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) |
 |
|
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 |
 |
|
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 |
 |
|
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.AccountNumberWHERE ImportBatchID = @ImportBatchID AND (BrokerCode = @BrokerCode OR @BrokerCode IS NULL) AND (ProviderCode = @ProviderCode OR @ProviderCode IS NULL) AND (ProviderCode <> 'PSil' OR TradeValue < @Limit) Harsh AthalyeIndia."The IMPOSSIBLE is often UNTRIED" |
 |
|
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.AccountNumberFROM vw_Account_Siblings AS A_SWHERE 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 |
 |
|
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 |
 |
|
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 ... |
 |
|
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 |
 |
|
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.AccountNumberFROM 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 |
 |
|
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 |
 |
|
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 |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
|
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_SiblingsASSELECT A.AccountNumber AS AccountNumberKey, A2.AccountNumberFROM 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: 36sSo 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 |
 |
|
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 |
 |
|
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 #UserAccountsRETURN This works fine even if it is a little unwieldy. I'll consider rewriting it using dynamic SQL in the future.Thanks,Daire |
 |
|
|
|
|
|
|