Eu tenho um problema para entender por que o SQL Server decide chamar a função definida pelo usuário para cada valor da tabela, mesmo que apenas uma linha deva ser buscada. O SQL atual é muito mais complexo, mas consegui reduzir o problema para isso:
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'
Para esta consulta, o SQL Server decide chamar a função GetGroupCode para cada valor único existente na tabela PRODUCT, mesmo que a estimativa e o número real de linhas retornadas de ORDERLINE sejam 1 (é a chave primária):
O mesmo plano no explorador de plano mostrando a contagem de linhas:
Tabelas:
ORDERLINE: 1.5M rows, primary key: ORDERNUMBER + ORDERLINE + RMPHASE (clustered)
ORDERHDR: 900k rows, primary key: ORDERID (clustered)
PRODUCT: 6655 rows, primary key: PRODUCT (clustered)
O índice que está sendo usado para a verificação é:
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
A função é realmente um pouco mais complexa, mas o mesmo acontece com uma função fictícia de múltiplas instruções como esta:
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
Consegui "consertar" o desempenho forçando o SQL Server a buscar o 1 produto principal, embora 1 seja o máximo que pode ser encontrado:
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'
Em seguida, a forma do plano também muda para algo que eu esperava que fosse originalmente:
Eu também acho que o índice PRODUCT_FACTORY sendo menor que o índice agrupado PRODUCT_PK teria um efeito, mas mesmo ao forçar a consulta a usar o PRODUCT_PK, o plano ainda é o mesmo que o original, com 6655 chamadas para a função.
Se eu excluir ORDERHDR completamente, o plano começará com um loop aninhado entre ORDERLINE e PRODUCT primeiro, e a função será chamada apenas uma vez.
Gostaria de entender o que poderia ser o motivo disso, já que todas as operações são feitas usando chaves primárias e como corrigi-lo se ocorrer em uma consulta mais complexa que não pode ser resolvida com facilidade.
Editar: Criar instruções da tabela:
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)
)
fonte