【问题标题】:Using `joins()` for first an INNER JOIN then a LEFT JOIN of the next table使用 `joins()` 首先是 INNER JOIN,然后是下一个表的 LEFT JOIN
【发布时间】:2018-02-27 20:40:47
【问题描述】:

我试图避免在 Rails 中对我的joins 进行字符串插值,因为我注意到将查询器链接在一起时灵活性降低了。 也就是我觉得joins(:table1)joins('inner join table1 on table1.id = this_table.table1_id')灵活很多。

我想要完成的是:

FROM table1
INNER JOIN table2 on table2.id = table1.table2_id
LEFT JOIN table3 on table3.id = table2.table3_id

内联全部

但是,我不知道如何使用 Rails 术语来做到这一点:

Table1.joins(table2: :table3)

导致最终表上的 INNER 连接。

FROM table1
INNER JOIN table2 on table2.id = table1.table2_id
INNER JOIN table3 on table3.id = table2.table3_id

左加入所有

如果我使用left_outer_joins...

Table1.left_joins(table2: :table3)

导致两个表上的 LEFT 连接(不需要)。

FROM table1
LEFT JOIN table2 on table2.id = table1.table2_id
LEFT JOIN table3 on table3.id = table2.table3_id

如何混合连接?

似乎无法在不指定关系的情况下链接连接......也就是说,这些不起作用:

Table1.joins(:table2).left_join(table2: :table3)
Table1.joins(:table2).left_join(:table3)

有什么方法可以按我的方式做吗?

【问题讨论】:

  • 您想避免使用find_by_sql
  • @FabrizioBertoglio - 是的......想利用 Rails 巧妙地从链式操作中混合多个连接;对 sql 进行硬编码更脆弱。
  • 我可以从您的帖子中了解更多关于 joins 的信息!

标签: ruby-on-rails ruby-on-rails-5


【解决方案1】:

这里有 3 个选项,但第一个选项应该是最好的。

选项 #1:
您可以重新排序您的 Joins,如下所示,我有相同的情况,我尝试了以下方法并且它有效。您在这里所做的是在开始时使用 Table2,因此它可以与 Table1 进行 join 并且可以与 Table3 进行 left_outer_join

Table2.joins(:table1).left_outer_joins(:table3)

选项 #2:
您可以在 Rails 中使用 Raw Sql,如下所示:

Table1.joins(:table2).joins('LEFT OUTER JOIN table3  ON  table3.id = table2.table3_id')

选项 #3:
另一种获取Table3数据的方法是使用includes,如下所示,但它会触发3个查询:

Table1.joins(:table2).includes(table2: :table3)

【讨论】:

  • 感谢您的建议 - 我试图避免使用原始 sql,以便我可以利用 Rails 允许的漂亮链接。我不知道includes——学到了一些新东西!但这并不是我想要做的……也许这不是我想要的方式。
  • 这是一个有趣的建议 - 谢谢!但是,这对我来说是站不住脚的,因为我将许多可能的查询链接到(在这种情况下)Table1。感谢您探索这些选项。
【解决方案2】:

Table1.joins(:table2).left_joins(table2: :table3) 为我工作。 (使用 Rails 6.0.3.2 和 Postgres)

【讨论】:

  • 这样会有内联和左外联table2的效果。
【解决方案3】:

据我所知,#left_outer_joins 没有“面向用户”的语法(我也无法使用 #merge 使其工作),但您可以将其实现为如下:

left_join_table3 = Table1
  .left_joins(:table2 => :table3)
  .arel.join_sources[1]

Table1.joins(:table2, left_join_table3)

# Generates the following SQL (for belongs_to associations):
#
# SELECT "table1".* FROM "table1"
# INNER JOIN "table2" ON "table2"."id" = "table1"."table2_id"
# LEFT OUTER JOIN "table3" ON "table3"."id" = "table2"."table3_id"

对于重用,即在一个范围内,如果您省略常量,我相信别名将是一致的,这听起来像是您正在努力实现的。

scope :my_scope, -> {
  left_join_table3 = left_joins(:table2 => :table3).arel.join_sources[1]
  joins(:table2, left_join_table3)
}

【讨论】:

    【解决方案4】:

    这不是最干净的方法,但我通过向 ActiveRecord 添加一个名为 join_sql 的方法解决了这个问题。

    class ActiveRecord::Base
      def self.join_sql(scope=current_scope)
        sql = scope.to_sql
        join_index = sql.index(/(LEFT OUTER|INNER JOIN)/)
        if join_index
          join_end_index = sql.index(/ (WHERE|GROUP BY|ORDER BY)/) || -1
          sql[join_index..join_end_index]
        end
      end
    end
    

    然后,您可以使用带有字符串而不是符号的 ActiveRecord 连接方法:

    Table1.joins(:table2).joins(Table2.left_joins(:table3).join_sql)
    

    【讨论】:

    • 注意,关于我如何从 sql 语句的其余部分解析出 join sql,我可能在这里遗漏了一些情况。如果您遇到我遗漏的其他案例,请告诉我。
    猜你喜欢
    • 2021-06-25
    • 1970-01-01
    • 2010-12-21
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    • 1970-01-01
    相关资源
    最近更新 更多