SQL Server 2005: column cannot be persisted because is non-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

How to make a Non-Deterministic function 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

Writing a Stored procedure to search for matching values in columns with optional Input parameters

Requirement: To write stored Procedure(s) such that the values passed in stored procedures are matched against the values in columns in the table and then arranged with highest to lowest matching number of attributes.Then are inserted in a dynamically created temporary table inside the stored procedure.
Problem:
I have say 15-20 attributes that are matched against to confirm the suggestions made in response to record search. Basically, There is a table that stores Patients information and multiple parameters may be passed into the stored procedure to search through so that a Temporary table is created that suggests records in decreasing order of matching attributes.
To frame the basic structure, I tried with 3 attributes and respective stored procedures to match them which in turn are collectively called from a calling procedure based on the input parameter list which in turn creates the required temporary table.
Here is the SQL code(Just to give the gist of what I have tried so far):
But as a matter of fact it is, I realize this is way too naive to be used in a real time application which may require 80-90% of accuracy.So, what exactly can replace this technique for better efficiency?
Create Procedure NameMatch
(
#Name nvarchar(20),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name =' + #Name
set #temp = 0.1*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure AgeMatch
(
#Name nvarchar(20),
#Age int,
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name =#Name and Age = + #Age)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age
set #temp = 0.2*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure Nationality
(
#Name nvarchar(20),
#Age int,
#Nation nvarchar(10),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name and Age = #Age and Nationality = #Nation )
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age + ' and Nationality = ' + #Nation
set #temp = 0.3*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
create procedure CallingProcedure
(
#Name nvarchar(20),
#Age int = null,
#Nation nvarchar(10)= null
)
As
declare #PercentMatch nvarchar(4)
Begin
create table #results(PatientName nvarchar(30), PercentMatch nvarchar(4))
if(#Nation IS NOT NULL)
Insert into #results exec Nationality #Nation, #Name output, #PercentMatch output
else if(#Age is not Null)
Insert into #results exec AgeMatch #Age, #Name output, #PercentMatch output
else
Insert into #results exec NameMatch #Name, #Name output, #PercentMatch output
End
Setting aside nuances of stored procedure syntax, given parameters 1-n that if not null should match columns 1-n and the results sorted by highest number of matches first, a dynamic query is not needed - plain SQL can do it.
select *
from patient
where #param1 is null or column1 = #param1
or #param2 is null or column2 = #param2
...
or #paramN is null or columnN = #paramN
order by if(column1 = #param1, 1, 0)
+ if(column2 = #param2, 1, 0)
...
+ if(columnN = #paramN, 1, 0) desc

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

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