I have a table called Products this is how it looks like and I am trying to create a constraint on [IsDefaultProductKey] column, that any time a value is added to it, it needs be an active product key.
CREATE TABLE [dbo].[Products](
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](64) NOT NULL,
[IsActive] [bit] NOT NULL,
[IsDefaultProductKey] [int] NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY CLUSTERED
(
[ProductId] 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].[Products] ADD CONSTRAINT [DF_products_IsActive] DEFAULT ((1)) FOR [IsActive]
GO
ALTER TABLE [dbo].[Products] WITH CHECK ADD CONSTRAINT [FK_Products_Product_IsDefaultProductKey] FOREIGN KEY([IsDefaultProductKey])
REFERENCES [dbo].[Products] ([ProductId])
GO
ALTER TABLE [dbo].[Products] CHECK CONSTRAINT [FK_Products_Product_IsDefaultProductKey]
GO
If these are the entries in the table, row 4 should not be allowed to have a value of 1, since 1 is inactive. How can I go about adding a constraint on the table for that
ProductId Name IsActive IsDefaultProductKey
1 Test1 0 NULL
2 Test2 1 NULL
3 Test3 0 2
4 Test4 0 1 (Should not let me do this)
Based on suggestion, I created this UDF. But still not acting 100% the way I want it.. Please suggest.
CREATE TABLE [dbo].[Products]( [ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](64) NOT NULL,
[IsActive] [bit] NOT NULL,
[IsDefaultProductKey] [int] NULL,
)
go
Create FUNCTION dbo.CheckProduct (#IsDefaultProductKey int)
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = 0
Select #retval = 1
FROM [Products]
WHERE ProductId = #IsDefaultProductKey and IsActive = 1
RETURN #retval
END;
GO
--Select CheckProduct(1)
ALTER TABLE [Products]
ADD CONSTRAINT chkActiveProduct
CHECK (IsDefaultProductKey = null or dbo.CheckProduct(IsDefaultProductKey) = 1);
go
You can use a CHECK CONSTRAINT that calls a UDF that queries the table to see if the ProductId referenced by IsDefaultProductKey is Active or not.
EDIT:
Since you need the constraint to check both ways, you would create a UDF that has parameters for ProductId, IsActive and IsDefaultProductKey.
Inside the function, if there is a non-NULL value for IsDefaultProductKey, then you need to query the table to see if the row with that ProductId is Active. If not, then the function needs to return false.
ALSO, if the IsActive parameter is passed a value of 0, then you need to check the table to make sure that no row has a IsDefaultProductKey equal to the value of the ProductId parameter. If there is such a row, then the function needs to return false.
But if neither of those cases occur, the function returns true, and in the CHECK CONSTRAINT, you then just test to see if the function returns true.
I did not understand your questions completely. However looks like you want to apply a check constraint based on the value of other column. The issue which I see in your SQL is you are applying a column level constraint, while I think you need to apply a table level constraint. Please see below sample based on my understanding of your question.
CREATE TABLE [dbo].[Products](
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](64) NOT NULL,
[IsActive] [bit] NOT NULL,
[IsDefaultProductKey] [int] NULL,
CONSTRAINT ck_contraint CHECK
(
(IsActive = 1 AND (IsDefaultProductKey>0) )
)
)
I think you need a trigger not a constraint to do this. Something like:
CREATE OR ALTER TRIGGER DefaultNotActive ON [dbo].[Products]
AFTER INSERT, UPDATE
AS
IF EXISTS (SELECT *
FROM [dbo].[Products] p
JOIN inserted AS i
ON p.[ProductId] = i.[IsDefaultProductKey]
WHERE p.[IsActive] = 0
)
BEGIN
RAISERROR ('Default Product is inactive.', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
It might need to be more complex if you also need the default product key to exist. Currently this will allow inserts\updates where the default product key is an id that does not have an existing row in the table
Thanks to Tab Allemnan, Here is the solution I found. Works both ways.
Create FUNCTION CheckProduct (#IsDefaultProductKey int, #ProductId int, #IsActive bit)
RETURNS bit
AS
BEGIN
BEGIN
DECLARE #ret bit;
if (#IsDefaultProductKey is not NULL)
begin
SELECT #ret = 1
FROM [Products] p
WHERE p.ProductID = #IsDefaultProductKey
AND p.IsActive = 1;
end
else -- If #IsDefaultProductKey is null
Select #ret = 1
If (#IsActive = 0) -- If Product is made inactive, make sure that its not a defaultkey for any product.
Begin
SELECT #ret = 0
FROM [Products] p
WHERE p.IsDefaultProductKey = #ProductId
End
IF (#ret IS NULL)
SET #ret = 0;
RETURN #ret;
END;
END;
--Select dbo.CheckProduct (2,1,0)
GO
ALTER TABLE [Products]
ADD CONSTRAINT chkActiveProduct
CHECK (dbo.CheckProduct(IsDefaultProductKey,ProductId, IsActive)=1);
go
Related
My goal is to make two if statements in SQL that check if a table and a column exist.
My first if statement is returning an error that says:
the table exists with the name.
In my second statement I don't know how to check if a column is already there/not there.
First SQL statement:
USE [Elearn2]
GO
IF EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[GDPR_SupportRequest]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[GDPR_SupportRequest]
(
[Id] [uniqueidentifier] NOT NULL,
[RequestDate] [datetime2](7) NOT NULL,
[RequestType] [nvarchar](max) NOT NULL,
[RequestQuery] [nvarchar](max) NOT NULL,
[UserId] [uniqueidentifier] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
ALTER TABLE [dbo].[GDPR_SupportRequest] WITH CHECK
ADD FOREIGN KEY([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id])
END
Second SQL statement:
IF EXISTS (SELECT * FROM UserMetaData WHERE Consent != Null)
BEGIN
ALTER TABLE UserMetaData
ADD Consent BIT NULL DEFAULT 0;
END
If someone could help me to fix the statement please let me know how.
Thank you
You are checking whether the table GDPR_SupportRequest exists and creating the table if it exists, instead you should create it if the table GDPR_SupportRequest doesn't exists already.
USE [Elearn2]
GO
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[GDPR_SupportRequest]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[GDPR_SupportRequest]
(
[Id] [uniqueidentifier] NOT NULL,
[RequestDate] [datetime2](7) NOT NULL,
[RequestType] [nvarchar](max) NOT NULL,
[RequestQuery] [nvarchar](max) NOT NULL,
[UserId] [uniqueidentifier] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
ALTER TABLE [dbo].[GDPR_SupportRequest] WITH CHECK
ADD FOREIGN KEY([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id])
END
You can use the below script to check if the column Consent exists in the table UserMetaData and if the column doesn't then you can add it.
IF NOT EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'UserMetaData'
AND COLUMN_NAME = 'Consent')
BEGIN
ALTER TABLE UserMetaData
ADD Consent BIT NULL DEFAULT 0;
END
I defined a non clustered index with Include and Filter on Students table. The SQL Server version is 2017.
Students table definition:
CREATE TABLE [dbo].[Students]
(
[Id] [INT] IDENTITY(1,1) NOT NULL,
[Name] [NVARCHAR](50) NOT NULL,
[CreatedOn] [DATETIME2](7) NOT NULL,
[Active] [BIT] NOT NULL,
[Deleted] [BIT] NOT NULL,
CONSTRAINT [PK_Students]
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
Non-clustered index with include and filter:
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20200508-225254]
ON [dbo].[Students] ([CreatedOn] ASC)
INCLUDE([Name])
WHERE ([Active] = (1) AND [Deleted] = (0))
ON [PRIMARY]
GO
This query uses NonClusteredIndex-20200508-225254
SELECT Name, CreatedOn FROM dbo.Students
WHERE Active = 1
AND Deleted = 0
ORDER BY CreatedOn
Actual execution plan
But when I use the parameterized query as following, it doesn't use the NonClusteredIndex-20200508-225254. Why does this happen? Where am I wrong?
DECLARE #Active BIT = 1
DECLARE #Deleted BIT = 0
SELECT Name, CreatedOn
FROM dbo.Students
WHERE Active = #Active
AND Deleted = #Deleted
ORDER BY CreatedOn
Actual execution plan
This is entirely expected.
When you compile a plan with parameters or variables it needs to produce a plan that will work for any possible value they may have.
You can add OPTION (RECOMPILE) to the statement so the runtime values of these are taken into account (basically they are replaced by literals with the runtime value) but that will mean a recompile every execution.
You are probably best having two separate queries, one for the case handled by the filtered index and one for the other cases.
You might have been hoping that SQL Server would do something like the below and dynamically switch between the clustered index scan + sort vs filtered index scan and no sort (the filters have startup predicates so only at most one branch is executed)
But getting this plan required a change to the filtered index to move Name into the key columns as below...
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20200508-225254]
ON [dbo].[Students] ([CreatedOn] ASC, [Name] asc)
WHERE ([Active] = (1) AND [Deleted] = (0))
... and rewriting the query to
DECLARE #Active BIT = 1
DECLARE #Deleted BIT = 0
SELECT NAME,
CreatedOn
FROM dbo.Students WITH (INDEX =[NonClusteredIndex-20200508-225254])
WHERE Active = 1
AND Deleted = 0
AND 1 = 1 /*Prevent auto parameterisation*/
AND ( #Active = 1 AND #Deleted = 0 )
UNION ALL
SELECT NAME,
CreatedOn
FROM dbo.Students
WHERE Active = #Active
AND Deleted = #Deleted
AND NOT ( #Active = 1
AND #Deleted = 0 )
ORDER BY CreatedOn
OPTION (MERGE UNION)
I have 2 tables House and AlarmInstall...
I need to create a trigger that will update a boolean Atribute "isInstaled" to false in my House table when i insert a uninstallDate (diferent then Null) in my Alarm Install Table.
I am a bit of a noob when it comes to trigger in SQL server. Any help will be appreciated!!!
These are the 2 tables
[dbo].[AlarmInstall](
[AlarmInstallId] [int] IDENTITY(1,1) NOT NULL,
[HouseId] [int] NOT NULL,
[InstallDate] [date] NOT NULL,
[uninstallDate] [date] NOT NULL,
[Model] [nchar](10) NOT NULL,
[dbo].[House](
[HouseId] [int] IDENTITY(1,1) NOT NULL,
[StreetId] [int] NOT NULL,
[DoorNr] [nchar](10) NOT NULL,
[CityId] [int] NOT NULL,
[IsInstalled] [bit] NULL,
Both tables are related thru HouseId that is PK in House Table and FK in AlarmInstall
tried with this trigger but all my Houses get flagged as true!!!
GO
CREATE TRIGGER STATECHANGE
ON dbo.AlarmInstall
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
update dbo.House SET IsInstalled=1
From dbo.AlarmInstall a
inner join inserted i on a.HouseId=i.HouseId
and i.InstallDate is not null
END
I think this could help: https://www.mssqltips.com/sqlservertip/4024/sql-server-trigger-after-update-for-a-specific-value/
You could also use a calculated field for isInstalled with the following definition:
CREATE TABLE House(
HouseId int NOT NULL,
[...]
uninstallDate date NULL,
isInstalled bit AS CASE WHEN uninstallDate IS NULL THEN 0 ELSE 1 END
But I don't see the value of creating it like this (or using a trigger), because when you don't have this field, you can always calculate it in a query:
SELECT
HouseId,
[...]
uninstallDate,
CASE WHEN uninstallDate IS NULL THEN 0 ELSE 1 END AS isInstalled
FROM
House
// Added to originat answer
CREATE TRIGGER dbo.StateChange
ON dbo.AlarmInstall
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON
BEGIN
IF UPDATE(uninstallDate)
BEGIN
DECLARE #houseId int
DECLARE #uninstallDate date
SELECT #houseId = HouseId, #uninstallDate = uninstallDate FROM inserted
UPDATE dbo.House
SET IsInstalled = CASE WHEN #uninstallDate IS NULL THEN 0 ELSE 1 END
WHERE HouseId = #houseId
END
END
Im running stored procedure which in charges to insert, update and delete table's entries.
While both insert and delete runs smoothly, the update operation updates all columns except DATETIME2 one.
The scenario - I test my Repository pattern (using C# code) in the following way:
delete the entire [BackgroundTaskAttachtment] table
Create 4 new entries
delete single entry created on step 2
Wait for 5 seconds
modify one of the entries
the result is having 3 entries in [BackgroundTaskAttachtment] table, with all properties set as expected, except the [UpdatedOnUtc] which not updated (it is equal to [CreatedOnUtc]
I marked the updated row (as you can see [FilePath] was successfully updated):
Would appreciate community insights,
Thank you
This is the stored procedure code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_ArrangeBackgroundTaskAttachtments]
(
#backgroundTaskId BIGINT,
#taskAttchs [dbo].[BackgroundTaskAttachtmentType] READONLY
)
AS
BEGIN
SET NOCOUNT ON;
--delete all removed attachtments
DELETE FROM [BackgroundTaskAttachtment]
WHERE [BackgroundTaskId] = #backgroundTaskId AND [Id] NOT IN (SELECT [Id] FROM #taskAttchs)
----Update exist key-value pairs
UPDATE [dbo].[BackgroundTaskAttachtment]
SET
[IsPrimary] = attachs.[IsPrimary],
[FilePath] = attachs.[FilePath],
[Bytes] = attachs.[Bytes],
[UpdatedOnUtc] = GETUTCDATE()
FROM #taskAttchs AS attachs
WHERE attachs.[Id] = [BackgroundTaskAttachtment].[Id]
--insert new records
SELECT #backgroundTaskId AS [BackgroundTaskId], [FilePath], [IsPrimary], [Bytes], GETUTCDATE() AS [CreatedOnUtc], GETUTCDATE() AS [UpdatedOnUtc]
INTO #Temp FROM #taskAttchs as atcs
WHERE atcs.[Id] NOT IN (SELECT [Id] FROM [BackgroundTaskAttachtment] AS bta WHERE bta.[BackgroundTaskId] = #backgroundTaskId )
INSERT INTO [BackgroundTaskAttachtment]([BackgroundTaskId], [IsPrimary], [Bytes], [FilePath], [CreatedOnUtc], [UpdatedOnUtc] )
SELECT [BackgroundTaskId], [IsPrimary], [Bytes], [FilePath], [CreatedOnUtc], [UpdatedOnUtc]
FROM #Temp
END
This is the table type (sent from CLR to SQL)
CREATE TYPE [dbo].[BackgroundTaskAttachtmentType] AS TABLE(
[Id] [BIGINT] NOT NULL,
[FilePath] [NVARCHAR](MAX) NULL,
[IsPrimary] [BIT] NOT NULL,
[BackgroundTaskId] [BIGINT] NULL,
[Bytes] [VARBINARY](MAX) NULL
)
GO
this is the table definition
CREATE TABLE [dbo].[BackgroundTaskAttachtment]
(
[Id] BIGINT IDENTITY(1,1) NOT NULL,
[BackgroundTaskId] BIGINT NOT NULL,
[IsPrimary] BIT NOT NULL DEFAULT 0,
[FilePath] NVARCHAR(MAX) NULL,
[Bytes] VARBINARY(MAX) NULL,
[CreatedOnUtc] DATETIME2 NOT NULL,
[UpdatedOnUtc] DATETIME2 NOT NULL,
[RowVersion] ROWVERSION NOT NULL,
CONSTRAINT [PK_dbo.BackgroundTaskAttachtment] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.BackgroundTaskAttachtment_BackgroundTask_Id]
FOREIGN KEY ([BackgroundTaskId])
REFERENCES [dbo].[BackgroundTask] ([Id])
ON DELETE CASCADE
);
Please try using SYSUTCDATETIME which returns datetime2.
The GETUTCDATE which you are using, returns datetime.
I am having a little bit of trouble with making a trigger in my SQL. I have two tables:
This one
Create table [user]
(
[id_user] Integer Identity(1,1) NOT NULL,
[id_event] Integer NULL,
[name] Nvarchar(15) NOT NULL,
[lastname] Nvarchar(25) NOT NULL,
[email] Nvarchar(50) NOT NULL, UNIQUE ([email]),
[phone] Integer NULL, UNIQUE ([phone]),
[pass] Nvarchar(50) NOT NULL,
[nick] Nvarchar(20) NOT NULL, UNIQUE ([nick]),
Primary Key ([id_user])
)
go
and this one
Create table [event]
(
[id_event] Integer Identity(1,1) NOT NULL,
[id_creator] Integer NOT NULL,
[name] Nvarchar(50) NOT NULL,
[date] Datetime NOT NULL, UNIQUE ([date]),
[city] Nvarchar(50) NOT NULL,
[street] Nvarchar(50) NOT NULL,
[zip] Integer NOT NULL,
[building_number] Integer NOT NULL,
[n_signed_people] Integer Default 0 NOT NULL Constraint [n_signed_people] Check (n_signed_people <= 20),
Primary Key ([id_akce])
)
Now I need a trigger for when I insert a new user with and id_event, or update existing one with one, to take the id_event I inserted, look in the table of events and increment the n_signed_people in a line with a coresponding id_event, until it is 20. When it is 20, it should say that the event is full. I made something like this, it is working when I add a new user with id, but now I need it to stop at 20 and say its full and also I am not sure if it will work, when I'll try to update existing user, by adding an id_event (I assume it was NULL before update).
CREATE TRIGGER TR_userSigning
ON user
AFTER INSERT
AS
BEGIN
DECLARE #idevent int;
IF (SELECT id_event FROM Inserted) IS NOT NULL --if the id_event is not empty
BEGIN
SELECT #idevent=id_event FROM Inserted; --the inserted id_event will be save in a local variable
UPDATE event SET n_signed_people = n_signed_people+1 WHERE #idevent = id_event;
END
END
go
Good evening,
I did notice some issues with your schema. I want to list the fixes I made in order.
1 - Do not use reserved words. Both user and event are reserved.
2 - Name your constraints. You will be glad they are not some random word when you want to drop one.
3 - I added a foreign key to make sure there is integrity in the relationship.
All this work was done in tempdb. Now, lets get to the fun stuff, the trigger.
-- Just playing around
use tempdb;
go
-- attendee table
if object_id('attendees') > 0
drop table attendees
go
create table attendees
(
id int identity (1,1) NOT NULL constraint pk_attendees primary key,
firstname nvarchar(15) NOT NULL,
lastname nvarchar(25) NOT NULL,
email nvarchar(50) NOT NULL constraint uc_email unique,
phone int NULL constraint uc_phone unique,
pass nvarchar(50) NOT NULL,
nick nvarchar(20) NOT NULL constraint uc_nick unique,
event_id int NOT NULL
)
go
-- events table
if object_id('events') > 0
drop table events
go
create table events
(
id int identity (1,1) NOT NULL constraint pk_events primary key,
creator int NOT NULL,
name nvarchar(50) NOT NULL,
planed_date datetime NOT NULL constraint uc_planed_date unique,
street nvarchar(50) NOT NULL,
city nvarchar(50) NOT NULL,
zip nvarchar(9) NOT NULL,
building_num int NOT NULL,
registered int
constraint df_registered default (0) NOT NULL
constraint chk_registered check (registered <= 20),
);
go
-- add some data
insert into events (creator, name, planed_date, street, city, zip, building_num)
values (1, 'new years eve', '20131231 20:00:00', 'Promenade Street', 'Providence', '02908', 99);
-- make sure their is integrity
alter table attendees add constraint [fk_event_id]
foreign key (event_id) references events (id);
I usually add all three options (insert, update, & delete). You coded for insert in the example above. But you did not code for delete.
Also, both the inserted and deleted tables can contain multiple rows. For instance, if two attendees decide to drop out, you want to minus 2 from the table.
-- create the new trigger.
CREATE TRIGGER [dbo].[trg_attendees_cnt] on [dbo].[attendees]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
-- declare local variable
DECLARE #MYMSG VARCHAR(250);
-- nothing to do?
IF (##rowcount = 0) RETURN;
-- do not count rows
SET NOCOUNT ON;
-- deleted data
IF NOT EXISTS (SELECT * FROM inserted)
BEGIN
UPDATE e
SET e.registered = e.registered - c.total
FROM
[dbo].[events] e
INNER JOIN
(SELECT [event_id], count(*) as total
FROM deleted group by [event_id]) c
ON e.id = c.event_id;
RETURN;
END
-- inserted data
ELSE IF NOT EXISTS (SELECT * FROM deleted)
BEGIN
UPDATE e
SET e.registered = e.registered + c.total
FROM
[dbo].[events] e
INNER JOIN
(SELECT [event_id], count(*) as total
FROM inserted group by [event_id]) c
ON e.id = c.event_id;
RETURN;
END;
-- updated data (no counting involved)
END
GO
Like any good programmer, I need to test my work to make sure it is sound.
Lets add 21 new attendees. The check constraint should fire. This only works since the error generated by the UPDATE rollback the insert.
-- Add 21 attendees
declare #var_cnt int = 0;
declare #var_num char(2);
while (#var_cnt < 22)
begin
set #var_num = str(#var_cnt, 2, 0);
insert into attendees (firstname, lastname, email, phone, pass, nick, event_id)
values ('first-' + #var_num,
'last-' + #var_num,
'email-'+ #var_num,
5554400 + (#var_cnt),
'pass-' + #var_num,
'nick-' + #var_num, 1);
set #var_cnt = #var_cnt + 1
end
go
Last but not least, we need to test a DELETE action.
-- Delete the last row
delete from [dbo].[attendees] where id = 20;
go