使用Rails查询返回单个字段

13

我在做一个应该很简单的事情时遇到了问题,但我不知道哪里出错了。我只是想在Rails 3中执行一个查询,返回单个字段而不是完整记录。

模型方法

def self.find_user_username(user_id)
  user_name = User.select("user_name").where(user_id)
  return user_name
end

我正在阅读 Rails 指南(http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields),其中简单地提到(其中 viewable_by、locked = fields):

Client.select("viewable_by, locked")

我尝试了选择器的前后更换,就像这样:

User.select("user_name").where(user_id) - AND - 
User.where(user_id).select("user_name")

但是两者都不起作用。事实上,我甚至尝试过:

user_name = User.select("yoda").where(user_id)

我没有收到任何错误。当我查看视图时,我有:

Friend.find_user_username(friend.user_id)

我一直得到的是哈希值: ActiveRecord::Relation:0x2f4942f0


当你执行 user_name = User.select("user_name").where(user_id).first 时会发生什么? - ScottJShea
我仍然只收到哈希。 - yellowreign
1
在Rails 6中,您可以使用pick(:id),它类似于pluck(:id).first - Kris
8个回答

31
User.where(:id => user_id).pluck(:user_name).first

应该能够实现你想要的功能。

pluck "接受列名作为参数,并返回具有相应数据类型的指定列的值数组"


1
如果我有一个经常执行并且(1)返回许多行或(2)返回许多列但我不关心的行的查询,我可能会使用类似这样的东西。这将减少从数据库返回的数据量,这可能会加快速度。但一般情况下我同意你的观点。 - Brandan
2
pluck 在 Rails 3.2 中被引入。 - Brandan
8
where之后,我会添加 .limit(1) 到这个查询中,否则你将会把所有结果从数据库中读入内存。 - stereoscott
2
没有必要限制1,因为id字段查询返回多个记录的可能性非常小。 - baash05
5
在上面的代码中,.first并不执行limit(1),因为它调用的不是ActiveRelation#first方法,而是调用了Array#first方法。ActiveRelation#pluck返回一个数组(不是另一个ActiveRelation对象)。如果你要调用ActiveRelation#first,那么它将执行limit(1)。如果你真的想在上面的示例中使用limit(1),你需要这样写:User.where(:id => user_id).limit(1).pluck(:user_name).first - jangosteve
显示剩余5条评论

9

Rails 6 引入了 ActiveRecord::Relation#pick

pick(:foo) 等同于 limit(1).pluck(:foo).first


2
很多人提到了.pluck。它确实有一个警告,它返回找到记录的列的数组。
如果列是唯一的,比如id,那就不是问题,DB会向Active Record发送相同内容,1个记录1个列,使用limit(1)或不使用(使用它是无用的,并且很可能会被DB查询优化器丢弃)。
但是,如果列不是唯一的,它实际上可能不是最佳性能。因为,在AR执行DB查询后,first被调用,返回从DB带来的所有结果的[0]limit在此之前被调用,并作为查询的一部分发送给DB,过滤结果。
考虑以下表格和设置:
> Log
=> Log(id: integer, user_id, integer, event_type: integer, timestamp: datetime)

> user_newest_logs = Log.where(user_id: 1).order(timestamp: :desc)

现在,让我们获取user_id = 1的最后事件日期时间:

1)这是最好的方法。数据库返回一个带有时间戳列的记录。

Original Answer: 最初的回答

> user_newest_logs.limit(1).pluck(:timestamp).first
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00

2) 数据库返回一条记录,包括所有列

最初的回答

> user_newest_logs.first
SELECT * FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> #<Log:0x00111111>
 id: 920821839,
 user_id: 1,
 event_type: 1,
 timestamp: Fri, 09 Aug 2019 23:00:00 UTC +00:00

> user_newest_logs.first.timestamp
SELECT * FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00

3) 数据库返回N条记录的时间戳列表

注:Original Answer翻译成“最初的回答”

> user_newest_logs.pluck(:timestamp)
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC
=> [Fri, 09 Aug 2019 23:00:00 UTC +00:00,
Fri, 09 Aug 2019 22:00:00 UTC +00:00,
Fri, 09 Aug 2019 21:00:00 UTC +00:00,
Fri, 09 Aug 2019 20:00:00 UTC +00:00,
...
]

> user_newest_logs.pluck(:timestamp).first
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00

> user_newest_logs.pluck(:timestamp).count    # NOT PRETTY HUH?!
=> 1523

因此,如果你使用pluck(:column).first,实际上比仅使用find_by.column更糟。

"最初的回答"


1

你需要添加.first来强制查询返回一条记录,因为where只会返回一个活动记录关系。

1.9.3p0 :048 > user = User.select('first_name').where('id = 1').first
  User Load (0.6ms)  SELECT first_name FROM `users` WHERE (id = 1) LIMIT 1
+------------+
| first_name |
+------------+
| Johnny     |
+------------+
1 row in set

1.9.3p0 :049 > user.first_name
=> "Johnny" 

1
你的问题不在于 select,而是在于 where。你现在的条件本质上是 WHERE #{user_id}。如果你在 Rails 控制台上执行这个语句,你会看到类似下面的输出:

1.9.2p290 :003 > puts User.select('user_name').where(1).to_sql
SELECT user_name FROM "users" WHERE (1)

你需要将条件作为哈希传递给where,并像其他人建议的那样获取第一条记录:
User.select('user_name').where(:id => user_id).first

由于某些原因,这仍然导致返回整个记录。对我来说,User.find(user_id).username 似乎可行。 - yellowreign

0

看起来虽然查询没有返回每个字段,但它确实返回了一个对象而不是特定字段的值。您可以将 return user_name 的返回值更改为 return user_name.user_name

def self.find_user_username(user_id)
  user_name = User.select("user_name").where(user_id)
  return user_name
end

或者你可以尝试使用 User.user_name.where(user_id).first 看看是否符合你的需求。


0

-3

顺便提一下,如果问题涉及到ActiveRecord::Relation,它是由where和select返回的对象类,正如你正在阅读的Rails指南中所说的那样。它被优化为在多个方法之间链接,并且当您实际访问关联的元素时,您会获得模型对象(因此在幕后运行SQL查询仅一次)。这意味着您获得了Relation对象,因为您只是返回它。

在这种情况下,也许最好的解决方案就是使用find并访问user_name属性。

def self.find_user_username(user_id)
  User.find(user_id).user_name
end

查询将从数据库获取所有其他属性,但您只会返回用户名。

4
这将获取数据库中整行的数据,Rails 将实例化完整的模型对象 — 如果您只需要名称,则使用 pluckfirst 更加高效,正如其他答案所建议的那样。 - DaveMongoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接