Rails `where` 的时间少于查询
Posted
技术标签:
【中文标题】Rails `where` 的时间少于查询【英文标题】:Rails `where` for time less than queries 【发布时间】:2014-07-19 03:05:50 【问题描述】:设置
Rails 的where
方法可以采用散列中的范围来生成查询,该查询将搜索范围内的值。例如:
User.where(cash_money: 10..1000)
#=> SELECT `users`.* FROM `users` WHERE (`users`.`cash_money` BETWEEN 10 AND 1000)
这也可以与时间戳一起使用
User.where(last_deposit: 10.days.ago..1000.days.ago)
#=> SELECT `users`.* FROM `users` WHERE (`users`.`last_deposit` BETWEEN '2014-05-19 14:42:36' AND '2011-09-02 14:42:36')
我发现您可以使用这样的哈希语法对数字进行简单的小于或大于操作
User.where(cash_money: 10..Float::INFINITY)
#=> SELECT `users`.* FROM `users` WHERE (`users`.`cash_money` >= 10)
对于少于查询,-Float::INFINITY
也可以这样做。
问题
有没有办法用时间戳做到这一点,以便我可以得到如下查询?
SELECT `users`.* FROM `users` WHERE (`users`.`last_deposit` >= '2014-05-19 14:42:36')
我不能将Float::INFINITY
或Date::Infinity
与范围一起使用,因为它们都与ArgumentError: bad value for range
出错。
当前的简单解决方案
User.where('`users`.`last_deposit` >= ?', 10.days.ago)
将生成相同的 SQL,但如果这可以使用字符串以外的对象完成,我愿意这样做。
潜在(Meh)答案
这有点糟糕,但可以通过使用Time.at(0)
和Time.at(Float::MAX)
的范围来完成。我感觉这些可能会导致同样糟糕的 SQL 查询。
【问题讨论】:
我最讨厌 Active Record 的一个方面是 where() 子句中对 gt/lt-or-equals(或范围)没有明显和直观的支持!传递像“column >= ?”这样的字符串只是让我想知道为什么我不首先自己输入查询。 我的回答 on this question 提供了截至撰写本文时最新版本的 Ruby 和 Rails(分别为 3.0.2 和 6.1)的更多详细信息。 【参考方案1】:编辑 2 5/9/20
如果您使用的是 Ruby 2.6,则可以使用无限范围来执行此操作,而在 Ruby 2.7 中,您可以使用无开始范围。
例如:
# Ruby >= 2.6
User.where(last_deposit: 10.days.ago..)
生成
SELECT "users".* FROM "users" WHERE "user"."last_deposit" >= '2020-04-29 21:58:39.109419'"
和
# Ruby >= 2.7
User.where(last_deposit: ..10.days.ago)
生成
SELECT "users".* FROM "users" WHERE "users"."last_deposit" <= '2020-04-29 22:01:05.582055'
编辑
这在 Rails 5 中是可能的!
User.where(last_deposit: 10.days.ago..DateTime::Infinity.new)
将生成 SQL
SELECT `users`.* FROM `users` WHERE (`users`.`last_deposit` >= '2018-06-30 17:08:54.130085').
原始(和 Rails
似乎没有办法使用基本的where
哈希语法来生成大于或小于时间戳的查询。我在Current Simple Solution
下的问题中概述了最简单和最易读的方式。
另一种方法是使用 ARel,但您必须进行一些不太常见的调用。首先,您可以获得 AR 类的 ARel 表的句柄,访问该列,传递大于gt
、大于或等于gteq
、小于lt
和/或小于或的结果等于lteq
方法,参数为where
。
在上述情况下,这将是这样的:
last_deposit_column = User.arel_table[:last_deposit]
last_deposit_over_ten_days_ago = last_deposit_column.gteq(10.days.ago)
User.where(last_deposit_over_ten_days_ago)
【讨论】:
冗长不是必需的,可以在一行上完成。我只是使用变量来明确每个步骤返回的内容。 使用User.where(last_deposit: ..10.days.ago)
之前的语法给我异常语法错误,意外')'
@ThangLeQuoc 您使用的是哪个版本的 Ruby? ruby --version
我在ruby 2.6.5p114 (2019-10-01 revision 67812)
无限范围在 Ruby 2.6+ 中并且在 Ruby 2.7 中引入了无限范围,因此您的系统没有任何问题。【参考方案2】:
您需要使用适当的无穷大。时间戳是DateTime
而不是Date
。请改用DateTime::Infinity.new
或DateTime::Infinity.new(-1)
表示负无限。
相关:Is there a way to express 'Infinite Time' in Ruby?
【讨论】:
有趣。我不知道Datetime::Infinity
。奇怪的是它必须被实例化为Float::INFINITY
没有。我意识到一个是浮点值的常量,另一个是类,但如果它是对称的,会更容易记住。
我可以确认它不适用于 Rails 4。 User.where(created_at: 1.day.ago..DateTime::Infinity.new)
导致 mysql 错误,因为它以 #<Date::Infinity:0x0...>
作为结束边界,而不是转换为使用 >
或其他内容。
奇怪,可能是 MySQL 的东西。哦,好吧,像这样的情况,当无穷大不能很好地工作时,我只是使用遥远的未来和/或遥远的过去。 User.where(created_at: 1000.years.ago..1000.years.from_now)
。虽然这并不比Time.at()
解决方案好多少。尝试转换为 dateTIme,但出现了类似的奇怪错误。注意到User.where(created_at: 1.day.ago.to_date..Float::INFINITY)
有效,可能还会觉得有趣。
您是偶然使用 Rails 5 的吗?我的直觉说,当查询从 Ruby 对象到 SQL 字符串时,它没有被正确转换为数据库可以理解的东西,因此 Ruby 对象的检查输出为 #<Date::Infinity:0x0...object_address>
格式。这可以在 Rails 5 中更新,这就是它在某些情况下有效的原因。
很有可能,我在 Rails 4 和 5 中有应用程序。虽然不确定这是否是问题所在。需要调查的东西。【参考方案3】:
你试过了吗?:
User.where(last_deposit: Time.at(0)...10.days.ago)
SQL:
SELECT `users`.* FROM `users` WHERE (`users`.`last_deposit` >= '1970-01-01 00:00:00' AND `users`.`last_deposit` < '2015-01-10 17:15:19')
【讨论】:
这适用于User.where('
users.
last_deposit` >= 案例。将其切换到10.days.ago...Time.at(Float::INFINITY)
也不起作用。它生成 SQL 但日期无效(至少对于 PG)。
范围限制之间的三个点让您在 'from' 值上获得一个 >=,在 'to' 值上使用 'AND
【参考方案4】:
试试这个:
User.where(last_deposit.gt(10.days.ago))
【讨论】:
last_deposit
不是必须是 ARel 列引用才能使其工作吗?
last_deposit = User.arel_table[:last_deposit]
我相信。
就目前而言,这个答案不完整/不正确 - 请参阅 Aaron 的 cmets。以上是关于Rails `where` 的时间少于查询的主要内容,如果未能解决你的问题,请参考以下文章
努力优化 Rails WHERE NOT IN 在 Rails 中的查询
Rails 在带有连接的 where 子句中运行 AND 查询
Rails N + 1查询问题时获取与where条件关联的记录