Mysql 多对多单查询优化

Mysql 多对多单查询优化,mysql,sql,database,Mysql,Sql,Database,我有3个多对多关系的表 CREATE TABLE news(id int, content varchar(64)); CREATE TABLE tags(id int, name varchar(64)); CREATE TABLE news_tags(id int, tag_id int, news_id int); INSERT INTO news VALUES (1, "Hello, world!"), (2, "Test news"), (3

我有3个多对多关系的表

 CREATE TABLE news(id int, content varchar(64));
CREATE TABLE tags(id int, name varchar(64));
CREATE TABLE news_tags(id int, tag_id int, news_id int);

INSERT INTO news VALUES
(1, "Hello, world!"),
(2, "Test news"),
(3, "test news 2"),
(4, "test news 3"),
(5, "test news 4");

INSERT INTO tags VALUES
(1, "general tag"),
(2, "sub tag 1"),
(3, "sub tag 2"),
(4, "normal tag");

INSERT INTO news_tags VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1);


INSERT INTO news_tags VALUES
(4, 1, 2),
(5, 2, 2),

(6, 1, 3),
(7, 4, 3),

(7, 2, 4),
(8, 3, 4),

(9, 1, 5);
我想选择新闻\u id什么

  • 在关系上,只有general(示例中的id 1)标记,没有任何其他子标记(在exmpl id 3上)

  • 有一对标签通用+子标签(id 2)

  • 我创建一个查询

    SELECT news_id FROM news_tags WHERE tag_id = 1 OR tag_id = 2 
        GROUP BY news_id 
        HAVING COUNT(news_id) = 2
    UNION
    SELECT news_id FROM news_tags WHERE tag_id = 1 AND news_id not in (SELECT news_id FROM news_tags WHERE tag_id in (2,3));
    
    但是有两个问题

  • 我认为这不是优化方式(有2个选择与联合+子选择查询)

  • 如果我搜索多个一对子标签,我需要添加新的选择与联合

  • 如何优化此查询


    实例

    您的问题不清楚,因为“子”标记和“一般”标记的概念没有定义

    但是,如果您想同时处理多个条件,您仍然可以使用一个
    groupby
    HAVING
    子句

    例如,如果您想要满足以下任一条件的
    news\u id
    s:

    • tag\u id
      =1
    • 或者
      tag\u id
      =2和
      tag\u id
      =3
    然后您可以使用:

    SELECT nt.news_id
    FROM news_tags nt
    GROUP BY nt.news_id 
    HAVING (COUNT(*) = 1 AND MIN(nt.tag_id) = 1) OR
           SUM( nt.tag_id IN (2, 3) ) = 2;
    

    您可以很容易地将这一想法扩展到标签的描述中(但是您需要加入
    标签
    表中进行说明。

    您的问题不清楚,因为“子”标签和“一般”标签的概念没有定义

    但是,如果您想同时处理多个条件,您仍然可以使用一个
    groupby
    HAVING
    子句

    例如,如果您想要满足以下任一条件的
    news\u id
    s:

    • tag\u id
      =1
    • 或者
      tag\u id
      =2和
      tag\u id
      =3
    然后您可以使用:

    SELECT nt.news_id
    FROM news_tags nt
    GROUP BY nt.news_id 
    HAVING (COUNT(*) = 1 AND MIN(nt.tag_id) = 1) OR
           SUM( nt.tag_id IN (2, 3) ) = 2;
    

    你可以很容易地将这个想法扩展到标签的描述中(但是你需要加入
    标签
    表来实现这一点。

    我建议根据关于阻止标签的额外注释,重新设计标签

    为新闻项分配标记是好的,但是您的表应该看起来像
    news\u标记(news\u id,tag\u id)
    ,主键位于
    news\u id
    tag\u id
    字段上方

    如果要使标记阻塞,一种方法是添加另一个多对多关系,称为
    news\u blocking\u标记(news\u id,tag\u id)
    。或者您可以定义
    news\u标记(news\u id,tag\u id,is\u blocking)
    ,这样您就知道哪些标记正在阻塞,哪些只是标记


    优化从设计数据库开始。我们这里只能给出一般性的指示。很好,你知道结果需要是什么,这已经是设计的一半了!

    我建议根据关于阻塞标记的额外评论,重新设计

    为新闻项分配标记是好的,但是您的表应该看起来像
    news\u标记(news\u id,tag\u id)
    ,主键位于
    news\u id
    tag\u id
    字段上方

    如果要使标记阻塞,一种方法是添加另一个多对多关系,称为
    news\u blocking\u标记(news\u id,tag\u id)
    。或者您可以定义
    news\u标记(news\u id,tag\u id,is\u blocking)
    ,这样您就知道哪些标记正在阻塞,哪些只是标记


    优化从设计数据库开始。我们这里只能给出一般性的指针。很好,您知道结果需要是什么,这已经是设计的一半了!

    看起来我找到了解决方案,没有对架构表进行任何更改

    SELECT news_id, tag_id
    FROM news_tags
    WHERE tag_id in (1,2,3) 
    GROUP BY news_id 
    HAVING (COUNT(news_id) = 1 AND tag_id = 1) OR (count(news_id) = 2 and tag_id in (1,2));
    
    首先,我们找到所有带有阻止标记的新闻,而不是带有过滤结果的新闻

    (COUNT(news_id) = 1 AND tag_id = 1)
    
    查找所有国家/地区仅带有“阻止”标记的所有记录(其部分可用于多对多查找所有记录仅使用单个标记的内容)

    查找成对的“阻止”标记和国家/地区标记

    如果我们需要更多的国家,我们需要添加或

    (count(news_id) = 2 and tag_id in (1,3))
    

    我需要做一些测试,但看起来它的工作很好,而且我的第一个查询更像是在没有对模式表进行更改的情况下找到了解决方案

    SELECT news_id, tag_id
    FROM news_tags
    WHERE tag_id in (1,2,3) 
    GROUP BY news_id 
    HAVING (COUNT(news_id) = 1 AND tag_id = 1) OR (count(news_id) = 2 and tag_id in (1,2));
    
    首先,我们找到所有带有阻止标记的新闻,而不是带有过滤结果的新闻

    (COUNT(news_id) = 1 AND tag_id = 1)
    
    查找所有国家/地区仅带有“阻止”标记的所有记录(其部分可用于多对多查找所有记录仅使用单个标记的内容)

    查找成对的“阻止”标记和国家/地区标记

    如果我们需要更多的国家,我们需要添加或

    (count(news_id) = 2 and tag_id in (1,3))
    

    我需要做一些测试,但看起来效果不错,而且比我的第一个查询更好。我已经做了很多次标记。我建议不要使用多对多映射表到标记表。而是将它们组合到

     CREATE TABLE tags (
         news_id MEDIUMINT UNSIGNED NOT NULL,  -- Assuming you don't need more than 16M
         tag VARCHAR(...) NOT NULL,
         PRIMARY KEY(news_id, tag),  -- For going one way
         INDEX(tag, news_id)         -- For going the other way
     ) ENGINE=InnoDB;    -- Important due to the way indexes are handled
    
    添加auto_inc是对空间和速度的浪费


    是的,有些查询非常复杂。但是任何技术都会导致混乱的代码;我相信这个模式是最好的。

    我已经做了很多次标记。我建议不要使用多对多映射表到标记表。而是将它们合并到

     CREATE TABLE tags (
         news_id MEDIUMINT UNSIGNED NOT NULL,  -- Assuming you don't need more than 16M
         tag VARCHAR(...) NOT NULL,
         PRIMARY KEY(news_id, tag),  -- For going one way
         INDEX(tag, news_id)         -- For going the other way
     ) ENGINE=InnoDB;    -- Important due to the way indexes are handled
    
    添加auto_inc是对空间和速度的浪费


    是的,有些查询非常复杂。但是任何技术都会导致混乱的代码;我相信这个模式是最好的。

    你能根据你的小提琴给出你的预期结果吗?@18Man on fiddle是正确的结果“子标签”的概念不清楚,从你显示的预期结果来看,很难理解它。没有它就无法优化了解您想要实现的目标。此外,您的news_标记必须使用“复合键”(而不是额外的id字段),并且您的所有表必须至少有主键(也称为索引)。@geertjanvdk例如,您有一些带有一些标记的新闻,现在您需要为某些国家阻止一些新闻,快速方法是添加一些标记,如标记“block”它的meen to block news for search for all country或add tags“block”“usa”它的meen to block news only forms usa country,not for the other country它的解释很清楚?@geertjanvdk所以当你按国家搜索新闻时,你需要获得新闻ID而不是(ID)ID中的新闻-包含新闻ID接受2个条件1)新闻标签表中的新闻“阻止”标签和没有其他国家标签(阻止所有国家)2)新闻标签表上的新闻有“阻止”和“国家标签”(阻止搜索计数