这是我在使用 PostgreSQL 9.1 和 Rails 3.0.7 时遇到的最令人费解的问题。我在同一个 Ubuntu 10.04 vbox 客户机上运行 Postgres 和 Rails 3:
"PostgreSQL 9.1.3 on i686-pc-linux-gnu, compiled by gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32-bit"
可能应用程序需要运行一个基于 created_at 时间戳的简单查询,该时间戳是基于服务器的时区的一天。就我而言,东部时间。我尝试了“with time zone”和“at time zone”的不同变体,并让它在 pgAdmin 中正常工作,但在 Rails 网站中却没有。
这是带有 ARel 查询的类方法。
def self.transactions2(business_id, begin_date, end_date )
trans = LogVouchers.select( %{
created_at
} ).
where( %{
created_at >= ( TIMESTAMP WITH TIME ZONE ? at time zone 'utc')::timestamp
and created_at < (TIMESTAMP WITH TIME ZONE ? at time zone 'utc')::timestamp
},
begin_date,
end_date )
end
这是控制器代码:
def voucher_transactions
default_date = Time.zone.now.beginning_of_day
@end_date = params[:end_date].blank? ? \
Date.new(default_date.year, default_date.month, default_date.day ) : \
Date.new(params[:end_date][:year].to_i, params[:end_date][:month].to_i, \
params[:end_date][:day].to_i)
@begin_date = params[:begin_date].blank? ? \
Date.new(default_date.year, default_date.month, default_date.day ) : \
@begin_date = Date.new(params[:begin_date][:year].to_i, \
params[:begin_date][:month].to_i, params[:begin_date][:day].to_i)
@data2 = LogVouchers.transactions2(business_id, @begin_date, @end_date + 1.day)
...
end
这是 rails 日志文件中的实际 SQL 代码和调试检查:
LogVouchers Load (0.7ms) SELECT
created_at
FROM "log_vouchers" WHERE (
created_at >= ( TIMESTAMP WITH TIME ZONE '2012-04-28' at time zone 'utc')::timestamp
and created_at < (TIMESTAMP WITH TIME ZONE '2012-04-29' at time zone 'utc')::timestamp
)
### 2012-05-01 21:18:04 -0400 voucher_transactions() @data2 =[
#<LogVouchers created_at: "2012-04-28 03:14:29">
, #<LogVouchers created_at: "2012-04-28 03:15:24">
, #<LogVouchers created_at: "2012-04-28 03:18:19">
, #<LogVouchers created_at: "2012-04-28 03:38:35">
, #<LogVouchers created_at: "2012-04-28 03:58:08">
, #<LogVouchers created_at: "2012-04-28 15:44:46">
, #<LogVouchers created_at: "2012-04-28 17:12:20">
, #<LogVouchers created_at: "2012-04-28 18:46:45">
, #<LogVouchers created_at: "2012-04-28 18:55:47">
, #<LogVouchers created_at: "2012-04-28 18:57:52">
, #<LogVouchers created_at: "2012-04-28 19:02:15">
, #<LogVouchers created_at: "2012-04-28 19:02:50">
, #<LogVouchers created_at: "2012-04-28 19:46:36">
, #<LogVouchers created_at: "2012-04-28 19:47:17">
, #<LogVouchers created_at: "2012-04-28 19:50:22">
, #<LogVouchers created_at: "2012-04-28 19:50:56">]
但是,如果我直接在 pgAdmin 的查询窗口上运行 SQL 代码,我会得到正确的结果:
"2012-04-28 15:44:46.641305"
"2012-04-28 17:12:20.615641"
"2012-04-28 18:46:45.277561"
"2012-04-28 18:55:47.40109"
"2012-04-28 18:57:52.616501"
"2012-04-28 19:02:15.542964"
"2012-04-28 19:02:50.888847"
"2012-04-28 19:46:36.16556"
"2012-04-28 19:47:17.084047"
"2012-04-28 19:50:22.672805"
"2012-04-28 19:50:56.376571"
请注意,UTC 时间“2012-04-28 03:14:29”附近的 created_at 的 5 条记录不在正确的结果集中。
现在最令人费解的问题是:
Rails 如何将相同的 SQL 语句发送到 Postgres 数据库并得到不同的结果?
我显然在这里遗漏了一些东西。有大神可以帮忙吗?
附加说明 2012-5-2
背景:默认情况下,rails 3.0 在所有 db 表中生成 created_at 列作为 Postgres 中的“没有时区的时间戳”。我需要根据我的服务器时区“美国/纽约”运行每日报告。由于涉及的表数量,我使用 ARel select 方法来连接多个表并传入 begin_date 和 end_date 时间戳。我很难得到正确的结果集返回。它应该返回带有 UTC 时间戳的记录:
2012-04-28 04:00:00 to 2012-04-29 04:00:00
但它返回了带有 UTC 时间戳的记录:
2012-04-28 00:00:00 to 2012-04-29 00:00:00
我能想到的是改变类方法:
def self.transactions2(business_id, begin_date, end_date )
st_tz_offset = Time.zone.now.formatted_offset(true)
st_begin_date_tz = "#{begin_date} 00:00:00#{st_tz_offset}"
st_end_date_tz = "#{end_date} 00:00:00#{st_tz_offset}"
trans = LogVouchers.select( %{
created_at
} ).
where( %{
created_at >= ( TIMESTAMP WITH TIME ZONE ? at time zone 'utc')
and created_at < (TIMESTAMP WITH TIME ZONE ? at time zone 'utc')
},
st_begin_date_tz ,
st_end_date_tz )
end
实际上,它告诉 Postgres 在比较之前将其转换timestamp with time zone
为timestamp without time zone
。这对我有用。我知道这太复杂了。我非常感谢有人指出实现这一目标的更好方法。
附加说明 2
- 如果可能,我会避免更改 Ubuntu/Linux 服务器、Postgres 服务器中的默认设置,因为它往往会导致其他副作用。
- 将来,这些每日截止时间戳将基于服务器的
TimeZone
设置current_user
而不是TimeZone
服务器的设置。因为同一个 Web 应用程序的用户可以跨越多个时区,并且每日报告必须对各自的登录用户有意义。