【问题标题】:More efficient way of comparing two tables in Redshift?在 Redshift 中比较两个表的更有效方法?
【发布时间】:2022-11-04 02:59:45
【问题描述】:

我有一系列包含填充表的逻辑的存储过程。我编辑存储过程逻辑以将新字段填充到表中。目前,为了检查存储过程如何影响表,我在更改之前获取表的完整副本并将其作为新表存储在 redshift 数据库中,例如'Table_test',这样我就可以检查行数是否相同并且列是否包含相同的数据。这似乎是一个非常低效的存储整个旧表以用于与新版本表进行比较的过程。

是否有更好/更有效的方法来比较 AWS Redshift 中的两个表?

【问题讨论】:

  • 您是否正在寻找一种更快的方法来比较这两张表?或者您是否正在寻找一种方法来比较它们而不保存表格的完整副本 - “table_test”?可能有更快的方法来比较表格,但你没有描述你现在是如何做的。还有一些方法可以创建表的“签名”,可用于查看结果是否具有相同的“签名”。这些可以针对整个表、行、列、块等进行,但您还没有说明您想要唯一标识什么样的差异 - 只是整个表匹配,列匹配?
  • 是的,这是比较两个表格的更快方法。目前,只是创建表的完整副本进行比较。我想知道如何两者兼而有之;整个表匹配和列匹配。谢谢 !
  • 请通过编辑而不是 cmets 进行澄清。

标签: sql amazon-web-services database-design amazon-redshift


【解决方案1】:

我过去为比较数据库之间的数据所做的是创建每列“类似 MD5”的签名。在您的情况下,您可以对“pre”表格内容和“post”表格内容执行类似的操作。这只会告诉您哪些列不同,但这可能就是您所需要的。

存在差异时进行调试可能很困难,但您可以将表的副本“保存”到 S3 以供调试使用。这可能会破坏您正在寻找的速度,并且您可能只想在出现问题或开启测试时以这种方式运行。您还可以按“日期”运行这样的过程,以便获得不匹配的日期和列。

由于非 Redshift 数据库并不总是像 Redshift 一样快,因此我以几种不同的方式制作了这样的签名。由于您将 Redshift 与 Redshift 进行比较,因此比较过程变得更容易和更快。在这种情况下,我要做的是为每一列执行 MD5(columnN::text),然后将 base64 结果的一部分转换为 BIGINT。然后你可以 sum() 每一列的这些值。 (SUM() 是聚合列信息并使用 MD5 结果子集的最简单方法。)由于 MD5 签名很大,使用结果子集很好,因为 MD5 散列将“唯一性”传播到结果中。溢出可能是一个问题,因此为每个值添加一个负常数可以帮助解决这个问题。生成的查询将类似于:

select 
sum(nvl(strtol(substring({{column.column_name}}, 17, 8), 16) - (1::bigint << 31), 0))
from <<CTE>>;

这是来自我用于此过程的 jinja2 模板,它允许我读取表 DDL 并将非文本列转换为 CTE 中的文本。希望这个 sn-p 足够清楚该过程是如何工作的。

================= 更新================

我觉得 jinja2 的使用有些混乱。 Jinja2 是一种模板语言,可用于根据某些输入扩展文本。它不执行任何签名和比较表的 SQL 工作。这是一种更快地为不同的表重复这项工作的方法。

下面是为表创建签名的示例:

