Postgres 9.5.3 JSON字段将日期与运营商在Mac OSX 10上的奇怪行为进行比较

时间:2016-11-22 23:58:27

标签: ruby-on-rails macos postgresql activerecord rails-activerecord

我的两台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查询但问题相同。

1 个答案:

答案 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是正确的方法吗?单独的表可能更适合。