ENUM for SQL Server without Check Constraint? - sql-server

I am trying to find an alternative for the ENUM datatype in SQL Server in my MySQL conversion process. I know the check constraint exists, but am looking to simply create new tables for to replace the enum objects altogether. So the gist of the goal:
Create Table Enum OrderType {
Buy = 1;
Sell = 2;
Short = 3;
Cover = 4;
}
Where C# could easily call these values as necessary. Any way to do this? Or must I use the check?

You can create a table with these values and reference them in the main table with usage of foreign key constraint. In the code below the FKTypes will prevent anything outside of range of IDs from being inserted.
create table Types
(
TypeId int not null primary key,
Value varchar(10) not null
)
go
insert Types(TypeId, Value) values (1, 'Buy'), (2, 'Sell'), (3, 'Short'), (4, 'Cover')
go
create table Main
(
ColData varchar(1000),
ColType int not null,
constraint FK_ColType foreign key references Types(TypeId)
)
go
I don't have SSMS at hand so this is not tested against typos, but you can give it a try if you find it useful.
Regards
Piotr

Related

sql server - Procedure that checks if row values from INSERT already exist in database

I need to create procedure that doesn't allow insertion of duplicate values and procedure has to check if column "zipcode" is in valid format (5 digits only).
I didn't find a proper solution yet and that's why I'm writing this.
Thanks in advance for any help!
EDIT: I know about unique constraints, but I'm doing assignment for job recruitment and I HAVE TO implement it via procedure.
Btw is there a alternative in MS SQL to keyword new from MySQL? I need that to do what I want I believe...
Hi for check you can use something like this
ALTER TABLE your_table
ADD CONSTRAINT chk_your_table CHECK (zipcode LIKE '[0-9][0-9][0-9][0-9][0-9]')
for duplicate use UNIQUE
The problem with using a stored procedure is that if any user has UPDATE permissions on the table they can bypass your checking and add invalid and duplicate data, you should therefore use table constraints on the table to handle this or a trigger:
CREATE TABLE #Zip
(
[Id] INT PRIMARY KEY Identity,
[ZIPCode] VARCHAR(5) UNIQUE NOT NULL
CONSTRAINT CK_ZIPCode CHECK ( [ZIPCode] LIKE '[0-9][0-9][0-9][0-9][0-9]' ),
Place VARCHAR(20) NOT NULL
)
Now if you run the following inserts:
Insert into #Zip VALUES ('12345', 'MyPlace')
Insert into #Zip VALUES ('12345', 'MyPlace2')
Insert into #Zip VALUES ('A2345', 'MyPlace3')
Insert into #Zip VALUES ('13345', 'MyPlace4')
The first one succeeds,
the second fails with
Violation of UNIQUE KEY constraint
the third fails with:
The INSERT statement conflicted with the CHECK constraint
"CK_ZIPCode".
and the 4th succeeds.
For any more complex checking you should use a TRIGGER.

Ensuring relationship integrity in a database modelling sets and subsets

