问题
I am getting strange results when using inline function. Here is the code:
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)
So in the first SELECT statement I've just OUTER APPLied the function and it returns the result.
In the next statement I've took the code from function and put it straight to the OUTER APPLY statement.
And the last statement is just the direct function call.
I can't get why do the FIRST query returns the value...
回答1:
This is a very interesting query. The behaviour of your first query depends upon whether you use OPTION (RECOMPILE)
or not.
As you point out, this:
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
returns this:
user_name user_name
066BDLER 066BDLER
but if you add OPTION (RECOMPILE)
like this:
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)
you correctly get this:
user_name user_name
066BDLER NULL
I suspect this is due to a bug in how the query optimiser short circuits these inline functions due to cardinality estimates. If you look at the query plan for the two queries you will see that the one without the OPTION RECOMPILE just returns a constant.
回答2:
I guess it is old bug described here. And it is Closed as Won't Fix
Please use this function like:
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
Just read the comments:
Might be the same basic issue as here stackoverflow.com/a/32414450/73226 – Martin Smith
That is it! Same issue, same link I have provided to MS Connect site.
UPDATE#2
Instead:
RETURN (SELECT @user_name AS user_name WHERE @user_name NOT LIKE '06%');
You need to use:
RETURN (SELECT CASE WHEN @user_name LIKE '06%' THEN NULL ELSE @user_name END)
回答3:
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
Ok, https://docs.microsoft.com/en-us/sql/t-sql/language-elements/select-local-variable-transact-sql
If the SELECT statement returns no rows, the variable retains its present value. If expression is a scalar subquery that returns no value, the variable is set to NULL.
For example
DECLARE @u CHAR(12) = '066BDLER'
SELECT @u = 'AAAA' WHERE 1 <> 1
SELECT @u
Under any conditions, the value of the SELECT @variable will not change, and DB ENGINE simplifies the statement to
|--Constant Scan
If use RECOMPILE sql build new plan with condition and plan change
|--Compute Scalar(DEFINE:([Expr1001]='066BDLER ')) |--Filter(WHERE:(STARTUP EXPR(NOT '066BDLER ' like '06%'))) |--Constant Scan
But RECOMPILE requires compilation costs, then use CASE for return
ps If you do not change the function, then you can bypass the assignment, otherwise why would you ever APPLY :)
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
来源:https://stackoverflow.com/questions/47834586/the-value-is-returned-instead-of-null-when-using-function-with-outer-apply