J'ai un problème à comprendre pourquoi SQL Server décide d'appeler une fonction définie par l'utilisateur pour chaque valeur de la table même si une seule ligne doit être récupérée. Le SQL réel est beaucoup plus complexe, mais j'ai pu réduire le problème à ceci:
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'
Pour cette requête, SQL Server décide d'appeler la fonction GetGroupCode pour chaque valeur unique qui existe dans la table PRODUCT, même si l'estimation et le nombre réel de lignes renvoyées par ORDERLINE est 1 (c'est la clé primaire):
Même plan dans l'explorateur de plans montrant le nombre de lignes:
Les tables:
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'index utilisé pour l'analyse est:
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
La fonction est en fait légèrement plus complexe, mais la même chose se produit avec une fonction multi-instructions factice comme celle-ci:
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
J'ai pu "corriger" les performances en forçant SQL Server à récupérer le premier produit, bien que 1 soit le maximum que l'on puisse trouver:
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'
Ensuite, la forme du plan change également pour être quelque chose que je m'attendais à ce qu'il soit à l'origine:
J'ai également pensé que l'index PRODUCT_FACTORY étant plus petit que l'index cluster PRODUCT_PK aurait un effet, mais même en forçant la requête à utiliser PRODUCT_PK, le plan est toujours le même que l'original, avec 6655 appels à la fonction.
Si je laisse complètement ORDERHDR, le plan commence par une boucle imbriquée entre ORDERLINE et PRODUCT en premier, et la fonction n'est appelée qu'une seule fois.
Je voudrais comprendre quelle pourrait être la raison de cela car toutes les opérations sont effectuées à l'aide de clés primaires et comment y remédier si cela se produit dans une requête plus complexe qui ne peut pas être résolue aussi facilement.
Modifier: créer des instructions de table:
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)
)