Triggers Mutually Exclusive Boolean Fields - sql

I have two boolean (bit) fields that I want to remain mutually exclusive. They can both be false, but if one gets set to true the other must be set to false.
I tried to create 2 triggers but wound up creating an infinite loop (that stopped after 32 iterations). Apparently they trigger each other. Here is the code I tried:
CREATE OR ALTER TRIGGER dbo.TriggerNameA
ON dbo.TableName
AFTER UPDATE
AS
IF UPDATE(FieldA)
BEGIN
UPDATE dbo.TableName
SET FieldB =
CASE WHEN dbo.TableName.FieldA = 1
THEN 0
ELSE 1
END
FROM dbo.TableName INNER JOIN inserted ON dbo.TableName.ID = inserted.ID
END;
;
GO
CREATE OR ALTER TRIGGER dbo.TriggerNameB
ON dbo.TableName
AFTER UPDATE
AS
IF UPDATE(FieldB)
BEGIN
UPDATE dbo.TableName
SET FieldA =
CASE WHEN dbo.TableName.FieldB = 1
THEN 0
ELSE 1
END
FROM dbo.TableName INNER JOIN inserted ON dbo.TableName.ID = inserted.ID
END;
;
GO
Any idea how to avoid the infinite loop?

Using a Check constraint removes the need for a trigger:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table MyTable
(
ID int Identity,
FieldA bit,
FieldB bit,
CONSTRAINT Chk_Fields CHECK (FieldA <> 1 OR FieldB <> 1)
)
INSERT INTO MyTable(FieldA, FieldB)
VALUES (0,0),(0,1),(1,0)
Query 1:
select *
from MyTable
Results:
| ID | FieldA | FieldB |
|----|--------|--------|
| 1 | false | false |
| 2 | false | true |
| 3 | true | false |
Query 2:
INSERT INTO MyTable(FieldA, FieldB)
VALUES (1,1),(0,1),(1,0)
Results:
The INSERT statement conflicted with the CHECK constraint "Chk_Fields". The conflict occurred in database "db_18_941bc9", table "dbo.MyTable".
Query 3:
UPDATE MyTable
SET FieldB = 1
WHERE ID = 3
Results:
The UPDATE statement conflicted with the CHECK constraint "Chk_Fields". The conflict occurred in database "db_18_941bc9", table "dbo.MyTable".
EDIT As #CharlieFace mentions in his comment, the equivalent check
constraint of (FieldA = 0 OR FieldB = 0) is more readable

Okay, I apologize for my previous answer, but I'm sure I got it now. Thanks to #DB<>Fiddle for pointing out that it still was experiencing recursion (or nesting, not sure).
So I used the trigger_nestlevel function. Tested and now (finally) it works:
create table MyTable
(
ID int Identity,
FieldA bit,
FieldB bit
)
go
CREATE OR ALTER TRIGGER dbo.Trigger1
ON dbo.MyTable
AFTER UPDATE
AS
IF TRIGGER_NESTLEVEL() = 1
BEGIN
IF UPDATE(FieldA)
BEGIN
UPDATE dbo.MyTable SET dbo.MyTable.FieldB = 0
FROM dbo.MyTable INNER JOIN inserted ON dbo.MyTable.ID = inserted.ID;
END;
END;
;
GO
CREATE OR ALTER TRIGGER dbo.Trigger2
ON dbo.MyTable
AFTER UPDATE
AS
IF TRIGGER_NESTLEVEL() = 1
BEGIN
IF UPDATE(FieldB)
BEGIN
UPDATE dbo.MyTable SET dbo.MyTable.FieldA = 0
FROM dbo.MyTable INNER JOIN inserted ON dbo.MyTable.ID = inserted.ID;
END;
END;
;
GO
INSERT INTO MyTable(FieldA, FieldB)
VALUES (0,0)
SELECT * FROM MyTable;
-- 0 0
UPDATE MyTable
SET FieldA = 1
WHERE ID = 1;
SELECT * FROM MyTable;
-- 1 0
UPDATE MyTable
SET FieldB = 1
WHERE ID = 1;
SELECT * FROM MyTable;
-- 0 1
Then I can go back and forth and they continue to switch the way I need. Whew!