I have a model that consists in these 3 tables (among others):
Item
PK id_item
Set
PK id_set
Subset
PK id_subset
Each Item MUST belong to one and just one Set (1..N)
You can define zero or more Subsets for each Set (0..N)
Each Item belongs to zero or one subset (0..N)
Ive modelled the database adding the following FK:
Item
PK id_item
FK id_set
FK id_subset
Set
PK id_set
Subset
PK id_subset
FK id_set
I cannot find a way to forbid the database to accept Items belonging to one Set (A) and to a Subset (B2) that belongs to a different Set (B).
Is there anyway to do so? Or is this just a bad design/modelling?
This is a SQL Server 2008 database
First, if an Item can belong to a subset, you must add a foreign key between the Item table and the subset table.
Second, add a check constraint on the Item table that will make sure that if the subset_id does not belong in the set_id, will raise an exception.
To do that, first you create a user defied function to test the values:
CREATE FUNCTION udf_CheckSubSet
(
#id_set int,
#id_subset int
)
RETURNS int
AS
BEGIN
IF #id_subset IS NULL OR EXISTS (
SELECT 1
FROM Subset
WHERE id_subset = #id_subset
AND id_set = #id_set
)
BEGIN
RETURN 1
END
-- logical else
RETURN 0
END
then you create the check constraint:
ALTER TABLE Item
ADD CONSTRAINT cc_Item_subset CHECK (dbo.udf_CheckSubSet(id_set, id_subset) = 1);
However, I also suggest to create a stored procedure to insert the item, and test inside the stored procedure before inserting the item.
The reason for this is that it's much more expensive (performance-wise) to handle exceptions then to simply test the input before inserting it to the table.
you might be wondering why do you even need the check constraint, if you already handle the problem with the stored procedure. The answer to this question is that the check constraint will not allow inserting updating the data in the table even if someone tries to do it directly from SSMS, or just write an insert or update statement.
Disclaimer: while it is possible to implement this kind of constraint using database schema alone, I strongly advise you against using the approach explained below in any real life project.
Academically speaking, in order to do what you want you have to migrate the identifying key from Set via both Set and Subset foreign keys. The schema will look like this:
use master;
go
if db_id('SampleDB') is not null
set noexec on;
go
create database SampleDB;
go
use SampleDB;
go
/*==============================================================*/
/* Table: Sets */
/*==============================================================*/
create table dbo.[Sets] (
[Id] int not null,
[Name] varchar(50) not null,
constraint [PK_Sets] primary key (Id)
)
go
/*==============================================================*/
/* Table: SubSets */
/*==============================================================*/
create table dbo.[SubSets] (
[SetId] int not null,
[SubsetId] int not null,
[Name] varchar(50) not null,
constraint [PK_SubSets] primary key (SetId, SubsetId)
)
go
alter table dbo.SubSets
add constraint FK_SubSets_Sets_SetId foreign key (SetId)
references dbo.Sets (Id)
go
/*==============================================================*/
/* Table: Items */
/*==============================================================*/
create table dbo.[Items] (
[Id] int not null,
[SetId] int not null,
[SubsetId] int null,
[Name] varchar(50) not null,
constraint [PK_Items] primary key (Id)
)
go
alter table dbo.Items
add constraint FK_Items_Sets_SetId foreign key (SetId)
references dbo.Sets (Id)
go
alter table dbo.Items
add constraint FK_Items_SubSets_SetIdSubsetId foreign key (SetId, SubsetId)
references dbo.SubSets (SetId, SubsetId)
go
set noexec off;
go
use master;
go
As you can see, the PK on the dbo.Subset table is somewhat lame. It serves its purpose, of course, but it could have been made simpler. Another unusual thing is that SubsetId column in dbo.Items table participates in 2 foreign keys that point to different tables.
You can insert some data into this schema, and it will be perfectly fine:
insert into dbo.Sets (Id, Name)
values
(1, 'Set 1'),
(2, 'Set 2');
go
insert into dbo.SubSets (SetId, SubsetId, Name)
values
(1, 1, 'Subset 1-1'),
(1, 2, 'Subset 1-2');
go
insert into dbo.Items (Id, SetId, SubsetId, Name)
values
(1, 1, 1, 'Banana'),
(2, 1, 1, 'Plate'),
(3, 1, 2, 'Charger'),
(4, 1, null, 'Toothpick'),
(5, 2, null, 'Cup');
And you will be hit with FK constraint violation when you try to add contradictory data, such as this:
insert into dbo.Items (Id, SetId, SubsetId, Name)
values
(6, 2, 1, 'Fake t-shirt');
The subset 1 does not belong to the set 2, so the command above will not succeed.
Now - why you should never use this design approach, unless being forced to do so at the gunpoint:
Not every business constraint can and should be implemented on the
schema level. Actually, writing it down in the stored procedure will
be easier to understand, maintain and work with, in most cases;
It contains rarely used tricks which are very confusing and
unexpected for most people, even seasoned database professionals. All
of this add up to the cost of maintenance;
Last but not least - queries that will work correctly with this kind
of schema will be, how shall I put this, awkward and difficult to write. Also, you will most probably encounter a lot of problems it you will try to combine this schema with any kind of ORM. Or maybe not; or maybe they will only manifest themselves once being put in production, etc.

