views:

199

answers:

7

Given the following structure:

City
Area
User

Every Area has 1 and only 1 City.
Every User has at least one but possibly multiple Areas.
Every User has 1 and only 1 City.

What is the most elegant way to model this?

Currently, I have:

User,
UserArea,
Area,
City

Where UserArea is a 1:M relationship w/ User, and Area is 1:1 with City.

The problem is this:

A user can have 3 or 4 Areas under the current model, but 2 of the Areas could be in City "1" and the other 2 Areas could be in City "2". This is a violation of business rules.

Should I just put in a constraint to prevent this sort of thing, or is a better approach to normalize further so that this type of paradox is not possible? If so, how does one model this system so that:

1 User = 1 City;
1 Area = 1 City;
1 User = M Areas;

Thanks for you insights.

+1  A: 

I would have a table each for Users, Areas and Cities, then have a fourth table with Columns User(FK), Cities(FK) and Areas(FK) where Users & Cities (in combination) is constrained to be Unique. Then whenever a User-Area combination is inserted, it won't allow a non-unique City.

Lance Roberts
City can't be unique in this fourth table, because a User will have many areas, each w/ the same city. So, you'd have UserID, CityID, AreaID and city ID would need to be duplicated for each area of that city.
Scott
@Scott, Thanks, fixed that.
Lance Roberts
A: 

Only thing I can think of offhand is:

Give the Area table a composite alternate key of CityID and AreaID. Make AreaID primary (so it can have only one city).

Use this alternate key (AK1) to form a FK relationship between Area and UserArea.

Give the User table a composite alternate key of UserID and CityID. Make UserID primary.

Use this alternate key (AK2) to form an FK relationship between User and UserArea.

So your UserArea table will look like this:

UserID CityID AreaID

The AK2-based foreign key will force you to pick a city that matches the user's home city, and the AK1-based foreign key will force you to pick an area that belongs to that city. In essence, AK1 and AK2 foreign keys will overlap, forcing what you want.

pjabbott
A: 

I think that your "User, UserArea, Area, City" approach is correct. Rely on the constraints and business logic to prevent violations.

David Aldridge
A: 

Can you provide more details on what is an area? Let me state my assumptions:
User lives in a city.
Every city has areas.
An area can fall into one city only.
A user can live in one city only
Given these conditions, you seem to have the following functional dependencies in your design spec:
Area -> City
User -> City
Your business model suggests that the user can have multiple addresses within the same city but cannot have an address in two different cities. Is this a realistic design constraint? If i can have multiple addresses, why not in different cities then?
If you want to store all the areas of a given user you need a third table (like you have suggested). The table would look like
UserArea(userID,AreaID). You need to implement the business logic using a trigger or a stored procedure.

bkm
Example:A User can ONLY be in New York City.Queens, Brooklyn, Manhattan, Long Island and Harlam are all areas, each of which belongs exclusively to "New York City".A user can have multiple areas, but only within one city.So a user can belong to "Queens, Brooklyn and Manhattan" or "Harlam, Bronx, Brooklyn", etc.A user CANNOT belong to "Queens, Harlam and Newark" because the area "Newark" is in the city "New Jersey" and not "New York".A user can belong to one or more areas, but each one of those areas must belong to the same city.
Scott
A: 

USER_AREAS only requires the following columns:

  • USER_ID (pk, fk for USERS.USER_ID)
  • AREA_ID (pk, fk for AREA.AREA_ID)

An area is associated to one city in the AREAS table; you know which cities are associated with the user by rolling up from the AREAS table:

AREA

  • AREA_ID (pk)
  • CITY-ID (fk for CITY.CITY_ID)

Putting CITY_ID in the USER_AREAS table is redundant. Secondly, placing CITY_ID in the USER_AREAS table does not guarantee that the AREA_ID in that record is actually associated with the CITY_ID in the AREA table. A CHECK constraint only enforces domain integrity by limiting the values that are accepted by a column, and can not reference columns in other tables must less a user defined function.

