Значение возвращается вместо NULL при использовании функции с OUTER APPLY

Я получаю странные результаты при использовании встроенной функции. Вот код:

IF EXISTS (
SELECT * FROM sys.objects AS o WHERE name = 'vendor_relation_users'
) DROP FUNCTION dbo.vendor_relation_users;
GO
CREATE FUNCTION [dbo].[vendor_relation_users]
(
    @user_name CHAR(12)
)
RETURNS TABLE
AS
    RETURN (SELECT @user_name AS user_name WHERE @user_name NOT LIKE '06%');
GO

DECLARE @u CHAR(12) = '066BDLER'
SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY [dbo].[vendor_relation_users](@u) AS is_v

SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY (SELECT @u AS user_name WHERE @u NOT LIKE '06%') AS is_v


SELECT * FROM [dbo].[vendor_relation_users](@u)

введите описание изображения здесь

Итак, в первом операторе SELECT я просто применил функцию OUTER, и она возвращает результат.

В следующем утверждении я взял код из функции и поместил его прямо в оператор OUTER APPLY.

И последнее утверждение - это просто прямой вызов функции.

Я не могу понять, почему запрос FIRST возвращает значение...

3 ответа

Это очень интересный запрос. Поведение вашего первого запроса зависит от того, используете ли вы OPTION (RECOMPILE) или нет.

Как вы указываете, это:

DECLARE @u CHAR(12) = '066BDLER'
SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY [dbo].[vendor_relation_users](@u) AS is_v

возвращает это:

user_name       user_name
066BDLER        066BDLER

но если вы добавите OPTION (RECOMPILE) как это:

SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY [dbo].[vendor_relation_users](@u) AS is_v
OPTION (RECOMPILE)   

Вы правильно поняли это:

user_name       user_name
066BDLER        NULL

Я подозреваю, что это связано с ошибкой в ​​том, как оптимизатор запросов замыкает эти встроенные функции из-за оценок количества элементов. Если вы посмотрите на план запроса для двух запросов, то увидите, что тот, у которого нет OPTION RECOMPILE, просто возвращает константу.

Я думаю, что это старая ошибка, описанная здесь. И это Closed as Won't Fix

Пожалуйста, используйте эту функцию как:

SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY (
    SELECT *
    FROM [dbo].[vendor_relation_users](a.user_name) 
    ) AS is_v

UPDATE # 1

Просто прочитайте комментарии:

Может быть та же самая основная проблема, что и здесь /questions/27083479/outer-apply-vozvraschenie-stolbtsov-neozhidanno-not-null-esli-net-sovpadenij/27083481#27083481 - Мартин Смит

Вот и все! Та же проблема, та же ссылка, которую я предоставил на сайт MS Connect.

UPDATE # 2

Вместо:

RETURN (SELECT @user_name AS user_name WHERE @user_name NOT LIKE '06%');

Вам необходимо использовать:

RETURN (SELECT CASE WHEN @user_name LIKE '06%' THEN NULL ELSE @user_name END)
CREATE FUNCTION [dbo].[vendor_relation_users]
(
    @user_name CHAR(12)
)
RETURNS TABLE
AS
  RETURN (SELECT CASE WHEN @user_name NOT LIKE '06%' THEN  @user_name  ELSE NULL END as [user_name]);  
GO

Хорошо, https://docs.microsoft.com/en-us/sql/t-sql/language-elements/select-local-variable-transact-sql

Если инструкция SELECT не возвращает строк, переменная сохраняет свое текущее значение. Если выражение является скалярным подзапросом, который не возвращает значения, переменная устанавливается в NULL.

Например

DECLARE @u CHAR(12) = '066BDLER'
SELECT  @u  = 'AAAA' WHERE 1 <> 1
SELECT @u

При любых условиях значение SELECT @variable не изменится, и DB ENGINE упростит оператор до

|- Постоянное сканирование

При использовании RECOMPILE sql создайте новый план с условием и изменением плана

        |--Compute Scalar(DEFINE:([Expr1001]='066BDLER    '))
             |--Filter(WHERE:(STARTUP EXPR(NOT '066BDLER    ' like '06%')))
                  |--Constant Scan

Но RECOMPILE требует затрат на компиляцию, затем используйте CASE для возврата

ps Если вы не измените функцию, то вы можете обойти назначение, иначе зачем вам когда-либо подавать заявку:)

DECLARE @u CHAR(12) = '066BDLER'
SELECT a.user_name, is_v.user_name 
FROM (SELECT @u AS user_name) a
OUTER APPLY [dbo].[vendor_relation_users](a.user_name) AS is_v
Другие вопросы по тегам