Is there any way to make this UDF deterministic?

2019-06-27 08:55发布

问题:

I assume this is not deterministic simply because DB_NAME() is not deterministic? If DB_NAME() is not deterministic, why is it not deterministic?

ALTER FUNCTION [TheSchema].[udf_IS_PRODUCTION] ()
RETURNS bit
    WITH SCHEMABINDING
AS 
    BEGIN
        RETURN CASE WHEN DB_NAME() = 'PRODUCTION' THEN CONVERT(bit, 1) ELSE CONVERT(bit, 0) END
    END

Update: This version works, is deterministic, allows the same code to be used in any database and removes the hardcoding of the database name (which also allows me to remove another automatic system health exception about database name coding)

ALTER FUNCTION [TheSchema].[udf_IS_PRODUCTION] ()
RETURNS bit
    WITH SCHEMABINDING
AS 
    BEGIN
        RETURN (SELECT IS_PRODUCTION FROM TheSchema.IS_PRODUCTION)
    END

FYI This is the code snippet in my system health self-reporting system which I use to monitor potential problems.

    SELECT  'Non-deterministic Scalar UDF' AS Problem
           ,QUOTENAME(ROUTINE_SCHEMA) + '.' + QUOTENAME(ROUTINE_NAME) AS ROUTINE_NAME
    FROM    INFORMATION_SCHEMA.ROUTINES WITH (NOLOCK)
    WHERE   IS_DETERMINISTIC = 'NO'
            AND ROUTINE_TYPE = 'FUNCTION'
            AND DATA_TYPE <> 'TABLE'
    ORDER BY ROUTINE_SCHEMA
           ,ROUTINE_NAME

回答1:

Sure, I can think of one way to make it deterministic. Deploy this function on your production database:

ALTER FUNCTION [TheSchema].[udf_IS_PRODUCTION] ()
RETURNS bit
    WITH SCHEMABINDING
AS 
BEGIN
    RETURN CONVERT(bit, 1)
END

And deploy this one to your test database:

ALTER FUNCTION [TheSchema].[udf_IS_PRODUCTION] ()
RETURNS bit
    WITH SCHEMABINDING
AS 
BEGIN
    RETURN CONVERT(bit, 0)
END

This may seem silly but IMO the database name should not be "hard coded" any more so than the return value of some UDF.

Better yet, just put this information in a configuration table somewhere.



回答2:

Couldn't you maybe rewrite your function not to determine the DB_NAME() internally, but get it send in as a parameter??

ALTER FUNCTION [TheSchema].[udf_IS_PRODUCTION] (DatabaseName VARCHAR(255))
RETURNS bit
WITH SCHEMABINDING
AS 
    BEGIN
        RETURN CASE WHEN DatabaseName = 'PRODUCTION' 
                    THEN CONVERT(bit, 1) 
                    ELSE CONVERT(bit, 0) 
               END
    END

Not it should be deterministic, right?

When you call it, you can use DB_NAME() as a function to determine the database name



回答3:

A deterministic function, by definition, is a function whose return value is uniquely identified by the values of its agruments.

Now, given the arguments to DB_NAME() (which are none), can you tell what will it return?



回答4:

In strictess sense of determinism, the result is not based on the input parameters, but on the state of an external object that is not within your control.

The name could be altered etc,

Alter Database Modify Name = new_name

On 2005, SQL doesn't prevent the function being created though when I tried it against the default schema. If you get into a situation where it refuses to accept a function based on non-determinism and you have to work around it (with the risks etc), the route around it is to create a view that uses the function, and then select from the view within the function.