Here's an alternative using an INSTEAD OF TRIGGER to ensure that on an UPDATE only one of the fields is set to 1. I have made a decision in the trigger that if FieldA in the virtual INSERTED table is set to 1 then I set FieldB to zero:
SetUp
create table MyTable
(
ID int Identity,
FieldA bit,
FieldB bit
)
CREATE TRIGGER TriggerNameA ON MyTable
INSTEAD OF UPDATE
AS
IF UPDATE(FieldA)
BEGIN
MERGE [MyTable] AS t1
USING (SELECT * FROM INSERTED) AS t2
ON t1.ID = t2.ID
WHEN MATCHED THEN
UPDATE SET t1.[FieldA] = t2.FieldA,
t1.[FieldB] = CASE WHEN t2.FieldA = 1 THEN 0 ELSE t2.FieldB END;
END
ELSE IF UPDATE(FieldB)
BEGIN
MERGE [MyTable] AS t1
USING (SELECT * FROM INSERTED) AS t2
ON t1.ID = t2.ID
WHEN MATCHED THEN
UPDATE SET t1.[FieldB] = t2.FieldB,
t1.[FieldA] = CASE WHEN t2.FieldB = 1 THEN 0 ELSE t2.FieldA END;
END;
-- Add Else for cases where FieldA & FieldB weren't updated if required
Result 1
INSERT INTO MyTable(FieldA, FieldB)
VALUES (0,0),(0,1),(1,0);
SELECT * FROM MyTable
ID FieldA FieldB
1 False False
2 False True
3 True False
Result 2
UPDATE MyTable
SET FieldB = 1
WHERE ID = 1
SELECT * FROM MyTable
ID FieldA FieldB
1 False True
2 False True
3 True False
Result 3
UPDATE MyTable
SET FieldB = 1
WHERE ID = 3
SELECT * FROM MyTable
ID FieldA FieldB
1 False True
2 False True
3 False True
Result 4
UPDATE MyTable
SET FieldA = 1
WHERE ID = 2
SELECT * FROM MyTable
ID FieldA FieldB
1 False True
2 True False
3 True False
DB<>Fiddle
Edited to Update fieldA or FieldB depending on what column is updated
in the UPDATE statement

Related

If exists (select top 1 1) with multiple instructions

