sql'替换在Rails 4.2中

我有一个需要一些连接/自定义查询的关联。 当试图找出如何实现这个重复的响应是finder_sql 。 然而在Rails 4.2(及以上)中:

ArgumentError:未知的键::finder_sql

我的查询来完成连接看起来像这样:

'SELECT DISTINCT "tags".*' 
' FROM "tags"' 
' JOIN "articles_tags" ON "articles_tags"."tag_id" = "tags"."id"' 
' JOIN "articles" ON "article_tags"."article_id" = "articles"."id"' 
' WHERE articles"."user_id" = #{id}'

我明白这可以通过以下方式实现:

has_many :tags, through: :articles

但是,如果联接的基数很大(即用户拥有数千篇文章 - 但系统只有一些标签),则需要加载所有文章/标签:

SELECT * FROM articles WHERE user_id IN (1,2,...)
SELECT * FROM article_tags WHERE article_id IN (1,2,3...) -- a lot
SELECT * FROM tags WHERE id IN (1,2,3) -- a few

当然也对一般情况感到好奇。

注意:也尝试使用proc语法,但似乎无法弄清楚:

has_many :tags, -> (user) {
  select('DISTINCT "tags".*')
    .joins('JOIN "articles_tags" ON "articles_tags"."tag_id" = "tags"."id"')
    .joins('JOIN "articles" ON "article_tags"."article_id" = "articles"."id"')
    .where('"articles"."user_id" = ?', user.id)
}, class_name: "Tag"

ActiveRecord :: StatementInvalid:PG :: UndefinedColumn:错误:列tags.user_id不存在

SELECT DISTINCT“tags”。* FROM“tags”JOIN“articles_tags”ON“articles_tags”。“tag_id”=“tags”。“id”JOIN“articles”ON“article_tags”。“article_id”=“articles”。“id “WHERE”标签“。”“user_id”= $ 1 AND(“articles”。“user_id”= 1)

这看起来好像是在尝试将user_id自动注入标签(并且该列只存在于文章中)。 注意:我为多个用户预加载,所以无法使用user.tags而没有其他修复(粘贴的SQL就是我所看到的那样!)。 思考?


虽然这并没有直接解决你的问题 - 如果你只需要你的数据的一个子集,你可以通过一个子查询预加载它:

users = User.select('"users".*"').select('COALESCE((SELECT ARRAY_AGG(DISTINCT "tags"."name") ... WHERE "articles"."user_id" = "users"."id"), '{}') AS tag_names')
users.each do |user|
  puts user[:tag_names].join(' ')
end

以上是针对Postgres的数据库(由于ARRAY_AGG ),但其他数据库可能存在同等的解决方案。

另一种选择可能是将视图设置为假连接表(需要数据库支持):

CREATE OR REPLACE VIEW tags_users AS (
  SELECT 
    "users"."id" AS "user_id", 
    "tags"."id" AS "tag_id"
  FROM "users"
    JOIN "articles" ON "users"."id" = "articles"."user_id"
    JOIN "articles_tags" ON "articles"."id" = "articles_tags"."article_id"
    JOIN "tags" ON "articles_tags"."tag_id" = "tags"."id"
  GROUP BY "user_id", "tag_id"
)

然后,您可以使用has_and_belongs_to_many :tags (未测试 - 可能希望设置为readonly并且可以删除某些连接并在使用适当的外键约束设置时使用)。


所以我的猜测是,当你尝试访问@user.tags时,你会得到错误,因为你在user.rb有该关联。

所以我认为会发生什么是当我们尝试访问@user.tags ,我们试图获取用户的tags ,并且导轨会搜索其user_id与当前提供的用户标识匹配的Tags 。 由于Rails默认将关联名称作为modelname_id格式,即使您没有user_id它也会尝试在该列中搜索,并且无论您希望如何,都会搜索(或添加WHERE "tags"."user_id" )或不是因为最终目标是找到属于当前用户的tags

当然,我的回答可能无法100%解释。 随意评论你的想法或如果你发现任何错误,让我知道。


