优化:亚马逊Redshift功能,以检查夏令时是否有效

时间:2016-04-08 10:33:23

标签: python sql function query-optimization amazon-redshift

我写了这个连接到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的东西。有人可以帮我优化一下吗? 任何绩效改进建议都值得赞赏,如:

  1. 以某种方式将时区详细信息移至本地数据库? (告诉我怎么做)
  2. 不要使用Python,使用别的东西(告诉我什么)

2 个答案:

答案 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。UTCUTC+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_abbrevspg_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);