Rails: Return 仅所有关联满足条件的对象
Rails: Return only objects where all associations meet a condition
设置:Rails 3.2.18,Postgres
我有两个对象,例如,将它们命名为 Author
和 Article
,设置如下:
class Author
has_many :articles
...
end
class Article
belongs_to :author
class << self
def published_over_one_year_ago
where(arel_table[:published_at].lt(1.year.ago))
end
end
end
我正在尝试查找所有 Author
记录,其中 所有 相关 Article
记录是在一年多以前发布的。此代码:
Author.joins(:article).merge(Article.published_over_one_year_ago)
... returns Author
对象 至少有一个 关联 Article
是在一年多以前发布的,但我需要 仅 Author
条记录,其中 所有 相关的 Article
条记录已超过一年。这可能与 arel / ActiveRecord,还是我必须使用原始 SQL?
可能 "Working" SQL 解决方案:
query = ERB.new(<<-SQL_END).result(binding)
'author'.id in (
select art.author_id
from articles art
group by art.author_id
having sum(case (art.published_at::date >= current_date) when true then 1 else 0 end) = 0 AND
sum(case(art.published_at::date < current_date) when true then 1 else 0 end) > 0
)
SQL_END
# The following now appears to do what I want it to do.
Author.includes(:articles).where(query)
UPDATE 我看到已经有一个使用 arel 的建议答案,非常出色。如果可能的话,有人建议直接使用 ActiveRecord 吗?
使用 Arel 和通用 Table 表达式:
articles = Article.arel_table
authors = Author.arel_table
cte_table = Arel::Table.new(:cte_table)
composed_cte =
Arel::Nodes::As.new(cte_table,
articles.project(articles[:author_id],
articles[:published_at].maximum.as("max_published_at"))
.group(articles[:author_id])
)
然后:
authors
.join(cte_table).on(cte_table[:author_id].eq(authors[:id]))
.with(composed_cte)
.where(cte_table[:max_published_at].lt(1.year.ago))
.project(authors[:id])
.to_sql
returns(为清楚起见格式化):
WITH "cte_table" AS (
SELECT articles.author_id,
MAX(articles.published_at) AS max_published_at
FROM articles
GROUP BY articles.author_id
)
SELECT authors.id
FROM authors
INNER JOIN cte_table
ON cte_table.author_id = authors.id
WHERE cte_tble.max_published_at < '2014-01-25 23:04:16.532796'
使用它:
来自
[2] test» Article.pluck(:author_id, :published_at)
D, [2015-01-26T00:10:06.763197 #21897] DEBUG -- : (0.6ms) SELECT "articles"."author_id", "articles"."published_at" FROM "articles"
=> [
[0] [
[0] 1,
[1] Sun, 25 Jan 2015 23:31:58 UTC +00:00
],
[1] [
[0] 1,
[1] Fri, 01 Jan 1999 00:00:00 UTC +00:00
],
[2] [
[0] 2,
[1] Fri, 01 Jan 1999 00:00:00 UTC +00:00
]
查询returns:
[2015-01-26T00:10:25.279587 #21897] DEBUG -- : (0.9ms) WITH "cte_table" AS (SELECT "articles"."author_id", MAX("articles"."published_at") AS max_published_at FROM "articles" GROUP BY "articles"."author_id") SELECT "authors"."id" FROM "authors" INNER JOIN "cte_table" ON "cte_table"."author_id" = "authors"."id" WHERE "cte_table"."max_published_at" < '2014-01-25 23:10:25.277902'
=> [
[0] {
"id" => "2"
}
]
编辑
不使用 Arel :
Author.joins(:articles)
.where("authors.id != ANY (?)",
Article.select('author_id')
.where("published_at > ?", 1.year.ago)
)
如果您将 .published_last_year
添加到文章中:
class Article < ActiveRecord::Base
belongs_to :author
def self.published_last_year
where("published_at > ?", 1.year.ago)
end
end
然后
Author.joins(:articles)
.where("authors.id != ANY (?)",
Article.published_last_year.select('author_id'))
Rails4 版本
Author.joins(:articles)
.where.not(id: Article.published_last_year.select('author_id'))
设置:Rails 3.2.18,Postgres
我有两个对象,例如,将它们命名为 Author
和 Article
,设置如下:
class Author
has_many :articles
...
end
class Article
belongs_to :author
class << self
def published_over_one_year_ago
where(arel_table[:published_at].lt(1.year.ago))
end
end
end
我正在尝试查找所有 Author
记录,其中 所有 相关 Article
记录是在一年多以前发布的。此代码:
Author.joins(:article).merge(Article.published_over_one_year_ago)
... returns Author
对象 至少有一个 关联 Article
是在一年多以前发布的,但我需要 仅 Author
条记录,其中 所有 相关的 Article
条记录已超过一年。这可能与 arel / ActiveRecord,还是我必须使用原始 SQL?
可能 "Working" SQL 解决方案:
query = ERB.new(<<-SQL_END).result(binding)
'author'.id in (
select art.author_id
from articles art
group by art.author_id
having sum(case (art.published_at::date >= current_date) when true then 1 else 0 end) = 0 AND
sum(case(art.published_at::date < current_date) when true then 1 else 0 end) > 0
)
SQL_END
# The following now appears to do what I want it to do.
Author.includes(:articles).where(query)
UPDATE 我看到已经有一个使用 arel 的建议答案,非常出色。如果可能的话,有人建议直接使用 ActiveRecord 吗?
使用 Arel 和通用 Table 表达式:
articles = Article.arel_table
authors = Author.arel_table
cte_table = Arel::Table.new(:cte_table)
composed_cte =
Arel::Nodes::As.new(cte_table,
articles.project(articles[:author_id],
articles[:published_at].maximum.as("max_published_at"))
.group(articles[:author_id])
)
然后:
authors
.join(cte_table).on(cte_table[:author_id].eq(authors[:id]))
.with(composed_cte)
.where(cte_table[:max_published_at].lt(1.year.ago))
.project(authors[:id])
.to_sql
returns(为清楚起见格式化):
WITH "cte_table" AS (
SELECT articles.author_id,
MAX(articles.published_at) AS max_published_at
FROM articles
GROUP BY articles.author_id
)
SELECT authors.id
FROM authors
INNER JOIN cte_table
ON cte_table.author_id = authors.id
WHERE cte_tble.max_published_at < '2014-01-25 23:04:16.532796'
使用它:
来自
[2] test» Article.pluck(:author_id, :published_at)
D, [2015-01-26T00:10:06.763197 #21897] DEBUG -- : (0.6ms) SELECT "articles"."author_id", "articles"."published_at" FROM "articles"
=> [
[0] [
[0] 1,
[1] Sun, 25 Jan 2015 23:31:58 UTC +00:00
],
[1] [
[0] 1,
[1] Fri, 01 Jan 1999 00:00:00 UTC +00:00
],
[2] [
[0] 2,
[1] Fri, 01 Jan 1999 00:00:00 UTC +00:00
]
查询returns:
[2015-01-26T00:10:25.279587 #21897] DEBUG -- : (0.9ms) WITH "cte_table" AS (SELECT "articles"."author_id", MAX("articles"."published_at") AS max_published_at FROM "articles" GROUP BY "articles"."author_id") SELECT "authors"."id" FROM "authors" INNER JOIN "cte_table" ON "cte_table"."author_id" = "authors"."id" WHERE "cte_table"."max_published_at" < '2014-01-25 23:10:25.277902'
=> [
[0] {
"id" => "2"
}
]
编辑
不使用 Arel :
Author.joins(:articles)
.where("authors.id != ANY (?)",
Article.select('author_id')
.where("published_at > ?", 1.year.ago)
)
如果您将 .published_last_year
添加到文章中:
class Article < ActiveRecord::Base
belongs_to :author
def self.published_last_year
where("published_at > ?", 1.year.ago)
end
end
然后
Author.joins(:articles)
.where("authors.id != ANY (?)",
Article.published_last_year.select('author_id'))
Rails4 版本
Author.joins(:articles)
.where.not(id: Article.published_last_year.select('author_id'))