简答

好吧,如果我理解正确,我认为我有解决方案,它只是使用核心ActiveRecord实用程序,并不使用finder_sql。

可能会使用:

user.tags.all.distinct

或者,在用户模型中将has_many标签更改为

has_many :tags, -> {distinct}, through: :articles

您可以在用户中创建一个帮助器方法来检索它:

def distinct_tags
  self.tags.all.distinct
end

证据

从你的问题我相信你有以下情况:

  • 用户可以有很多文章。
  • 一篇文章属于单个用户。
  • 标签可以属于许多文章。
  • 文章可以有很多标签。
  • 您想要检索用户与其创建的文章关联的所有不同标签。
  • 考虑到这一点,我创建了以下迁移:

    class CreateUsers < ActiveRecord::Migration
      def change
        create_table :users do |t|
          t.string :name, limit: 255
    
          t.timestamps null: false
        end
      end
    end
    
    class CreateArticles < ActiveRecord::Migration
      def change
        create_table :articles do |t|
          t.string :name, limit: 255
          t.references :user, index: true, null: false
    
          t.timestamps null: false
        end
    
        add_foreign_key :articles, :users
      end
    end
    
    class CreateTags < ActiveRecord::Migration
      def change
        create_table :tags do |t|
          t.string :name, limit: 255
    
          t.timestamps null: false
        end
      end
    end
    
    class CreateArticlesTagsJoinTable < ActiveRecord::Migration
      def change
        create_table :articles_tags do |t|
          t.references  :article, index: true, null:false
          t.references  :tag, index: true, null: false
        end
    
        add_index :articles_tags, [:tag_id, :article_id], unique: true
        add_foreign_key :articles_tags, :articles
        add_foreign_key :articles_tags, :tags
      end
    end
    

    和模型:

    class User < ActiveRecord::Base
      has_many :articles
      has_many :tags, through: :articles
    
      def distinct_tags
        self.tags.all.distinct
      end
    end
    
    class Article < ActiveRecord::Base
      belongs_to :user
      has_and_belongs_to_many :tags
    end
    
    class Tag < ActiveRecord::Base
      has_and_belongs_to_many :articles
    end
    

    接下来使用大量数据对数据库进行种子处理:

    10.times do |tagcount|
      Tag.create(name: "tag #{tagcount+1}")
    end
    
    5.times do |usercount|
      user = User.create(name: "user #{usercount+1}")
    
      1000.times do |articlecount|
        article = Article.new(user: user)
        5.times do |tagcount|
          article.tags << Tag.find(tagcount+usercount+1)
        end
        article.save
      end
    end
    

    最后在rails console中:

    user = User.find(3)
    user.distinct_tags
    

    导致以下输出:

      Tag Load (0.4ms)  SELECT DISTINCT `tags`.* FROM `tags` INNER JOIN `articles_tags` ON `tags`.`id` = `articles_tags`.`tag_id` INNER JOIN `articles` ON `articles_tags`.`article_id` = `articles`.`id` WHERE `articles`.`user_id` = 3
     => #<ActiveRecord::AssociationRelation [#<Tag id: 3, name: "tag 3", created_at: "2016-10-18 22:00:52", updated_at: "2016-10-18 22:00:52">, #<Tag id: 4, name: "tag 4", created_at: "2016-10-18 22:00:52", updated_at: "2016-10-18 22:00:52">, #<Tag id: 5, name: "tag 5", created_at: "2016-10-18 22:00:52", updated_at: "2016-10-18 22:00:52">, #<Tag id: 6, name: "tag 6", created_at: "2016-10-18 22:00:52", updated_at: "2016-10-18 22:00:52">, #<Tag id: 7, name: "tag 7", created_at: "2016-10-18 22:00:52", updated_at: "2016-10-18 22:00:52">]> 
    
    链接地址: http://www.djcxy.com/p/36687.html

    上一篇: sql' Replacement in Rails 4.2

    下一篇: Combine Multiple Regex into One