Question

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.

Was it helpful?

Solution 7

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])

OTHER TIPS

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.

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.

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

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.

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).

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top