У меня проблема с пониманием того, почему SQL-сервер решает вызывать пользовательскую функцию для каждого значения в таблице, даже если нужно выбрать только одну строку. Реальный SQL намного сложнее, но мне удалось свести проблему к следующему:
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'
Для этого запроса SQL Server решает вызвать функцию GetGroupCode для каждого отдельного значения, существующего в таблице PRODUCT, даже если оценочное и фактическое количество строк, возвращаемых из ORDERLINE, равно 1 (это первичный ключ):
Тот же план в проводнике планов, показывающий количество строк:
Таблицы:
ORDERLINE: 1.5M rows, primary key: ORDERNUMBER + ORDERLINE + RMPHASE (clustered)
ORDERHDR: 900k rows, primary key: ORDERID (clustered)
PRODUCT: 6655 rows, primary key: PRODUCT (clustered)
Индекс, используемый для сканирования:
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
Функция на самом деле немного сложнее, но то же самое происходит с фиктивной функцией из нескольких операторов, например:
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
Мне удалось «исправить» производительность, заставив SQL-сервер выбрать первый продукт, хотя 1 - это максимум, который можно найти:
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'
Затем форма плана также изменится, и я ожидаю, что она будет изначально:
Я также считаю, что индекс PRODUCT_FACTORY, меньший, чем кластеризованный индекс PRODUCT_PK, будет иметь влияние, но даже при принудительном запросе на использование PRODUCT_PK план будет таким же, как и оригинальный, с 6655 вызовами функции.
Если я полностью опущу ORDERHDR, тогда план начинается с вложенного цикла между ORDERLINE и PRODUCT, а функция вызывается только один раз.
Я хотел бы понять, в чем может быть причина этого, поскольку все операции выполняются с использованием первичных ключей и как это исправить, если это происходит в более сложном запросе, который не может быть решен так легко.
Изменить: Создать операторы таблицы:
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)
)