【问题标题】:Rails: efficient way to collect data of one model from another with many-to-many relationshipRails:以多对多关系从另一个模型收集数据的有效方法
【发布时间】:2014-05-26 02:26:13
【问题描述】:

我有两个模型,书签和标签。标签是通过acts-as-taggable-on gem 实现的,不过这里主要解释一下。

书签模型包含一个 url 字段。给定一个书签实例@bookmark,@bookmart.tags 返回它的标签。不同的书签可以共享同一个标签(即 Tag 中的 many 部分)。

标签有名字和taggings_count。 taggings_count 字段存储标签被标记到的书签数量。在幕后,有一个标签表,但这并不重要。

现在的问题是,我想检索所有由具有特定 url 值的书签标记的标签,结果应按标记某个标签的书签数量排序(该数量不一样作为 taggings_count 字段,它表示所有书签的标记计数,但需要此处为特定 url 的书签)。怎么做才能让生成的sql高效?

我知道我可以直接用sql写来提高效率,但我也想知道Rails是否可以在不损害太多性能的情况下做同样的事情,这样我就不必在我的Rails应用程序中注入sql代码

以下是表定义,在taggings表中,taggable_id作为Bookmark的外键,tag_id作为Tag的外键:

CREATE TABLE `bookmarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  `description` text,
  `private` tinyint(1) DEFAULT NULL,
  `read` tinyint(1) DEFAULT NULL,
  `list_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `taggings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tag_id` int(11) DEFAULT NULL,
  `taggable_id` int(11) DEFAULT NULL,
  `taggable_type` varchar(255) DEFAULT NULL,
  `tagger_id` int(11) DEFAULT NULL,
  `tagger_type` varchar(255) DEFAULT NULL,
  `context` varchar(128) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `taggings_idx` (`tag_id`,`taggable_id`,`taggable_type`,`context`,`tagger_id`,`tagger_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `taggings_count` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_tags_on_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

【问题讨论】:

  • 如果你关心SQL查询的效率,你应该给我们:3张表的索引,“taggings”表的列。
  • 另外,您使用的是哪个 Rails 版本?哪个数据库?
  • @tompave 检查我的表定义更新。我正在使用 Rails 4.1.0,它将部署到带有 mysql 的服务器
  • 你在使用 Rails 的活动记录吗?或另一个数据库接口。看来您只需要您关心的书签的 id。 bookmark.where(url:your url).pluck(:id)[0] 如果只有一个。然后只需执行一个 tabable.where(id:) 并获取标签网址。
  • @bobbdelsol 是的,我正在使用活动记录。并且可能有多个具有相同 url 的书签。

标签: ruby-on-rails ruby acts-as-taggable-on


【解决方案1】:

这是一个解决方案:

bookmark_ids  = Bookmark.where(url: "http://foobar.com").pluck(:id)
taggings      = Tagging.where(taggable_id: bookmark_ids).where(taggable_type: "Bookmark")
tag_ids       = taggings.pluck(:tag_id).uniq
tags          = Tag.where(id: tag_ids).order(taggings_count: :desc)

理论上可以使用 ActiveRecord 中的 joins() 方法编写,但我不知道您使用的 gem 是如何定义关联的。
这可能有效,也可能无效:

Tag.order(taggings_count: :desc)
   .joins(taggings: :bookmark)
   .where(bookmark: { url: "http://foobar.com" })

您也可以编写原始 SQL,但在 Rails 中感觉很脏。

【讨论】:

  • 谢谢你, tompave。您在我的问题中遗漏了一件事,即结果应该按此设计中的 taggings_count 字段以外的其他内容排序。但是您的回答促使我了解更多关于acts-as-taggable-on 的信息。我找到了this 并认为也许我可以重新设计它并将网址作为上下文。然后我可以使用 Bookmark.tag_counts_on('certain url') 来获取 url 的标签。在新设计中,可以使用 taggings_count。
  • 我明白了。对不起,我错过了。如果关联到位,可以查看方法includes and references
  • 我将您的第一个解决方案与我发布的解决方案进行了比较,生成的 sql 结果是相似的,gem 更紧凑,在 ruby​​ 和 sql 中。我编辑了您的代码以包含上下文作为条件,并将第二条和第三条语句组合在一起,不确定将它们写成两条是否会导致两条语句而不是一条被执行。顺便说一句,连接无法工作,我不知道是否有一种方法可以配置有关如何连接表的rails。
  • 好的,但您永远不应该以这种方式编辑其他人的答案。即使答案不正确,您也应该更正它。我的答案失去了意义,我不会以这种方式编写代码:我对库的了解不足,无法像您所做的那样引用命名空间,而且我不会链接方法以这样的方式打电话。 问题和答案集模型的目的是您应该选择正确的答案,或者自己提供一个。错误的答案可以被忽略或投反对票。
  • 我恢复了更改。如果您对我的回答不满意,您可以投反对票并选择另一个作为获胜者。
【解决方案2】:

事实证明很简单,acts-as-taggable-on 实际上可以对关联进行操作。文档有那个样本,我只是没有注意到:

Bookmark.where(url: url).tag_counts_on(:tags).sort_by(&:taggings_count).reverse.map(&:name)

唯一的问题是,无论它们在何种环境中流行,结果仍然是按这些标签的流行度排序的。不过,这并不重要。关键是这个rails语句只生成一个sql:

SELECT tags.*, taggings.tags_count AS count FROM "tags" JOIN (SELECT taggings.tag_id, COUNT(taggings.tag_id) AS tags_count FROM "taggings" INNER JOIN bookmarks ON bookmarks.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Bookmark' AND taggings.context = 'tags') AND (taggings.taggable_id IN(SELECT bookmarks.id FROM "bookmarks"  WHERE "bookmarks"."url" = 'http://kindleren.com/forum.php?gid=49')) GROUP BY taggings.tag_id HAVING COUNT(taggings.tag_id) > 0) AS taggings ON taggings.tag_id = tags.id

【讨论】:

    猜你喜欢
    • 2021-09-05
    • 2019-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 2014-05-06
    相关资源
    最近更新 更多