Question

I have a database with two tables: Users and Categories.

Users has these fields:

UserId        unique identifier
UserName      nvarchar
CategoryId    int

Categories has these fields:

CategoryId    int
CategoryName  nvarchar

At the moment, every user is in one category. I want to change this so that each user can be in any number of categories. What is the best way to do this?

My site does a lot of really expensive searches, so the solution needs to be as efficient as possible. I don't really want to put the list of categories each user has in a third table, as this means that when I pull back a search, each user will be represented in several rows at once (at least, this is what would happen if I implemented a search with my current, fairly crude, understanding of sql.)

EDIT:

If setting up a many-many relationship, is it possible to return only one row for each user?

For instance:

DECLARE @SearchUserID nvarchar(200) = 1;

SELECT *
FROM Users JOIN Categories JOIN CategoriesPerUser
WHERE UserId = @SearchUserID

This would return one row for each category the user belonged to. It is possible to have it only return one row?

Was it helpful?

Solution

At the moment you have a one-to-many relationship, that is to say category can be assocaited with many users, but a user can only be assocaited with one category.

You need to change this to a many-to-many relationship so that each user can be assocaited with many categories and each category can be assocaited with many users.

This is achieves by adding a table which links a userid and a category id (and removing categoryid from the user table)

Table: UserToCategory
UserId     int
CategoryId int

As for your last paragraph, this is the most efficient way of modelling your requirement. You should make a combination of UserId/CategoryId the PrimaryKey in this table to stop a user being associated with the same category twice. This stops the problem of a user returned twice for a particular category.

The SQL to find, for example, all users associated with a category would be

SELECT u.*
FROM Users u
INNER JOIN UserToCategory uc 
   ON u.UserId = uc.UserID
WHERE uc.CategoryId = 123

Edit after comments: If you have a query that finds a number of users, and you want a distinct list of categories associated with those users this could be done like

SELECT c.*
FROM Categories c
WHERE CategoryId IN
(
  SELECT uc.CategoryID
  FROM UserToCategory uc
    INNER JOIN Users u ON uc.UserId = u.UserID
  WHERE <some criteria here to filter users>
)

OTHER TIPS

I would drop CategoryId out of Users and go for the 3d table:

UserCategories
- UserId
- CategoryId

If you want to search all the categories for a user you can use for example:

SELECT uc.CategoryId, c.CategoryName 
FROM UserCategories uc
JOIN Categories c ON uc.CategoryId = c.CategoryId
WHERE uc.UserId = @UserId

As this is an n-to-n Relationship (one category can have several user and one user can have several categories), the typical way to implement this would be to have a junction table.

But as you said, you don't want to create a third table for reasons of already implemented features, i guess you could also change the column "CategoryId" in the User table to "CategorieIds", which could then contain a text field. This text field could contain a list of integers, separated by a special character ("," for example). as far as i'm concerned, you should then do the split operation on your implementing code, since i don't know of any practical way to do this in sql (maybe someone could correct me here...).

You could also keep you categoryId Column then, if you wanted to implement something like a 'main' category per user.

Hope this helps and I hope to have suggested a correct way to implement this!

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