如何让 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

附带说明一下,最好对表名使用单数。 producttagproduct_tag(就像 rating 已经是单数)而不是复数:products 等。

【讨论】:

评分子查询比左连接好吗? 可能是一样的。查询计划将显示。主要区别在于,如果产品有很多标签(比如 redwhite)和多个评级(比如 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`

【讨论】:

paul group by product.product_id 真的很重要,因为 product_id 是主键,MySQL 不需要在 group by 子句中列出所有选定的(非聚合)字段。但是(大多数)其他 SQL 服务器会这样做。 返回 #1052 - 字段列表中的列 'product_id' 不明确 唯一需要的LEFT JOIN 是表rating。由于WHERE tags.tag_name IN ? 条件,其他两个联接可以是INNER JOINs。 @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 与标签搜索一起获得平均评分?的主要内容,如果未能解决你的问题,请参考以下文章

在 MySQL 中达到平均水平

是否可以将网格搜索与外部定义的评分功能一起使用?

我想获得 pyspark 中平均评分最高的语言

如何让 dataTable 与 PHP 一起工作

如何使用相同的 Google Adwords API 服务获得平均搜索量和平均 CPC?

通过文本匹配和到点的距离对文档进行评分