2011-05-26 49 views
0

我想让我的脑袋围绕一个复杂的mysql语句(对我来说很复杂!)。 基本上,我需要返回产品表 中的所有产品的清单,并附带额外的返回值(其各自的星级评定表(评级表),其中 必须按该产品的所有评级的总和计算) 。我怎样才能让mysql在标签搜索中获得平均评分?

SQL语句还必须包括过滤基于 多个“标签”字,例如搜索被链接 (通过product_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; 

回答

3
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

+0

是比左连接更好的评分子查询吗? – manji 2011-05-26 22:20:15

+0

也许是一样的。查询计划将显示。主要区别在于,如果存在具有多个标签(例如,红色和白色)和多个评级(例如3,5,8,12)的产品,则“左连接”将计算(3 ,5,8,12,3,5,8,12),它是'56/8 = 7',子查询将计算(3,5,8,12)的平均值, :'28/4 = 7'。这种方式可能会更快(大表格)。 – 2011-05-26 22:28:31

1
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` 
+0

保罗'组由product.product_id'真的是所有的计算,因为PRODUCT_ID是主键和MySQL并不需要在'group by'子句中列出所有选定的(非聚合)字段。 (大多数)其他SQL服务器。 – Johan 2011-05-26 22:07:41

+0

它返回#1052 - 字段列表中的列'product_id'不明确 – Paul 2011-05-26 22:10:04

+0

唯一需要的'LEFT JOIN'与表'rating'一起使用。由于'WHERE tags.tag_name IN?'条件,其他两个连接可以是'INNER JOIN'。 – 2011-05-26 22:13:04

1
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' 
+0

结果似乎没有返回项目3,它具有'白色'标签 – Paul 2011-05-26 22:08:48

+0

@Paul,这是因为项目3没有评分,修复了查询还包括没有评级的项目。 – Johan 2011-05-26 22:19:55