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

Posted

技术标签:

【中文标题】Rails:在 where 语句中使用大于/小于【英文标题】:Rails: Using greater than/less than with a where statement 【发布时间】:2012-07-04 06:48:04 【问题描述】:

我正在尝试查找 ID 大于 200 的所有用户,但我在特定语法上遇到了一些问题。

User.where(:id > 200) 

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

都失败了。

有什么建议吗?

【问题讨论】:

【参考方案1】:

试试这个

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

【讨论】:

还可以查看 Ernie Miller 的 Squeel gem 有什么理由更喜欢使用?,而不是内联200 自动转义200(如果用户可以输入值,就避免了SQL注入攻击的可能性) 请注意,Rails 6.1 中有一种新的比较语法。请参考this答案。【参考方案2】:

最先进的技术

Ruby 2.7 引入了beginless ranges,这使得指定>< 及其包含的表亲(>=<=)变得更加容易。

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也可以做到这一点。

我刚刚发布了一个类似的问题,询问有关使用日期here on SO 执行此操作的问题。

&gt;=&gt;

为了避免人们不得不深入了解和关注 cmets 的对话,这里是重点。

上面的方法只生成一个&gt;=查询,没有一个&gt;。有很多方法可以处理这种替代方案。

对于离散数字

您可以使用上面的number_you_want + 1 策略,我对id &gt; 200 的用户感兴趣,但实际上寻找id &gt;= 201。这适用于整数和数字,您可以增加一个感兴趣的单位。

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

反转逻辑

我们可以使用x &gt; y == !(x &lt;= y)这个事实并使用where not链。

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

生成 SQL

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

这需要额外的时间来阅读和推理,但适用于您无法使用 + 1 策略的非离散值或列。

Arel 表

如果您想变得花哨,可以使用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..Float::INFINITY,您可以只写201..。更多信息in this blog post.

【讨论】:

这个答案最适合 Rails 4 IMO。已经使用了一段时间,效果很好。 出于好奇,为什么这优于公认的答案? Superior 具有误导性。一般来说,如果您能够在字符串上使用散列语法,那么您可以通过 ARel 查询获得更大的灵活性,这就是为什么许多人更喜欢这种解决方案的原因。根据您的项目/团队/组织,您可能想要一些更容易让某人查看代码来找出可接受的答案。 我不相信你可以使用基本的where 匹配器来做到这一点。对于&gt;,为了简单起见,我建议使用&gt;= (number_you_want + 1)。如果您真的想确保它只是一个 &gt; 查询,您可以访问 ARel 表。从ActiveRecord 继承的每个类都有一个arel_table getter 方法,该方法返回该类的Arel::Table。使用[] 方法(如User.arel_table[:id])访问表中的列。这将返回一个Arel::Attributes::Attribute,您可以调用gt 并传入200。然后可以将其传递给where。例如User.where(User.arel_table[:id].gt(200)). @bluehallu 你能举个例子吗?以下内容不适用于我User.where(created_at: 3.days.ago..DateTime::Infinity.new)【参考方案3】:

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

【讨论】:

【参考方案4】:

更新

Rails 核心团队决定暂时恢复此更改,以便更详细地讨论它。请参阅this comment 和this PR 了解更多信息。

我仅出于教育目的而留下我的答案。


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) 

这里是a link to PR,您可以在其中找到更多示例。

【讨论】:

目前还不清楚这个拉取请求是否会被接受。根据(github.com/rails/rails/pull/39613#issuecomment-659553274)[thiscomment),核心团队需要先讨论这个问题。 @F*** Winkler,感谢您的观察。我正在监视这个拉取请求。如果有什么变化,我会立即更新这个答案。【参考方案5】:

Arel 是你的朋友:

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

【讨论】:

很好的回应,我们应该更喜欢使用 arel =)【参考方案6】:

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

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)

【讨论】:

【参考方案7】:

如果你想要更直观的写作,它有一个名为 squeel 的 gem,它可以让你这样写你的指令:

User.whereid > 200

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

您所要做的就是将 squeel 添加到您的 Gemfile:

gem "squeel"

在 Ruby 中编写复杂的 SQL 语句时,这可能会让您的生活轻松很多。

【讨论】:

我建议避免使用 squeel。长期难以维持,有时会出现奇怪的行为。某些 Active Record 版本也有问题 我使用 squeel 已经有几年了,仍然对它很满意。但也许值得尝试另一种 ORM,例如 sequel ( squeel),它似乎有望取代 ActiveRecord。【参考方案8】:

我经常在日期字段中遇到这个问题(比较运算符很常见)。

进一步阐述米海的答案,我认为这是一种可靠的方法。

您可以向模型添加这样的范围:

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 将处理查询生成的那部分。

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

更短:

User.where("id > 200")

【讨论】:

我认为这必须是动态的,并且发布者希望通过使用参数化查询(where("id &gt; ?", 200) 语法)来避免 SQL 注入。这并没有实现。

以上是关于Rails:在 where 语句中使用大于/小于的主要内容,如果未能解决你的问题,请参考以下文章

SQL的where语句

SQL-W3School:SQL WHERE 语句

联合索引(x+y),那么sql语句的where条件上大于小于范围的哪种写法效率高?

SQL WHERE 子句

5.WHERE 子句

SQL Where 子句详解