Ho un problema nel capire perché SQL Server decide di chiamare la funzione definita dall'utente per ogni valore nella tabella anche se è necessario recuperare solo una riga. L'attuale SQL è molto più complesso, ma sono stato in grado di ridurre il problema fino a questo:
select
S.GROUPCODE,
H.ORDERCATEGORY
from
ORDERLINE L
join ORDERHDR H on H.ORDERID = L.ORDERID
join PRODUCT P on P.PRODUCT = L.PRODUCT
cross apply dbo.GetGroupCode (P.FACTORY) S
where
L.ORDERNUMBER = 'XXX/YYY-123456' and
L.RMPHASE = '0' and
L.ORDERLINE = '01'
Per questa query, SQL Server decide di chiamare la funzione GetGroupCode per ogni singolo valore esistente nella tabella PRODOTTO, anche se la stima e il numero effettivo di righe restituite da ORDERLINE è 1 (è la chiave primaria):
Lo stesso piano in Esplora piani che mostra i conteggi delle righe:
tabelle:
ORDERLINE: 1.5M rows, primary key: ORDERNUMBER + ORDERLINE + RMPHASE (clustered)
ORDERHDR: 900k rows, primary key: ORDERID (clustered)
PRODUCT: 6655 rows, primary key: PRODUCT (clustered)
L'indice utilizzato per la scansione è:
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
La funzione è in realtà leggermente più complessa, ma la stessa cosa accade con una fittizia funzione multiistruzione come questa:
create function GetGroupCode (@FACTORY varchar(4))
returns @t table(
TYPE varchar(8),
GROUPCODE varchar(30)
)
as begin
insert into @t (TYPE, GROUPCODE) values ('XX', 'YY')
return
end
Sono stato in grado di "correggere" le prestazioni forzando il server SQL a recuperare il primo prodotto 1, sebbene 1 sia il massimo che sia mai stato trovato:
select
S.GROUPCODE,
H.ORDERCAT
from
ORDERLINE L
join ORDERHDR H
on H.ORDERID = M.ORDERID
cross apply (select top 1 P.FACTORY from PRODUCT P where P.PRODUCT = L.PRODUCT) P
cross apply dbo.GetGroupCode (P.FACTORY) S
where
L.ORDERNUMBER = 'XXX/YYY-123456' and
L.RMPHASE = '0' and
L.ORDERLINE = '01'
Quindi anche la forma del piano cambia in qualcosa che mi aspettavo che fosse originariamente:
Ho anche pensato che l'indice PRODUCT_FACTORY essendo più piccolo dell'indice cluster PRODUCT_PK avrebbe un effetto, ma anche costringendo la query a utilizzare PRODUCT_PK, il piano è sempre lo stesso dell'originale, con 6655 chiamate alla funzione.
Se tralascio completamente ORDERHDR, il piano inizia prima con un ciclo nidificato tra ORDERLINE e PRODUCT e la funzione viene chiamata una sola volta.
Vorrei capire quale potrebbe essere la ragione di ciò poiché tutte le operazioni vengono eseguite utilizzando le chiavi primarie e come risolverlo se si verifica in una query più complessa che non può essere risolta così facilmente.
Modifica: crea istruzioni per la tabella:
CREATE TABLE dbo.ORDERHDR(
ORDERID varchar(8) NOT NULL,
ORDERCATEGORY varchar(2) NULL,
CONSTRAINT ORDERHDR_PK PRIMARY KEY CLUSTERED (ORDERID)
)
CREATE TABLE dbo.ORDERLINE(
ORDERNUMBER varchar(16) NOT NULL,
RMPHASE char(1) NOT NULL,
ORDERLINE char(2) NOT NULL,
ORDERID varchar(8) NOT NULL,
PRODUCT varchar(8) NOT NULL,
CONSTRAINT ORDERLINE_PK PRIMARY KEY CLUSTERED (ORDERNUMBER,ORDERLINE,RMPHASE)
)
CREATE TABLE dbo.PRODUCT(
PRODUCT varchar(8) NOT NULL,
FACTORY varchar(4) NULL,
CONSTRAINT PRODUCT_PK PRIMARY KEY CLUSTERED (PRODUCT)
)