Le principali differenze di prestazioni
Le differenze principali qui sono che la query con le prestazioni migliori sta spingendo verso il basso il predicato di ricerca CodeMasterIDsu tutte e 4 le tabelle (2 tabelle temporali (effettive e cronologiche)) in cui la selezione nella vista sembra non farlo fino alla fine (operatore filtro) .
TL DR;
Il problema è dovuto ai parametri che non spingono verso il basso le funzioni della finestra in alcuni casi come le viste. La soluzione più semplice è quella di aggiungere OPTION(RECOMPILE)alla chiamata di visualizzazione in modo che l'ottimizzatore 'veda' i parametri in fase di esecuzione se questa è una possibilità. Se è troppo costoso ricompilare il piano di esecuzione per ogni chiamata di query, l'utilizzo di una funzione con valori di tabella in linea che prevede un parametro potrebbe essere una soluzione. C'è un eccellente Blogpost di Paul White su questo. Per un modo più dettagliato su come trovare e risolvere il tuo problema specifico, continua a leggere.
La query con le migliori prestazioni
Tavolo Codemaster


Tabella delle offerte


Adoro l'odore del cercare predicati al mattino
La grande brutta domanda
Tavolo Codemaster


Questa è una zona solo predicato
La tabella delle offerte

Ma l'ottimizzatore non ha letto "The art of the deal ™"

... e non impara dal passato
Fino a quando tutti quei dati non raggiungono l'operatore del filtro

Quindi, cosa dà?
Il problema principale qui è che l'ottimizzatore non "vede" i parametri in fase di esecuzione a causa delle funzioni della finestra nella vista e non è in grado di utilizzare il SelOnSeqPrj (selezionare il progetto sequenza, più in basso in questo post per riferimento) .
Sono stato in grado di replicare gli stessi risultati con un campione di prova e usando SP_EXECUTESQLper parametrizzare la chiamata alla vista. Vedi addendum per DDL / DML
esecuzione di una query su una vista di prova con una funzione finestra e un INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Il risultato è circa 4,5 secondi di tempo di CPU e 3,2 secondi di tempo trascorso
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
Quando aggiungiamo il dolce abbraccio di 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;
È tutto buono.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Perché
Tutto ciò supporta ancora una volta il punto di non essere in grado di applicare il @P1predicato alle tabelle a causa della funzione finestra e della parametrizzazione risultanti nell'operatore filtro

Non solo un problema per le tabelle temporali
Vedi addendum 2
Anche quando non si usano le tabelle temporali, ciò accade:

Lo stesso risultato si vede scrivendo la query in questo modo:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Ancora una volta, l'ottimizzatore non sta spingendo verso il basso il predicato prima di applicare la funzione finestra.
Quando si omette 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;
Tutto bene
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.
quindi dove ci lascia tutto ciò?
Il ROW_NUMBER()è calcolato prima che il filtro viene applicato sui cattivi query.
E tutto ciò ci porta a questo blog post del 2013 di Paul White
su funzioni e viste delle finestre.
Una delle parti importanti per il nostro esempio è questa affermazione:
Sfortunatamente, la regola di semplificazione SelOnSeqPrj funziona solo quando il predicato esegue un confronto con una costante. Per tale motivo, la query seguente produce il piano non ottimale su SQL Server 2008 e versioni successive:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;

Questa parte corrisponde a ciò che abbiamo visto dichiarando noi stessi il parametro / usando SP_EXECUTESQLnella vista.
Le soluzioni attuali
1: OPZIONE (RICOMPILE)
Sappiamo che OPTION(RECOMPILE)'vedere' il valore in fase di esecuzione è una possibilità. Quando la ricompilazione del piano di esecuzione per ciascuna chiamata di query è troppo costosa, esistono altre soluzioni.
2: Funzione con valori di tabella incorporata con un parametro
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
Il risultato nei predicati di ricerca previsti
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
Con circa 9 letture logiche sul mio test
3: Scrivere la query senza l'uso di una vista.
L'altra 'soluzione' potrebbe essere la scrittura della query interamente senza l'uso di una vista.
4: Non mantenere il ROW_NUMBER() funzione nella vista, ma specificandola nella chiamata alla vista.
Un esempio di questo sarebbe:
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;
Dovrebbero esserci altri modi creativi per aggirare questo problema, la parte importante è sapere cosa lo causa.
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