我的两台Mac机都遇到了问题,但我的Linux机器上却没有。使用像这样的日期比较运算符在我的Mac上不起作用:
# Ruby code
dt_start = DateTime.current - 10.days
dt_end = DateTime.current
id = 1
# last_seen field looks like this in db when we store it:
# {"1":"2016-11-21T22:17:47.269Z"}
User.where("(last_seen->'?')::text <> 'null'", id
).where( "(last_seen->'?')::text > ?", id, dt_start
).where( "(last_seen->'?')::text <= ?", id, dt_end)
SELECT "public"."users".* FROM "public"."users" WHERE ((last_seen->'1')::text <> 'null') AND ((last_seen->'1')::text > '2016-11-12 18:13:03.432534') AND ((last_seen->'1')::text <= '2016-11-22 18:13:03.432534')
在我的Mac上没有返回记录,但在Linux上运行
在拆分该查询后,当我使用>
运算符时,无论我放置的日期范围如何,都不会记录任何记录。
User.where( "(last_seen->'?')::text > ?", id, 10.years.ago).count
SELECT COUNT(*) FROM "public"."users" WHERE ((last_seen->'1')::text > '2006-11-22 23:46:59.199255')
=> 0
当我只使用<
运算符时,无论日期是什么日期,我都会获得所有非空last_seen
字段的记录。
User.where( "(last_seen->'?')::text < ?", id, 10.years.ago).count
SELECT COUNT(*) FROM "public"."users" WHERE ((last_seen->'1')::text > '2006-11-22 23:46:59.199255')
=> 42
我甚至通过在Mac上切换时间来测试我的linux时区即UTC。有什么想法吗?
更新 因此,格式化为ISO 8601的DateTime和ActiveSupport :: TimeWithZone返回不同的格式:
DateTime.current.iso8601 # => "2016-11-23T19:18:36+00:00"
Time.zone.now.iso8601 # => "2016-11-23T19:18:44Z"
由于last_seen
JSON字段使用ActiveSupport :: TimeWithZone存储日期,我尝试更改SQL查询以匹配该格式,但同样的问题:
last_seen: {"1"=>"2016-10-20T14:30:00Z"}
SELECT COUNT(*) FROM "public"."users" WHERE ((last_seen->'1')::text <> 'null') AND ((last_seen->'1')::text > '2016-01-23T19:03:11Z') AND ((last_seen->'1')::text <= '2016-11-23T19:01:10Z')
=> 0
然后我更改了last_seen
JSON以使用DateTime获得第二种格式,并使用DateTime查询但问题相同。
答案 0 :(得分:0)
您说您的JSON列包含以下内容:
{"1":"2016-11-21T22:17:47.269Z"}
该对象中的值是真正的ISO-8601时间戳。 ActiveRecord正在生成的查询:
SELECT "public"."users".*
FROM "public"."users"
WHERE ... ((last_seen->'1')::text > '2016-11-12 18:13:03.432534') ...
使用的是非完全ISO-8601时间戳,而不是T
中日期和时间组件之间缺少的'2016-11-12 18:13:03.432534'
。您text
比较的结果将取决于'T'
和' '
的比较方式,并且不能保证您希望它是什么,甚至不能跨平台保持一致。
如果您打算做这类事情,您需要确保格式一致。我会选择严格的ISO-8601,因为这是One True Timestamp格式,它会在任何地方表现一致。 #iso8601
方法会为您处理格式:
User.where("(last_seen->'?')::text <> 'null'", id)
.where( "(last_seen->'?')::text > ?", id, dt_start.iso8601)
.where( "(last_seen->'?')::text <= ?", id, dt_end.iso8601)
自己调用#iso8601
将为ActiveRecord提供一个字符串,这样您就可以绕过AR想要使用的任何时间戳到字符串格式。如果你的一秒精度不够好,precision
也会有一个iso8601
参数。
顺便说一下,你确定JSON是正确的方法吗?单独的表可能更适合。