Rails:在where语句中使用大于/小于

我试图找到id大于200的所有用户,但我在特定的语法上遇到了一些麻烦。

User.where(:id > 200)

而且

User.where("? > 200", :id)

都失败了。

有什么建议吗?

167521 次浏览

试试这个

User.where("id > ?", 200)

技术现状

Ruby 2.7引入了beginless范围,这使得指定><和它们的近亲(>=<=)更加容易。

User.where(id: 200..).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" >= 200"
# There is no difference w/ a non-inclusive endless range (e.g. `200...`)


User.where(id: ..200).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" <= 200"
User.where(id: ...200).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" < 200"

这也适用于时间戳!

User.where(created_at: 1.day.ago..).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"created_at\" >= '2021-09-12 15:38:32.665061'"
User.where(created_at: ..1.day.ago).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"created_at\" <= '2021-09-12 15:38:37.756870'"

原创答案&更新

我只在Rails 4中对此进行了测试,但有一种有趣的方法可以使用带有where散列的范围来获得这种行为。

User.where(id: 201..Float::INFINITY)

将生成SQL语句

SELECT `users`.* FROM `users`  WHERE (`users`.`id` >= 201)

对于小于-Float::INFINITY也可以做同样的事情。

我刚刚发布了一个类似的问题,询问如何用日期这里SO这样做。

>= vs >

为了避免人们不得不深入挖掘和跟踪评论对话,这里有一些重点。

上面的方法只生成>=查询,生成>查询。有很多方法可以处理这种选择。

对于离散数

你可以像上面那样使用number_you_want + 1策略,我对具有id > 200的用户感兴趣,但实际上寻找的是id >= 201。这对于整数和数字来说很好,您可以在其中增加单个感兴趣的单位。

如果你将数字提取到一个命名良好的常量中,这可能是最容易阅读和理解的。

反向逻辑

我们可以使用x > y == !(x <= y)这一事实并使用where not chain。

User.where.not(id: -Float::INFINITY..200)

生成SQL语句

SELECT `users`.* FROM `users` WHERE (NOT (`users`.`id` <= 200))

这需要额外的一秒钟来读取和推理,但对于不能使用+ 1策略的非离散值或列有效。

下属表

如果你想要更花哨,你可以使用Arel::Table

User.where(User.arel_table[:id].gt(200))

将生成SQL语句

"SELECT `users`.* FROM `users` WHERE (`users`.`id` > 200)"

具体如下:

User.arel_table              #=> an Arel::Table instance for the User model / users table
User.arel_table[:id]         #=> an Arel::Attributes::Attribute for the id column
User.arel_table[:id].gt(200) #=> an Arel::Nodes::GreaterThan which can be passed to `where`

这种方法会让你得到你感兴趣的确切的 SQL,但是没有很多人直接使用Arel表,会发现它很混乱和/或令人困惑。你和你的团队知道什么对你来说是最好的。

奖金

从Rails 5开始,你也可以用日期来做这件事!

User.where(created_at: 3.days.ago..DateTime::Infinity.new)

将生成SQL语句

SELECT `users`.* FROM `users` WHERE (`users`.`created_at` >= '2018-07-07 17:00:51')

两倍奖金

一旦Ruby 2.6发布(2018年12月25日),你就可以使用新的无限范围语法了!你可以只写201..而不是201..Float::INFINITY。更多信息在这篇博文中

如果你想要一个更直观的书写,它存在一个名为squeel的宝石,它可以让你像这样书写指令:

User.where{id > 200}

注意“大括号”字符{}和id只是一个文本。

你所要做的就是在你的Gemfile中添加squeel:

gem "squeel"

在用Ruby编写复杂的SQL语句时,这可能会大大简化您的工作。

更好的用法是在用户模型where(arel_table[:id].gt(id))中创建作用域

短:

User.where("id > 200")

我经常在日期字段中遇到这个问题(比较操作符非常常见)。

进一步阐述Mihai的回答,我相信这是一个可靠的方法。

你可以像这样给模型添加作用域:

scope :updated_at_less_than, -> (date_param) {
where(arel_table[:updated_at].lt(date_param)) }

... 然后在你的控制器中,或者在你使用模型的任何地方

result = MyModel.updated_at_less_than('01/01/2017')

... 一个更复杂的连接示例如下所示:

result = MyParentModel.joins(:my_model).
merge(MyModel.updated_at_less_than('01/01/2017'))

这种方法的一个巨大优势是:(A)它允许您从不同的作用域组合查询;(b)当您两次连接到同一个表时避免别名冲突,因为arel_table将处理查询生成的那一部分。

雷尔是你的朋友:

User.where(User.arel_table[:id].gt(200))

另一种奇特的可能性是……

User.where("id > :id", id: 100)

如果您想在多个地方进行替换,该功能允许您创建更容易理解的查询,例如……

User.where("id > :id OR number > :number AND employee_id = :employee", id: 100, number: 102, employee: 1205)

这比查询中有很多?更有意义…

User.where("id > ? OR number > ? AND employee_id = ?", 100, 102, 1205)

更新

Rails核心团队决定暂时恢复此更改,以便更详细地讨论它。更多信息请参见这样的评论这个公关

我留下我的答案只是为了教育目的。


Rails 6.1中用于比较的新“语法”(已恢复)

Rails 6.1为where条件中的比较操作符添加了新的“语法”,例如:

Post.where('id >': 9)
Post.where('id >=': 9)
Post.where('id <': 3)
Post.where('id <=': 3)

所以您的查询可以改写如下:

User.where('id >': 200)

这里是公共关系的连结,在这里你可以找到更多的例子。

对于Ruby 2.6可以接受如下范围:

# => 2.6
User.where(id: 201..)
# < 2.6
User.where(id: 201..Float::INFINITY)