我想在我的选择请求中使用 EXIST
来创建一个“已发表”列,具体取决于文章是否在某一年的一本书中发表。
所以我做了以下
p params[:years] # "2002"
p params[:years].is_a?(String) # true
Article.select(<<~SQL.squish, years: params[:years])
articles.*, EXISTS(
SELECT 1
FROM books
WHERE books.year IN (:years)
) AS published
SQL
但它给了我以下错误
<块引用>不支持的参数类型:哈希。而是构建一个 Arel 节点
如果我像这样尝试
p params[:years] # "2002"
p params[:years].is_a?(String) # true
Article.select(<<~SQL.squish)
articles.*, EXISTS(
SELECT 1
FROM books
WHERE books.year IN (#{params[:years]})
) AS published
SQL
<块引用>
PG::UndefinedFunction:错误:运算符不存在:字符变化 = bigint WHERE books.year IN (2022)
但是 params[:years] 是一个字符串,我不知道如何做我想做的事,而且我不理解这两个错误。
编辑:如果我像这样使用单引号
p params[:years] # "2002"
p params[:years].is_a?(String) # true
Article.select(<<~SQL.squish)
articles.*, EXISTS(
SELECT 1
FROM books
WHERE books.year IN ('#{params[:years]}')
) AS published
SQL
它适用于单个值,但如果 params[:years] 是一个数组,它可以工作但输出不正确,如果我在 2003 年有一本书中的文章,它将设置为 false
p params[:years] # ["2002", "2003"]
p params[:years].is_a?(String) # false
Article.select(<<~SQL.squish)
articles.*, EXISTS(
SELECT 1
FROM books
WHERE books.year IN ('#{params[:years]}')
) AS published
SQL
答案 0 :(得分:2)
ActiveRecord 中的 Object.select
方法不支持像 .where
那样经过消毒的占位符插入。这就是为什么您会收到您收到的错误 - select
需要一个字段列表,但您的参数之一是哈希。
获得您正在寻找的功能的一种方法是,正如错误消息所暗示的那样,使用 Arel(如果您不熟悉它,请将 Arel 视为 ActiveRecord 在内部用于构建的“构建块”其 SQL 查询)。 Max's answer 可以很好地将您的查询翻译成 Arel。
或者,您可以像 ActiveRecord 的其他部分一样清理您自己的 SQL。为清楚起见,将其拆分为 SQL 解析和 select 语句:
sql = Article.sanitize_sql([<<~SQL, year: params[:years]])
articles.*, EXISTS(
SELECT 1
FROM books
WHERE books.year IN (:years)
) AS published
SQL
Article.select(sql)
我想说,手动清理您的 SQL 将使您克服当前遇到的绊脚石,但是如果您要在整个商店进行类似类型的操作,您将受益于了解 Arel以及如何使用它。
答案 1 :(得分:1)
通过将用户输入插入到 SQL 查询中,您可以为 SQL injection attack 敞开大门。
这很容易通过使用 Arel 来构造查询来防止:
Article.select(
Article.arel_table[Arel.star],
Book.select(1)
.where(year: params[:years])
.arel
.exists
.as('published')
)
SELECT
"articles".*,
EXISTS (SELECT 1 FROM "books" WHERE "books"."year" IN (?, ?, ?)) AS published
FROM "articles"