check constraints multiple options - sql-server

i have a table with a column i want to apply constraints too but it has two variables.
Table is called: MyOrderTable
Column is called : Orderref
example data would be:
OrderRef
80
75
110
110
80
110
What i want to do is create a constraint where if the value of OrderRef is below 100 it cannot be a duplicate number to any exisiting value in that column but if it is above 100 it can be a duplicate - can this be done - assume i will need a function to do this?
tried the following but it did not work:
ALTER TABLE MYORDERTABLE
ADD CONSTRAINT TAS_CK
CHECK (
MYORDERTABLE_CHECK(ORDERREF) = 1)
CREATE FUNCTION MYORDERTABLE_CHECK (
#ORDERREF INT
)
RETURNS TINYINT
AS
BEGIN
DECLARE #RESULT TINYINT
IF EXISTS(SELECT * FROM MYORDERTABLE WHERE ORDERREF < 100)
SET #RESULT= 1
ELSE
SET #RESULT= 0
RETURN #RESULT
END
i was still allowed to enter data under 100 for the orderref column

The CHECK fires up AFTER the row has been inserted into the table, not
before.
here in comments noticed about that
So you should use instead of insert trigger
use tempdb
go
create table t (
id int not null identity(1,1),
orderref int not null
)
go
create trigger tr_t on t instead of insert
as
begin
if exists(
select 1
from inserted i
join t on i.orderref < 100 and i.orderref = t.orderref
)
begin
raiserror('orderref < 100 already exists', 16, 1)
return
end
insert t(orderref)
select orderref from inserted
end
go
insert t (orderref) values (1)
insert t (orderref) values (2)
insert t (orderref) values (100)
insert t (orderref) values (100)
insert t (orderref) values (5)
insert t (orderref) values (5) -- error here
select * from t

Related

How can I update the rows that are existed before the insert using Trigger ( SQL Server )?

I'm looking for a method to update old rows before an insert or update using a Trigger ,
For example , I have this table
ID PersonID Name Status
1 001 Alex False
2 002 Mark True
What I need exactly is that when I insert in this table a new row (3,003,Jane,True) , the column status should be affected to False ( all old rows ) only the new row will have True
So the expected result when applying the trigger will be like this :
ID PersonID Name Status
1 001 Alex False
2 002 Mark False
3 003 Jane True
How can I do this?
What I have tried:
ALTER TRIGGER [dbo].[dbo.TR_SetStatus] ON [dbo].[Person]
after INSERT
NOT FOR REPLICATION
AS
DECLARE #CursorTestID INT = 1;
DECLARE #RowCnt BIGINT = 0;
BEGIN
DECLARE #count INT;
SELECT #RowCnt = COUNT(*) FROM Person;
WHILE #CursorTestID <= #RowCnt
BEGIN
update Person set status=0
SET #CursorTestID = #CursorTestID + 1
END
END
I have two questions:
How can I update the rows that are existed before the insert using Trigger ( SQL Server )?
How can I pass a parameter to a trigger? (as an example PersonID)
DROP TABLE IF EXISTS dbo.Test;
CREATE TABLE dbo.Test
(
id tinyint identity(1,1)not null primary key,
person_id char(3)not null,
name varchar(50)not null,
status varchar(5) not null
)
insert dbo.Test(person_id,name,status)
values('001','alex','false'),('002','mark','true');
go
SELECT *FROM DBO.Test
DROP TRIGGER IF EXISTS dbo.II_Test;
GO
CREATE TRIGGER dbo.II_Test
ON dbo.Test
INSTEAD OF INSERT
AS
BEGIN
UPDATE DBO.Test SET status='FALSE';
INSERT DBO.Test(person_id,name,status)
SELECT I.person_id,I.name,I.status
FROM inserted AS I
END
GO
insert dbo.Test(person_id,name,status)
values('003','JANE','true');
select * from dbo.Test
Could you please check if the above is suitable for you
How can I update the rows that are existed before the insert using Trigger ( SQL Server )
For example, you can use INSTEAD OF-trigger
How can I pass a parameter to a trigger? (as an example PersonID)
This is not supported at all. If you need parameters the better way, I guess, is to use stored procedure
Finally, I solved my problem ( the first question ) :
ALTER TRIGGER [dbo].[dbo.TR_SetActive] ON [dbo].[test]
after INSERT
NOT FOR REPLICATION
AS
BEGIN
update dbo.test set status=0 WHERE Id < (SELECT MAX(Id) FROM dbo.test)
END
For the second question , I have used to get the last record as parameter:
set #PersonId = (select PersonId from inserted)
Can be simplfy with :
CREATE TRIGGER dbo.E_I_Test
ON dbo.Test
FOR INSERT
AS
BEGIN
UPDATE T
SET status = CASE WHEN I.id IS NULL THEN 0 ELSE 1 END
FROM dbo.Test as T
LEFT OUTER JOIN inserted AS I
ON T.id = I.id;
BEWARE.... status is a reserved Transact SQL keyword. Should not be use for any SQL identifier (table, name, column neme...)
Corrections made...

