我写了这个连接到dateutil.tz
的函数,请参考下面的代码:
CREATE OR REPLACE FUNCTION schema_name.fnc_name(ts timestamp without time zone, timezone character varying)
RETURNS boolean STABLE AS $$
from datetime import datetime
from dateutil.tz import gettz
tstz = ts.replace(tzinfo=gettz(timezone))
is_dst = datetime.timetuple(tstz).tm_isdst
return is_dst
$$ LANGUAGE plpythonu;
这个函数很慢,我需要在执行周期中调用超过10亿行。
我真的很喜欢redshift和timezone的东西。有人可以帮我优化一下吗? 任何绩效改进建议都值得赞赏,如:
答案 0 :(得分:2)
使用IMMUTABLE
而不是STABLE
,因为在给定输入值的情况下返回值将始终相同。来自documentation:
STABLE:给定相同的参数,该函数保证为单个语句中处理的所有行返回相同的结果。 在不同语句中调用时,该函数可以返回不同的结果。此类别允许优化器在单个语句中优化函数的多个调用,以便对语句进行单次调用。
IMMUTABLE:鉴于相同的参数,该函数始终返回相同的结果,永远。当查询使用常量参数调用
IMMUTABLE
函数时,优化程序会预先评估函数。
此外,要启用Redshift缓存结果,传入DATE
而不是TIMESTAMP
。这将减少使用的输入值的数量,以便它们更可能使用先前计算的(和缓存的)值。
答案 1 :(得分:2)
注意:这个答案是关于PostgreSQL的。大多数解决方案也应该能够应用于redshift,因为它基于较旧版本的PostgreSQL。但是,您可能需要搜索此解决方案部分的替代方法,因为我无法在redshift上测试此方法(f.ex.使用CONVERT_TIMEZONE(tz, ts)
函数而不是ts AT TIME ZONE tz
表达式。)
首先,您需要了解有多种"类型"时区。 F.ex. Europe/London
是时区名称,数据库包含有关其夏令时规则的信息。但是,时区偏移(f.ex。UTC
,UTC+2
或任何时间间隔)是静态的,永远不会被视为夏令时(neither in python)。还有时区缩写,它们只是时区偏移的别名,但是它们的DST变体有一个替代名称(f.ex。CET
在夏令时中为CEST
)所以它们永远不会(或者总是)被认为是夏令时(请注意,PostgreSQL接受(并调整)虚假的日期时间输入,例如2016-01-12 10:00 CEST
,这实际上是2016-01-12 09:00 CET
)。此外,还有POSIX风格的时区,例如EST5EDT
,它们可以有自己的夏令时规则。
对于纯SQL检测,您需要查询pg_timezone_abbrevs
和pg_timezone_names
系统视图:
create or replace function tstz_isdst(ts timestamp without time zone, tz text)
returns boolean
immutable
language sql
as $func$
with tz_info as (
select utc_offset, true fix_dst, is_dst
from pg_timezone_abbrevs
where lower(abbrev) = lower(tz)
union all
select utc_offset, false, is_dst
from pg_timezone_names
where lower(name) = lower(tz)
union all
select -coalesce(substring(tz from '([\+\-]?\d+(:\d+){1,2}(.\d+)?)')::interval,
substring(tz from '[\+\-]?\d+')::integer * interval '1 hour'),
false, false
)
select case
when fix_dst then is_dst
when ts = (ts at time zone tz at time zone 'UTC' + utc_offset) then is_dst
else not is_dst
end
from tz_info
limit 1
$func$;
select tstz_isdst('2016-01-12 10:00', 'GMT'),
tstz_isdst('2016-04-12 10:00', 'BST'),
tstz_isdst('2016-03-27 01:30', 'GMT0BST'), -- not exists
tstz_isdst('2016-10-30 01:30', 'Europe/London'); -- ambiguous
请注意,对于不存在或模糊的日期时间+时区组合(当前正在进行夏令时更改),此函数将返回false
。
但是这个函数可能仍然没有,你需要什么,因为在PostgreSQL中pg_timezone_names
视图的计算速度非常慢(在我的测试中为300-600毫秒),因此查询每一行可能不是最佳的在一张桌子里。但你可以改用联接:
select t.ts, t.tz, case
when tz_abbr.is_dst is not null
then tz_abbr.is_dst
when tz_name.utc_offset is not null
then case
when t.ts = (t.ts at time zone t.tz at time zone 'UTC' + tz_name.utc_offset)
then tz_name.is_dst
else not tz_name.is_dst
end
else t.ts <> (t.ts at time zone t.tz at time zone 'UTC' -
coalesce(substring(t.tz from '([\+\-]?\d+(:\d+){1,2}(.\d+)?)')::interval,
substring(t.tz from '[\+\-]?\d+')::integer * interval '1 hour'))
end is_dst
from (values(timestamp '2016-01-12 10:00', 'GMT'),
(timestamp '2016-04-12 10:00', 'BST'),
(timestamp '2016-03-27 01:30', 'GMT0BST'),
(timestamp '2016-10-30 01:30', 'Europe/London')) t(ts, tz)
left join pg_timezone_abbrevs tz_abbr on lower(tz_abbr.abbrev) = lower(t.tz)
left join pg_timezone_names tz_name on lower(tz_name.name) = lower(t.tz);