从 now() 函数中减去小时数
Subtract hours from the now() function
我们有一台机器 运行 24x7。每天我报告它每小时生产的件数。例如,在我们的例子中,一个工作日意味着“2015-06-16 06:00:00”到“2015-06-17 06:00:00”。
这是我的代码:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count (distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
and '2015-06-17 06:00:00'
and sourceid = '44'
group by hours
order by hours asc
我的 Postgres 版本:"PostgreSQL 9.4.1, compiled by Visual C++ build 1800, 32-bit"
我正在处理的两列的数据类型:
eventtime timestamp without time zone
sourceid integer NOT NULL
时区是"Europe/Berlin"。
通过上面的查询,我得到了我想要的信息,但我必须每天更改日期。是否可以使用 now()
函数作为我的案例的默认值,这样我就不必每天手动更改日期?
您可以使用 CURRENT_DATE
:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count(distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' between CURRENT_DATE + interval '6 hour' and
CURRENT_DATE + interval '30 hour' and
sourceid = '44'
group by hours
order by hours asc;
编辑:
Erwin 的评论是关于问题而不是这个答案。使用 between
代替 date/times 是个坏主意。我想这应该在每个这样做的问题中重复。但问题是作为天数边界的 date/time 值被计算了两次。
正确的逻辑是:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count(distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' >= CURRENT_DATE + interval '6 hour' and
eventtime at time zone 'CET' < CURRENT_DATE + interval '30 hour' and
sourceid = '44'
group by hours
order by hours asc;
注意第二个限制的“<”。 Here 是一个很好的关于这个主题的博客。尽管 Aaron 专注于 SQL 服务器,但警告(和一些解决方案)也适用于其他数据库。
回答 timestamp
您需要了解数据类型 timestamp
(timestamp without time zone
) 和 timestamptz
(timestamp with time zone
) 的性质。如果您不这样做,请先阅读此内容:
- Ignoring time zones altogether in Rails and PostgreSQL
AT TIME ZONE
构造将 timestamp
转换为 timestamptz
,对于您的情况,这几乎肯定是 错误的移动 :
<strike>where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
and '2015-06-17 06:00:00'</strike>
首先,它会降低性能。将 AT TIME ZONE
应用于列 eventtime
会使表达式 而不是 sargable。 Postgres 不能在 eventtime
上使用普通索引。但即使没有索引,可搜索表达式也更便宜。调整过滤器值而不是操作每一行值。
您可以用匹配的表达式索引进行补偿,但这可能只是一种误解和错误。
那个表达式会发生什么?
AT TIME ZONE 'CET'
通过附加您当前时区的时间偏移将 timestamp
值 eventtime
转换为 timestamptz
。当使用时区 name(不是数字偏移量或缩写)时,这也会考虑 DST 规则(夏令时),因此您会得到“冬季”时间戳的不同偏移量.基本上你得到了问题的答案:
给定时区中给定时间戳对应的 UTC 时间戳是多少?
当向用户显示结果时,它被格式化为本地时间戳,并带有会话当前时区的相应时间偏移。 (可能与表达式中使用的相同,也可能不同)。
右边的字符串文字没有数据类型,所以类型是从表达式中的赋值派生的。由于现在是 timestamptz
,两者都转换为 timestamptz
,假设会话的当前时区。
当前会话的时区设置的给定时间戳对应的 UTC 时间戳是多少。
偏移量可能因 DST 规则而异。
长话短说,如果您总是使用相同的时区:CET
或'Europe/Berlin'
- 对于现在的时间戳也是如此,但对于历史或(可能)未来的时间戳则不然,你可以删减。
第二个问题 表达式:BETWEEN
is almost always wrong with timestamp
values。参见:
- Optimize BETWEEN date statement
- Find overlapping date ranges in PostgreSQL
SELECT date_trunc('hour', eventtime) AS hour
, count(DISTINCT serialnumber) AS ct -- sure you need distinct?
FROM t_el_eventlog
WHERE eventtime >= now()::date - interval '18 hours'
AND eventtime < now()::date + interval '6 hours'
AND sourceid = 44 -- don't quote the numeric literal
GROUP BY 1
ORDER BY 1;
now()
是 SQL 标准 CURRENT_TIMESTAMP
的 Postgres 实现。 return timestamptz
(不是 timestamp
!)。您可以使用任何一种。
now()::date
等同于 CURRENT_DATE
。两者都取决于当前时区设置。
您应该有一个 index 的形式:
CREATE INDEX foo ON t_el_eventlog(sourceid, eventtime)
或者,要允许仅索引扫描:
CREATE INDEX foo2 ON t_el_eventlog(sourceid, eventtime, serialnumber)
如果您在不同的时区操作,事情会变得更加复杂,您应该对所有内容使用 timestamptz
。
替代 timestamptz
在问题更新之前,时区似乎很重要。在处理不同时区时,"today" 是当前时区的函数依赖。人们往往会忘记这一点。
要仅使用会话的当前时区设置,请使用与上述相同的查询。如果在不同的时区执行,实际上结果是错误的。 (同样适用于上述内容。)
要保证给定时区('Europe/Berlin' 在您的情况下)的正确结果,无论会话的当前时区设置如何,请改用此表达式:
((now() AT TIME ZONE 'Europe/Berlin')::date - interval '18 hours')
AT TIME ZONE 'Europe/Berlin' -- 2nd time to convert back
请注意 AT TIME ZONE
构造 returns timestamp
用于 timestamptz
输入,反之亦然。
如开头所述,所有血淋淋的细节都在这里:
- Ignoring time zones altogether in Rails and PostgreSQL
我们有一台机器 运行 24x7。每天我报告它每小时生产的件数。例如,在我们的例子中,一个工作日意味着“2015-06-16 06:00:00”到“2015-06-17 06:00:00”。
这是我的代码:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count (distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
and '2015-06-17 06:00:00'
and sourceid = '44'
group by hours
order by hours asc
我的 Postgres 版本:"PostgreSQL 9.4.1, compiled by Visual C++ build 1800, 32-bit"
我正在处理的两列的数据类型:
eventtime timestamp without time zone sourceid integer NOT NULL
时区是"Europe/Berlin"。
通过上面的查询,我得到了我想要的信息,但我必须每天更改日期。是否可以使用 now()
函数作为我的案例的默认值,这样我就不必每天手动更改日期?
您可以使用 CURRENT_DATE
:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count(distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' between CURRENT_DATE + interval '6 hour' and
CURRENT_DATE + interval '30 hour' and
sourceid = '44'
group by hours
order by hours asc;
编辑:
Erwin 的评论是关于问题而不是这个答案。使用 between
代替 date/times 是个坏主意。我想这应该在每个这样做的问题中重复。但问题是作为天数边界的 date/time 值被计算了两次。
正确的逻辑是:
select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
count(distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' >= CURRENT_DATE + interval '6 hour' and
eventtime at time zone 'CET' < CURRENT_DATE + interval '30 hour' and
sourceid = '44'
group by hours
order by hours asc;
注意第二个限制的“<”。 Here 是一个很好的关于这个主题的博客。尽管 Aaron 专注于 SQL 服务器,但警告(和一些解决方案)也适用于其他数据库。
回答 timestamp
您需要了解数据类型 timestamp
(timestamp without time zone
) 和 timestamptz
(timestamp with time zone
) 的性质。如果您不这样做,请先阅读此内容:
- Ignoring time zones altogether in Rails and PostgreSQL
AT TIME ZONE
构造将 timestamp
转换为 timestamptz
,对于您的情况,这几乎肯定是 错误的移动 :
<strike>where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
and '2015-06-17 06:00:00'</strike>
首先,它会降低性能。将 AT TIME ZONE
应用于列 eventtime
会使表达式 而不是 sargable。 Postgres 不能在 eventtime
上使用普通索引。但即使没有索引,可搜索表达式也更便宜。调整过滤器值而不是操作每一行值。
您可以用匹配的表达式索引进行补偿,但这可能只是一种误解和错误。
那个表达式会发生什么?
AT TIME ZONE 'CET'
通过附加您当前时区的时间偏移将timestamp
值eventtime
转换为timestamptz
。当使用时区 name(不是数字偏移量或缩写)时,这也会考虑 DST 规则(夏令时),因此您会得到“冬季”时间戳的不同偏移量.基本上你得到了问题的答案:给定时区中给定时间戳对应的 UTC 时间戳是多少?
当向用户显示结果时,它被格式化为本地时间戳,并带有会话当前时区的相应时间偏移。 (可能与表达式中使用的相同,也可能不同)。
右边的字符串文字没有数据类型,所以类型是从表达式中的赋值派生的。由于现在是
timestamptz
,两者都转换为timestamptz
,假设会话的当前时区。当前会话的时区设置的给定时间戳对应的 UTC 时间戳是多少。
偏移量可能因 DST 规则而异。
长话短说,如果您总是使用相同的时区:CET
或'Europe/Berlin'
- 对于现在的时间戳也是如此,但对于历史或(可能)未来的时间戳则不然,你可以删减。
第二个问题 表达式: is almost always wrong with BETWEEN
timestamp
values。参见:
- Optimize BETWEEN date statement
- Find overlapping date ranges in PostgreSQL
SELECT date_trunc('hour', eventtime) AS hour
, count(DISTINCT serialnumber) AS ct -- sure you need distinct?
FROM t_el_eventlog
WHERE eventtime >= now()::date - interval '18 hours'
AND eventtime < now()::date + interval '6 hours'
AND sourceid = 44 -- don't quote the numeric literal
GROUP BY 1
ORDER BY 1;
now()
是 SQL 标准 CURRENT_TIMESTAMP
的 Postgres 实现。 return timestamptz
(不是 timestamp
!)。您可以使用任何一种。
now()::date
等同于 CURRENT_DATE
。两者都取决于当前时区设置。
您应该有一个 index 的形式:
CREATE INDEX foo ON t_el_eventlog(sourceid, eventtime)
或者,要允许仅索引扫描:
CREATE INDEX foo2 ON t_el_eventlog(sourceid, eventtime, serialnumber)
如果您在不同的时区操作,事情会变得更加复杂,您应该对所有内容使用 timestamptz
。
替代 timestamptz
在问题更新之前,时区似乎很重要。在处理不同时区时,"today" 是当前时区的函数依赖。人们往往会忘记这一点。
要仅使用会话的当前时区设置,请使用与上述相同的查询。如果在不同的时区执行,实际上结果是错误的。 (同样适用于上述内容。)
要保证给定时区('Europe/Berlin' 在您的情况下)的正确结果,无论会话的当前时区设置如何,请改用此表达式:
((now() AT TIME ZONE 'Europe/Berlin')::date - interval '18 hours')
AT TIME ZONE 'Europe/Berlin' -- 2nd time to convert back
请注意 AT TIME ZONE
构造 returns timestamp
用于 timestamptz
输入,反之亦然。
如开头所述,所有血淋淋的细节都在这里:
- Ignoring time zones altogether in Rails and PostgreSQL