MySQL 多个表中最喜欢的项目
MySQL favorite items from multiple tables
当有多个 table 可供选择时,存储收藏夹的最有效方法是什么。
例如:您有一个用户(ID、姓名...)、食物(ID、卡路里...)、鸡尾酒(ID、酒精...)和糖果(ID、姓名、 ...),用户可以从这些 table 中收藏多个元素。解决这个问题的最佳方法是什么。
我的想法是创建一个 many-to-many 关系,我让用户与上面的每个 table 建立关系(User-Food、User-Cocktails、User-Sweets) 或最喜欢的 table,其中我拥有上述所有 table 的外键。
我猜后者不是最佳解决方案。
你基本上有两个选择:
create table favorites (
favoritesId int auto_increment primary key,
userId,
which varchar(255),
id int,
foreign key (userId) references users(userId)
);
它简短明了,可以让您添加新实体。
或者,您可以单独列出每一个:
create table favorites (
favoritesId int auto_increment primary key,
foodId int,
cocktailsId int,
. . .
foreign key (userId) references users(userId),
foreign key (foodId) references foods(foodId),
foreign key (cocktailsId) references cocktails(cocktailsId),
. . .
);
这不太简洁,但它确实允许您添加显式外键引用。如果需要,还可以轻松地在一行中表示多个收藏夹。
你可以创建一个 Favorite
table
并从另外四个 table
中添加 FOREIGN KEY
看起来像:
CREATE TABLE User (`ID` int not null, `Name` varchar(5)) ;
INSERT INTO User (`ID`, `Name`)
VALUES (1, 'Jogn'), (2, 'Henry') ;
CREATE TABLE Food (`ID` int, `calories` int) ;
INSERT INTO Food (`ID`, `calories`)
VALUES (1, 123), (2, 456) ;
CREATE TABLE Cocktails (`ID` int, `Alcoholic` int) ;
INSERT INTO Cocktails (`ID`, `Alcoholic`)
VALUES (1, 30), (2, 40) ;
CREATE TABLE Sweets (`ID` int, `Name` varchar(5)) ;
INSERT INTO Sweets (`ID`, `Name`)
VALUES (1, 'candy'), (2, 'cake') ;
CREATE TABLE Favorite
(`ID` int
, `UserID` int not null
, `FoodID` int
, `CocktailsID` int
, `SweetsID` int
)
;
ALTER TABLE `User` ADD CONSTRAINT PK_User PRIMARY KEY (`ID`);
ALTER TABLE `Food` ADD CONSTRAINT PK_Food PRIMARY KEY (`ID`);
ALTER TABLE `Cocktails` ADD CONSTRAINT PK_Cocktails PRIMARY KEY (`ID`);
ALTER TABLE `Sweets` ADD CONSTRAINT PK_Sweets PRIMARY KEY (`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_User
FOREIGN KEY (`UserID`) REFERENCES `User`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_Food
FOREIGN KEY (`FoodID`) REFERENCES `Food`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_Cocktails
FOREIGN KEY (`CocktailsID`) REFERENCES `Cocktails`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Sweetss
FOREIGN KEY (`SweetsID`) REFERENCES `Sweets`(`ID`);
INSERT INTO Favorite
(`ID`, `UserID`, `FoodID`, `CocktailsID`, `SweetsID`)
VALUES
(1, 1, '1', NULL, NULL),
(2, 2, NULL, '2', NULL),
(3, 1, NULL, NULL, '1')
;
当我为 Sweets 插入不存在的值时
INSERT INTO Favorite
(`ID`, `UserID`, `FoodID`, `CocktailsID`, `SweetsID`)
VALUES
(3, 1, NULL, NULL, 4)
;
它会抛出异常
Cannot add or update a child row: a foreign key constraint fails (`db_9_1a5d3f`.`favorite`, CONSTRAINT `FK_Sweetss` FOREIGN KEY (`SweetsID`) REFERENCES `sweets` (`ID`))
你可以通过这个 SQL 脚本查询它们(也许创建一个视图):
select
T1.ID FavoriteID,
T1.`UserID`,
case when T2.ID is not null then 'Food'
when T3.ID is not null then 'Cocktails'
when T4.ID is not null then 'Sweets'
end type,
COALESCE(T2.ID,T3.ID,T4.ID) ID
from Favorite T1
left join Food T2 on T1.`FoodID` = T2.`ID`
left join Cocktails T3 on T1.`CocktailsID` = T3.`ID`
left join Sweets T4 on T1.`SweetsID` = T4.`ID`
| FavoriteID | UserID | type | ID |
|------------|--------|-----------|----|
| 1 | 1 | Food | 1 |
| 2 | 2 | Cocktails | 2 |
| 3 | 1 | Sweets | 1 |
如果你想测试:SQL Fiddle Test Demo Link
创建一个 favorite
table,所有项目类型都只有一个列。
CREATE TABLE favorite
(user integer,
item integer,
item_type varchar(256),
PRIMARY KEY (user,
item,
item_type),
FOREIGN KEY (user)
REFERENCES user
(id));
item
是来自相应 table(food
、cocktail
或 sweet
)的 ID。
需要 item_type
来确保唯一性,因为 item
对于两个不同类型的项目可能是相等的。这是例如table 名称('food'
、'cocktail'
或 'sweet'
)。它还可以作为辅助列来指定 item
指向的项目的类型。
优点:
- 引入新项目类型时不需要架构更改。
- 添加和删除收藏夹不需要额外的逻辑,每个收藏夹一行 -- 简单。
缺点:
- 您不能在
item
上添加外键约束,因为它每行引用不同的 table。您必须实施触发器以确保参照完整性。
创建一个 favorite
table,每个项目类型都有一列。
CREATE TABLE favorite
(id integer
AUTO_INCREMENT,
user integer,
food integer,
cocktail integer,
sweet integer,
PRIMARY KEY (id),
FOREIGN KEY (food)
REFERENCES food
(id),
FOREIGN KEY (cocktail)
REFERENCES cocktail
(id),
FOREIGN KEY (sweet)
REFERENCES sweet
(id),
UNIQUE (user,
food),
UNIQUE (user,
cocktail),
UNIQUE (user,
sweet));
优点:
- 可以使用外键约束。
缺点:
- 引入新项目类型时需要更改架构。
- 如果用户可以对每种项目类型拥有任意数量的收藏夹,事情就会变得有点复杂。然后需要为用户提供更多行。即,当添加新收藏夹时,必须检查是否可以更新现有行,收藏夹类型的相应列是否为空,或者是否需要插入新行,因为相应列已经不为空在用户的所有行中。删除收藏时,可能应该删除所有
NULL
行。这在应用程序中需要额外的逻辑,或者必须封装在触发器或过程中。
不过,如果用户对每种项目类型只能有一个最爱,这可能是一个不错的选择。
引入一个共同的 item
table 并让不同的项目 table 引用那个共同的 table。收藏夹 table 将仅引用常见的 table.
CREATE TABLE item
(id integer
AUTO_INCREMENT,
PRIMARY KEY (id));
INSERT INTO item
SELECT NULL
FROM food
UNION ALL
SELECT NULL
FROM cocktail
UNION ALL
SELECT NULL
FROM sweet;
ALTER TABLE food
ADD (item integer,
FOREIGN KEY (item)
REFERENCES item
(id));
ALTER TABLE cocktail
ADD (item integer,
FOREIGN KEY (item)
REFERENCES item
(id));
ALTER TABLE sweet
ADD (item integer,
FOREIGN KEY (item)
REFERENCES item
(id));
UPDATE food
SET item = (SELECT count(*)
FROM food s
WHERE s.id <= food.id);
UPDATE cocktail
SET item = (SELECT count(*)
FROM cocktail s
WHERE s.id <= food.id)
+ (SELECT count(*)
FROM food);
UPDATE sweet
SET item = (SELECT count(*)
FROM sweet s
WHERE s.id <= food.id)
+ (SELECT count(*)
FROM food)
+ (SELECT count(*)
FROM cocktail);
ALTER TABLE food
MODIFY item integer
NOT NULL;
ALTER TABLE cocktail
MODIFY item integer
NOT NULL;
ALTER TABLE sweet
MODIFY item integer
NOT NULL;
CREATE TABLE favorite
(user integer,
item integer,
PRIMARY KEY (user,
item),
FOREIGN KEY (user)
REFERENCES user
(id),
FOREIGN KEY (item)
REFERENCES item
(id));
您可以选择在 item
中添加帮助列以指定 id
的项目类型。您还可以选择从不同的项目 tables(food
、cocktail
或 sweet
)中删除 id
并使用 item
作为主键.
优点:
- 引入新项目类型时不需要架构更改。
- 可以使用外键约束。
- 添加和删除收藏夹不需要额外的逻辑,每个收藏夹一行 -- 简单。
缺点:
- 介绍起来有点棘手
- 可能需要更改现有的应用程序逻辑,因为任何类型的项目现在都需要在创建时额外插入
item
。也可以由一些触发器处理——实际上它只需要创建一个新的 item.id
来满足相应项目 table 中的外键约束(food
、cocktail
或 sweet
), 所以很简单。
总体上可能是 "cleanest" 解决方案。
当有多个 table 可供选择时,存储收藏夹的最有效方法是什么。
例如:您有一个用户(ID、姓名...)、食物(ID、卡路里...)、鸡尾酒(ID、酒精...)和糖果(ID、姓名、 ...),用户可以从这些 table 中收藏多个元素。解决这个问题的最佳方法是什么。
我的想法是创建一个 many-to-many 关系,我让用户与上面的每个 table 建立关系(User-Food、User-Cocktails、User-Sweets) 或最喜欢的 table,其中我拥有上述所有 table 的外键。
我猜后者不是最佳解决方案。
你基本上有两个选择:
create table favorites (
favoritesId int auto_increment primary key,
userId,
which varchar(255),
id int,
foreign key (userId) references users(userId)
);
它简短明了,可以让您添加新实体。
或者,您可以单独列出每一个:
create table favorites (
favoritesId int auto_increment primary key,
foodId int,
cocktailsId int,
. . .
foreign key (userId) references users(userId),
foreign key (foodId) references foods(foodId),
foreign key (cocktailsId) references cocktails(cocktailsId),
. . .
);
这不太简洁,但它确实允许您添加显式外键引用。如果需要,还可以轻松地在一行中表示多个收藏夹。
你可以创建一个 Favorite
table
并从另外四个 table
FOREIGN KEY
看起来像:
CREATE TABLE User (`ID` int not null, `Name` varchar(5)) ;
INSERT INTO User (`ID`, `Name`)
VALUES (1, 'Jogn'), (2, 'Henry') ;
CREATE TABLE Food (`ID` int, `calories` int) ;
INSERT INTO Food (`ID`, `calories`)
VALUES (1, 123), (2, 456) ;
CREATE TABLE Cocktails (`ID` int, `Alcoholic` int) ;
INSERT INTO Cocktails (`ID`, `Alcoholic`)
VALUES (1, 30), (2, 40) ;
CREATE TABLE Sweets (`ID` int, `Name` varchar(5)) ;
INSERT INTO Sweets (`ID`, `Name`)
VALUES (1, 'candy'), (2, 'cake') ;
CREATE TABLE Favorite
(`ID` int
, `UserID` int not null
, `FoodID` int
, `CocktailsID` int
, `SweetsID` int
)
;
ALTER TABLE `User` ADD CONSTRAINT PK_User PRIMARY KEY (`ID`);
ALTER TABLE `Food` ADD CONSTRAINT PK_Food PRIMARY KEY (`ID`);
ALTER TABLE `Cocktails` ADD CONSTRAINT PK_Cocktails PRIMARY KEY (`ID`);
ALTER TABLE `Sweets` ADD CONSTRAINT PK_Sweets PRIMARY KEY (`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_User
FOREIGN KEY (`UserID`) REFERENCES `User`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_Food
FOREIGN KEY (`FoodID`) REFERENCES `Food`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Favorite_Cocktails
FOREIGN KEY (`CocktailsID`) REFERENCES `Cocktails`(`ID`);
ALTER TABLE `Favorite` ADD CONSTRAINT FK_Sweetss
FOREIGN KEY (`SweetsID`) REFERENCES `Sweets`(`ID`);
INSERT INTO Favorite
(`ID`, `UserID`, `FoodID`, `CocktailsID`, `SweetsID`)
VALUES
(1, 1, '1', NULL, NULL),
(2, 2, NULL, '2', NULL),
(3, 1, NULL, NULL, '1')
;
当我为 Sweets 插入不存在的值时
INSERT INTO Favorite
(`ID`, `UserID`, `FoodID`, `CocktailsID`, `SweetsID`)
VALUES
(3, 1, NULL, NULL, 4)
;
它会抛出异常
Cannot add or update a child row: a foreign key constraint fails (`db_9_1a5d3f`.`favorite`, CONSTRAINT `FK_Sweetss` FOREIGN KEY (`SweetsID`) REFERENCES `sweets` (`ID`))
你可以通过这个 SQL 脚本查询它们(也许创建一个视图):
select
T1.ID FavoriteID,
T1.`UserID`,
case when T2.ID is not null then 'Food'
when T3.ID is not null then 'Cocktails'
when T4.ID is not null then 'Sweets'
end type,
COALESCE(T2.ID,T3.ID,T4.ID) ID
from Favorite T1
left join Food T2 on T1.`FoodID` = T2.`ID`
left join Cocktails T3 on T1.`CocktailsID` = T3.`ID`
left join Sweets T4 on T1.`SweetsID` = T4.`ID`
| FavoriteID | UserID | type | ID |
|------------|--------|-----------|----|
| 1 | 1 | Food | 1 |
| 2 | 2 | Cocktails | 2 |
| 3 | 1 | Sweets | 1 |
如果你想测试:SQL Fiddle Test Demo Link
创建一个
favorite
table,所有项目类型都只有一个列。CREATE TABLE favorite (user integer, item integer, item_type varchar(256), PRIMARY KEY (user, item, item_type), FOREIGN KEY (user) REFERENCES user (id));
需要item
是来自相应 table(food
、cocktail
或sweet
)的 ID。item_type
来确保唯一性,因为item
对于两个不同类型的项目可能是相等的。这是例如table 名称('food'
、'cocktail'
或'sweet'
)。它还可以作为辅助列来指定item
指向的项目的类型。优点:
- 引入新项目类型时不需要架构更改。
- 添加和删除收藏夹不需要额外的逻辑,每个收藏夹一行 -- 简单。
缺点:
- 您不能在
item
上添加外键约束,因为它每行引用不同的 table。您必须实施触发器以确保参照完整性。
创建一个
favorite
table,每个项目类型都有一列。CREATE TABLE favorite (id integer AUTO_INCREMENT, user integer, food integer, cocktail integer, sweet integer, PRIMARY KEY (id), FOREIGN KEY (food) REFERENCES food (id), FOREIGN KEY (cocktail) REFERENCES cocktail (id), FOREIGN KEY (sweet) REFERENCES sweet (id), UNIQUE (user, food), UNIQUE (user, cocktail), UNIQUE (user, sweet));
优点:
- 可以使用外键约束。
缺点:
- 引入新项目类型时需要更改架构。
- 如果用户可以对每种项目类型拥有任意数量的收藏夹,事情就会变得有点复杂。然后需要为用户提供更多行。即,当添加新收藏夹时,必须检查是否可以更新现有行,收藏夹类型的相应列是否为空,或者是否需要插入新行,因为相应列已经不为空在用户的所有行中。删除收藏时,可能应该删除所有
NULL
行。这在应用程序中需要额外的逻辑,或者必须封装在触发器或过程中。
不过,如果用户对每种项目类型只能有一个最爱,这可能是一个不错的选择。
引入一个共同的
item
table 并让不同的项目 table 引用那个共同的 table。收藏夹 table 将仅引用常见的 table.CREATE TABLE item (id integer AUTO_INCREMENT, PRIMARY KEY (id)); INSERT INTO item SELECT NULL FROM food UNION ALL SELECT NULL FROM cocktail UNION ALL SELECT NULL FROM sweet; ALTER TABLE food ADD (item integer, FOREIGN KEY (item) REFERENCES item (id)); ALTER TABLE cocktail ADD (item integer, FOREIGN KEY (item) REFERENCES item (id)); ALTER TABLE sweet ADD (item integer, FOREIGN KEY (item) REFERENCES item (id)); UPDATE food SET item = (SELECT count(*) FROM food s WHERE s.id <= food.id); UPDATE cocktail SET item = (SELECT count(*) FROM cocktail s WHERE s.id <= food.id) + (SELECT count(*) FROM food); UPDATE sweet SET item = (SELECT count(*) FROM sweet s WHERE s.id <= food.id) + (SELECT count(*) FROM food) + (SELECT count(*) FROM cocktail); ALTER TABLE food MODIFY item integer NOT NULL; ALTER TABLE cocktail MODIFY item integer NOT NULL; ALTER TABLE sweet MODIFY item integer NOT NULL; CREATE TABLE favorite (user integer, item integer, PRIMARY KEY (user, item), FOREIGN KEY (user) REFERENCES user (id), FOREIGN KEY (item) REFERENCES item (id));
您可以选择在
item
中添加帮助列以指定id
的项目类型。您还可以选择从不同的项目 tables(food
、cocktail
或sweet
)中删除id
并使用item
作为主键.优点:
- 引入新项目类型时不需要架构更改。
- 可以使用外键约束。
- 添加和删除收藏夹不需要额外的逻辑,每个收藏夹一行 -- 简单。
缺点:
- 介绍起来有点棘手
- 可能需要更改现有的应用程序逻辑,因为任何类型的项目现在都需要在创建时额外插入
item
。也可以由一些触发器处理——实际上它只需要创建一个新的item.id
来满足相应项目 table 中的外键约束(food
、cocktail
或sweet
), 所以很简单。
总体上可能是 "cleanest" 解决方案。