Select * from View takes 4 minutesHow do I force SQL Server to use my spatial index through a view?Improving performance of a viewI've created a view for a report, it now takes 30 minutes to run - Any ideas?Optimizing query. FOR XML PATH or some alternatives?Row estimates always too lowBad performance using “NOT IN”Query Locks Table And Takes Long Time to RunWhy is this faster and is it safe to use? (WHERE first letter is in the alphabet)SQL Server statement takes forever while running instantly in OracleSQL Server - Identical query on near identical database takes 20 seconds on sql2012 but does not complete after 50+ hours on sql2014
Is there a way to auto-resolve a fight and account for resource consumption?
Why is double encryption that's equivalent to single encryption no better than single encryption?
Antonym for “boilerplate” or “cookie-cutter”
Where to search for PhD level jobs in OR?
What examples are there of unrelated physical quantities measured in the same units?
Is Bcrypt a hashing algorithm or is my study material wrong?
Does animal blood, esp. human, really have similar salinity as ocean water, and does that prove anything about evolution?
How to raise an event in Sitecore 9+
Did the Allies reverse the threads on secret microfilm-hiding buttons to thwart the Germans?
responsibility for arrangement of elements - Frontend or Backend?
How can I sell my shares in a privately-owned company I used to be employed by?
Why is more music written in sharp keys than flat keys?
Is there a high level reason why the inverse square law of gravitation yields periodic orbits without precession?
What do I get by paying more for a bicycle?
What is the difference between democracy and ochlocracy?
Letters associated with prime numbers
Shrinkage priors
How can an immortal member of the nobility be prevented from taking the throne?
Do European politicians typically put their pronouns on their social media pages?
What do you call this when cats hunch their backs and their fur stands on end?
Use of expression "statistically significantly positive"
Why do the new Star Trek series have so few episodes in each season?
What does exhaust smell on oil and transmission dipstick mean?
How much caffeine would there be if I reuse tea leaves in a second brewing?
Select * from View takes 4 minutes
How do I force SQL Server to use my spatial index through a view?Improving performance of a viewI've created a view for a report, it now takes 30 minutes to run - Any ideas?Optimizing query. FOR XML PATH or some alternatives?Row estimates always too lowBad performance using “NOT IN”Query Locks Table And Takes Long Time to RunWhy is this faster and is it safe to use? (WHERE first letter is in the alphabet)SQL Server statement takes forever while running instantly in OracleSQL Server - Identical query on near identical database takes 20 seconds on sql2012 but does not complete after 50+ hours on sql2014
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty
margin-bottom:0;
I am running in to an issue where when I run a query against a view it takes 4+ minutes. However, when I run the guts of the query it finishes in like 1 second.
The only thing I am not sure about is the tables being joined are both temporal tables.
Ad hoc query plan:
https://www.brentozar.com/pastetheplan/?id=BykohB2p4
View query plan:
https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E
Any suggestions on where to try and figure this out?
View code:
ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
cm.CodeMasterID,
cm.ProjectName,
cm.[Status],
d.CompanyID,
d.DealTypeMasterID,
cm.[Description],
d.PassiveInd,
d.ApproxTPGOwnership,
d.NumberBoardSeats,
d.FollowonInvestmentInd,
d.SocialImpactInd,
d.EquityInd,
d.DebtInd,
d.RealEstateInd,
d.TargetPctgReturn,
d.ApproxTotalDealSize,
cm.CurrencyCode,
d.ConflictCheck,
cm.CreatedDate,
cm.CreatedBy,
cm.LastUpdateDate,
cm.LastUpdateBy,
d.ExpensesExceedThresholdDate,
d.CurrentTPGCheckSize,
d.PreferredEquityInd,
d.ConvertibleDebtInd,
d.OtherRealAssetsInd,
d.InitialTPGCheckSize,
d.DirectLendingInd,
cm.NameApproved,
cm.FolderID,
cm.CodaProcessedDateTime,
cm.DeadDate,
d.SectorMasterID,
d.DTODataCompleteDate,
cm.ValidFrom AS CodeMasterValidFrom,
cm.ValidTo AS CodeMasterValidTo,
d.validFrom AS DealValidFrom,
d.validTo AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO
Added the Partition by and get similar results to the ad hoc query.
sql-server
add a comment
|
I am running in to an issue where when I run a query against a view it takes 4+ minutes. However, when I run the guts of the query it finishes in like 1 second.
The only thing I am not sure about is the tables being joined are both temporal tables.
Ad hoc query plan:
https://www.brentozar.com/pastetheplan/?id=BykohB2p4
View query plan:
https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E
Any suggestions on where to try and figure this out?
View code:
ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
cm.CodeMasterID,
cm.ProjectName,
cm.[Status],
d.CompanyID,
d.DealTypeMasterID,
cm.[Description],
d.PassiveInd,
d.ApproxTPGOwnership,
d.NumberBoardSeats,
d.FollowonInvestmentInd,
d.SocialImpactInd,
d.EquityInd,
d.DebtInd,
d.RealEstateInd,
d.TargetPctgReturn,
d.ApproxTotalDealSize,
cm.CurrencyCode,
d.ConflictCheck,
cm.CreatedDate,
cm.CreatedBy,
cm.LastUpdateDate,
cm.LastUpdateBy,
d.ExpensesExceedThresholdDate,
d.CurrentTPGCheckSize,
d.PreferredEquityInd,
d.ConvertibleDebtInd,
d.OtherRealAssetsInd,
d.InitialTPGCheckSize,
d.DirectLendingInd,
cm.NameApproved,
cm.FolderID,
cm.CodaProcessedDateTime,
cm.DeadDate,
d.SectorMasterID,
d.DTODataCompleteDate,
cm.ValidFrom AS CodeMasterValidFrom,
cm.ValidTo AS CodeMasterValidTo,
d.validFrom AS DealValidFrom,
d.validTo AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO
Added the Partition by and get similar results to the ad hoc query.
sql-server
add a comment
|
I am running in to an issue where when I run a query against a view it takes 4+ minutes. However, when I run the guts of the query it finishes in like 1 second.
The only thing I am not sure about is the tables being joined are both temporal tables.
Ad hoc query plan:
https://www.brentozar.com/pastetheplan/?id=BykohB2p4
View query plan:
https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E
Any suggestions on where to try and figure this out?
View code:
ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
cm.CodeMasterID,
cm.ProjectName,
cm.[Status],
d.CompanyID,
d.DealTypeMasterID,
cm.[Description],
d.PassiveInd,
d.ApproxTPGOwnership,
d.NumberBoardSeats,
d.FollowonInvestmentInd,
d.SocialImpactInd,
d.EquityInd,
d.DebtInd,
d.RealEstateInd,
d.TargetPctgReturn,
d.ApproxTotalDealSize,
cm.CurrencyCode,
d.ConflictCheck,
cm.CreatedDate,
cm.CreatedBy,
cm.LastUpdateDate,
cm.LastUpdateBy,
d.ExpensesExceedThresholdDate,
d.CurrentTPGCheckSize,
d.PreferredEquityInd,
d.ConvertibleDebtInd,
d.OtherRealAssetsInd,
d.InitialTPGCheckSize,
d.DirectLendingInd,
cm.NameApproved,
cm.FolderID,
cm.CodaProcessedDateTime,
cm.DeadDate,
d.SectorMasterID,
d.DTODataCompleteDate,
cm.ValidFrom AS CodeMasterValidFrom,
cm.ValidTo AS CodeMasterValidTo,
d.validFrom AS DealValidFrom,
d.validTo AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO
Added the Partition by and get similar results to the ad hoc query.
sql-server
I am running in to an issue where when I run a query against a view it takes 4+ minutes. However, when I run the guts of the query it finishes in like 1 second.
The only thing I am not sure about is the tables being joined are both temporal tables.
Ad hoc query plan:
https://www.brentozar.com/pastetheplan/?id=BykohB2p4
View query plan:
https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E
Any suggestions on where to try and figure this out?
View code:
ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
cm.CodeMasterID,
cm.ProjectName,
cm.[Status],
d.CompanyID,
d.DealTypeMasterID,
cm.[Description],
d.PassiveInd,
d.ApproxTPGOwnership,
d.NumberBoardSeats,
d.FollowonInvestmentInd,
d.SocialImpactInd,
d.EquityInd,
d.DebtInd,
d.RealEstateInd,
d.TargetPctgReturn,
d.ApproxTotalDealSize,
cm.CurrencyCode,
d.ConflictCheck,
cm.CreatedDate,
cm.CreatedBy,
cm.LastUpdateDate,
cm.LastUpdateBy,
d.ExpensesExceedThresholdDate,
d.CurrentTPGCheckSize,
d.PreferredEquityInd,
d.ConvertibleDebtInd,
d.OtherRealAssetsInd,
d.InitialTPGCheckSize,
d.DirectLendingInd,
cm.NameApproved,
cm.FolderID,
cm.CodaProcessedDateTime,
cm.DeadDate,
d.SectorMasterID,
d.DTODataCompleteDate,
cm.ValidFrom AS CodeMasterValidFrom,
cm.ValidTo AS CodeMasterValidTo,
d.validFrom AS DealValidFrom,
d.validTo AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO
Added the Partition by and get similar results to the ad hoc query.
sql-server
sql-server
edited May 29 at 20:09
user761786
asked May 29 at 18:19
user761786user761786
3381 gold badge3 silver badges11 bronze badges
3381 gold badge3 silver badges11 bronze badges
add a comment
|
add a comment
|
1 Answer
1
active
oldest
votes
The main performance differences
The main differences here are that the better performing query is pushing down the seek predicate on CodeMasterID
on all 4 tables (2 temporal tables (actual & history)) where the select on the view appears to not do that until the end (filter operator).
TL DR;
The issue is due to to parameters not pushing down to window functions in certain cases such as views. The easiest solution is adding OPTION(RECOMPILE)
to the view call as to make the optimizer 'see' the params at runtime if that is a possibility. If it is too expensive to recompile the execution plan for each query call, using an inline table valued function that expects a parameter could be a solution. There is an excellent Blogpost by Paul White on this. For a more detailed way on finding and resolving your particular issue, keep on reading.
The better performing query
Codemaster table
Deal table
I love the smell of seek predicates in the morning
The big bad query
Codemaster table
This is a predicate only zone
The Deal table
But the optimizer did not read 'The art of the deal™"
...and does not learn from the past
Until all that data reaches the filter operator
So, what gives?
The main problem here is the optimizer not 'seeing' the parameters at runtime due to the window functions in the view and not being able to use the SelOnSeqPrj
(select on sequence project, further down in this post for reference).
I was able to replicate the same results with a test sample and using SP_EXECUTESQL
to parameterize the call to the view. See addendum for the DDL / DML
executing a query against a test view with a window function and an INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Resulting in about 4.5s of cpu time and 3.2s elapsed time
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
When we add the sweet embrace of OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
It is all good.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Why
This all again supports the point of not being able to apply the @P1
predicate to the tables because of the window function & parameterization resulting in the filter operator
Not only an issue for temporal tables
See addendum 2
Even when not using temporal tables, this happens:
The same result is seen when writing the query like this:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Again, the optimizer is not pushing down the predicate before applying the window function.
When omitting the ROW_NUMBER()
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
All is well
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
so where does all that leave us?
The ROW_NUMBER()
is calculated before the filter is applied on the bad queries.
And all this leads us to this blogpost from 2013 by Paul White
on window functions and views.
One of the important parts for our example is this statement:
Unfortunately, the SelOnSeqPrj simplification rule only works when the
predicate performs a comparison with a constant. For that reason, the
following query produces the sub-optimal plan on SQL Server 2008 and
later:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;
This part corresponds to what we have seen when declaring the parameter ourselves / using SP_EXECUTESQL
on the view.
The actual solutions
1: OPTION(RECOMPILE)
We know that OPTION(RECOMPILE)
to 'see' the value at runtime is a possibility. When recompiling the execution plan for each query call is too expensive, there are other solutions.
2: Inline table valued function with a parameter
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
Resulting in the expected seek predicates
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
With about 9 logical reads on my test
3: Writing the query without the use of a view.
The other 'solution' could be writing the query entirely without the use of a view.
4: Not keeping the ROW_NUMBER()
function in the view, instead specifying it in the call to the view.
An example of this would be:
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
There should be other creative ways around this issue, the important part is knowing what causes it.
Addendum #1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
Addendum #2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
add a comment
|
Your Answer
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "182"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f239370%2fselect-from-view-takes-4-minutes%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
The main performance differences
The main differences here are that the better performing query is pushing down the seek predicate on CodeMasterID
on all 4 tables (2 temporal tables (actual & history)) where the select on the view appears to not do that until the end (filter operator).
TL DR;
The issue is due to to parameters not pushing down to window functions in certain cases such as views. The easiest solution is adding OPTION(RECOMPILE)
to the view call as to make the optimizer 'see' the params at runtime if that is a possibility. If it is too expensive to recompile the execution plan for each query call, using an inline table valued function that expects a parameter could be a solution. There is an excellent Blogpost by Paul White on this. For a more detailed way on finding and resolving your particular issue, keep on reading.
The better performing query
Codemaster table
Deal table
I love the smell of seek predicates in the morning
The big bad query
Codemaster table
This is a predicate only zone
The Deal table
But the optimizer did not read 'The art of the deal™"
...and does not learn from the past
Until all that data reaches the filter operator
So, what gives?
The main problem here is the optimizer not 'seeing' the parameters at runtime due to the window functions in the view and not being able to use the SelOnSeqPrj
(select on sequence project, further down in this post for reference).
I was able to replicate the same results with a test sample and using SP_EXECUTESQL
to parameterize the call to the view. See addendum for the DDL / DML
executing a query against a test view with a window function and an INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Resulting in about 4.5s of cpu time and 3.2s elapsed time
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
When we add the sweet embrace of OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
It is all good.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Why
This all again supports the point of not being able to apply the @P1
predicate to the tables because of the window function & parameterization resulting in the filter operator
Not only an issue for temporal tables
See addendum 2
Even when not using temporal tables, this happens:
The same result is seen when writing the query like this:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Again, the optimizer is not pushing down the predicate before applying the window function.
When omitting the ROW_NUMBER()
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
All is well
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
so where does all that leave us?
The ROW_NUMBER()
is calculated before the filter is applied on the bad queries.
And all this leads us to this blogpost from 2013 by Paul White
on window functions and views.
One of the important parts for our example is this statement:
Unfortunately, the SelOnSeqPrj simplification rule only works when the
predicate performs a comparison with a constant. For that reason, the
following query produces the sub-optimal plan on SQL Server 2008 and
later:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;
This part corresponds to what we have seen when declaring the parameter ourselves / using SP_EXECUTESQL
on the view.
The actual solutions
1: OPTION(RECOMPILE)
We know that OPTION(RECOMPILE)
to 'see' the value at runtime is a possibility. When recompiling the execution plan for each query call is too expensive, there are other solutions.
2: Inline table valued function with a parameter
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
Resulting in the expected seek predicates
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
With about 9 logical reads on my test
3: Writing the query without the use of a view.
The other 'solution' could be writing the query entirely without the use of a view.
4: Not keeping the ROW_NUMBER()
function in the view, instead specifying it in the call to the view.
An example of this would be:
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
There should be other creative ways around this issue, the important part is knowing what causes it.
Addendum #1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
Addendum #2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
add a comment
|
The main performance differences
The main differences here are that the better performing query is pushing down the seek predicate on CodeMasterID
on all 4 tables (2 temporal tables (actual & history)) where the select on the view appears to not do that until the end (filter operator).
TL DR;
The issue is due to to parameters not pushing down to window functions in certain cases such as views. The easiest solution is adding OPTION(RECOMPILE)
to the view call as to make the optimizer 'see' the params at runtime if that is a possibility. If it is too expensive to recompile the execution plan for each query call, using an inline table valued function that expects a parameter could be a solution. There is an excellent Blogpost by Paul White on this. For a more detailed way on finding and resolving your particular issue, keep on reading.
The better performing query
Codemaster table
Deal table
I love the smell of seek predicates in the morning
The big bad query
Codemaster table
This is a predicate only zone
The Deal table
But the optimizer did not read 'The art of the deal™"
...and does not learn from the past
Until all that data reaches the filter operator
So, what gives?
The main problem here is the optimizer not 'seeing' the parameters at runtime due to the window functions in the view and not being able to use the SelOnSeqPrj
(select on sequence project, further down in this post for reference).
I was able to replicate the same results with a test sample and using SP_EXECUTESQL
to parameterize the call to the view. See addendum for the DDL / DML
executing a query against a test view with a window function and an INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Resulting in about 4.5s of cpu time and 3.2s elapsed time
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
When we add the sweet embrace of OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
It is all good.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Why
This all again supports the point of not being able to apply the @P1
predicate to the tables because of the window function & parameterization resulting in the filter operator
Not only an issue for temporal tables
See addendum 2
Even when not using temporal tables, this happens:
The same result is seen when writing the query like this:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Again, the optimizer is not pushing down the predicate before applying the window function.
When omitting the ROW_NUMBER()
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
All is well
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
so where does all that leave us?
The ROW_NUMBER()
is calculated before the filter is applied on the bad queries.
And all this leads us to this blogpost from 2013 by Paul White
on window functions and views.
One of the important parts for our example is this statement:
Unfortunately, the SelOnSeqPrj simplification rule only works when the
predicate performs a comparison with a constant. For that reason, the
following query produces the sub-optimal plan on SQL Server 2008 and
later:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;
This part corresponds to what we have seen when declaring the parameter ourselves / using SP_EXECUTESQL
on the view.
The actual solutions
1: OPTION(RECOMPILE)
We know that OPTION(RECOMPILE)
to 'see' the value at runtime is a possibility. When recompiling the execution plan for each query call is too expensive, there are other solutions.
2: Inline table valued function with a parameter
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
Resulting in the expected seek predicates
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
With about 9 logical reads on my test
3: Writing the query without the use of a view.
The other 'solution' could be writing the query entirely without the use of a view.
4: Not keeping the ROW_NUMBER()
function in the view, instead specifying it in the call to the view.
An example of this would be:
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
There should be other creative ways around this issue, the important part is knowing what causes it.
Addendum #1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
Addendum #2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
add a comment
|
The main performance differences
The main differences here are that the better performing query is pushing down the seek predicate on CodeMasterID
on all 4 tables (2 temporal tables (actual & history)) where the select on the view appears to not do that until the end (filter operator).
TL DR;
The issue is due to to parameters not pushing down to window functions in certain cases such as views. The easiest solution is adding OPTION(RECOMPILE)
to the view call as to make the optimizer 'see' the params at runtime if that is a possibility. If it is too expensive to recompile the execution plan for each query call, using an inline table valued function that expects a parameter could be a solution. There is an excellent Blogpost by Paul White on this. For a more detailed way on finding and resolving your particular issue, keep on reading.
The better performing query
Codemaster table
Deal table
I love the smell of seek predicates in the morning
The big bad query
Codemaster table
This is a predicate only zone
The Deal table
But the optimizer did not read 'The art of the deal™"
...and does not learn from the past
Until all that data reaches the filter operator
So, what gives?
The main problem here is the optimizer not 'seeing' the parameters at runtime due to the window functions in the view and not being able to use the SelOnSeqPrj
(select on sequence project, further down in this post for reference).
I was able to replicate the same results with a test sample and using SP_EXECUTESQL
to parameterize the call to the view. See addendum for the DDL / DML
executing a query against a test view with a window function and an INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Resulting in about 4.5s of cpu time and 3.2s elapsed time
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
When we add the sweet embrace of OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
It is all good.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Why
This all again supports the point of not being able to apply the @P1
predicate to the tables because of the window function & parameterization resulting in the filter operator
Not only an issue for temporal tables
See addendum 2
Even when not using temporal tables, this happens:
The same result is seen when writing the query like this:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Again, the optimizer is not pushing down the predicate before applying the window function.
When omitting the ROW_NUMBER()
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
All is well
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
so where does all that leave us?
The ROW_NUMBER()
is calculated before the filter is applied on the bad queries.
And all this leads us to this blogpost from 2013 by Paul White
on window functions and views.
One of the important parts for our example is this statement:
Unfortunately, the SelOnSeqPrj simplification rule only works when the
predicate performs a comparison with a constant. For that reason, the
following query produces the sub-optimal plan on SQL Server 2008 and
later:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;
This part corresponds to what we have seen when declaring the parameter ourselves / using SP_EXECUTESQL
on the view.
The actual solutions
1: OPTION(RECOMPILE)
We know that OPTION(RECOMPILE)
to 'see' the value at runtime is a possibility. When recompiling the execution plan for each query call is too expensive, there are other solutions.
2: Inline table valued function with a parameter
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
Resulting in the expected seek predicates
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
With about 9 logical reads on my test
3: Writing the query without the use of a view.
The other 'solution' could be writing the query entirely without the use of a view.
4: Not keeping the ROW_NUMBER()
function in the view, instead specifying it in the call to the view.
An example of this would be:
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
There should be other creative ways around this issue, the important part is knowing what causes it.
Addendum #1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
Addendum #2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
The main performance differences
The main differences here are that the better performing query is pushing down the seek predicate on CodeMasterID
on all 4 tables (2 temporal tables (actual & history)) where the select on the view appears to not do that until the end (filter operator).
TL DR;
The issue is due to to parameters not pushing down to window functions in certain cases such as views. The easiest solution is adding OPTION(RECOMPILE)
to the view call as to make the optimizer 'see' the params at runtime if that is a possibility. If it is too expensive to recompile the execution plan for each query call, using an inline table valued function that expects a parameter could be a solution. There is an excellent Blogpost by Paul White on this. For a more detailed way on finding and resolving your particular issue, keep on reading.
The better performing query
Codemaster table
Deal table
I love the smell of seek predicates in the morning
The big bad query
Codemaster table
This is a predicate only zone
The Deal table
But the optimizer did not read 'The art of the deal™"
...and does not learn from the past
Until all that data reaches the filter operator
So, what gives?
The main problem here is the optimizer not 'seeing' the parameters at runtime due to the window functions in the view and not being able to use the SelOnSeqPrj
(select on sequence project, further down in this post for reference).
I was able to replicate the same results with a test sample and using SP_EXECUTESQL
to parameterize the call to the view. See addendum for the DDL / DML
executing a query against a test view with a window function and an INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Resulting in about 4.5s of cpu time and 3.2s elapsed time
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
When we add the sweet embrace of OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
It is all good.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Why
This all again supports the point of not being able to apply the @P1
predicate to the tables because of the window function & parameterization resulting in the filter operator
Not only an issue for temporal tables
See addendum 2
Even when not using temporal tables, this happens:
The same result is seen when writing the query like this:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Again, the optimizer is not pushing down the predicate before applying the window function.
When omitting the ROW_NUMBER()
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
All is well
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
so where does all that leave us?
The ROW_NUMBER()
is calculated before the filter is applied on the bad queries.
And all this leads us to this blogpost from 2013 by Paul White
on window functions and views.
One of the important parts for our example is this statement:
Unfortunately, the SelOnSeqPrj simplification rule only works when the
predicate performs a comparison with a constant. For that reason, the
following query produces the sub-optimal plan on SQL Server 2008 and
later:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;
This part corresponds to what we have seen when declaring the parameter ourselves / using SP_EXECUTESQL
on the view.
The actual solutions
1: OPTION(RECOMPILE)
We know that OPTION(RECOMPILE)
to 'see' the value at runtime is a possibility. When recompiling the execution plan for each query call is too expensive, there are other solutions.
2: Inline table valued function with a parameter
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
Resulting in the expected seek predicates
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
With about 9 logical reads on my test
3: Writing the query without the use of a view.
The other 'solution' could be writing the query entirely without the use of a view.
4: Not keeping the ROW_NUMBER()
function in the view, instead specifying it in the call to the view.
An example of this would be:
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
There should be other creative ways around this issue, the important part is knowing what causes it.
Addendum #1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
Addendum #2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
edited Jun 4 at 7:27
answered May 29 at 21:58
Randi VertongenRandi Vertongen
10.4k3 gold badges13 silver badges35 bronze badges
10.4k3 gold badges13 silver badges35 bronze badges
add a comment
|
add a comment
|
Thanks for contributing an answer to Database Administrators Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f239370%2fselect-from-view-takes-4-minutes%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown