【问题标题】:SQL - Returning Each Record Only OnceSQL - 每条记录只返回一次
【发布时间】:2011-01-01 05:34:54
【问题描述】:

这是我的桌子:

tblBusiness
BusinessID, BusinessName

tblTags
TagID, Tag

tblBusinessTagLink
BusinessID, TagID

任何企业都可以应用多个标签。现在假设用户正在过滤,以便他们只找到标记为“办公用品”和“技术”的企业

我应该使用什么 SQL 语句?有没有比我在这里展示的更适合我的桌子的设计?

【问题讨论】:

    标签: sql tags


    【解决方案1】:
    SELECT
      b.BusinessId,
      b.BusinessName 
    FROM
      tblBusiness AS b
      INNER JOIN tblBusinessTagLink AS l ON l.BusinessId = b.BusinessId
      INNER JOIN tblTags            AS t ON t.TagId      = l.TagId
    WHERE
      t.TagName IN ('Technology', 'Office Supplies')
    GROUP BY
      b.BusinessId,
      b.BusinessName 
    

    这会选择属于任一类别的所有企业。要仅选择这两个类别中的那些,您可以附加一个

    HAVING COUNT(*) = 2
    

    您使用的方法(三个表来表示 m:n 关系)是解决此任务的标准方法,您可以保留它。

    就个人而言,我不会对表名使用“匈牙利表示法”(即没有“tbl”),也不会使用复数表名(即不是“Tags”),尤其是当其他表也不是复数时。


    回答下面的第一条评论:

    对于较大的数据集,此查询的性能依赖于索引。自然,所有主键都需要一个索引。在tblBusinessTagLink 中,您应该有一个涵盖这两个字段的复合索引和一个附加索引,用于在复合索引中没有出现在第一位的字段。

    WHERE keywords LIKE '%technology%' 的想法很糟糕,主要是因为对于除起始字段搜索之外的任何 LIKE 条件,都无法使用索引(即,随着数据集的增长,性能会迅速下降),部分原因是它应该是以WHERE ','+keywords+',' LIKE '%,technology,%' 开头,否则您将得到部分匹配/误报。

    另外,通过TagId 查询可能会更高效一些。这样您就可以完全从 JOIN 中删除一个表:

    FROM 
      tblBusiness AS b 
      INNER JOIN tblBusinessTagLink AS l ON l.BusinessId = b.BusinessId 
    WHERE 
      l.TagId IN (1, 2)
    

    但是,如果您打算通过TagName 查询,则该字段上的索引也是绝对必要的。

    【讨论】:

    • 感谢有关匈牙利符号和复数名称的注释。我仍在尝试撤消在构建 Access 数据库时学到的所有东西。这个查询的性能会随着业务条目数量的增加而下降吗?或者随着所选标签数量的增加?或两者?我计划让用户可以选择应用了多达五个或六个标签的记录。有时我想知道将标签存储在关键字字段中是否会更快,这样我就可以从 tblBusiness WHERE 关键字中选择 b.BusinessID、b.BusinessName,例如 '%technology%' 和...
    • 在我的回答中查看其他 cmets。
    【解决方案2】:

    你可以使用简单的JOIN来获取记录

    SELECT t.Tag, b.BusinessName 
    FROM tblBusiness b, tblTags t, tblBusinessTagLink l
    WHERE t.TagID = l.TagID 
    AND l.BusinessID = b.BusinessID 
    AND t.Tag = 'Office Supplies'
    

    【讨论】:

    • 我不明白一些事情。 “t”和“b”是你的表名吗? join语句在哪里或者这个sql语句不需要join?此外,您没有指定标签“技术”。
    • 我对其进行了修改,如果选择了两个类别并且我在查询中没有得到任何结果,它会尝试返回正确的记录。 SELECT t.Tag, b.BusinessName FROM tblBusiness AS b, tblTags AS t, tblBusinessTagLink AS l WHERE (t.Tag = 'Office Supplies' AND t.TagID = [l].[TagID] AND l.BusinessID = [b] .[BusinessID]) AND (t.Tag = 'Technology' AND t.TagID = [l].[TagID] AND l.BusinessID = [b].[BusinessID]);
    • 为什么不使用 ANSI 连接语法?
    • -1 用于隐式连接结构。它有效,但它是一种可怕的编码方式。
    【解决方案3】:

    您可以使用INTERSECT set 操作来合并 2 个查询(一个用于“办公用品”,一个用于“技术”)。

    但是,如果您使用的是 MySQL(不支持 INTERSECT),则可以使用带有 'HAVING COUNT(*) = 2' like this 的 UNION ALL。


    编辑:

    您也可以像这样使用第二个选项而不使用 UNION ALL:

    select Name from tblBusiness
    left join tblBusinessTagLink on tblBusinessTagLink.BusinessID = tblBusiness.ID
    left join tblTags on tblTags.TagID = tblBusinessTagLink.TagID
    where Tag = 'Office Supplies' or Tag = 'Technology'
    group by name
    having count(Name) = 2;
    

    【讨论】:

    • 我想弄清楚您的解决方案是否与 Tomalak 的相同。如果用户想使用“AND”而不是“OR”一次应用 3 或 4 个标签,你会怎么做?
    • 我相信 Tomalak 的解决方案和我的解决方案是相同的(但是我强烈建议在他的 'where x in (y, z)' 和我的 'where x = y or x = z' 之间进行基准测试)。您还应该注意,如果一个企业有两次相同的标签,则两者都不会正常工作。
    • 感谢您的评论。我想我会采取措施防止用户两次输入相同的标签。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2016-05-22
    • 2018-09-13
    • 1970-01-01
    • 2023-01-15
    相关资源
    最近更新 更多