December 3, 2008 at 9:56 am
Hi Guys, I've had a look around the web and around this forum in particular but can't seem to find what I'm looking for, hope you can help.
I'm designing a DB to track info related to company PDAs (serial num, model num, sim number, etc...) and I need to be able to restrict entries in my tables to those that exist in other tables OR to be NULL.
Basically a device can exist without a Sim card, or have a sim from the sim table. No two devices can have the same sim.
I thought a foreign key was what I'm looking for but I get errors when I try to have more than one NULL
Here's a simplification of my tables
DEVICE TABLE
PK | Serial | Model | Sim ID
---------------------------------
10 | 01234| NokiaB | 10
20 | 65465| HTC-A | 20
30 | 98556| Palm-C | NULL
40 | 55045| Nokia-B| NULL
Sim Table
PK | fonenum | Sim serial | Tariff
---------------------------------
10 | 0055123 | 123456 | Voice+Data
20 | 0055321 | 123457 | Data only
Is it possible to restrict entries in this way? Do I need a foreign key and just haven't set it up right?
FYI: Server is MS SQL 2000 and my client is Server Management Studio 2008
Any help would be greatfully recieved, even if it's just to say I'm completely barking up the wrong tree
Thanks
December 3, 2008 at 5:09 pm
Hello,
Thanks for the sample data. It was great for testing.
Columnnames can vary.
I would implement it via a foreign key (validation) +trigger (preventing duplicates)
--check if sim is valid using a foreign key
foreignkey fk_device sim
master table Sim Table
parent table DEVICE TABLE
non-unique index on device.simid (to speed up foreign key)
--Triggers to rollback transaction on duplicate sims, 1 for insert, 1 for update
--inserted contains new data
--deleted contains old data
CREATE TRIGGER dbo.TR_DEVICES_INSERT ON [dbo].[devices]
FOR INSERT
AS
SET NOCOUNT ON
--sim shouldn't be in devices
--nulls=nulls is false
--if we find one of the new sims (inserted) in our table, rollback
if exists
(select inserted.simid from inserted
inner join dbo.devices devices
on inserted.simid=devices.simid
and inserted.simid is not null
)
BEGIN
RAISERROR ('Sim already taken', 16, 1)
ROLLBACK TRANSACTION
END
--Triggers to rollback transaction on duplicate sims, 1 for insert, 1 for update
CREATE TRIGGER dbo.TR_DEVICES_UPDATE ON [dbo].[devices]
FOR update
AS
SET NOCOUNT ON
--no duplicate sims in inserted
if exists
(select simid from inserted
group by simid
having count(*)>1
)
BEGIN
RAISERROR ('Duplicate sim in inserted', 16, 1)
ROLLBACK TRANSACTION
END
--second check
--only accept an existing sim when it changes device (different pk)
--testdata
--existing pk 10, sim 10
--existing pk 20, sim 20
--existing pk 30, sim 30
--update pk 30->sim 10 fails because inserted.pk(30)<>devices.pk(10)
--update pk10->sim 20+pk20->sim 10 is succesfull (swapped, separate fails)
--example combined update
/*update devices
set simid=sim
--select *
from devices
inner join
(select 20 as pk,10 as sim
union all
select 10 as pk,20 as sim
union all
select 30 as pk,30 as sim
) A
on a.pk=devices.pk
*/
if exists(
select inserted.simid from inserted
inner join dbo.devices devices
on inserted.simid=devices.simid
and inserted.pk<>devices.pk
)
BEGIN
RAISERROR ('Duplicate sim in update', 16, 1)
ROLLBACK TRANSACTION
END
Should work.
December 4, 2008 at 9:28 am
Jo, Thank you so much! That is exactly what I was wanting to acheive. Thank you so very much!
I've switched the real table and field names into the script you gave me and it works a charm. I want to make the error messages a little more helpful by specifying which other serial number is using the SIM but I think I can work this out for myself.
Thanks again
There was just one little bit of your response I didn't quite grasp tho
Jo Pattyn (12/3/2008)
non-unique index on device.simid (to speed up foreign key)
What does this entail? i had a poke around in the indexes part of the object explorer and "New Index" is greyed out. the below primary key is listed there.
My apologies if this is not the best way to post this info in the forum, if it's not then a little advice on how to post SQL Scripts would be appreciated
USE [BV-LEMSHIP]
GO
/****** Object: Index [PK_tblDevice_1] Script Date: 12/04/2008 16:22:21 ******/
ALTER TABLE [dbo].[tblDevice] ADD CONSTRAINT [PK_tblDevice_1] PRIMARY KEY CLUSTERED
(
[DeviceSerialNumber] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Once again thanks a million for your help
JB
December 4, 2008 at 1:09 pm
Cheers!
About the non-clustered index on device.simid.
By default, when you create a foreign key, sql server doesn't automatically add an index on both columns involved in the master-parent relationship (at least not in sql server 2000)
You already have a primary key (comes with an index) on sim.pk. The other side, device.simid, should also have an index (thus preventing table scans when checking the foreign-key constraint).
ps, just noticed that the insert-trigger also needs a duplicate check at the records from the inserted table (like the first check in the update trigger).
December 8, 2008 at 5:04 am
Jo Pattyn (12/4/2008)
About the non-clustered index on device.simid.By default, when you create a foreign key, sql server doesn't automatically add an index on both columns involved in the master-parent relationship (at least not in sql server 2000)
Understood, thank you.
Jo Pattyn (12/4/2008)
ps, just noticed that the insert-trigger also needs a duplicate check at the records from the inserted table (like the first check in the update trigger).
Yeah I noticed that and added it in, you were just checking I was paying attention really weren't you
I didn't get it to allow me to swap Sims in a single transaction tho. it would appear that the example in your comments...
--update pk10->sim 20+pk20->sim 10 is succesfull (swapped, separate fails)
causes the trigger to fire anyway.
I've been tearin my hair out trying to think of a way to acheive this (to be honest I hadn't thought of it before you mentioned it).
The best idea I can come up with would be (please remember I've not developed anything in SQL before!):
1) within the trigger, assemble list of changed records which clash with exiting records
2) use some sort of iterative loop to check if simid of each conflicting sim is changing anyway in which case allow the update
Is this sort of loop possible?
December 9, 2008 at 2:35 pm
Back again.
What works:
Allows: swapping in a single update
Disallows: swapping in 2 separate updates
Sample data devices:
pk 10 , sim 10
pk 20 , sim 20
Allowed:
update devices
set simid=sim
from devices
inner join
(
--setting multiple values in 1 update
select 20 as pk,10 as sim
union all
select 10 as pk,20 as sim
) A
on a.pk=devices.pk
Result:
pk 10 , sim 20
pk 20 , sim 10
Disallowed:
begin transaction
update devices
set simid=10
where pk=20
update devices
set simid=20
where pk=10
commit transaction
Changed the insert trigger to match the update trigger.
CREATE TRIGGER dbo.TR_DEVICES ON [dbo].[devices]
FOR INSERT,UPDATE
AS
SET NOCOUNT ON
DECLARE @duplicatesim int
SET @duplicatesim=NULL
--no duplicate sims in inserted
if exists
(
select simid from inserted
group by simid
having count(*)>1
)
BEGIN
--feedback
SELECT TOP 1 @duplicatesim=simid from inserted
group by simid
having count(*)>1
RAISERROR ('Duplicate sim %d in insert detected', 16, 1,@duplicatesim)
ROLLBACK TRANSACTION
END
--second check
--only accept an existing sim when it changes device (different pk)
--testdata
--existing pk 10, sim 10
--existing pk 20, sim 20
--existing pk 30, sim 30
--update pk 30->sim 10 fails because inserted.pk(30)<>devices.pk(10)
--update pk10->sim 20+pk20->sim 10 is succesfull (swapped, separate fails)
--example combined update
/*update devices
set simid=sim
--select *
from devices
inner join
(select 20 as pk,10 as sim
union all
select 10 as pk,20 as sim
union all
select 30 as pk,30 as sim
) A
on a.pk=devices.pk
*/
if exists(
select inserted.simid from inserted
inner join dbo.devices devices
on inserted.simid=devices.simid
and inserted.pk<>devices.pk
)
BEGIN
--feedback
SELECT top 1 @duplicatesim=inserted.simid from inserted
inner join dbo.devices devices
on inserted.simid=devices.simid
and inserted.simid is not null
and inserted.pk<>devices.pk
RAISERROR ('Duplicate sim %d in update', 16, 1,@duplicatesim)
ROLLBACK TRANSACTION
END
December 10, 2008 at 2:25 am
Jo Pattyn (12/9/2008)
Back again.What works:
Allows: swapping in a single update
Disallows: swapping in 2 separate updates
...
ROLLBACK TRANSACTION
END
Jo, you're an absolute gent (unless of course you're a lady)
Thanks for all your help, I'll have a play with this later.
I really appreciate it.
JB
Viewing 7 posts - 1 through 7 (of 7 total)
You must be logged in to reply to this topic. Login to reply
This website stores cookies on your computer.
These cookies are used to improve your website experience and provide more personalized services to you, both on this website and through other media.
To find out more about the cookies we use, see our Privacy Policy