views:

136

answers:

4

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  A: 

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?

Quassnoi
That is the definition of strictly deterministic, and all internal functions have to be strictly deterministic in order to be flagged as deterministc and then used to make either strictly deterministic or deterministic user-defined functions.
Cade Roux
+1  A: 

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.

Andrew
The function is valid and works correctly, it is just non-deterministic. I have an automatic system health report which flags all non-deterministic scalar function for review. Apparently, DB_NAME is not strictly deterministic, and for all built-in functions, strictly deterministic and deterministic are treated the same - only strictly deterministic functions are marked as deterministic. UDFs which depend on database state are still deterministic, just not strictly deterministic.
Cade Roux
Your report looks to be very precise and specific to strictly deterministic, in that sense, it is correct.
Andrew
I will add my health report code to the answer - there is only one deterministic flag for UDFs.
Cade Roux
Please make a trivial change to your answer so I can give you an up-vote, I must have clicked too many times.
Cade Roux
+4  A: 

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.

Aaronaught
For obvious reasons, I would prefer the code to be identical between databases. Also, I would prefer to encapsulate the call to make the system a little more maintainable, clear, and readable. A version which uses the configuration table becomes deterministic. Thanks.
Cade Roux
It also allows be to clear out another health exception regarding hardcoded database names. Super.
Cade Roux
+2  A: 

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

marc_s
I'd prefer not to have callers do that work, it might encourage them to simply inline the logic anyway, defeating the purpose of the encapsulation. I posted my final solution.
Cade Roux