Tengo un problema para entender por qué el servidor SQL decide llamar a la función definida por el usuario para cada valor de la tabla, aunque solo se debe buscar una fila. El SQL real es mucho más complejo, pero pude reducir el problema a esto:
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, SQL Server decide llamar a la función GetGroupCode para cada valor individual que exista en la Tabla de PRODUCT, aunque la estimación y el número real de filas devueltas desde ORDERLINE es 1 (es la clave principal):
El mismo plan en el explorador de planes que muestra los recuentos de filas:
Mesas:
ORDERLINE: 1.5M rows, primary key: ORDERNUMBER + ORDERLINE + RMPHASE (clustered)
ORDERHDR: 900k rows, primary key: ORDERID (clustered)
PRODUCT: 6655 rows, primary key: PRODUCT (clustered)
El índice que se utiliza para el escaneo es:
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
La función es en realidad un poco más compleja, pero lo mismo sucede con una función ficticia de varias instrucciones 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
Pude "arreglar" el rendimiento al forzar al servidor SQL a obtener el primer producto, aunque 1 es el máximo que se puede encontrar:
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'
Luego, la forma del plan también cambia para ser algo que esperaba que fuera originalmente:
También pensé que el índice PRODUCT_FACTORY era más pequeño que el índice agrupado PRODUCT_PK tendría un efecto, pero incluso al obligar a la consulta a usar PRODUCT_PK, el plan sigue siendo el mismo que el original, con 6655 llamadas a la función.
Si dejo completamente ORDERHDR, entonces el plan comienza con un bucle anidado entre ORDERLINE y PRODUCT primero, y la función se llama solo una vez.
Me gustaría entender cuál podría ser la razón de esto, ya que todas las operaciones se realizan utilizando claves primarias y cómo solucionarlo si ocurre en una consulta más compleja que no se puede resolver tan fácilmente.
Editar: Crear declaraciones de tabla:
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)
)