You can't enforce the business rule of a user's areas only ever belonging to a single city in the database. It would have to be done at the application level (whatever sproc manages inserting/updating the USER_AREAS table).

OMG Ponies
Rexum, you're right that putting cityID in the User_Areas is redundant, and that's the tradeoff here. Either you have redundancy that is handled via constraints, or you have incomplete normalization that allows update/deletion anomolies to exist.You're wrong though in that "placing City_Id in the User_Areas table does not guarante that the Area_Id in that record is actually associated with the CityID in the Area table..." The sql code added below does exactly that via Foreign Key relationships on Multiple Columns. Please see the answer posted below. I'd be interested in your comments.
Scott
The redundancy IS incomplete normalization - You know what city a user is associated to by the area association. You're now storing that info redundantly in every user record, and again in every UserAreas record. Without the FK ref to two tables (didn't know it could be done), there was no way to guarantee that the city-id was actually associated with the area-id.
OMG Ponies
A: 

I'm not sure what you mean by "areas".

I believe the urban division is as follows:

The planet has countries. A country has regions (states, provinces etc.) Regions have areas (cities, towns, villages etc.) Areas (if big enough) can have districts.

User => Country + Region/Area + City (+ District)

Could you please elaborate on areas?

User
mastermind, please see my comment two posts up which gives an example of what I'm referring to.
Scott
A: 

This answer was provided to me from SQLServerCentral, and it does exactly what I was looking for. There is a redundancy (as rexum pointed out in this forum), but there is no possibility of anomolies.

I'm very interested in your comments and suggestions.


CREATE TABLE [dbo].[Cities](
    [CityID] [int] IDENTITY(1,1) NOT NULL,
  [CityName] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Cities] PRIMARY KEY CLUSTERED
(
   [CityID] ASC
)
)
CREATE TABLE [dbo].[Users](
   [UserID] [int] IDENTITY(1,1) NOT NULL,
  [UserName] [varchar](50) NOT NULL,
  [CityID] [int] NOT NULL,
 CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
  [UserID] ASC
)
)

ALTER TABLE [dbo].[Users]  WITH CHECK ADD  CONSTRAINT [FK_Users_Cities] FOREIGN KEY([CityID])
REFERENCES [dbo].[Cities] ([CityID])
GO
ALTER TABLE [dbo].[Users] CHECK CONSTRAINT [FK_Users_Cities]
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_UsersCity] ON [dbo].[Users]
(
   [UserID] ASC,
   [CityID] ASC
)

CREATE TABLE [dbo].[Areas](
    [AreaID] [int] IDENTITY(1,1) NOT NULL,
  [AreaName] [varchar](50) NOT NULL,
  [CityID] [int] NOT NULL,
 CONSTRAINT [PK_Areas] PRIMARY KEY CLUSTERED
(
  [AreaID] ASC
))


GO
ALTER TABLE [dbo].[Areas]  WITH CHECK ADD  CONSTRAINT [FK_Areas_Cities] FOREIGN KEY([CityID])
REFERENCES [dbo].[Cities] ([CityID])
GO
ALTER TABLE [dbo].[Areas] CHECK CONSTRAINT [FK_Areas_Cities]
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_AreasCity] ON [dbo].[Areas]
(
 [AreaID] ASC,
   [CityID] ASC
)
GO
CREATE TABLE [dbo].[UserCityArea](
   [UserID] [int] NOT NULL,
    [CityID] [int] NOT NULL,
    [AreaID] [int] NOT NULL,
 CONSTRAINT [PK_UserCityArea] PRIMARY KEY CLUSTERED
(
   [UserID] ASC,
   [CityID] ASC,
   [AreaID] ASC
)
)

GO
ALTER TABLE [dbo].[UserCityArea]  WITH CHECK ADD FOREIGN KEY([UserID], [CityID])
REFERENCES [dbo].[Users] ([UserID], [CityID])
GO
ALTER TABLE [dbo].[UserCityArea]  WITH CHECK ADD FOREIGN KEY([AreaID], [CityID])
REFERENCES [dbo].[Areas] ([AreaID], [CityID])
Scott
OMG Ponies