How to add default constraint with condition in Sql Server? - sql-server

I am using Sql Server 2014
CREATE TABLE [dbo].[Record](
[RecordId] [int] IDENTITY(1,1) NOT NULL,
[BookId] [int] NULL,
[TopicId] [int] NULL,
[BookName] [varchar](250) NULL,
[TopicName] [varchar](250) NULL,
[RecordPath] [varchar](250) NULL,
[RecordName] [varchar](250) NULL,
[DurationInSec] [float] NULL,
CONSTRAINT [PK_Record] PRIMARY KEY CLUSTERED
(
[RecordId] ASC
)
ALTER TABLE dbo.Record ADD CONSTRAINT DC_RecordPath DEFAULT
(CASE WHEN BookId = 1 THEN 'path/to/1' ELSE RecordPath END)
I want a default constraint with condition as above, but this is not permitted.
Is there any workaround without using triggers?
Edit: Computed column is not working, because the column is filled by a few stored procedures.
I have two conditions;
If BookId is 1 then RecordPath is path 1
Else for any BookId, RecordPath is RecordPath comes from stored procedure

This is what you need if you need that to check only the record with BookId = 1
CHECK (BookId = 1 AND RecordPath = 'path/to/1') OR (BookId != 1)
Esentialy this says: If BookId equals to 1, then the path has to be the one specified, or the BookId can be anything else and for those records the record path can be anything.
RecordPath = RecordPath is not necessary for the second part, since it is always TRUE
Or do you want a computed default?
That is not possible, DEFAULT constraint cannot depend on other columns. To achieve something similar, you'll need a TRIGGER or you can use a computed column or a view instead and use that column/view in your application when reading data.

try this
ALTER TABLE dbo.Record ADD CONSTRAINT DC_RecordPath
CHECK ((CASE WHEN BookId = 1 THEN 'path/to/1' ELSE 'path/to/2' END)=RecordPath)

Related

Is there an easier way to do a "Cascading Insert" in SQL Server 2016?

I inherited SQL code that I need to work on that was set up similar to the following:
CREATE TABLE [dbo].[Ni](
[FooID] [int] IDENTITY(1,1) NOT NULL,
[Bar] [nvarchar](60) NULL,
[LocationID] [int] NULL,
[Thing1] [float] NULL
CONSTRAINT [PK_Ni] PRIMARY KEY CLUSTERED
(
[FooID] ASC
);
CREATE UNIQUE NONCLUSTERED INDEX [UQ_LocationBar] ON [dbo].[Ni]
(
[LocationID] ASC,
[Bar] ASC
);
CREATE TABLE [dbo].[Ni_Two](
[ID] [int] IDENTITY(1,1) NOT NULL,
[FooID] [int] NOT NULL,
[Thing2] [int] NOT NULL,
[Thing3] [int] NOT NULL
CONSTRAINT [PK_Ni_Two] PRIMARY KEY CLUSTERED
(
[ID] ASC
);
ALTER TABLE [dbo].[Ni_Two] WITH CHECK ADD CONSTRAINT [FK_NiTwo_FooID] FOREIGN KEY([FooID])
REFERENCES [dbo].[Ni] ([FooID]);
CREATE TABLE [dbo].[KillMe](
[ID] [int] NOT NULL,
[FooID] [int] NULL,
[Thing4] [int] NOT NULL,
[Thing5] [int] NOT NULL
PRIMARY KEY CLUSTERED
(
[ID] ASC
);
ALTER TABLE [dbo].[KillMe] WITH NOCHECK ADD CONSTRAINT [FK_KillMe_FooID] FOREIGN KEY([FooID])
REFERENCES [dbo].[Ni] ([FooID]);
CREATE TABLE [dbo].[PleaseStop](
[ID] [int] NOT NULL,
[KillMeID] [int] NOT NULL,
[Thing7] [int] NOT NULL,
[Thing8] [int] NOT NULL
PRIMARY KEY CLUSTERED
(
[Id] ASC
);
ALTER TABLE [dbo].[PleaseStop] WITH CHECK ADD CONSTRAINT [FK_PleaseStop_KillMe] FOREIGN KEY([KillMeID])
REFERENCES [dbo].[KillMe] ([ID]);
At issue is that with this design is with [Ni].dbo.[Bar]. That unique constraint is put in there as a requirement. Every FooID is unique, and every Bar assigned to LocationID must be unique.
Now the requirements have changed. With each quarterly import there will be a few entries where the Bar field must be updated.
I have tried:
UPDATE dbo.[Ni]
SET Bar = Imp.Bar
, LocationID = Imp.LocationID
, Thing1 = Imp.Thing1
FROM dbo.[Ni]
INNER JOIN ImportData Imp ON [Ni].FooID = Imp.FooID
This will give me a Violation of UNIQUE KEY constraint error.
I don't want to change the schema because I don't know what other effects it will have on the code. The author of the program has since left the company . . . and here I am.
The program runs quarterly (I.E. four times a year) as part of a maintenance routine.
Can I do this without using WHILE statements? Because that's going to be a pain.
Thanks!
So either update them all in a single query, eg
CREATE TABLE [dbo].[Ni](
[FooID] [int] IDENTITY(1,1) NOT NULL,
[Bar] [nvarchar](60) NULL,
[LocationID] [int] NULL,
[Thing1] [float] NULL
CONSTRAINT [PK_Ni] PRIMARY KEY CLUSTERED
(
[FooID] ASC
)
);
CREATE UNIQUE NONCLUSTERED INDEX [UQ_LocationBar] ON [dbo].[Ni]
(
[LocationID] ASC,
[Bar] ASC
);
insert into Ni(bar) values ('a'),('b'),('c');
with newValues as
(
select * from (values (1,'c'),(3,'x')) newValues (FooId, Bar)
),
toUpdate as
(
select ni.FooId, ni.Bar, NewValues.Bar NewBar
from Ni
join NewValues
on ni.FooID = newValues.FooId
)
update toUpdate set Bar = NewBar
or disable and rebuild the unique index
begin transaction
alter index [UQ_LocationBar] on ni disable
update ni set bar = 'b' where fooid = 1
update ni set bar = 'a' where fooid = 2
alter index [UQ_LocationBar] on ni rebuild
commit transaction
Am I allowed to disable and re-enable the constraint in a stored procedure?
It requires additional permissions, of course, but there's no restriction on running DDL inside a stored procedure, and in SQL Server DDL is fully transactional, so you can commit/rollback to prevent partial updates and to prevent other sessions from seeing partial results.

Unable to create Foreign Key relationship with another table

I'm trying to do this,
CREATE TABLE [dbo].[tblPerson](
[ID] [int] NOT NULL,
[Personame] [varchar](50) NULL)
[Email] [varchar](50) NULL,
[GenderId] [int] NULL
CREATE TABLE [dbo].[tblGender]( [id] [int] NOT NULL, [Gender] nvarchar NOT NULL)
Alter table tblPerson Add Constraint tblPerson_GenderId_FK Foreign Key (GenderId) references tblGender(id)
I am getting Below Error not sure why
Msg 547, Level 16, State 0, Line 2
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "tblPerson_GenderId_FK". The conflict occurred in database "Sqlkudavenkat", table "dbo.tblGender", column 'id'.
I suspect that tblGenderis new table, in that case you need to populate it with values before you can create the foreign key.
Suppose in tblPerson you have used 2 values in GenderID
0 meaning male
1 meaning female
then your queries could look like this :
CREATE TABLE [dbo].[tblGender]( [id] [int] NOT NULL, [Gender] nvarchar(100) NOT NULL)
insert into tblGender(id, gender)
select distinct p.GenderID,
case when p.GenderID = 0 then 'Male'
when p.GenderID = 1 then 'Female'
else 'it came from outer space'
end
from tblPerson p
Alter table tblPerson Add Constraint tblPerson_GenderId_FK Foreign Key (GenderId) references tblGender(id)

SQL Server stored procedure with geolocation not returning data

I have written the following stored procedure to return data based on a lat/long and category id being passed.
I need to return a list of traders whose coverage area falls within the passed lat long (and that they cover the category being passed). So I am looking to draw a circle around the traders lat/long position, x number of meters using the radius they will operate from (this is stored in the Traders.OperatingRadius column). If the passed lat long coord is within this, then they should be included in the return list.
CREATE PROCEDURE FindTradersWithinRadiusLatLong
#LAT decimal(9,6),
#LONG decimal(9,6),
#CATEGORY int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #GEO1 GEOGRAPHY;
SET #GEO1 = geography::Point(#LAT, #LONG, 4326)
SELECT
x.Id, x.Name,
x.Latitude, x.Longitude,
x.Distance, x.IsArchived
FROM
(SELECT
Traders.Id, Traders.Name,
Latitude, Longitude,
CategoryId = TraderCategories.Id,
OperatingRadius,
Traders.IsArchived,
Distance = (#geo1.STDistance(geography::Point(ISNULL(Latitude, 0), ISNULL(Longitude, 0), 4326)))
FROM
((Addresses
INNER JOIN
Traders ON Addresses.TraderId = Traders.Id)
INNER JOIN
TraderCategories ON Traders.Id = TraderCategories.TraderId)) AS x
WHERE
x.Distance <= x.OperatingRadius
AND x.CategoryId = #CATEGORY
AND (x.IsArchived = 0 OR x.IsArchived = NULL);
END
GO
TraderCategories is a linking table as follows;
Table TraderCategories
int FK TraderId
int FK CategoryId
Now I have added an address with;
latitiude - 43.590000, Longitude - -111.120000
There is also a TraderCategory Relationship for category with Id 1
I have then tried calling the stored procedure with the above and no matches are being returned.
The table definitions are as follows:
CREATE TABLE [Bemfeito].[Addresses]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Address1] [nvarchar](max) NULL,
[Address2] [nvarchar](max) NULL,
[Address3] [nvarchar](max) NULL,
[TraderId] [int] NULL,
[Latitude] [decimal](9, 6) NOT NULL,
[Longitude] [decimal](9, 6) NOT NULL,
[OperatingRadius] [real] NOT NULL DEFAULT (CONVERT([real],(0)))
CONSTRAINT [PK_Addresses]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
ALTER TABLE [Bemfeito].[Addresses] WITH CHECK
ADD CONSTRAINT [FK_Addresses_Traders_TraderId]
FOREIGN KEY([TraderId]) REFERENCES [Bemfeito].[Traders] ([Id])
GO
ALTER TABLE [Bemfeito].[Addresses] CHECK CONSTRAINT [FK_Addresses_Traders_TraderId]
GO
CREATE TABLE [Bemfeito].[Traders]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Email] [nvarchar](max) NULL
[Name] [nvarchar](max) NULL
CONSTRAINT [PK_Traders]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
CREATE TABLE [Bemfeito].[TraderCategories]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[CategoryId] [int] NULL,
[TraderId] [int] NULL,
CONSTRAINT [PK_TraderCategories]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
ALTER TABLE [Bemfeito].[TraderCategories] WITH CHECK
ADD CONSTRAINT [FK_TraderCategories_Categories_CategoryId]
FOREIGN KEY([CategoryId]) REFERENCES [Bemfeito].[Categories] ([Id])
GO
ALTER TABLE [Bemfeito].[TraderCategories] CHECK CONSTRAINT [FK_TraderCategories_Categories_CategoryId]
GO
ALTER TABLE [Bemfeito].[TraderCategories] WITH CHECK
ADD CONSTRAINT [FK_TraderCategories_Traders_TraderId]
FOREIGN KEY([TraderId]) REFERENCES [Bemfeito].[Traders] ([Id])
GO
ALTER TABLE [Bemfeito].[TraderCategories] CHECK CONSTRAINT [FK_TraderCategories_Traders_TraderId]
GO
and finally for completion the category
CREATE TABLE [Bemfeito].[Categories]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](max) NULL,
[Value] [int] NOT NULL,
CONSTRAINT [PK_Categories]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
Can anyone tell me where I am going wrong here please?
If you look at this microsoft reference here you should notice that the argument passed to STDistance is a geometry datatype while you are passing a Point datatype.
the line currently written like this
,Distance = (#geo1.STDistance(geography::Point(ISNULL(Latitude, 0), ISNULL(Longitude, 0), 4326))
should be written as follows.
,Distance = (#geo1.STDistance(geography::STGeomFromText('Point('+ISNULL(Longitude, 0)+' '+ISNULL(Latitude, 0)')',4326))

How do I use A create function that compares 2 tables and does not return an int?

I am writing a database for a website for school, in Microsoft SQL Server Express
The database has a lot of different tables and also a lot of primary keys, it is not allowed to change the table itself.
The problems is that I have 2 tables, Verkoper & Gebruiker, (sorry the names are in dutch), these are the 2 tables:
create table Verkoper (
Gebruiker char(10) not null,
Bank char(8) ,
Bankrekening int,
Controle_Optie char(10) not null,
Creditcard char(19)
constraint pk_Verkoper primary key(Gebruiker),
constraint fk_Verkoper_Gebruiker foreign key(Gebruiker)
references Gebruiker(Gebruikersnaam)
)
create table gebruiker(
Gebruikersnaam char(10) not null,
Voornaam char(10) not null,
Achternaam char (15) not null ,
Adresregel1 char(25) not null,
AdresRegel2 char(25),
Postcode char(7) not null,
Plaatsnaam char(25) not null,
Land char(15) not null,
GeboorteDag char(10) not null,
Mailbox char(25) not null,
Wachtwoord char(15)not null,
Vraag int not null,
Antwoordtekst char(20) not null,
Verkoper char(4) not null,
constraint pk_gebruiker primary key(Gebruikersnaam),
constraint fk_Gebruiker_Vraag foreign key(Vraag)
references Vraag(Vraagnummer)
)
I wanna check if gebruiker.Verkoper = 'wel'(yes) or 'niet'(no). If gebruiker.verkoper = 'niet' then you should not be allowed to add it to the verkoper table.
So in short 2 tables gebruiker and verkoper if you wanna add a person to Verkoper, gebruiker.verkoper has to be true.
I already tried to make a function:
Create function dbo.checkVerkoper()
RETURNS int
AS BEGIN RETURN(
select count(*)
from Verkoper
inner join gebruiker
on verkoper.gebruiker = gebruiker.gebruikersnaam
AND gebruiker.verkoper = 'wel'
)
END
go
alter table verkoper
add constraint ck_VerkoperGebruiker
check(dbo.checkVerkoper () = 1 )
This does work for 1 person, but if you add more the value will be more then 1, and so the check will be false no one will be add, if you have 4 persons and 3 are not allowed and 1 is then the value is still 1 and you can still add someone to verkoper tabel.
You can write a stored Procedure
In that
Check whether your conditions are met
if met, then Do the insert(s)

Check Constraint – only allow one column to be true if another column is true

Take the following example table:
CREATE TABLE [dbo].[tbl_Example](
[PageID] [int] IDENTITY(1,1) NOT NULL,
[RequireLogin] [bit] NOT NULL,
[RequireAdmin] [bit] NOT NULL,
[HideIfLoggedIn] [bit] NOT NULL
)
How would one rewrite the above to include check constraints as follows:
Force [RequireAdmin] to be False if [RequireLogin] is False (i.e only allow [RequireAdmin] to be True if [RequireLogin] is True whilst allowing [RequireLogin] to be True and [RequireAdmin] to be False
Only allow [HideIfLoggedIn] to be True if [RequireLogin] is False
You typically do nested case statements in the check in order to get that type of logic to work. Remember that a case in a check must still be an evaluation, so it would take the form
CHECK (case when <exp> then 1 end = 1).
Looking over your exact requirements however it seems that this would also work and is probably easier to read:
CREATE TABLE [dbo].[tbl_Example]
(
[PageID] [int] IDENTITY(1,1) NOT NULL,
[RequireLogin] [bit] NOT NULL,
[RequireAdmin] [bit] NOT NULL,
[HideIfLoggedIn] [bit] NOT NULL
)
ALTER TABLE [dbo].[tbl_Example] ADD CONSTRAINT
[RequireAdmin] CHECK
((RequireAdmin = RequireLogin) OR
(RequireLogin=1));
ALTER TABLE [dbo].[tbl_Example] ADD CONSTRAINT
[HideIfLoggedIn] CHECK
((RequireLogin=1 AND HideIfLoggedIn=0) OR
(RequireLogin=0 AND HideIfLoggedIn=1) OR
(RequireLogin=0 AND HideIfLoggedIn=0))
If I have the algebra correct:
alter table dbo.Example
add constraint RequireAdmin_RequireLogin_ck
check ( not ( RequireAdmin = 'true' and RequireLogin = 'false' ) ) ;
alter table dbo.Example
add constraint HideIfLoggedIn_RequireLogin_ck
check ( not ( HideIfLoggedIn = 'true' and RequireLogin = 'true' ) ) ;
Note that these check constraints are defined at the table level not the column level, as they must because they reference multiple columns. RThomas' answer is not valid Transact-SQL for this reason!

Resources