【问题标题】:Optimization: Amazon Redshift funcitons to check if daylight savings is in effect优化:Amazon Redshift 功能检查夏令时是否有效
【发布时间】:2016-07-29 14:06:29
【问题描述】:

我写了这个连接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;

这个函数很慢,我需要在一个执行周期中为超过十亿行调用它。

我对红移和时区的东西真的很陌生。有人可以帮我优化吗? 感谢任何性能改进建议,例如:

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

【问题讨论】:

  • 顺便说一句,一个流行的约定是始终以 UTC 存储日期/时间,这有利于避免时区问题。
  • 您能否提供更多信息,说明您为什么需要这个?是否可以更改数据在原始表中的存储方式以避免将来不得不这样做?
  • 我的整个想法是时区标准化。我必须这样做。我只有 UTC 时区。

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


【解决方案1】:

使用IMMUTABLE 而不是STABLE,因为在给定输入值的情况下,返回值将始终相同。来自documentation

STABLE: 给定相同的参数,该函数保证为单个语句中处理的所有行返回相同的结果。 函数在不同语句中调用时可以返回不同的结果。此类别允许优化器将单个语句中函数的多次调用优化为对该语句的单个调用。

IMMUTABLE:给定相同的参数,函数总是返回相同的结果,永远。当查询调用带有常量参数的IMMUTABLE 函数时,优化器会预先评估该函数。

此外,为了使 Redshift 能够缓存结果,传入 DATE 而不是 TIMESTAMP。这将减少使用的输入值的数量,因此它们更有可能使用先前计算(和缓存)的值。

【讨论】:

  • 所以简而言之,我应该将每个时区的函数结果保存在一个表中并在我的查询中进行连接?
  • 没有。 Redshift 将为您做到这一点。我建议您更改使用 IMMUTABLE 而不是 STABLE,并传入 DATE 而不是 TIMESTAMP。您可能需要调整代码以将 DATE 转换为所需的格式。最终结果:更少的输入值,这有助于 Redshift 自动缓存结果,这应该会加快速度。
  • 那是错误的。现在举个例子,今年 DST 于 3 月 13 日星期日的 2:00 AM 开始,通过以这种方式编写函数(没有时间传递日期),我错过了那些 2 小时。或者我听不懂你说的。
  • 啊!是的,如果它从非午夜边界开始,它将无法按您的意愿运行。
  • 我会尝试去掉分钟部分,只保留时间戳直到小时,希望能加快查询(按您的逻辑)。再次感谢。
【解决方案2】:

看看CONVERT_TIMEZONE函数

【讨论】:

  • 感谢您的回答。我知道该功能,这将如何帮助我了解特定日期的夏令时是否有效?
  • CONVERT_TIMEZONE 将遵守夏令时。你打算用 is_dst 做什么?您要解决的更普遍的问题是什么?
【解决方案3】:

注意:这个答案是关于 PostgreSQL 的。大多数解决方案也应该能够应用于 redshift,因为它基于旧版本的 PostgreSQL。但是,您可能需要为此解决方案的某些部分寻找替代方案,因为我无法在 redshift 上对此进行测试(例如,使用 CONVERT_TIMEZONE(tz, ts) 函数而不是 ts AT TIME ZONE tz 表达式)。

首先,您需要了解时区有多种“类型”。前任Europe/London 是一个时区名称,数据库有关于其夏令时规则的信息。但是,时区偏移量(例如UTCUTC+2 或任何时间间隔)是静态的,永远不会被视为夏令时 (neither in python)。还有时区缩写,它们只是时区偏移的别名,但它们的 DST 变体有一个备用名称(例如 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);

【讨论】:

  • 非常感谢。我会试试的。
  • 对不起,正如你所说,它是 postgres 的解决方案。
  • @DeepanshuKalra 你把AT TIME ZONEs 改成CONVERT_TIMEZONE()了吗?错误信息是什么?
  • 我什至没有到达那里。因为'redshift 中没有 pg_timezone_abbrevs 或 pg_timezone_names。
  • 感谢您冗长的详细回答,尽管我很想知道,但由于时间关系,我创建了一个静态表并填充了 2040 年之前的数据。我正在加入该表这样就解决了目的。
猜你喜欢
  • 2012-05-26
  • 2013-12-23
  • 2018-01-12
  • 2015-02-26
  • 2012-08-06
  • 1970-01-01
  • 2019-12-02
  • 2020-05-08
相关资源
最近更新 更多