ruby-on-rails - ActiveRecord `.references` 加入比必要更多的表? (并导致错误)

标签 ruby-on-rails ruby postgresql ruby-on-rails-4 activerecord

模型布局

Article (has_many :attachments, :comments, :tags)
  - id: string

Comment (has_many :attachments)
  - id: integer
  - article_id: string

Attachment
  - id: integer
  - reference_type: string
  - reference_id: string

Tag
  - id: integer
  - article_id: string

错误查询

rails console 我运行:

Article.includes(:tags, :comments => :attachments).references(:tags)

由此产生的错误是:

Article.includes(:tags, :comments => :attachments).references(:tags)
  SQL (0.7ms)  SELECT "articles"."id" AS t0_r0, "articles"."created_at" AS t0_r1, "articles"."updated_at" AS t0_r2, "tags"."id" AS t1_r0, "tags"."article_id" AS t1_r1, "tags"."created_at" AS t1_r2, "tags"."updated_at" AS t1_r3, "comments"."id" AS t2_r0, "comments"."article_id" AS t2_r1, "comments"."created_at" AS t2_r2, "comments"."updated_at" AS t2_r3, "attachments"."id" AS t3_r0, "attachments"."reference_type" AS t3_r1, "attachments"."reference_id" AS t3_r2, "attachments"."created_at" AS t3_r3, "attachments"."updated_at" AS t3_r4 FROM "articles" LEFT OUTER JOIN "tags" ON "tags"."article_id" = "articles"."id" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" LEFT OUTER JOIN "attachments" ON "attachments"."reference_id" = "comments"."id" AND "attachments"."reference_type" = $1  [["reference_type", "Comment"]]
ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR:  operator does not exist: character varying = integer
LINE 1: ...OIN "attachments" ON "attachments"."reference_id" = "comment_...
                                                             ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "articles"."id" AS t0_r0, "articles"."created_at" AS t0_r1, "articles"."updated_at" AS t0_r2, "tags"."id" AS t1_r0, "tags"."article_id" AS t1_r1, "tags"."created_at" AS t1_r2, "tags"."updated_at" AS t1_r3, "comments"."id" AS t2_r0, "comments"."article_id" AS t2_r1, "comments"."created_at" AS t2_r2, "comments"."updated_at" AS t2_r3, "attachments"."id" AS t3_r0, "attachments"."reference_type" AS t3_r1, "attachments"."reference_id" AS t3_r2, "attachments"."created_at" AS t3_r3, "attachments"."updated_at" AS t3_r4 FROM "articles" LEFT OUTER JOIN "tags" ON "tags"."article_id" = "articles"."id" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" LEFT OUTER JOIN "attachments" ON "attachments"."reference_id" = "comments"."id" AND "attachments"."reference_type" = $1
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `async_exec'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `block in exec_no_cache'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:566:in `block in log'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:560:in `log'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `exec_no_cache'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/postgresql_adapter.rb:587:in `execute_and_clear'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/postgresql/database_statements.rb:103:in `exec_query'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:373:in `select'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:41:in `select_all'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/connection_adapters/abstract/query_cache.rb:70:in `select_all'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/relation/finder_methods.rb:389:in `find_with_associations'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/relation.rb:699:in `exec_queries'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/relation.rb:580:in `load'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/relation.rb:260:in `records'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activerecord-5.0.0/lib/active_record/relation.rb:683:in `inspect'
... 1 levels...
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/railties-5.0.0/lib/rails/commands/console_helper.rb:9:in `start'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/railties-5.0.0/lib/rails/commands/commands_tasks.rb:78:in `console'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/railties-5.0.0/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/railties-5.0.0/lib/rails/commands.rb:18:in `<top (required)>'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:293:in `require'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:293:in `block in require'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:259:in `load_dependency'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:293:in `require'
    from /Users/jeremy/Documents/Synack/Code/experiments/joins-test/bin/rails:9:in `<top (required)>'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:287:in `load'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:287:in `block in load'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:259:in `load_dependency'
    from /Users/jeremy/.rvm/gems/ruby-2.2.2@rails5/gems/activesupport-5.0.0/lib/active_support/dependencies.rb:287:in `load'
    from /Users/jeremy/.rvm/rubies/ruby-2.2.2/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
    from /Users/jeremy/.rvm/rubies/ruby-2.2.2/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'

查询成功

如果没有 .references() 子句,查询将正常工作,并分别获取所有模型,创建四个单独的请求。

rails console 我运行:

Article.includes(:tags, :comments => :attachments)

我明白了:

Article.includes(:tags, :comments => :attachments)
  Article Load (5.1ms)  SELECT "articles".* FROM "articles"
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" WHERE "tags"."article_id" = 'seedacorn-123'
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = 'seedacorn-123'
  Attachment Load (0.3ms)  SELECT "attachments".* FROM "attachments" WHERE "attachments"."reference_type" = $1 AND "attachments"."reference_id" IN ('1', '2')  [["reference_type", "Comment"]]
 => #<ActiveRecord::Relation [#<article id: "seedacorn-123", created_at: "2016-07-15 22:41:27", updated_at: "2016-07-15 22:41:27">]> 

问题

所以我想,我真正想知道的是,为什么 ActiveRecord 将 所有 包含的模型添加到 JOIN 而不仅仅是我添加到 .references()?这是为了提高性能吗?还是出于其他原因需要这样做?

我没有将 :comments:comments => :attachments 添加到 .references() 中,但它们仍然被添加到加入。这会导致类型转换错误,但我想知道为什么一开始会发生这种情况?

如果您知道如何使用某种创造性的解决方案来解决这个问题,我正在拼命地尝试弄清楚。核心问题是我的多态 Attachment 模型可以使用 String 或 Integer 主键引用其他模型,因此它的 reference_type 是一个字符串。然后,在引入 JOIN 的情况下,查询其他模型时无法正确进行类型转换。

最佳答案

github.com/rail/rails 上发布问题后找到了此问题的答案.

事实证明,这是 Rails 中已知的(不幸的是预期的)行为。如果您需要使用单独的查询加载关联并且作为JOIN的一部分,您可以使用preload而不是 includes,它将始终在单独的查询中加载关联。

同样,如果你想强制加载一个关联作为 JOIN 的一部分,并且你没有使用 includes,你可以替换 referenceseager_load

所以

Article.includes(:tags, :comments => :attachments).references(:tags)

会变成

Article.preload(:tags, :comments => :attachments).eager_load(:tags)

关于ruby-on-rails - ActiveRecord `.references` 加入比必要更多的表? (并导致错误),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38406304/

相关文章:

ruby-on-rails - 获取 Stripe 的美分价格

javascript - 按 enter 时 jQuery UI 自动完成字段不提交

ruby - 在 Ruby 中,如何从套接字读取 UTF-8?

html - 使用 ruby​​ 和 nokogiri 以 HTML 注释为标记解析 HTML

database - 哪个索引最适合 PostgreSQL inet 数据类型?

r - 使用 RPostgreSQL 将数据写入表而不改变结构

PostgreSQL:将 2 列结果集转换为单行表

ruby-on-rails - 对属性的Tire/Elasticsearch过滤

ruby-on-rails - 月/日的慢性解析

ruby - dry-struct 如何有条件地验证一个属性?