如何让 mysql 与标签搜索一起获得平均评分?
Posted
技术标签:
【中文标题】如何让 mysql 与标签搜索一起获得平均评分?【英文标题】:How can I get mysql to pull in an average rating along with tag search? 【发布时间】:2011-05-26 21:47:14 【问题描述】:我正试图了解一个复杂的 mysql 语句(无论如何对我来说很复杂!)。 基本上,我需要从产品表中返回所有产品的列表 带有额外的返回值(它们各自的星级(评级表),其中 必须根据该产品所有评分的平均值计算)。
sql 语句还必须包括根据产品筛选产品的能力 多个“标签”词,例如搜索所有链接的产品 (通过product_tags表到tags表)到构造时指定的词 sql语句。所以,如果我需要检索带有“红色”和“白色”标签的产品, 结果将返回产品 1 和 3 及其各自的平均评分。
下面是示例表的 sql 转储。
DROP TABLE IF EXISTS `product_tags`;
DROP TABLE IF EXISTS `rating`;
DROP TABLE IF EXISTS `tags`;
DROP TABLE IF EXISTS `products`;
CREATE TABLE IF NOT EXISTS `products` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(255) CHARACTER SET latin1 NOT NULL,
`date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `products` (`product_id`, `product_name`, `date_added`) VALUES
(1, 'first item', '2011-05-26 21:56:06'),
(2, 'second item', '2011-05-26 21:56:06'),
(3, 'third item', '2011-05-26 21:56:06');
CREATE TABLE IF NOT EXISTS `product_tags` (
`product_id` int(10) unsigned NOT NULL,
`tag_id` int(10) unsigned NOT NULL,
KEY `product_id` (`product_id`),
KEY `tag_id` (`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `product_tags` (`product_id`, `tag_id`) VALUES
(1, 4),
(1, 1),
(1, 8),
(2, 3),
(2, 9),
(3, 8),
(3, 7),
(1, 6),
(2, 5),
(3, 2),
(3, 10);
CREATE TABLE IF NOT EXISTS `rating` (
`product_id` int(11) NOT NULL,
`rating` float NOT NULL,
KEY `product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `rating` (`product_id`, `rating`) VALUES
(1, 5),
(1, 0),
(2, 3),
(2, 4.5),
(1, 2),
(2, 4);
CREATE TABLE IF NOT EXISTS `tags` (
`tag_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`tag_name` varchar(50) NOT NULL,
PRIMARY KEY (`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=11 ;
INSERT INTO `tags` (`tag_id`, `tag_name`) VALUES
(1, 'red'),
(2, 'green'),
(3, 'yellow'),
(4, 'cyan'),
(5, 'blue'),
(6, 'pink'),
(7, 'purple'),
(8, 'grey'),
(9, 'black'),
(10, 'white');
ALTER TABLE `product_tags`
ADD CONSTRAINT `product_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `product_tags` (`tag_id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `product_tags_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `product_tags` (`product_id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `rating`
ADD CONSTRAINT `rating_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`product_id`) ON DELETE CASCADE ON UPDATE CASCADE;
【问题讨论】:
【参考方案1】:SELECT
p.product_id
, p.product_name
, p.date_added
, ( SELECT AVG(r.rating)
FROM rating r
WHERE r.product_id = p.product_id
)
AS avg_rating
FROM
products p
JOIN
product_tags pt
ON pt.product_id = p.product_id
JOIN
tags t
ON t.tag_id = pt.tag_id
WHERE
t.tag_name IN ('red','white')
GROUP BY
p.product_id
附带说明一下,最好对表名使用单数。 product
、tag
、product_tag
(就像 rating
已经是单数)而不是复数:products
等。
【讨论】:
评分子查询比左连接好吗? 可能是一样的。查询计划将显示。主要区别在于,如果产品有很多标签(比如red
和 white
)和多个评级(比如 3、5、8、12),LEFT JOIN
将计算 (3, 5,8,12,3,5,8,12),即56/8 = 7
,其中子查询将计算 (3,5,8,12) 的平均值,最终结果是相同的:28/4 = 7
。这种方式可能会更快(使用大桌子)。【参考方案2】:
SELECT `products`.`product_id`, `product_name`, `date_added`,
AVG(`rating`) avg_rating,
GROUP_CONCAT(`tags`.`tag_name`) all_tags
FROM `products`
JOIN `product_tags` ON `products`.`product_id` = `product_tags`.`product_id`
JOIN `tags` ON `product_tags`.`tag_id` = `tags`.`tag_id`
LEFT JOIN `rating` ON `products`.`product_id` = `rating`.`product_id`
WHERE `tags`.`tag_name` in (?)
GROUP BY `products`.`product_id`
【讨论】:
paulgroup by product.product_id
真的很重要,因为 product_id 是主键,MySQL 不需要在 group by
子句中列出所有选定的(非聚合)字段。但是(大多数)其他 SQL 服务器会这样做。
返回 #1052 - 字段列表中的列 'product_id' 不明确
唯一需要的LEFT JOIN
是表rating
。由于WHERE tags.tag_name IN ?
条件,其他两个联接可以是INNER JOIN
s。
@Paul:使用SELECT products.product_id, ...
【参考方案3】:
select
p.product_id
, p.product_name
, group_concat(distinct tag_name) as tags
, ifnull(avg(r.rating),'no rating') as avg_rating
from products p
left join rating r on (r.product_id = p.product_id)
inner join product_tags pt on (pt.product_id = p.product_id)
inner join tags t on (t.tag_id = pt.tag_id)
where tag_name in ('red','white')
group by p.product_id
结果:
1, 'first item', 'red', '2.33333333333333'
3, 'third item', 'white', 'no rating'
【讨论】:
结果似乎没有返回带有“白色”标签的项目 3 @Paul,这是因为第 3 项没有评分,已修复查询以包括没有评分的项目。以上是关于如何让 mysql 与标签搜索一起获得平均评分?的主要内容,如果未能解决你的问题,请参考以下文章