sql – 加入相同的表两次条件

在某些情况下,如果有多个连接具有相同的表,则ActiveRecord会设置别名表名称.我被困在这种连接包含范围(使用’merge’)的情况下.

我有多对多的关系:

Models table_name: users

Second models table_name: posts

Join table name: access_levels

一个帖子通过access_levels有很多用户,反之亦然.

两者,用户模型和Post模型共享相同的关系:

has_many:access_levels,– > {merge(AccessLevel.valid)}

AccessLevel模型的范围如下所示:

# v1
  scope :valid,-> {
    where("(valid_from IS NULL OR valid_from < :Now) AND (valid_until IS NULL OR valid_until > :Now)",:Now => Time.zone.Now)
  }

  # v2
  # scope :valid,-> {
  #   where("(#{table_name}.valid_from IS NULL OR #{table_name}.valid_from < :Now) AND (#{table_name}.valid_until IS NULL OR #{table_name}.valid_until > :Now)",:Now => Time.zone.Now)
  # }

我想这样打电话给sth:

Post.joins(:access_levels).joins(:users).where (...)

ActiveRecord为第二个连接(‘access_levels_users’)创建一个别名.我想在AccessLevel模型的“有效”范围内引用这个表名.

V1显然生成一个PG :: AmbiguousColumn-Error.
V2导致在access_levels两个条件前缀,这在语义上是错误的.

这是我如何生成查询:(简体)

# inside of a policy
scope = Post.
  joins(:access_levels).
  where("access_levels.level" => 1,"access_levels.user_id" => current_user.id)

# inside of my controller
scope.joins(:users).select([
        Post.arel_table[Arel.star],"hstore(array_agg(users.id::text),array_agg(users.email::text)) user_names"
      ]).distinct.group("posts.id")

生成查询看起来像这样(使用上面的有效范围v2):

SELECT "posts".*,hstore(array_agg(users.id::text),array_agg(users.email::text)) user_names

  FROM "posts"
  INNER JOIN "access_levels" ON "access_levels"."post_id" = "posts"."id" AND (("access_levels"."valid_from" IS NULL OR "access_levels"."valid_from" < '2014-07-24 05:38:09.274104') AND ("access_levels"."valid_until" IS NULL OR "access_levels"."valid_until" > '2014-07-24 05:38:09.274132'))
  INNER JOIN "users" ON "users"."id" = "access_levels"."user_id"
  INNER JOIN "access_levels" "access_levels_posts" ON "access_levels_posts"."post_id" = "posts"."id" AND (("access_levels"."valid_from" IS NULL OR "access_levels"."valid_from" < '2014-07-24 05:38:09.274675') AND ("access_levels"."valid_until" IS NULL OR "access_levels"."valid_until" > '2014-07-24 05:38:09.274688'))

  WHERE "posts"."deleted_at" IS NULL AND "access_levels"."level" = 4 AND "access_levels"."user_id" = 1 GROUP BY posts.id

ActiveRecord为access_levels表的第二个连接设置一个合适的别名“access_levels_posts”.
问题是合并的有效范围前缀为’access_levels’而不是’access_levels_posts’.我也试图用isl来生成范围:

# v3
scope :valid,-> {
  where arel_table[:valid_from].eq(nil).or(arel_table[:valid_from].lt(Time.zone.Now)).and(
    arel_table[:valid_until].eq(nil).or(arel_table[:valid_until].gt(Time.zone.Now))
  )
}

结果查询保持不变.

解决方法

在更接近 similar question here这个问题后,我想出了一个更简单,更干净(对我的眼睛)解决这个问题的方案.我在这里粘贴另一个问题的答案的相关部分,以及您的范围.

关键是找到一个方法来访问当前的isl_table对象,如果它们正在被使用,那么它的table_aliases在执行时就在该范围内.使用该表,您将能够知道是否在具有表名称别名(多个连接在同一个表上)的JOIN中使用范围,或者如果另一方面该范围没有表名称的别名.

# based on your v2
scope :valid,-> {
  where("(#{current_table_from_scope}.valid_from IS NULL OR 
          #{current_table_from_scope}.valid_from < :Now) AND 
         (#{current_table_from_scope}.valid_until IS NULL OR 
          #{current_table_from_scope}.valid_until > :Now)",:Now => Time.zone.Now) 
  }

def self.current_table_from_scope
  current_table = current_scope.arel.source.left

  case current_table
  when Arel::Table
    current_table.name
  when Arel::Nodes::TableAlias
    current_table.right
  else
    fail
  end
end

我使用current_scope作为基础对象来查找arel表,而不是先前使用self.class.arel_table或甚至relation.arel_table的尝试.我正在调用该对象的源来获取一个Arel::SelectManager,这反过来将给你#left上的当前表.在这一刻,有两个选项:你有一个Arel :: Table(没有别名,表名在#name上),或者你有一个Arel :: Nodes :: TableAlias与其#right上的别名.

如果你有兴趣,这里有一些参考我在路上使用:

> A similar question on SO,用一大堆代码回答,你可以使用而不是你的美丽和简洁的能力.
>这Rails issue和这other one.

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 &#39;EastRiver&#39; 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...