Check Constraints using Functions - sql-server

I was taking some time trying to write a check constraint that is pointed to a function and I found this article about it:
article about check constraints using functions
My question is: Check Constraints are definitively a better option than triggers?
here you have an example:
CREATE TABLE tmgr.MyTable
(
id INT IDENTITY PRIMARY KEY,
NAME NVARCHAR(50),
TypeId int
)
CREATE FUNCTION dbo.fn_CheckConstraintType
(
#Entity nvarchar(128),
#TypeId int
)
RETURNS int
AS
BEGIN
DECLARE #ReturnValue INT = 0
IF EXISTS (SELECT 1
FROM dbo.TypeEntity
WHERE Entityid = ( SELECT Entityid
FROM dbo.Entity
WHERE Name = #Entity
)
AND TypeId = #TypeId)
SET #ReturnValue = 1
RETURN #ReturnValue
END
Here you have the check constraint
ALTER TABLE dbo.MyTable WITH NOCHECK ADD CONSTRAINT CK_MyTable_CheckConstraintType CHECK (dbo.fn_CheckConstraintType('MyTableType', [TypeId])=1)
I want to be sure that this approach is the best option or there is any other option before using triggers. Check Constraints happen before the insert/update statement is this the main different of using triggers? too many question! sorry!
Thanks

Related

Compare two KeyValue types data in SQL Server 2012

I have the below data available in a table
DECLARE #AddressTbl As Table (ID int identity,Address varchar(100))
INSERT INTO #AddressTbl
VALUES ('State:AndhraPradesh,Dist:Prakasam')
Next time when I enter the same value I should be notified that this value exists in the table.
For this I will use an sp with warning message that the data is available. But I want how to implement the logic to compare the data.
Create Procedure usp_InsertAddress
(
#Address varchar(100)
)
AS
DECLARE #ID INT
SELECT #ID=(SELECT ID FROM #AddressTbl WHERE Address = #Address)
IF #ID IS NULL
BEGIN
INSERT INTO #AddressTbl
VALUES ('State:AndhraPradesh,Dist:Prakasam')
END;
I may enter the address like 'Dist:Prakasam,State:AndhraPradesh'
and there may be some blank spaces also. So need to parse the address and check the key and values.
I will use permanent table instead of table variable.
Appreciate your help.
You can use if exists for check. Or you can add unique CONSTRAINT in your table. The UNIQUE constraint ensures that all values in a column are different. This will return error if you are trying to insert duplicate value.
Create Procedure usp_InsertAddress
(
#Address varchar(100)
)
AS
if exists(SELECT ID FROM #AddressTbl WHERE Address = #Address)
begin
select 'Value is Already Exist in Table'---For Warning
end
else
BEGIN
INSERT INTO #AddressTbl
VALUES ('State:AndhraPradesh,Dist:Prakasam')
select 'Value Inserted Sucessful'---For Success
END;

How to set the default parameter value of a function for a user-defined table type?

I have a user-defined table type:
CREATE TYPE [dbo].[EntityKeysTable] AS TABLE
(
[EntityKey] [uniqueidentifier] NOT NULL,
PRIMARY KEY CLUSTERED( [EntityKey] ASC) WITH (IGNORE_DUP_KEY = OFF)
)
And a function that uses it... But that function is being used by different processes in our system and I don't want to go in and have to deal with the new parameter everywhere, so I rather just make it optional by having a default value in it...
ALTER FUNCTION [dbo].[fnMyFunctionNameHere]
(#siteId UNIQUEIDENTIFIER,
#entityKeys AS EntityKeysTable READONLY,
#partialDeploy AS BIT = false)
RETURNS TABLE
AS
RETURN (
WITH Overrides AS (
SELECT * ....
I did it for the #partialDeploy but unsure about how to do the same for the #entityKeys.
I am sorry for my prior misleading information.
Table valued functions cannot be null. But you can define them with no records in it. So, you would check for (select count(*) from #entityKeys) = 0 to check if there are entityKeys or not.
if (select count(*) from #entityKeys) = 0
--load #entityKeys as your wish
Some time ago I also searched for the answer to this question, but found nothing. So I did such:
create PROCEDURE dbo.Test
#table dbo.testType readonly
AS
BEGIN
declare #table2 dbo.testType
if(not exists(select * from #table))
.... do something like
insert into #table2(t1,t2) values ....
else
insert into #table2(t1,t2) select t1,t2 from #table
... do main code using #table2
END
User defined table types is optional. So you may not pass it to function.

SQL defaults - best practice?

What is the best thing to do in the CREATE and UPDATE stored procedures for a table with default constraints?
When I create a new column for a table, I try to set a propper default value (Default constraint).
Example:
CREATE TABLE Orders
(
O_ID INT NOT NULL
,State INT DEFAULT 0 -- 0 => Not Verified, 1 => Verified, 2 => Processing ....
,P_ID INT
,OrderDate DATE DEFAULT GETDATE()
)
What is the best thing to do in the CREATE and UPDATE stored procedures for this table?
Use the same defaults as in the constraint?
CREATE PROCEDURE UpdateOrder
(
#O_ID INT
,#State INT = 0
,#P_ID INT
,#OrderDate DATE
)
AS
UPDATE
Orders
SET
State = #State
,P_ID = #PID
,OrderDate = #OrderDate
WHERE
O_ID = #O_ID
That would be kind of repetitive, as it's already defaulted in the table.
On the other hand, it allows your parameters to be optional. I would say your choices are to default them to the same as the table (as you suggest), or to default them to null and the table will fill in the default values. The second way is less repetitive and error-prone.
If you want OrderDate to be updated during UPDATE, you need to include it in the UPDATE statement and use getdate() in place of #OrderDate
UPDATE
Orders
SET
State = #State ,
P_ID = #PID ,
OrderDate = getdate()
WHERE O_ID = #O_ID
To comply with DRY, one workaround would be to store your defaults in a table maybe:
CREATE TABLE dbo.MyDefaults
(
OrderState INT
);
INSERT dbo.MyDefaults(OrderState) SELECT 0;
You can't really do this with GETDATE(), so let's just leave that as is - not something you're likely to change, anyway. So now we can pull our default value from the table, instead of hard-coding it. Let's create a scalar function, because we can't use a subquery in a default constraint:
CREATE FUNCTION dbo.DefaultOrderState()
RETURNS INT
AS
BEGIN
RETURN (SELECT TOP (1) OrderState FROM dbo.MyDefaults);
END
GO
(If you have a lot of these, you might consider an EAV approach instead of dedicated columns.)
So now we can have our Orders table, and note that the constant "0" is never mentioned:
CREATE TABLE dbo.Orders
(
O_ID INT NOT NULL,
[State] INT NOT NULL DEFAULT (dbo.DefaultOrderState()),
P_ID INT,
OrderDate DATE NOT NULL DEFAULT (SYSDATETIME())
);
GO
And our update procedure can also grab the defaults (except you haven't defined whether you really want to reset the state to 0 if it is not currently 0 and no value is supplied to the procedure). Again the "0" constant is not mentioned.
CREATE PROCEDURE dbo.UpdateOrder
#O_ID INT,
#State INT = NULL,
#P_ID INT,
#OrderDate DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Orders
SET [State] = COALESCE(#State, dbo.DefaltOrderState()),
P_ID = #P_ID,
OrderDate = COALESCE(#OrderDate, OrderDate, SYSDATETIME())
WHERE
O_ID = #O_ID;
END
GO
For the update, you could send in null for "No Change" (or another sentinel if the column is nullable). A similar approach would work for inserts.
CREATE PROCEDURE UpdateOrder
(
#O_ID INT
,#State INT
,#P_ID INT = -1
,#OrderDate DATE
)
AS
UPDATE
Orders
SET
State = IsNull(#State,State)
,P_ID = case when #P_ID < 0 then P_ID else #P_ID end -- assuming this int is not nullable and something like -1 is the default value
,OrderDate = COALESCE(#OrderDate,OrderDate)
WHERE
O_ID = #O_ID

ALTER TABLE with programmatically determined constant DEFAULT value

I am trying to add a column (MSSQL 2005) to a table (Employee) with a default constraint of a primary key of another table (Department). Then I am going to make this column a FK to that table. Essentially this will assign new employees to a base department based off the department name if no DepartmentID is provided.
This does not work:
DECLARE #ErrorVar INT
DECLARE #DepartmentID INT
SELECT #DepartmentID = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
ALTER TABLE [Employee]
ADD [DepartmentID] INT NULL
CONSTRAINT [DepartmentIDOfAssociate] DEFAULT (#DepartmentIDAssociate)
SELECT #ErrorVar = ##Error
IF (#ErrorVar <> 0)
BEGIN
GOTO FATAL_EXIT
END
The Production, Test, and Development databases have grown out of synch and the DepartmentID for the DepartmentName = ‘RocketScience’ may or may not be the same so I don’t want to just say DEFAULT (somenumber). I keep getting “Variables are not allowed in the ALTER TABLE statement” no matter which way I attack the problem.What is the correct way to do this? I have tried nesting the select statement as well which gets “Subqueries are not allowed in this context. Only scalar expressions are allowed.”
In Addition, what would be really great I could populate the column values in one statement instead of doing the {ALTER null} {Update values} {ALTER not null} steps. I read something about the WITH VALUES command but could not get it to work.
Thanks!!!
The accepted answer worked great (Thanks marc_s) but after I thought about it for a while I decided to go another route.Mainly because there has to be a function left on the server which I think ends up being called every time an employee is added.If someone messed with the function later then no one could enter an employee and the reason would not be obvious.
(Even if that is not true then there are still extra functions on the server that do not need to be there)
What I did was assemble the command dynamically in a variable and then call that using the EXECUTE command.
Not only that but since I used the DEFAULT keyword with NOT NULL the table was back populated and I didn't have to run multiple commands to get it done. I found that one out by luck...
DECLARE #ErrorVar INT
DECLARE #DepartmentIDRocketScience INT
DECLARE #ExecuteString NVARCHAR(MAX)
SELECT #DepartmentIDRocketScience = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
SET #ExecuteString = ''
SET #ExecuteString = #ExecuteString + 'ALTER TABLE [Employee] '
SET #ExecuteString = #ExecuteString + 'ADD [DepartmentID] INT NOT NULL '
SET #ExecuteString = #ExecuteString + 'CONSTRAINT [DF_DepartmentID_RocketScienceDepartmentID] DEFAULT ' +CAST(#DepartmentIDAssociate AS NVARCHAR(MAX))
EXECUTE (#ExecuteString)
SELECT #ErrorVar = ##Error
IF (#ErrorVar <> 0)
BEGIN
GOTO FATAL_EXIT
END
You could wrap the code to find your department ID into a stored function and use that in your DEFAULT constraint statement:
CREATE FUNCTION dbo.GetDepartment()
RETURNS INT
AS
BEGIN
DECLARE #DepartmentID INT
SELECT #DepartmentID = DepartmentID
FROM Department
WHERE RealName = 'RocketScience'
RETURN #DepartmentID
END
And then:
ALTER TABLE [Employee]
ADD [DepartmentID] INT NULL
CONSTRAINT [DepartmentIDOfAssociate] DEFAULT (dbo.GetDepartment())
Does that help?
Marc
If you apply your foreign key constraint after adding the column, you can use any value for the default value when altering the table. Then you can run an update statement with your variable value.
ALTER TABLE Employee
ADD DepartmentID INT NOT NULL
Default -1;
UPDATE Employee
SET Employee.DepartmentID = #DepartmentID
WHERE DepartmentID = -1;
ALTER TABLE Employee
ADD CONSTRAINT fk_employee_to_department FOREIGN KEY (DepartmentID)
REFERENCES Department;

Best way to do serializable updates to a table

I'm a long time Firebird user and it has a feature called Generators (I think Oracle also has it and it's called Sequences). I'm new to SQL Server and I need to simulate the same feature. I can't use an identity field to solve my problem. I need a named series of values, not a unique number for each row.
My biggest concern was about more than one user calling the procedure at the same time and getting duplicated values, so I decided to lock using the SERIALIZABLE isolation level.
So I came up with this code (I'm using SQL Server 2005):
CREATE TABLE dbo.GENERATORS
(
[NAME] VARCHAR(30) NOT NULL,
[VALUE] INT,
CONSTRAINT UNQ_GENERATORS_NAME UNIQUE NONCLUSTERED ( [NAME] )
)
GO
CREATE PROCEDURE GEN_ID
(
#GENERATOR_NAME VARCHAR(30)
)
AS
DECLARE #RETURN_VALUE INT ;
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE GENERATORS
SET [VALUE] = [VALUE] + 1
WHERE [NAME] = #GENERATOR_NAME
IF ##ROWCOUNT = 0
BEGIN
INSERT INTO dbo.GENERATORS ( [NAME], [VALUE] )
VALUES ( #GENERATOR_NAME, 1 )
SET #RETURN_VALUE = 1
END
ELSE
BEGIN
SELECT #RETURN_VALUE = [VALUE]
FROM GENERATORS
WHERE [NAME] = #GENERATOR_NAME
END
COMMIT TRANSACTION
RETURN #RETURN_VALUE
GO
So my questions are:
Is this a good solution?
Is there any better way?
Thanks.
Instead of UPDATE and SELECT, to get the value back that you just added, you can do
UPDATE GENERATORS
SET #RETURN_VALUE = [VALUE] = [VALUE] + 1
WHERE [NAME] = #GENERATOR_NAME
which may remove the need for isolation level
I would prefer to return #RETURN_VALUE as an OUTPUT parameter, rather than as a Return value - leaving the Return Value free for any error code during execution.

Resources