I have a stored procedure that checks for the existence of a record in a table and if it exists, updates the record, else, inserts a new record.
#myFieldID int = NULL,
#newTitle varchar(50) = NULL
IF EXISTS (SELECT TOP 1 1
FROM table
WHERE myField = #myFieldID)
UPDATE table1
SET title = #newTitle
WHERE myFieldID = #myFieldID
UPDATE table2
SET newTitle = #newTitle
WHERE myFieldID = #myFieldID
ELSE
INSERT INTO table1
(
title
)
SELECT #newTitle
The problem is, when I have the second UPDATE, I get an error "Incorrect syntax near the keyword 'ELSE'"
How do I update 2 tables within the same conditional "IF"? Or, do I simply have to test for the existence of the same record twice?
The issue is you're trying to multiple updates in a single IF statement with no BEGIN/END. Change your if statement to
IF EXISTS (SELECT TOP 1 1
FROM table
WHERE myField = #myFieldID)
BEGIN
UPDATE table1
SET title = #newTitle
WHERE myFieldID = #myFieldID
UPDATE table2
SET newTitle = #newTitle
WHERE myFieldID = #myFieldID
END
ELSE

How to copy records from table and insert into same table using stored procedure SQL Server

I have following table:
I want to copy only those records which are from version 0 and their student_id is never repeated in version 1, that means unchanged records. and I want to insert all copied records to same table with version 1. What will be stored procedure for this.
using group by and having max(version) = 0:
insert into student_name (student_id, student_name, version)
select student_id, max(student_name), 1
from student_name
group by student_id
having max(version) = 0
As a stored procedure, taking a parameter for version, that inserts records for students who do not have a record for that version: and outputs the rows that were inserted:
create procedure dbo.insert_new_version (#version int) as
begin;
set nocount, xact_abort on;
insert into student_name (student_id, student_name, version)
output inserted.*
select
student_id
, student_name = max(student_name)
, version = #version
from student_name
group by student_id
having max(version) < #version
end;
rextester demo: http://rextester.com/JSTNI40605
returns:
+-----------+------------+--------------+---------+
| record_id | student_id | student_name | version |
+-----------+------------+--------------+---------+
| 11 | 3 | ccc | 1 |
+-----------+------------+--------------+---------+
You can select the records by doing:
select t.*
from t
where t.version = 0 and
not exists (select 1
from t t2
where t2.student_id = t.student_id and t2.version = 1
);
The rest is just an insert.
I'd suggest using a while loop to go through the table and identify the items that you need to copy, then these values will be
evaluated and if they meet the criterion for re-inserting and then insert them.
Your code should look like the following. Add CREATE PROC part and Edit table names where applicable
(Caveat: I have written this on notepad so if you get a few errors, just try to fix them)
DECLARE #counter int = 0, #row_Count int = 0, #currentId int,
#currentName nvarchar(100), #version int, #current_Student_id int
SET #row_Count = (SELECT COUNT(record_Id) from yourTable)
WHILE #counter <= #row_Count
BEGIN
SET #currentId = (SELECT record_Id FROM (SELECT row_number() over (order by id)
AS RowNum, record_Id FROM yourTable) sub WHERE RowNum=#counter)
SET #currentName = (SELECT student_name FROM yourTable WHERE record_Id = #currentId)
SET #current_Student_id = (SELECT student_id FROM yourTable WHERE record_Id = #currentId)
SET #version = (SELECT version FROM yourTable WHERE record_Id = #currentId)
--USE IF to check if the current version is 0 and the student ID has not been inserted already
IF (SELECT COUNT(record_Id) FROM yourTable WHERE student_id = #current_Student_id AND version = 1) < 1
AND #version = 0
BEGIN
INSERT INTO yourTable (student_id, student_name, version)
VALUES
(
#current_Student_id,
#currentName,
1
)
END
SET #counter = #counter + 1;
END
You can select and insert like this
Insert INTO tableName select t1.student_Id, t1.student_name,1 from tablename t1
where t1.version = 0 and not exists
(select 1 from tablename t2 where t2.student_id = t.student_id and t2.version = 1);
You can try this by LEFT Join:
INSERT INTO tbl
SELECT T1.record_id,T1.student_Id,T1.student_name, 1
FROM tbl T1 LEFT JOIN tbl T2
ON T1.student_Id = T2.student_Id AND T2.version = 1
WHERE T1.version = 0 AND T2.record_id IS NULL

Inserts and Updates in SQL Server for a stored procedure

I have a table like this in SQL Server:
varID(PK) dataID(PK) is_used
A 1 0
B 1 0
Then I'm loading data to update is_used to 1 if the varID/dataID combo exists and add it in otherwise.
So I have to insert/update these varID/dataID combos.
varID(PK) dataID(PK)
B 1
C 1
So the updated table would look like this:
varID(PK) dataID(PK) is_used
A 1 0
B 1 1
C 1 1
What's the easiest way to do this? I will do it in a stored procedure.
Procedure tries to update is_used given a key. If unsuccessful, inserts new row. Note I put 0 as default value for is_used - I think is_used = 1 for (C, 1) is an inadvertence.
create proc AddVarDataCombo (#varID varchar(100), #dataID int)
as
set nocount on
update ATable
set is_used = 1
where varID = #varID
and dataID = #dataID
if ##rowcount = 0
begin
insert into ATable
values (#varID, #dataID, 0)
end

How do you reference new columns immediately after they are created in your SQL script

How do I need to write my SQL script to ensure my new column is visible on following lines after it is created.
This is the general form of my SQL:
BEGIN TRANSACTION
if (not exists(select 1 from THIS_TABLE))
BEGIN
ALTER TABLE THIS_TABLE add THIS_COLUMN int
END
COMMIT
BEGIN TRANSACTION
IF (NOT EXISTS (SELECT 1 FROM THIS_TABLE
WHERE THIS_COLUMN = 1))
BEGIN
UPDATE THIS_TABLE SET THIS_COLUMN = 1
END
COMMIT
This is the error I'm getting:
Invalid column name 'THIS_COLUMN'.
on this line:
IF (NOT EXISTS (SELECT 1 FROM THIS_TABLE
WHERE THIS_COLUMN = 1))
The column has to be created before a query that uses it can be parsed. You can accomplish this by putting the update in a different batch, using the "go" keyword:
alter table t1 add c1 int
go
update t1 set c1 = 1
Or by running the second transaction as dynamic SQL:
alter table t1 add c1 int
exec ('update t1 set c1 = 1')
What Andomar said is correct, you need to use the go keyword.
However, the big problem is that your logic looks wrong. Let me go through each use case:
If THIS_TABLE is not empty
If the table is not empty, the if below returns false and you will never add the new column.
if (not exists(select 1 from THIS_TABLE))
BEGIN
ALTER TABLE THIS_TABLE add THIS_COLUMN int
END
Then, the next script obviously fails, because there is no such column THIS_COLUMN:
IF (NOT EXISTS (SELECT 1 FROM THIS_TABLE
WHERE THIS_COLUMN = 1))
If THIS_TABLE is empty
If the table is empty, the column is added:
if (not exists(select 1 from THIS_TABLE))
BEGIN
ALTER TABLE THIS_TABLE add THIS_COLUMN int
END
But then the next if will always be true and the update statement will affect zero rows (because table is empty).
IF (NOT EXISTS (SELECT 1 FROM THIS_TABLE
WHERE THIS_COLUMN = 1))
BEGIN
UPDATE THIS_TABLE SET THIS_COLUMN = 1
END

Check whether a value is present in a column or not in sql server 2005

Hai guys,
I have a table with a column named Is_Deleted and my query is
select Is_Deleted from Stock where Stock.Mat_Id=1
alt text http://www.freeimagehosting.net/uploads/aaaff13d8a.jpg
Now i have to write a condition to check whether all values are 1 else i have to terminate my loop.. How it can be done? any suggestions...
This should do what you're after.
IF EXISTS(SELECT 1 FROM (select DISTINCT Is_Deleted from Stock where Stock.Mat_Id=1) a WHERE Is_Deleted <> 1)
BEGIN
-- Terminate the loop
END
ELSE
BEGIN
-- Perform action
END
select exists(select 1 from Stock where Mat_Id = 1 and
(is_deleted is null or is_deleted <> 1))
A simple exists is all you need
IF EXISTS (SELECT * FROM Stock.Is_Deleted = 0 AND Stock.Mat_Id = 1)
...
If you are looping through different Mat_Ids...
...
--Get first #Mat_Id
WHILE #Mat_Id IS NOT NULL
BEGIN
IF EXISTS (SELECT * FROM Stock.Is_Deleted = 0 AND Stock.Mat_Id = #Mat_Id)
BEGIN
...
END
SET #Mat_Id = NULL
--Get next #Mat_Id
END
...