Capture IDENTITY column value during insert and use as value for another column in same transaction

While performing an insert to a table which has an IDENTITY column, is it possible to use the IDENTITY value as the value for another column, in the same transaction?
For example:
DECLARE #TestTable TABLE
(
PrimaryId INT NOT NULL IDENTITY(1, 1),
SecondaryId INT NOT NULL
);
INSERT INTO #TestTable (SecondaryId)
SELECT
SCOPE_IDENTITY() + 1; -- set SecondaryId = PrimaryId + 1
SELECT * FROM #TestTable;
Expected:
| PrimaryId | SecondaryId |
+-----------+-------------+
| 1 | 2 |
I thought I might be able to achieve this with the SCOPE_IDENTITYor ##IDENTITY system functions, but unfortunately this does not work, as it is NULL at the time the transaction is executed.
Cannot insert the value NULL into column 'SecondaryId', table '#TestTable'; column does not allow nulls. INSERT fails.
I know I could use a computed column for this example, but I'm curious if what I'm trying to do is even possible.
Could you change your approach and use a SEQUENCE instead of an IDENTITY column?
CREATE SEQUENCE TestSequence
START WITH 1
INCREMENT BY 1 ;
GO
CREATE TABLE TestTable (PrimaryId INT NOT NULL DEFAULT NEXT VALUE FOR TestSequence, SecondaryId INT NOT NULL);
GO
INSERT INTO TestTable (
SecondaryId
)
SELECT NEXT VALUE FOR TestSequence + 1; -- set SecondaryId = PrimaryId + 1;
GO 3
SELECT * FROM TestTable;
GO
DROP TABLE TestTable;
DROP SEQUENCE TestSequence;
I would go with a trigger, this should also work for multi row inserts, You will need to remove the not null for SecondaryID, not sure if that's acceptable.
create trigger trg_TestTable
on dbo.TestTable
after insert
AS
BEGIN
update TestTable
set SecondaryId = i.PrimaryId
from inserted i
join TestTable a
on i.PrimaryId = a.PrimaryId;
END
GO
One thing you could do is use the OUTPUT INSERTED option of the INSERT COMMAND to capture the IDENTITY.
In this example the IDENTITY field is ScheduleID.
CREATE PROCEDURE dbo.spScheduleInsert
( #CustomerID int,
#ItemID int,
#StartDate Date,
#TimeIn DateTime,
#TimeOut DateTime,
#ReturnIdentityValue int OUTPUT )
AS
BEGIN
DECLARE #TempScheduleIdentity table ( TempScheduleID int )
INSERT INTO Schedule ( CustomerID,ItemID,StartDate,TimeIn,TimeOut )
OUTPUT INSERTED.ScheduleID into #TempScheduleIdentity
VALUES (#CustomerID,#ItemID,#StartDate,#TimeIn,#TimeOut)
SELECT #ReturnIdentityValue = (SELECT TempScheduleID FROM #TempScheduleIdentity)
END
Once you have the #ReturnIdentityValue...you could then update the records other field with the value.

SQL Server trigger on Insert and Update

I am looking to create a SQL Server trigger that moves a record from one table to an identical replica table if the record matches a specific condition.
Questions: do I need to specify each column, or can I use a wildcard?
Can I use something like:
SET #RecID = (SELECT [RecoID] FROM Inserted)
IF NULLIF(#RecID, '') IS NOT NULL
(then insert....)
THANKS!
There's a lot of stuff you "CAN" do in a trigger, but that doesn't mean you should. I'd would urge to to avoid setting scalar variables within a trigger at all costs. Even if you 100% sure your table will never have more that 1 row inserted per transaction because that's how the app is designed... You'll be in for very rude awakening when you find out that not all transactions come through the application.
Below is a quick demonstration of both types of triggers...
USE tempdb;
GO
IF OBJECT_ID('tempdb.dbo.PrimaryTable', 'U') IS NOT NULL
DROP TABLE dbo.PrimaryTable;
GO
IF OBJECT_ID('tempdb.dbo.TriggerScalarLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerScalarLog;
GO
IF OBJECT_ID('tempdb.dbo.TriggerMultiRowLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerMultiRowLog;
GO
CREATE TABLE dbo.PrimaryTable (
Pt_ID INT NOT NULL IDENTITY (1,1) PRIMARY KEY CLUSTERED,
Col_1 INT NULL,
Col_2 DATE NOT NULL
CONSTRAINT df_Col2 DEFAULT (GETDATE())
);
GO
CREATE TABLE dbo.TriggerScalarLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
CREATE TABLE dbo.TriggerMultiRowLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
--=======================================================
CREATE TRIGGER dbo.PrimaryCrudScalar ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
DECLARE
#Pt_ID INT,
#Col1_Old INT,
#Col1_New INT,
#Col2_Old DATE,
#Col2_New DATE;
SELECT
#Pt_ID = ISNULL(i.Pt_ID, d.Pt_ID),
#Col1_Old = d.Col_1,
#Col1_New = i.Col_1,
#Col2_Old = d.Col_2,
#Col2_New = i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
INSERT dbo.TriggerScalarLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
VALUES (#Pt_ID, #Col1_Old, #Col1_New, #Col2_Old, #Col2_New);
GO -- DROP TRIGGER dbo.PrimaryCrudScalar;
CREATE TRIGGER PrimaryCrudMultiRow ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
INSERT dbo.TriggerMultiRowLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
SELECT
ISNULL(i.Pt_ID, d.Pt_ID),
d.Col_1,
i.Col_1,
d.Col_2,
i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
GO -- DROP TRIGGER dbo.TriggerMultiRowLog;
--=======================================================
--=======================================================
-- --insert test...
INSERT dbo.PrimaryTable (Col_1)
SELECT TOP 100
o.object_id
FROM
sys.objects o;
SELECT 'INSERT Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'INSERT Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
UPDATE pt SET
pt.Col_1 = pt.Col_1 + rv.RandomVal,
pt.Col_2 = DATEADD(DAY, rv.RandomVal, pt.Col_2)
FROM
dbo.PrimaryTable pt
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 10000 + 1) ) rv (RandomVal);
SELECT 'UPDATE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'UPDATE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
DELETE pt
FROM
dbo.PrimaryTable pt;
SELECT 'DELETE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'DELETE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
You could, but I'd recommend against it. If your source table changed things would start failing.
Also, in your example if you were to ever have more than one row inserted at a time you would get thrown an error (or have unpredictable results). I'd recommend a more set based approach:
INSERT table2 ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table2 t ON i.RecoID = t.RecoID
WHERE t.RecoID IS NULL;
EDIT:
If you want to stop the insert happening on your original table then you'll need to do something along the lines of:
CREATE TRIGGER trigger_name
ON table_orig
INSTEAD OF INSERT
AS
BEGIN
-- make sure we aren't triggering from ourselves from another trigger
IF TRIGGER_NESTLEVEL() <= 1
return;
-- insert into the table_copy if the inserted row is already in table_orig (not null)
INSERT table_copy ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NOT NULL;
-- insert into table_orig if the inserted row is not already in table_orig (null)
INSERT table_orig ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NULL;
END;
The instead of will stop the insert if you don't want it to actually be inserted, so you'll need to do that yourself (the second insert statement).
Please note I changed some nulls to not nulls and the table we are left joining to in some cases.

SQL need to insert into table which only consists of a primary key but no auto increment

I need to insert a value into a table which only consists of one column, that is, the primary key.
Furthermore, NULL is not allowed, Identity is set to FALSE and both Identity Seed and Identity Increment are set to 0.
I try to insert with INSERT INTO table(id) VALUES (null) which obviously does not work. INSERT INTO table(id) default values also does not work.
How can I fill this column with the correctly incremented ID?
Implementing Identity or Sequence would be the best solution, but if you really cannot alter the schema the alternative is to lock the table in a transaction, create the new value, unlock the table. Note this can have performance consequences.
create table dbo.ids ( id int primary key clustered );
GO
insert dbo.ids values ( 1 ), ( 2 ), ( 3 ), ( 4 ) ;
GO
declare #newid int;
begin transaction
set #newid = ( select top( 1 ) id from dbo.ids with ( tablockx, holdlock ) order by id desc ) + 1 ;
insert into dbo.ids values ( #newid );
select #newid;
commit
GO 20
You can use while function in that insert
declare #id int
select #id = max(id) from table
while #id <= (... put here max nuber of your id you want to insert)
begin
insert into table values (#id)
set #id = #id+1 end
end
This can be a solution too.
declare #newid integer
begin tran
select #newid = isnull(max(id), 0) + 1 from table with (xlock,holdlock)
insert into table values(#newid)
select #newid
commit tran

SQL Server : stored procedure or function

I have a table with 2 columns (A as bool and B as text), these columns can be:
both are null
if A is False, then B should be null
if A is True, then B should be not null
There are rules. I want to create a stored procedure or function to check these rules when row adding or updating (via trigger). What is better, stored procedure or function? If function, which type? In general, which variant is the best (return boolean or other way etc)?
I think you're after a CHECK Constraint.
Example:
ALTER TABLE Xxx
ADD CONSTRAINT chk_Xxx
CHECK ( (A IS NULL AND B IS NULL)
OR (A = 0 AND B IS NULL)
OR (A = 1 AND B IS NOT NULL)
) ;
I would use a CHECK CONSTRAINT wired up to a UDF.
Here is a silly example verifying that if you insert a Person, their age will be greater than 17.
if NOT exists (select * from sysobjects
where id = object_id('dbo.udfOlderThan17Check') and sysstat & 0xf = 0)
BEGIN
print 'Creating the stubbed version of dbo.udfOlderThan17Check'
EXEC ( 'CREATE FUNCTION dbo.udfOlderThan17Check ( #j as smallint ) RETURNS bit AS BEGIN RETURN 0 END')
END
GO
ALTER FUNCTION dbo.udfOlderThan17Check ( #Age smallint )
RETURNS bit AS
BEGIN
declare #exists int
select #exists = 0
if ( #Age IS NULL )
BEGIN
select #exists = 1 -- NULL VALUES SHOULD NOT BLOW UP THE CONSTRAINT CHECK
END
if ( #exists = 0 )
BEGIN
if #Age > 17
begin
select #exists = 1
end
END
return #exists
END
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Person]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
DROP TABLE [dbo].[Person]
END
GO
CREATE TABLE [dbo].[Person]
(
PersonUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID()
, Age smallint not null
)
GO
ALTER TABLE dbo.Person ADD CONSTRAINT PK_Person
PRIMARY KEY NONCLUSTERED (PersonUUID)
GO
ALTER TABLE dbo.Person
ADD CONSTRAINT [CK_Person_AgeValue] CHECK ([dbo].[udfOlderThan17Check]( [Age] ) != 0)
GO
Here are some "tests":
INSERT INTO dbo.Person (Age) values (33)
INSERT INTO dbo.Person (Age) values (16)
INSERT INTO dbo.Person (Age) select 333 UNION select 58
INSERT INTO dbo.Person (Age) select 444 UNION select 4
select * from dbo.Person

Resources