How to make a Non-Deterministic function deterministic? [duplicate] - sql

I have the following user-defined function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName else case when #FirstName = '' then #LastName else (#LastName + ' ' + #FirstName) end end)
return #Result
end
I can't create an Index on a computed column using this function cause it's not deterministic.
Someone could explain why is it not deterministic and eventually how to modify to make it deterministic?
Thanks

You just need to create it with schemabinding.
SQL Server will then verify whether or not it meets the criteria to be considered as deterministic (which it does as it doesn't access any external tables or use non deterministic functions such as getdate()).
You can verify that it worked with
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[FullNameLastFirst]'), 'IsDeterministic')
Adding the schemabinding option to your original code works fine but a slightly simpler version would be.
CREATE FUNCTION [dbo].[FullNameLastFirst] (#IsPerson BIT,
#LastName NVARCHAR(100),
#FirstName NVARCHAR(100))
RETURNS NVARCHAR(201)
WITH SCHEMABINDING
AS
BEGIN
RETURN CASE
WHEN #IsPerson = 0
OR #FirstName = '' THEN #LastName
ELSE #LastName + ' ' + #FirstName
END
END

You need to declare the User Defined Function WITH SCHEMABINDING to appease the 'deterministic' requirement of an index on the computed column.
A function declared WITH SCHEMABINDING will retain additional knowledge about the object dependencies used in the function (e.g. columns in the table), and will prevent any changes to these columns, unless the function itself is dropped beforehand.
Deterministic functions can also assist Sql Server in optimizing its execution plans, most notably the Halloween Protection problem.
Here's an example of creating an index on a computed column using a schema bound function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
with schemabinding
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName
else case when #FirstName = '' then #LastName
else (#LastName + ' ' + #FirstName) end end)
return #Result
end
create table Person
(
isperson bit,
lastname nvarchar(100),
firstname nvarchar(100),
fullname as [dbo].[FullNameLastFirst] (isperson, lastname, firstname)
)
go
insert into person(isperson, lastname, firstname) values (1,'Firstname', 'Surname')
go
create index ix1_person on person(fullname)
go
select fullname from Person with (index=ix1_person) where fullname = 'Firstname Surname'
go

Related

SQL Server 2005: column cannot be persisted because is non-deterministic [duplicate]

I have the following user-defined function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName else case when #FirstName = '' then #LastName else (#LastName + ' ' + #FirstName) end end)
return #Result
end
I can't create an Index on a computed column using this function cause it's not deterministic.
Someone could explain why is it not deterministic and eventually how to modify to make it deterministic?
Thanks
You just need to create it with schemabinding.
SQL Server will then verify whether or not it meets the criteria to be considered as deterministic (which it does as it doesn't access any external tables or use non deterministic functions such as getdate()).
You can verify that it worked with
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[FullNameLastFirst]'), 'IsDeterministic')
Adding the schemabinding option to your original code works fine but a slightly simpler version would be.
CREATE FUNCTION [dbo].[FullNameLastFirst] (#IsPerson BIT,
#LastName NVARCHAR(100),
#FirstName NVARCHAR(100))
RETURNS NVARCHAR(201)
WITH SCHEMABINDING
AS
BEGIN
RETURN CASE
WHEN #IsPerson = 0
OR #FirstName = '' THEN #LastName
ELSE #LastName + ' ' + #FirstName
END
END
You need to declare the User Defined Function WITH SCHEMABINDING to appease the 'deterministic' requirement of an index on the computed column.
A function declared WITH SCHEMABINDING will retain additional knowledge about the object dependencies used in the function (e.g. columns in the table), and will prevent any changes to these columns, unless the function itself is dropped beforehand.
Deterministic functions can also assist Sql Server in optimizing its execution plans, most notably the Halloween Protection problem.
Here's an example of creating an index on a computed column using a schema bound function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
with schemabinding
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName
else case when #FirstName = '' then #LastName
else (#LastName + ' ' + #FirstName) end end)
return #Result
end
create table Person
(
isperson bit,
lastname nvarchar(100),
firstname nvarchar(100),
fullname as [dbo].[FullNameLastFirst] (isperson, lastname, firstname)
)
go
insert into person(isperson, lastname, firstname) values (1,'Firstname', 'Surname')
go
create index ix1_person on person(fullname)
go
select fullname from Person with (index=ix1_person) where fullname = 'Firstname Surname'
go

Passing Parameters to a stored Procedure

I am working on a project that accepts 3 different parameters, the date is required and the First and Last name are optional. We setup the query below, but even if I change the parameters on the report (SSRS) it still looks at #LetterCreated as being '1/1/1950', any ideas on how I can get this to just accept the parameters? We set the date this way because we want the report to show with all of the reports when it is initially opened.
Alter Proc
AS
BEGIN
SET NOCOUNT ON;
DECLARE #LetterCreated DATETIME,
#FirstName VARCHAR(20),
#LastName VARCHAR(20)
SELECT #LetterCreated = '1/1/1950'
SELECT #FirstName = ''
SELECT #LastName = ''
SELECT
LETTERCREATETIME,
Firstname,
LastName,
From RedFlagAddress
WHERE
CASE WHEN #LetterCreated='1/1/1950'
THEN '1/1/1950'
ELSE ISNULL(LETTERCREATETIME, '07/05/81')
END = #LetterCreated
AND (LastName LIKE #LASTNAME + '%' AND FirstName LIKE #FirstNAME + '%')
END
Any suggestions would be greatly appreciated.
You are setting the #lettercreated date in the procedure. Variables defined within the procedure are not visible outside it.
You should declare the parameters as parameters, and set the default in the declaration
ALTER PROC yourproc
(
#LetterCreated DATETIME = '1950-1-1',
#FirstName VARCHAR(20) = '',
#LastName VARCHAR(20) = ''
)
as
begin
select
LETTERCREATETIME,
Firstname,
LastName,
From
RedFlagAddress
where
(
ISNULL(LETTERCREATETIME, '07/05/81') = #LetterCreated
or
#LetterCreated = '1950-1-1'
)
AND LastName LIKE #LASTNAME + '%'
AND FirstName LIKE #FirstNAME + '%'
end
I'm guessing here but what you may want is
IF parameter #LetterCreated is null then it should not be used as a filter at all
IF the data in RedFlagData.LETTERCREATETIME is Null then it should be matched to a filter date of '07/05/81' FWR
Assuming that you have the 'allow nulls' check on the RDL/RDLC set for the #LetterCreated parameter, the where needs to be changed, the optional filter can be set like so:
ISNULL(#LetterCreated, LETTERCREATETIME) = ISNULL(LETTERCREATETIME, '07/05/81')
If you get rid of the magic date, then you can guarantee no date filter applied even if LETTERCREATETIME is null, by the filter:
ISNULL(#LetterCreated, LETTERCREATETIME) = ISNULL(LETTERCREATETIME)
OR
(#LetterCreated IS NULL AND LETTERCREATETIME IS NULL)
Thus:
ALTER PROC XYZ
(
#LetterCreated DATETIME,
#FirstName VARCHAR(20) = NULL,
#LastName VARCHAR(20) = NULL
)
as
begin
select
LETTERCREATETIME,
Firstname,
LastName,
From
RedFlagAddress
where
(
ISNULL(#LetterCreated, LETTERCREATETIME) = LETTERCREATETIME
OR
(#LetterCreated IS NULL AND LETTERCREATETIME IS NULL)
)
AND LastName LIKE ISNULL(#LastName, '') + '%'
AND FirstName LIKE ISNULL(#FirstName, '') + '%'
end
One caveat : The performance of this query will be terrible, given the amount of functional manipulation in the where clause, and the LIKEs and ORs - Hopefully RedFlagAddress is a relatively small table? If not, you may need to rethink your approach
Similar to podiluska's answer, you should set the parameters as part of the SP, as podiluska indicates, but then you should set the default values in SSRS, in the Parameters there.
This will let the users see the defaults and use those for subscriptions, or override them as needed.
Alter Proc Proc_Name
(
#LetterCreated DATETIME,
#FirstName VARCHAR(20) = null,
#LastName VARCHAR(20) = null
)
AS
BEGIN
SET NOCOUNT ON;
This will pass null values as Default values to you Proc now you code should be able to handle null values if no values are provided for FirstName and LastName

SQL Server : return string procedure INITCAP

This is what I've done.
create proc INITCAP(#string varchar(30))
as
begin
SET #string = UPPER(LEFT(#string,1)) + LOWER(RIGHT(#string, LEN(#string) -1))
end
declare #lastname varchar
set #lastname = exec INITCAP 'MILLER'
declare #firstname varchar
set #firstname = exec INITCAP 'StEvE'
UPDATE Employee SET firstname = #firstname, lastname = #lastname WHERE empID = 7934
I keep getting the errors:
Msg 156, Level 15, State 1, Procedure INITCAP, Line 97
Incorrect syntax near the keyword 'exec'.
Msg 156, Level 15, State 1, Procedure INITCAP, Line 100
Incorrect syntax near the keyword 'exec'.
What shall I do? I want the procedure INITCAP to work as it does in Oracle: to return a name like: "Steve", "Miller"
Solution #1 (I wouln't use this solution)
You could use OUTPUT parameters thus:
create proc INITCAP(#string varchar(30) OUTPUT)
as
begin
SET #string = UPPER(LEFT(#string,1)) + LOWER(SUBSTRING(#string, 2, 8000))
end
go
declare #lastname varchar
set #lastname = 'MILLER'
exec INITCAP #lastname OUTPUT
declare #firstname varchar
set #firstname = 'StEvE'
exec INITCAP #firstname OUTPUT
Solution #2: Instead, I would choose to create an inline function thus:
CREATE FUNCTION dbo.Capitalize1(#string varchar(30))
RETURNS TABLE
AS
RETURN
SELECT UPPER(LEFT(#string,1)) + LOWER(SUBSTRING(#string, 2, 8000)) AS Result;
Usage:
UPDATE e
SET firstname = cap.Result
FROM Employee e
CROSS APPLY dbo.Capitalize1(e.firstname) cap;
Solution #3: Another option could be a scalar function with schemabinding option (for performance reasons):
CREATE FUNCTION dbo.Capitalize2(#string varchar(30))
RETURNS VARCHAR(30)
WITH SCHEMABINDING
AS
BEGIN
RETURN UPPER(LEFT(#string,1)) + LOWER(SUBSTRING(#string, 2, 8000));
END;
Usage:
UPDATE Employee
SET firstname = dbo.Capitalize2(firstname);
Do you really need a stored Proc for this ??? I would do something like this a UDF would do the job just fine i think....
CREATE FUNCTION dbo.udf_SomeFunction (#String VARCHAR(30))
RETURNS VARCHAR(30)
AS
BEGIN
DECLARE #rtnString VARCHAR(30);
SET #rtnString = UPPER(LEFT(#string,1)) + LOWER(RIGHT(#string, LEN(#string) -1))
RETURN(#rtnString);
END;
You can call this function in your SELECT statement , Having a proc doing the same job doesnt give you this flexibility
UPDATE
UPDATE Employee
SET firstname = dbo.udf_SomeFunction (firstname)
, lastname = dbo.udf_SomeFunction (lastname)
WHERE empID = 7934
You should use a function to achieve what you need and use a syntax that you want in setting the variables. Please not that you have to put GO between function creation and the rest.
create function INITCAP(#string varchar(30))
returns varchar(30)
as
begin
return UPPER(LEFT(#string,1)) + LOWER(RIGHT(#string, LEN(#string) -1))
end
go
declare #lastname varchar
set #lastname = dbo.INITCAP('MILLER')
declare #firstname varchar
set #firstname = dbo.INITCAP('StEvE')
UPDATE Employee SET firstname = #firstname, lastname = #lastname WHERE empID = 7934

Search ID, first name and last name in SQL using Like

Hi I have this stored procedure for Searching employee information. I want the user to have the choice to enter ID, or First Name or Last Name. but when I execute this stored procedure, it requires the other parameters.
create proc searchtry
#empID int,
#firstname varchar(20),
#lastname varchar(20)
as
begin
select fld_EmployeeId,fld_EmployeeFirstName,fld_EmployeeLastName
from Reference.tbl_EmployeeDetails
where fld_EmployeeId like #empID
OR fld_EmployeeFirstName like #firstname
OR fld_EmployeeLastName like #lastname
end
You should give default values to the parameters
create proc searchtry
#empID int = NULL,
#firstname varchar(20) = '',
#lastname varchar(20) = ''
as
begin
select fld_EmployeeId,fld_EmployeeFirstName,fld_EmployeeLastName
from Reference.tbl_EmployeeDetails
where fld_EmployeeId like #empID
OR fld_EmployeeFirstName like #firstname
OR fld_EmployeeLastName like #lastname
end

Sql Server deterministic user-defined function

I have the following user-defined function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName else case when #FirstName = '' then #LastName else (#LastName + ' ' + #FirstName) end end)
return #Result
end
I can't create an Index on a computed column using this function cause it's not deterministic.
Someone could explain why is it not deterministic and eventually how to modify to make it deterministic?
Thanks
You just need to create it with schemabinding.
SQL Server will then verify whether or not it meets the criteria to be considered as deterministic (which it does as it doesn't access any external tables or use non deterministic functions such as getdate()).
You can verify that it worked with
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[FullNameLastFirst]'), 'IsDeterministic')
Adding the schemabinding option to your original code works fine but a slightly simpler version would be.
CREATE FUNCTION [dbo].[FullNameLastFirst] (#IsPerson BIT,
#LastName NVARCHAR(100),
#FirstName NVARCHAR(100))
RETURNS NVARCHAR(201)
WITH SCHEMABINDING
AS
BEGIN
RETURN CASE
WHEN #IsPerson = 0
OR #FirstName = '' THEN #LastName
ELSE #LastName + ' ' + #FirstName
END
END
You need to declare the User Defined Function WITH SCHEMABINDING to appease the 'deterministic' requirement of an index on the computed column.
A function declared WITH SCHEMABINDING will retain additional knowledge about the object dependencies used in the function (e.g. columns in the table), and will prevent any changes to these columns, unless the function itself is dropped beforehand.
Deterministic functions can also assist Sql Server in optimizing its execution plans, most notably the Halloween Protection problem.
Here's an example of creating an index on a computed column using a schema bound function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
with schemabinding
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName
else case when #FirstName = '' then #LastName
else (#LastName + ' ' + #FirstName) end end)
return #Result
end
create table Person
(
isperson bit,
lastname nvarchar(100),
firstname nvarchar(100),
fullname as [dbo].[FullNameLastFirst] (isperson, lastname, firstname)
)
go
insert into person(isperson, lastname, firstname) values (1,'Firstname', 'Surname')
go
create index ix1_person on person(fullname)
go
select fullname from Person with (index=ix1_person) where fullname = 'Firstname Surname'
go