select
(
sum(nvl(strtol(substring(USAF, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(WBAN, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(STATION_NAME, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(CTRY, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(STATE, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(ICAO, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(LAT, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(LON, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(ELEV, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(begin_date, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(END_date, 17, 8), 16) - (1::bigint << 31), 0))
)  as "hash"
from (
select
md5(USAF::text) as USAF
, md5(WBAN::text) as WBAN
, md5(STATION_NAME::text) as STATION_NAME
, md5(CTRY::text) as CTRY
, md5(STATE::text) as STATE
, md5(ICAO::text) as ICAO
, md5(LAT::text) as LAT
, md5(LON::text) as LON
, md5(ELEV::text) as ELEV
, md5(floor(extract(epoch from begin_date))::text) as begin_date
, md5(floor(extract(epoch from END_date))::text) as END_date
from public.gsod_stations
);

您可以看到,对于每一列,都需要计算一些哈希值(在本例中为 md5),并且如何完成取决于数据类型。然后将这些散列相加以创建列级散列,最后将这些列级散列相加以创建表级散列。 (1::bigint << 31 的偏移量用于帮助防止大表溢出。)这并不难理解,但是为每个表创建这个 SQL 是一件很痛苦的事情。

这就是 jinja2 的用武之地。通过模板中的 SQL 和模板中表的 DDL,jinja 可以生成 SQL。

Jinja SQL 模板(Redshift SQL 语法):

{% for table in tables %}
{%- if table.table_name == target or target is not defined %}

{% set vars = {"first_column":true} %}

select
(
{% for column in table.col_names -%}
{%- if not column.skip_compare -%}
{%- if vars.first_column -%}
  {%- if vars.update({"first_column": false}) -%} {%- endif -%}
{%- else -%}
  {% raw %}+ {% endraw %} 
{%- endif -%}
    sum(nvl(strtol(substring({{column.column_name}}, 17, 8), 16) - (1::bigint << 31), 0))
{%- else -%}
    -- skipping {{column.column_name}}
{%- endif %}
{% endfor -%}
  )  as "hash"
from (
select
{%- set vars = {"first_column":true} %}
{% for column in table.col_names -%}
{%- if not column.skip_compare -%}
{%- if vars.first_column -%}
  {%- if vars.update({"first_column": false}) -%} {%- endif -%}
{%- else -%}
  {% raw %}, {% endraw %}
{%- endif -%}
{%- if column.RS_col_type in ["date", "timestamp"] -%}
    md5(floor(extract(epoch from {{column.column_name}}))::text) as {{column.column_name}}
{%- elif column.RS_col_type in ["boolean", "bool"] -%}
    md5(({{column.column_name}}::int)::text) as {{column.column_name}}
{%- else -%}
    md5({{column.column_name}}::text) as {{column.column_name}}
{%- endif -%}
{%- else -%}
    -- skipping {{column.column_name}}
{%- endif %}
{% endfor -%}
  from {{table.RS_schema}}.{{table.table_name}}
)
;

{% endif %}
{% endfor %}

还有一个示例 json DDL 文件(包含 2 个表的信息):

{"tables":  
    [
    {"table_name":"gsod_stations", 
        "RS_schema":"public",
        "col_names": [
                        {"column_name":"USAF", "RS_col_type":"varchar(10)", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"WBAN", "RS_col_type":"integer", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"STATION_NAME", "RS_col_type":"varchar(80)", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"CTRY", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"STATE", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"ICAO", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"LAT", "RS_col_type":"float4", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"LON", "RS_col_type":"float4", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"ELEV", "RS_col_type":"float4", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"begin_date", "RS_col_type":"date", "RS_col_params":"ENCODE   zstd"},
                        {"column_name":"END_date", "RS_col_type":"date", "RS_col_params":"ENCODE   zstd"}
        ],
             "RS_sort_stmnt":"SORTKEY (USAF,WBAN)",
             "RS_dist_stmnt":"DISTKEY (USAF)"},
    {"table_name":"gsod_weather_station_data", 
        "RS_schema":"public",
        "col_names": [
                        {"column_name":"station_wban", "RS_col_type":"varchar(12)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"station_id", "RS_col_type":"varchar(10)", "RS_col_params":"NOT NULL ENCODE zstd"},
                        {"column_name":"wban", "RS_col_type":"integer", "RS_col_params":"NOT NULL ENCODE zstd"},
                        {"column_name":"yearmoda", "RS_col_type":"date", "RS_col_params":"NOT NULL ENCODE raw"},
                        {"column_name":"temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"NULL ENCODE zstd"},
                        {"column_name":"tcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"dewpoint", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"dcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"slp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"slpcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"stp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"stpcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"visibility", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"vcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"wind_speed", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"wcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"max_wind_speed", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"max_wind_gust", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"max_temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"maxtflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"min_temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"mintflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"precip", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"pflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"snow_depth", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"fog", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"rain", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"snow", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"hail", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"thunder", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"tornado", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"STATION_NAME", "RS_col_type":"varchar(80)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"CTRY", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"STATE", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"ICAO", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"LAT", "RS_col_type":"decimal(8,3)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"LON", "RS_col_type":"decimal(8,3)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"ELEV", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
                        {"column_name":"begin_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd", "skip_compare":true},
                        {"column_name":"END_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd"}
        ],
            "RS_sort_stmnt":"SORTKEY (yearmoda, station_wban)",
            "RS_dist_stmnt":"DISTKEY (station_wban)"}
    ]
}

可以运行生成的 SQL 并为每个表创建签名。您希望在表之间以及可能在数据库之间比较这些签名。这正是我开发这个过程的原因。由于您要比较的表/数据库可能会有所不同,因此您将如何进行比较也会有所不同,但基本上是比较这些哈希值以查看这些表是否包含相同的内容。

注意事项:

  • 虽然 Redshift 在生成 md5 值并将它们求和方面非常快,但对于大型数据集,其他数据库就没有那么快了。对于此类数据库,我不得不将哈希“简化”为更简单的东西。
  • 任何散列都是有损的,并且有一些有限的可能性让 2 个事物散列相同,或者在这种情况下 2 个散列的总和错误匹配。这种可能性非常低,但不是零。
  • Md5 散列非常独特,散列的每个部分也是唯一的,只是程度不同。通过选择 md5 的一部分作为总和,可以提高操作速度,同时保持非常高的置信度,即不存在错误比较。
  • 此过程只能表示“匹配”或“不匹配”。要定位差异,需要对表的子集进行一些散列和/或直接比较表值。换句话说,如果(何时)您需要调试为什么事情不匹配,那么只有旧版本表的哈希签名对您没有帮助。建议将旧版本的表存档用于此类调试目的。

【讨论】:

  • 你能分享一下这个 jinja2 模板是如何工作的吗?我在比较 2 个大表并检查内容是否相同的同一条船上。不幸的是 fnv_hash() 或校验和() 无法创建表中所有字段的哈希值。
  • 我可以扩展 jinja 并给你一些例子。但是,我现在是OOO,所以这将是几天。这里的基本思想是将所有值转换为字符串,然后获取字符串的 md5,然后将其转换为 long 并对整列求和。这段代码的 sn-p 是对 long 和 sum 的转换。
  • 已更新答案以提供示例代码和使用说明
猜你喜欢
  • 2018-12-21
  • 2013-11-19
  • 1970-01-01
  • 2014-05-15
  • 1970-01-01
  • 2022-08-12
  • 1970-01-01
  • 1970-01-01
  • 2021-04-27
相关资源
最近更新 更多