【问题标题】:Returning row where all related WHEREs match返回所有相关 WHERE 匹配的行
【发布时间】:2016-01-24 03:39:16
【问题描述】:

我需要返回一个与多对多关系上的所有 WHERE 语句匹配的结果。解释:

我有一个这样的表结构。

CREATE TABLE `product` (
  `product_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`product_id`));

CREATE TABLE `product_has_tag` (
  `product_id` INT UNSIGNED NOT NULL,
  `tag_id` INT UNSIGNED NOT NULL
);

CREATE TABLE `tag` (
  `tag_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `value` VARCHAR(30) NULL,
  PRIMARY KEY (`tag_id`));

现在我遇到这样的情况:

Product: {
  [product_id:1], 
  [product_id:2], 
  [product_id:3]};
Tags: {
  [tag_id:1, value:'XX'],
  [tag_id:2, value:'YY']}
ProductHasTag: {
  [product_id:1, tag_id:1],
  [product_id:1, tag_id:2],
  [product_id:2, tag_id:1],
  [product_id:3, tag_id:2]}

基本上,有一种产品同时有标签,其余的只有一种。现在我需要一个 SQL 搜索条件,它只返回具有两个标签关联的产品。像

SELECT * FROM `product` 
LEFT JOIN product_has_tag USING(product_id) 
LEFT JOIN tag USING(tag_id) 
WHERE value LIKE '%X%' AND value LIKE '%Y%'

这显然不起作用,因为搜索的值不在一个标签中,而是在两个标签中。 任何帮助表示赞赏。

【问题讨论】:

  • 也许我不明白这个问题,但你不能在你的WHERE 中使用OR 吗?
  • @cricket_007 感谢您查看我的问题!不幸的是,使用 OR 将返回所有产品,我只需要 id 为 1 的产品

标签: mysql select join many-to-many where


【解决方案1】:

您只需将WHERE 更改为HAVING 并使用SUM CASE WHEN

SELECT `product`.`product_id`    -- *
FROM `product` 
LEFT JOIN product_has_tag 
  USING(product_id) 
LEFT JOIN tag 
   USING(tag_id) 
GROUP BY `product`.`product_id`
HAVING SUM(CASE WHEN `value` LIKE '%X%' THEN 1 END) > 0
   AND SUM(CASE WHEN `value` LIKE '%Y%' THEN 1 END) > 0

SqlFiddleDemo

【讨论】:

  • 看起来很棒而且很简单,并且可以按预期工作......您是否有机会详细说明上述性能?这样的事情成本很高吗?
  • @RandomWhiteTrash 性能主要取决于您拥有的表结构和索引。需要注意的是,在任何解决方案中,您将使用 value LIKE '%X%' is not SARGable 并且查询优化器不会在 value 列上使用索引。
【解决方案2】:

您需要像处理两个不同的列一样处理这两个不同的标签。这需要一些花哨的加入工作。

此查询提供符合您的第一个条件的产品。

SELECT p.*, t1.value tag1
  FROM product p
  JOIN product_has_tag pt1 ON p.product_id =  pt1.product_id
  JOIN tag t1 ON pt1.tag_id = t1.tag_id AND t1.value LIKE '%X%'

你可以在这个查询中添加一些东西来获得你想要的两个标签。

SELECT p.*, t1.value tag1, t2.value tag2
  FROM product p
  JOIN product_has_tag pt1 ON p.product_id =  pt1.product_id
  JOIN tag t1 ON pt1.tag_id = t1.tag_id AND t1.value LIKE '%X%'
  JOIN product_has_tag pt2 ON p.product_id =  pt2.product_id
  JOIN tag t2 ON pt2.tag_id = t2.tag_id AND t2.value LIKE '%Y%'

JOIN 项目将导致此查询将您的产品行与两个匹配的标签隔离开来。

您在此查询中的value LIKE '%match%' 模式将导致性能不佳。如果您可以使用value LIKE 'match%'value = 'match',性能将能够利用value 列上的索引。

【讨论】:

  • 感谢奥利的回答!我可能有 10 个标签和 10 个搜索词条,因此多个加入不希望邀请。 +1 将我的注意力转向“%match%”的低效率!
  • 不用担心多重连接级联。如果你能克服LIKE 的低效率,这一切仍然会表现得相当不错。
【解决方案3】:

可以对匹配的标签进行求和,只返回2个匹配的结果。

SELECT * FROM product
LEFT JOIN product_has_tag USING(product_id) 
LEFT JOIN tag USING(tag_id) 
WHERE value LIKE '%X%' OR value LIKE '%Y%' 
GROUP BY product_id  HAVING COUNT(DISTINCT tag_id) = 2

【讨论】:

  • 感谢您的回答!看起来简单而诱人,但我的结构/查询可能更复杂,因为其他连接(如翻译)可能每行返回一个以上的命中。因此,除非我误解了上述内容,否则即使只有一个 OR 命中,我也可能意外得到 SUM(*)=2……我正确吗?
  • @RandomWhiteTrash 是的,你是对的,请参阅sqlfiddle.com/#!9/5fa22/2/0 顺便说一下它必须是SUM(1) or COUNT(*)
  • @lad2025 addet DISTINCT 每个产品只计算一次标签
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-22
相关资源
最近更新 更多