Variable length int columns in SQL
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?
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>
)
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!
精彩评论