two digit year as primary key in sql server

I have a table with these columns:
ID int,
d date
Now what I need is to define the primary key in such a way that ID would be unique for each year; meaning that there can not be two same IDs in 2004, but it is possible to have two same IDs in two different years.
Like:
insert into myTable values(1, '1-1-2004'), (1, '1-1-2005')
but not like:
insert into myTable values(1, '3-1-2005'), (1, '1-1-2005')
I tried this:
primary key(ID, datepart(YY, d))
but I get syntax error.
One way of doing this, if you can alter the table structure, is to add a persisted computed column for the year part, and then add a primary key for (id, computer_col), like this:
CREATE TABLE myTable (
id INT NOT NULL,
d DATE NOT NULL,
y AS DATEPART(YEAR,d) PERSISTED NOT NULL,
PRIMARY KEY(id,y)
)
I'm not saying this is a good solution in any way, but it should work. Using a trigger on insert or a check constraint might be better.
Using your test data this will allow the first insert statement, but disallow the second as it violates the primary key constraint.

How do I create a multiple-table check constraint?

Please imagine this small database...
Diagram
removed dead ImageShack link - volunteer database diagram
Tables
Volunteer Event Shift EventVolunteer
========= ===== ===== ==============
Id Id Id EventId
Name Name EventId VolunteerId
Email Location VolunteerId
Phone Day Description
Comment Description Start
End
Associations
Volunteers may sign up for multiple events.
Events may be staffed by multiple volunteers.
An event may have multiple shifts.
A shift belongs to only a single event.
A shift may be staffed by only a single volunteer.
A volunteer may staff multiple shifts.
Check Constraints
Can I create a check constraint to
enforce that no shift is staffed by
a volunteer that's not signed up for
that shift's event?
Can I create a check constraint to
enforce that two overlapping shifts
are never staffed by the same
volunteer?
The best place to enforce data integrity is the database. Rest assured that some developer, intentionally or not, will find a way to sneak inconsistent stuff into the database if you let them!
Here's an example with check constraints:
CREATE FUNCTION dbo.SignupMismatches()
RETURNS int
AS BEGIN RETURN (
SELECT count(*)
FROM Shift s
LEFT JOIN EventVolunteer ev
ON ev.EventId = s.EventId
AND ev.VolunteerId = s.VolunteerId
WHERE ev.Id is null
) END
go
ALTER TABLE Shift ADD CONSTRAINT chkSignup CHECK (dbo.SignupMismatches() = 0);
go
CREATE FUNCTION dbo.OverlapMismatches()
RETURNS int
AS BEGIN RETURN (
SELECT count(*)
FROM Shift a
JOIN Shift b
ON a.id <> b.id
AND a.Start < b.[End]
AND a.[End] > b.Start
AND a.VolunteerId = b.VolunteerId
) END
go
ALTER TABLE Shift ADD CONSTRAINT chkOverlap CHECK (dbo.OverlapMismatches() = 0);
Here's some tests for the new data integrity checks:
insert into Volunteer (name) values ('Dubya')
insert into Event (name) values ('Build Wall Around Texas')
-- Dubya tries to build a wall, but Fails because he's not signed up
insert into Shift (VolunteerID, EventID, Description, Start, [End])
values (1, 1, 'Dunbya Builds Wall', '2010-01-01', '2010-01-02')
-- Properly signed up? Good
insert into EventVolunteer (VolunteerID, EventID)
values (1, 1)
insert into Shift (VolunteerID, EventID, Description, Start, [End])
values (1, 1, 'Dunbya Builds Wall', '2010-01-01', '2010-01-03')
-- Fails, you can't start the 2nd wall before you finished the 1st
insert into Shift (VolunteerID, EventID, Description, Start, [End])
values (1, 1, 'Dunbya Builds Second Wall', '2010-01-02', '2010-01-03')
Here are the table definitions:
set nocount on
if OBJECT_ID('Shift') is not null
drop table Shift
if OBJECT_ID('EventVolunteer') is not null
drop table EventVolunteer
if OBJECT_ID('Volunteer') is not null
drop table Volunteer
if OBJECT_ID('Event') is not null
drop table Event
if OBJECT_ID('SignupMismatches') is not null
drop function SignupMismatches
if OBJECT_ID('OverlapMismatches') is not null
drop function OverlapMismatches
create table Volunteer (
id int identity primary key
, name varchar(50)
)
create table Event (
Id int identity primary key
, name varchar(50)
)
create table Shift (
Id int identity primary key
, VolunteerId int foreign key references Volunteer(id)
, EventId int foreign key references Event(id)
, Description varchar(250)
, Start datetime
, [End] datetime
)
create table EventVolunteer (
Id int identity primary key
, VolunteerId int foreign key references Volunteer(id)
, EventId int foreign key references Event(id)
, Location varchar(250)
, [Day] datetime
, Description varchar(250)
)
Question 1 is easy. Just have your Shift table refer directly to EventVolunteer table and you are all set
What I would do is have an Identity column on the EventVolunteer table that auto-increments, with a unique constraint on the EventId, VolunteerId pair. Use the EventVolunteerId (identity) as the foreign key to the Shift table. This enforces the constraint you'd like fairly simply, whilst normalizing your data somewhat.
I understand this is not the answer to your general question, however I'd see this as the best solution to your specific problem.
Edit:
I should have read the question fully. This solution will prevent one volunteer from doing two shifts at the same event, even if they don't overlap. Perhaps moving the shift start and end times to the EventVolunteer and having the check constraint on times on that table would suffice, though then you have shift data outside the Shift table which does not sound intuitive to me.
There is a way to do it by using triggers, which i wouldn't recommend. I would recommend not putting your buisness logic at the database level. The db doesn't need to know who, is staffing a certain shift at which time. That logic should be put in your buisness layer. I would recommend using a repository construction pattern. Scott gutherie has a very good chapter in his mvc 1.0 book which describes this (Link below).
http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx

