I have a small table (code below) with PageKey (int), SendNotification (bit) with 5 possible PageKeys at the moment.
The requirement is to not have more than 1 flag (SendNotification) active at the same time, so I want to create a trigger that would not allow changing the flag to True when there is already another pageKey with active flag.
I wrote the following trigger but it doesn't seem to work. Also, if you have any other (better) solutions to accomplish this in SQL Server 2012, I'd appreciate it.
CREATE TRIGGER trg_Notification_Flag
ON dbo.t_Notification_Flag
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #countOfExistingFlags INT;
DECLARE #PageKey INT;
SELECT #countOfExistingFlags = COUNT(*)
FROM dbo.t_Notification_Flag
WHERE SendNotification = 1;
SELECT #PageKey = inserted.PageKey
FROM inserted;
IF #countOfExistingFlags > 1
BEGIN
PRINT 'There is an existing active flag. No changes has been done.';
ROLLBACK TRANSACTION;
END;
ELSE
BEGIN
UPDATE dbo.t_Notification_Flag
SET SendNotification = 1
WHERE PageKey = #PageKey;
END;
END;
GO
Table is:
CREATE TABLE [dbo].[t_Notification_Flag]
(
[PageKey] [int] NOT NULL,
[SendNotification] [bit] NOT NULL,
[NotificationMessage] [nvarchar](100) NOT NULL,
[NotificationHeader] [nvarchar](50) NULL,
[Country_Key] [int] NULL
) ON [PRIMARY]
You can achieve your goal without trigger - but just with filtered unique index.
Something like:
create table dbo.#Temp (PageKey int, Send_Notification bit)
create unique index IX_Temp on dbo.#temp(Send_Notification) where Send_Notification = 1
This will not allow your table to have more than one record having Send_Notification = 1, so if you need to set Send_Notification flag for some record - you'll have to clear it first for the record currently having this flag set.
I would not use a trigger at all for this. Instead I would use a CHECK CONSTRAINT that checks a UDF that returns true if there are no other active flags, and false if there are.
Related
I am new to SQL Server and T-SQL, but I do have some experience building applications in MS Access.
This stored procedure runs fine when I execute it from the application, however when I am debugging it in SSMS, I get an error
Unable to Step. Invalid Operation.
but it will allow me to step through. Based on my research, it seems like I am creating a race condition but I have not been able to correctly fix the issue. I would also appreciate any advice to optimize this or fix any issues that are apparent.
What the code does:
This code is to enter a new customer into the database. The first select statement looks for an existing ID. If the ID is null, it will add a new customer. If the ID is not null, it will not add the customer. The same goes for the second IF statement to check if #pName2 and #pName3 are null before inserting these values.
Here is my code:
#pUID nvarchar(16) = null,
#pName1 nvarchar(50) = null,
#pName2 nvarchar(50) = null,
#pName3 nvarchar(50) = null,
#pAddress1 nvarchar(30) = null,
#pAddress2 nvarchar(30) = null,
#pAddress3 nvarchar(30) = null,
#pZipCode nvarchar(30) = null
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ID INT
SELECT #ID = ID FROM tblCustomer WHERE strUID = #pUID
IF #ID IS NULL
BEGIN
DECLARE #Customer_ID INT
INSERT INTO tblCustomer(strUID, strName)
VALUES(#pUID, #pName1)
SET #Customer_ID = ##IDENTITY
IF (#pName2 <> '') OR (#pName3 <> '')
BEGIN
INSERT INTO tblSecondaryCustomer(CustomerID, strName2, strName3)
VALUES(#Customer_ID, #pName2, #pName3)
END
INSERT INTO tblAddress(CustomerID, strAddress1, strAddress2, strAddress3, strZipCode)
VALUES(#Customer_ID, #pAddress1, #pAddress2, #pAddress3, #pZipCode)
END
END
Try replacing your IF statement with the following:
IF NOT EXISTS(SELECT ID FROM tblCustomer WHERE strUID = #pUID)
It doesn't seem your using #ID other than a check for existence...and you can use the ISNULL function to make sure you cover NULL cases...
IF (ISNULL(#pName2,'') <> '') OR (ISNULL(#pName3,'') <> '')
HTH
Dave
My insert procedure is working fine the way i want. But update is not working with scope identity.
SET ANSI_NULLS ON
ALTER PROCEDURE [dbo].[spr_unitCreation]
(
#Unit_Name VARCHAR(50) = NULL,
#Unit_Abbreviation VARCHAR(50) = NULL,
#Unit_type VARCHAR(50) = NULL,
#Decimal_Places VARCHAR(50) = NULL,
#Description VARCHAR(50) = NULL,
#Super_Unit VARCHAR(50) = NULL,
#Per_Unit VARCHAR(50) = NULL,
#unit_Id INT OUTPUT,
#abc VARCHAR(50) = NULL
)
AS
BEGIN
IF #abc = 'update' BEGIN
SET NOCOUNT ON;
SELECT #unit_Id AS SCOPE_IDENTITY
UPDATE tbl_UnitCreation
SET Unit_Name = #Unit_Name,
Unit_Abbreviation = #Unit_Abbreviation,
Unit_type = #Unit_type,
Decimal_Places = #Decimal_Places,
Description = #Description,
Super_Unit = #Super_Unit,
Per_Unit = #Per_Unit
WHERE unit_Id = #unit_Id
END
END
SELECT * FROM tbl_UnitCreation
SCOPE_IDENTITY returns the last identity value inserted into an identity column. You are not inserting a row.
To update a row all you need is to pass a value to #unit_Id when executing [spr_unitCreation]. Also remove the line "SELECT #unit_Id AS SCOPE_IDENTITY" from your code.
Based on the comments, you need to find the correct id by searching on relevant details. So you can get the id like this:
SELECT #unit_Id = unit_Id
FROM tbl_UnitCreation
WHERE Unit_Name=#Unit_Name -- NB: Ensure this column contains your relevant details
Another commonly used option is to use the OUTPUT clause of the UPDATE statement, inserting all the updated/"inserted" primary keys and Unit_name into a tablevariable.
https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-2017
What I want to do is add a value to a column. Now it might be that the column does not yet exist.
So what I do is check if that column exists and if not I'm adding it and then insert the value.
IF COL_LENGTH('version', 'minor') = NULL
BEGIN
ALTER TABLE version ADD minor INT null;
END
GO
UPDATE version SET minor= 4;
The problem is that the parser complains about this as the column minor does not exist at parse time.
Is there a way to make this pass in a single script?
Use either:
SET ANSI_NULLS OFF
GO
IF COL_LENGTH('version', 'minor') = NULL
BEGIN
ALTER TABLE [version] ADD minor INT null;
END
GO
UPDATE [version] SET minor= 4;
OR
IF COL_LENGTH('version', 'minor') IS NULL
BEGIN
ALTER TABLE [version] ADD minor INT null;
END
GO
UPDATE [version] SET minor= 4;
You are trying to compare NULL = NULL, with ANSI_NULLS ON
Raj
I would imagine that this would be an easy question for someone who works a lot with T-SQL and especially Triggers:
I want to enforce the following constraints on all updates and inserts to this table:
If DiscountTypeId = 1, then FlatFee must not be NULL.
If DiscountTypeId = 2, then DiscountRate must not be null.
If either one of these two conditions fail on an insert or update to the table, I'd like to return an appropriate error.
The trigger appears not to do anything yet. .Can you provide the necessary changes so it performs as described?
USE [PandaVisa2008]
GO
/****** Object: Table [dbo].[CustomerSpeed] Script Date: 11/04/2010 15:51:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CustomerSpeed](
[CustomerSpeedId] [int] NOT NULL,
[CustomerId] [int] NULL,
[SpeedId] [int] NOT NULL,
[DiscountTypeId] [int] NOT NULL,
[FlatFee] [money] NULL,
[DiscountRate] [decimal](3, 3) NULL,
CONSTRAINT [PK_AgentFee] PRIMARY KEY CLUSTERED
(USE [PandaVisa2008]
GO
/****** Object: Trigger [dbo].[TRG_CustomerSpeed_OnInsertUpdate] Script Date: 11/04/2010 15:38:06 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[TRG_CustomerSpeed_OnInsertUpdate]
ON [dbo].[CustomerSpeed]
FOR INSERT, UPDATE
AS
BEGIN
DECLARE #DiscountTypeId INT
DECLARE #FlatFee MONEY
DECLARE #DiscountRate DECIMAL(3, 3)
SELECT
#DiscountTypeId = DiscountTypeId,
#FlatFee = FlatFee,
#DiscountRate = DiscountRate
FROM
inserted
IF #DiscountTypeId = 1
AND #FlatFee IS NULL
BEGIN
RAISERROR (N'If #DiscountTypeId is 1, FlatFee must not be NULL',
10,
1)
END
IF #DiscountTypeId = 2
AND #DiscountRate IS NULL
BEGIN
RAISERROR (N'If #DiscountTypeId is 2, #DiscountRate must not be NULL',
10,
1)
END
END
[CustomerSpeedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[CustomerSpeed] WITH CHECK ADD CONSTRAINT [CK_CustomerSpeed] CHECK (([DiscountRate]>(0) AND [DiscountRate]<(1)))
GO
ALTER TABLE [dbo].[CustomerSpeed] CHECK CONSTRAINT [CK_CustomerSpeed]
GO
EDIT
I got it to work. I haven't read up on Triggers to remedy my fundamental lack of understanding, but t his seemed to work, although I believe that the Check Constraint is the better approach:
ALTER TRIGGER [dbo].[TRG_CustomerSpeed_OnInsertUpdate]
ON [dbo].[CustomerSpeed]
FOR INSERT, UPDATE
AS
BEGIN
IF EXISTS (SELECT
1
FROM
inserted I
WHERE I.DiscountTypeId = 1
AND I.FlatFee IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR (N'If DiscountTypeId is 1, FlatFee must not be NULL',
10,
1)
END
IF EXISTS (SELECT
1
FROM
inserted I
WHERE I.DiscountTypeId = 2
AND I.DiscountRate IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR (N'If DiscountTypeId is 2, DiscountRate must not be NULL',
10,
1)
END
/*
IF #DiscountTypeId = 2
AND #DiscountRate IS NULL
BEGIN
Rollback Transaction
RAISERROR (N'If #DiscountTypeId is 2, DiscountRate must not be NULL',
10,
1)
END
*/
END
Your comments are welcomed.
I'd use a CHECK constraint, not a triggers
ALTER TABLE Mytable WITH CHECK ADD
CONSTRAINT CK_MyTable_GoodName CHECK (
NOT (DiscountTypeId = 1 AND Flatfee IS NULL)
AND
NOT (DiscountTypeId = 2 AND DiscountRate IS NULL)
)
Also, need to consider "if DiscountTypeId <> 1, does Flatfee have to be NULL" etc
You fundamentally do not understand triggers. The very first thing you need to do is go read about triggers in Books Online with particular emphasis on learning about the inserted and deleted psuedotables. Next thing you need to know is a trigger should NEVER be written as if it will handle only one record at a time. Triggers operate on batches of records and trigger code must account for that.
I don't believe triggers can raise errors, problem #1.
I have the following trigger, but because a trigger needs to handle multiple records, I'm not sure how to correctly handle this, in my trigger code.
Can someone please suggest how I can change the TSql below to correctly handle multiple records, instead of just a single record (as is listed, below).
Table Schema and defaults.
CREATE TABLE [dbo].[tblArticle](
[IdArticle] [int] IDENTITY(1,1) NOT NULL,
[IdArticleStatus] [tinyint] NOT NULL,
[Title] [nvarchar](200) NOT NULL,
[CleanTitle] [nvarchar](300) NOT NULL,
[UniqueTitle] [nvarchar](300) NOT NULL,
[Content] [nvarchar](max) NOT NULL
GO
ALTER TABLE [dbo].[tblArticle] ADD CONSTRAINT [DF_tblArticle_CleanTitle]
DEFAULT (newid()) FOR [CleanTitle]
GO
ALTER TABLE [dbo].[tblArticle] ADD CONSTRAINT [DF_tblArticle_UniqueTitle]
DEFAULT (newid()) FOR [UniqueTitle]
GO
Trigger, which only handles a single record ... not multiple.
ALTER TRIGGER [dbo].[ArticlesAfterInsertOrUpdate]
ON [dbo].[tblArticle]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #IdArticle INTEGER,
#Title NVARCHAR(300),
#CleanTitle NVARCHAR(300),
#UniqueTitle NVARCHAR(300),
#NewCleanTitle NVARCHAR(300),
#CleanTitleCount INTEGER
-- Only Update the CleanTitle and UniqueTitle if *required*
-- This means, create a unique subject of the title, then check if this clean value
-- is different to the current clean value. If so, then update both clean and unique.
-- Otherwise, don't do anything (because it will include this row in the count check, below).
IF UPDATE(Title) BEGIN
-- TODO: How will this handle multiple records???
SELECT #IdArticle = IdArticle, #Title = Title, #CleanTitle = CleanTitle
FROM INSERTED
-- Create the 'Slugs'.
SET #NewCleanTitle = dbo.CreateUniqueSubject(#Title)
SET #UniqueTitle = #NewCleanTitle
IF #NewCleanTitle != #CleanTitle BEGIN
-- We need to update the clean and unique, so lets get started...
-- Grab the count :: eg. how many other _clean_ titles already exist?
-- Note: this is the _only_ reason why we have this
-- column - because it has an index on it.
SELECT #CleanTitleCount = COUNT(IdArticle)
FROM [dbo].[tblArticle]
WHERE CleanTitle = #NewCleanTitle
-- If we have some previous titles, then we need to append a number
-- to the end of the current slug.
IF #CleanTitleCount > 0
SET #UniqueTitle = #NewCleanTitle + CAST((#CleanTitleCount + 1) AS VARCHAR(10))
-- Now update the unique subject field.
UPDATE [dbo].[tblArticle]
SET CleanTitle = #NewCleanTitle,
UniqueTitle = #UniqueTitle
WHERE IdArticle = #IdArticle
END
END
END
GO
Please help!
Don't really need to know what the custom function does, just that it returns the same value for each given input (i.e. the Title). It gets a bit complicated to perform this type of logic in a trigger, but you can certainly make it happen. There are definitely other ways of making it work as well, best approach would depend entirely on your environment, however the following logic will get you what you're looking for as a starting point:
ALTER TRIGGER [dbo].[ArticlesAfterInsertOrUpdate]
ON [dbo].[tblArticle]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON
-- Only Update the CleanTitle and UniqueTitle if *required*
-- This means, create a unique subject of the title, then check if this clean value
-- is different to the current clean value. If so, then update both clean and unique.
-- Otherwise, don't do anything (because it will include this row in the count check, below).
IF UPDATE(Title) BEGIN
-- Materialize with the newCleanTitle value for simplicity sake, could
-- do this inline below, not sure which would work better in your environment
if object_id('tempdb..#tempIData') > 0
drop table #tempIData;
select *,
dbo.CreateUniqueSubject(i.Title) as newCleanTitle
into #tempIData
from inserted i
where i.CleanTitle <> dbo.CreateUniqueSubject(i.Title);
with iData as
( -- Get the data inserted along with a running tally of any duplicate
-- newCleanTitle values
select i.IdArticle as IdArticle,
i.CleanTitle, i.newCleanTitle,
-- Need to get the count here as well to account for cases where
-- we insert multiple records with the same resulting cleanTitle
cast(row_number() over(partition by i.newCleanTitle order by i.IdArticle) as bigint) as cntCleanTitle
from #tempIData i
),
srcData as
( -- Get the existing count of data by CleanTitle value for each
-- newCleanTitle included in the inserted data
select t.CleanTitle as CleanTitle,
cast(coalesce(count(*),0) as bigint) as cntCleanTitle
from dbo.tblArticle t
join
( -- Need a distinct list of newCleanTitle values
select a.newCleanTitle
from iData a
group by a.newCleanTitle
) i
-- Join on CleanTitle as we need to get the existing running
-- count for each distinct CleanTitle values
on t.CleanTitle = i.newCleanTitle
group by t.CleanTitle
)
-- Do the update...
update a
set a.CleanTitle = i.newCleanTitle,
a.UniqueTitle =
case
when i.cntCleanTitle + coalesce(s.cntCleanTitle,0) > 1
then i.newCleanTitle + cast((cast(i.cntCleanTitle as bigint) + cast(coalesce(s.cntCleanTitle,0) as bigint)) as nvarchar(10))
else
i.newCleanTitle
end
from dbo.tblArticle a
join iData i
on a.IdArticle = i.IdArticle
left join srcData s
on i.newCleanTitle = s.CleanTitle;
if object_id('tempdb..#tempIData') > 0
drop table #tempIData;
END
END