【问题标题】:Need a MySQL JOIN to return all categories a product is in, per product, even if only one category is searched for需要一个 MySQL JOIN 来返回产品所在的所有类别,每个产品,即使只搜索一个类别
【发布时间】:2023-04-06 01:26:01
【问题描述】:

我定义了以下表格:

CREATE TABLE products (
    product_id INTEGER(11) NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    section VARCHAR(255) DEFAULT NULL,
    PRIMARY KEY (product_id)
) ENGINE=MyISAM;

CREATE TABLE categories (
    category_id INTEGER(11) NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    PRIMARY KEY (category_id)
) ENGINE=MyISAM;

CREATE TABLE product_categories (
    product_id INTEGER(11) NOT NULL,
    category_id INTEGER(11) NOT NULL,
    PRIMARY KEY (product_id, category_id)
) ENGINE=MyISAM;

实际上还有更多,这是优化更大、更复杂的查询的一部分。其中一部分是将一些缓慢的子查询移动到视图中,到目前为止这已经很有帮助。

当我添加 categories/product_categories 表并在允许用户按 products.section 或 categories.category_id 搜索时加入它们时,查询变得非常慢。 UI 将这些作为搜索参数传入,我试图为每个产品获取一行,其中包含其 id、名称、部分以及与之关联的类别名称的逗号分隔列表。通过以下视图和查询,我能够加快速度:

CREATE OR REPLACE
ALGORITHM = MERGE
VIEW view_products_with_categories
AS
SELECT 
    p.product_id,
    p.name,
    p.section,
    pc.name AS category
products p
LEFT JOIN product_categories pc on p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.category_id;

SELECT
    product_id,
    name,
    section,
    GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
GROUP BY product_id;

假设我们有以下行:

product_id  name               section          category_id category
332913      Model Train Engine child-and-baby   1160        child-and-baby>baby-and-pre-schooltoys>playsets
332913      Model Train Engine child-and-baby   1308        toys>baby-and-preschool>playsets
332913      Model Train Engine child-and-baby   1312        toys>carstrains-and-planes>cars-and-vehicles

上面的简单查询给了我以下信息:

product_id  name                section         categories
332913      Model Train Engine  child-and-baby  child-and-baby>baby-and-pre-schooltoys>playsets,toys>baby-and-preschool>playsets,toys>carstrains-and-planes>cars-and-vehicles

这很好,正如预期的那样。但是,我希望用户能够按 category_id 进行搜索。目前,我们的 UI 对类别名称进行了一些自动完成魔术,并为动态生成的 SQL 添加了一个过滤器,其中包含 category_id。如果我在 GROUP_CONCAT 查询中留下 category_id,它将是 1160。假设他们要搜索第二个 (1308),因此我们将查询修改如下:

SELECT
    product_id,
    name,
    section,
    GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
WHERE category_id = 1308
GROUP BY product_id;

现在我们得到以下信息:

product_id  name                section         categories
332913      Model Train Engine  child-and-baby  toys>baby-and-preschool>playsets

再次,正是您所期望的。但是,客户希望查看与具有他们正在搜索的一个或多个类别的产品相关联的所有类别。因此,让我们制作一些简化的示例数据来向您展示我在寻找什么:

product_id  name       section     category_id  category
1           product_1  section_1   1            category_1
1           product_1  section_1   2            category_2 
1           product_1  section_1   3            category_3
2           product_2  section_2   3            category_3
2           product_2  section_2   4            category_4 
2           product_2  section_2   5            category_5

如果用户搜索 category_id = 3,我希望他们得到以下信息:

product_id  name      section   categories
1           product_1 section_1 category_1, category_2, category_3 
2           product_2 section_2 category_3, category_4, category_5 

但我目前只得到:

product_id  name      section   categories
1           product_1 section_1 category_3 
2           product_2 section_2 category_3

我只是想不出没有子查询的方法,它的缓慢是我首先转向视图的原因。我希望我缺少一些非常明显的东西,可能是由于睡眠不足,所以任何帮助都将不胜感激。

更新:我还应该提到,产品可能不属于任何类别,因此我的代码中存在 LEFT JOIN。

【问题讨论】:

  • 解决性能问题的正确方法是分析执行计划,如果你在查询前加上EXPLAIN 关键字就可以得到。您的子查询可能很慢,仅仅是因为product_category 表中的category_idproducts 表中的section 缺少索引。另外,您使用 MyISAM 引擎而不是可靠的事务性 InnoDB 是否有任何特殊原因?
  • 获取关于左连接的要点,但是,在您在此处陈述的问题中,您说“客户希望查看与具有他们正在搜索的一个或多个类别的产品相关联的所有类别为了”。这意味着您可以使用内部联接,不是吗? (因此在下面的解决方案中使用了内连接。
  • @piotrm:确实可能是由于这两个字段上缺少索引。使用 MyISAM 是因为最初设计该系统的顾问坚持要设置它。除了每晚的 ETL/聚合作业外,它是只读的,因此表级锁并不是真正的问题,并且 FK 完整性由 ETL 过程处理。他在其他一些事情上错了,所以我切换到 InnoDB 或者最好是 PostgreSQL 没有问题。
  • @danb:是的,如果他们正在搜索特定类别。他们可能只是在搜索特定的产品、部分或类别。或者全部 3 个,或者没有。我正在玩一个基于你的查询,它将我的视图与 product_id 上的 product_categories 结合起来,基本上有一个行的笛卡尔积,然后可以按 product_id、category_id 分组。希望今天下午有另一个更新。
  • @ChrisDoggett 在下面查看我的替代方案。它适用于您描述的情况并仍然使用该视图。

标签: mysql join sql-view sqlperformance


【解决方案1】:

以下查询有效:(虽然我没有使用视图)

select pc1.product_id, products.name, products.section,  
  group_concat(categories.name)
from products
inner join product_categories pc1 
on (pc1.product_id =    products.product_id and pc1.category_id =  3)
inner join product_categories pc2 
on (pc2.product_id = products.product_id)
inner join categories  
on (categories.category_id = pc2.category_id)
group by pc1.product_id, products.name, products.section

如果您想使用该视图,以下将起作用:

SELECT  vpc.product_id,vpc.name,vpc.section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories vpc
inner join product_categories pc 
on (pc.product_id = vpc.product_id and pc.category_id=3) 
GROUP BY vpc.product_id, vpc.name, vpc.section;

【讨论】:

  • 谢谢,桥表的第二个版本的内部连接起到了作用,我现在可以搜索所有 3 个字段。我会用有效的方法更新我的答案。有一个小技巧,比如确保搜索过滤器使用 pc1,而不是 pc2,如果将 category_id 搜索移动到 WHERE 子句而不是 JOIN,就像我需要的那样。除此之外,工作得很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-12
  • 2017-09-27
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
  • 1970-01-01
  • 2022-12-12
相关资源
最近更新 更多