开发者

Checking that items are not incorrectly linked - is this what "Foreign Key Constraints" are for?

开发者 https://www.devze.com 2023-03-09 01:00 出处:网络
I\'m working in SQLAzure at the moment. I\'m setting up a design where each User has a number of Address\'s

I'm working in SQLAzure at the moment.

I'm setting up a design where each User has a number of Address's

When the user then places an Order, then I want to link that Order to both the User and to a couple of Addresses.

So my tables look like:

User

  • Id
  • Name
  • etc

Address

Order

  • Id
  • UserId (Foreign Key)
  • DeliveryAddressId (Foreign Key)
  • BillingAddressId (Foreign Key)
  • etc

Is there a way I can I set up a check within SQL Server so that a user can't under any circumstances (e.g. by hacking an HTML POST) submit an Order with an AddressId which is not linked to the same as the submitted UserId. I've looked at "foreign key constraints" in the docs, but this doesn't seem to be quite what I'm looking for.

Any suggestions of what to try - or what tutorials to read - would be most appreciated.


In addition to your primary key in the Address table (on Id), you should also declare another key constraint, a UNIQUE constraint, on (Id,UserId).

ALTER TABLE Address ADD CONSTRAINT UQ_Address_UserCheck UNIQUE (Id,UserID)

You can then either replace your existing FKs from Order to address, or add additional ones, that check both columns

ALTER TABLE Order ADD CONSTRAINT
     FK_Order_DeliveryAddress_UserCheck FOREIGN KEY (DeliveryAddressID,UserID)
     references Address (Id,UserId)

As I say, you can add these all as additional constraints, if you want to.


So, with some slight naming tweaks, your tables are rendered as this:

create table Users (
    UserID int IDENTITY(1,1) not null,
    Name varchar(30) not null,
    /* Other columns */
    constraint PK_Users PRIMARY KEY (UserID),
    constraint UQ_User_Names UNIQUE (Name)
)
go
create table Addresses (
    AddressID int IDENTITY(1,1) not null,
    UserID int not null,
    Street varchar(35) not null,
    /* Other columns */
    constraint PK_Addresses PRIMARY KEY (AddressID),
    constraint FK_Addresses_Users FOREIGN KEY (UserID) references Users (UserID),
    constraint UQ_Addresses_UserCheck UNIQUE (UserID,AddressID)
)
go
create table Orders (
    OrderID int IDENTITY (1,1) not null,
    UserID int not null,
    DeliveryAddressID int not null,
    BillingAddressID int not null,
    /* Other columns - there may be other nullability concerns above */
    constraint PK_Orders PRIMARY KEY (OrderID),
    constraint FK_Orders_Users FOREIGN KEY (UserID) references Users (UserID),
    constraint FK_Orders_DeliveryAddresses FOREIGN KEY (DeliveryAddressID) references Addresses (AddressID),
    constraint FK_Orders_BillingAddresses FOREIGN KEY (BillingAddressID) references Addresses (AddressID),
    /* Further constraints - ensure UserID -> AddressID match */
    constraint FK_Orders_DeliveryAddress_UserCheck FOREIGN KEY (UserID,DeliveryAddressID) references Addresses (UserID,AddressID),
    constraint FK_Orders_BillingAddress_UserCheck FOREIGN KEY (UserID,BillingAddressID) references Addresses (UserID,AddressID)
)

And trying it our with some inserts that should work, except for the last (where there's a user/address mismatch), it works:

declare @UID1 int
declare @UID2 int
declare @AID1_1 int
declare @AID1_2 int
declare @AID2_1 int
declare @AID2_2 int
insert into Users (Name)
select 'User1'
set @UID1 = SCOPE_IDENTITY()
insert into Users (Name)
select 'User2'
set @UID2 = SCOPE_IDENTITY()

insert into Addresses (UserID,Street)
select @UID1,'Street1'
set @AID1_1 = SCOPE_IDENTITY()
insert into Addresses (UserID,Street)
select @UID1,'Street2'
set @AID1_2 = SCOPE_IDENTITY()
insert into Addresses (UserID,Street)
select @UID2,'Street1'
set @AID2_1 = SCOPE_IDENTITY()
insert into Addresses (UserID,Street)
select @UID2,'Street2'
set @AID2_2 = SCOPE_IDENTITY()

insert into Orders (UserID,DeliveryAddressID,BillingAddressID)
select @UID1,@AID1_1,@AID1_2 union all
select @UID2,@AID2_1,@AID2_1

insert into Orders (UserID,DeliveryAddressID,BillingAddressID)
select @UID1,@AID1_1,@AID2_1

Results:

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(2 row(s) affected)
Msg 547, Level 16, State 0, Line 31
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Orders_BillingAddress_UserCheck". The conflict occurred in database "Test", table "dbo.Addresses".
The statement has been terminated.


create an ON INSERT trigger to do any additional logic that you want to enforce.

the downside will be the user will get an error message when they try with a bad address... to be proactive, you should do this check also in the GUI.


Would having a composite foreign key from Order (UserID, DeliveryAddressID) to Address (UserID, ID) do it? (Similarly for BillingAddressID)

0

精彩评论

暂无评论...
验证码 换一张
取 消