Is it possible to change the value of an auto-increment integer primary key field?

If I set a database column as an autonumber primary key field, will the autonumbering feature prevent those values from being modifiable?
Or does the primary key nature of the column make it unmodifiable?
Both? Neither?
As always, it depends on your database. However, the answer is probably neither.
In SQL Server, you are restricted from inserting primary keys manually into an identity column, unless you use the following:
SET IDENTITY_INSERT [dbo].[MyTable] ON
INSERT INTO [dbo].[MyTable] (id, name) VALUES (1000, 'Foo')
SET IDENTITY_INSERT [dbo].[MyTable] OFF
Neither. You can modify it all you want. Setting a column as autoincrement just specifies the default value for new rows, that's it. After that it acts just like a normal column.
EDIT: Apparently this isn't true for SQL Server, but you didn't specify so I just went with the popular answer.
Did you mean auto-increment?
In MS-Sql no,
In mysql yes (may depend on engine)
It's not DB-agnostic. However, MySQL and SQLite (for example) allow you to modify autoincrement primary keys:
mysql:
create table foo (id INTEGER AUTO_INCREMENT, PRIMARY KEY(id));
insert into foo values (null);
select * from foo;
update foo set id = 2 where id = 1;
select * from foo;
sqlite (see http://www.sqlite.org/faq.html#q1 for what AUTOINCREMENT actually means):
create table foo(id INTEGER PRIMARY KEY AUTOINCREMENT);
insert into foo VALUES(null);
select * from foo;
update foo set id = 2 where id = 1;
select * from foo;

Resources