删除 Redshift 中的循环

Posted

技术标签:

【中文标题】删除 Redshift 中的循环【英文标题】:Remove loop in Redshift 【发布时间】:2021-05-31 12:51:03 【问题描述】:

我有一个表,其中包含有关我数据库中不同表的信息。对于其中的每个表,我希望(每天)进行定期日志记录。问题是我已经使用 for 循环完成了这项工作,并且在 Redshift 中执行需要花费大量时间。我给出了用于创建表、日志记录表和使用 for 循环的过程的查询。 请建议一种替代方法来实现这一点而无需 for 循环。 包含不同表信息的表结构:

CREATE TABLE public.info_schema_table
(
    info_schema_name character varying(200) ENCODE lzo,
    info_object_name character varying(200) ENCODE lzo,
    info_object_type character varying(200) ENCODE lzo,
    info_object_full_name character varying(400) ENCODE lzo
)
DISTSTYLE EVEN;

这里,object name 是指表名,object_type 包含表是视图还是表,object_full_name 存储表的连接名称和模式,即如果模式名称是“test_schema”,那么全名是“test_schema” .table_name”。需要存储日志的表结构:

CREATE TABLE public.redshift_logging_table
(
    log_schema_name character varying(30) ENCODE lzo,
    log_object_name character varying(30) ENCODE lzo,
    log_object_type character varying(30) ENCODE lzo,
    log_refresh_date date ENCODE az64,
    log_refresh_count bigint ENCODE az64,
    log_total_count bigint ENCODE az64
)
DISTSTYLE EVEN;

这里,refresh_date 存储记录日期,refresh_count 存储在特定日期插入表中的记录数,total_count 包含到记录日期为止表中的记录总数。

为了澄清,这里是 info_schema_table 的一个示例记录:

这是我用来填充记录表的过程:

CREATE OR REPLACE PROCEDURE public.REDSHIFT_LOGGING_PROCEDURE()
AS $$
DECLARE
var_total_count bigint;
var_records_today bigint;
my_row record;
my_cursor CURSOR  
FOR select  info_schema_name, info_object_name, info_object_type , info_object_full_name  from INFO_SCHEMA_TABLE ;
BEGIN
  open my_cursor;
  LOOP
        FETCH my_cursor INTO my_row;
        EXIT WHEN NOT FOUND;
            BEGIN

             EXECUTE ' select count(1)::bigint  from '||my_row.INFO_OBJECT_FULL_NAME INTO var_total_count;
             var_records_today=var_total_count-(select log_total_count  from REDSHIFT_LOGGING_TABLE where REFRESH_DATE=current_date-1 and LOG_OBJECT_NAME=my_row.INFO_OBJECT_NAME);
             
             insert into REDSHIFT_LOGGING_TABLE
              (LOG_SCHEMA_NAME,LOG_OBJECT_NAME,LOG_OBJECT_TYPE,LOG_REFRESH_DATE, LOG_REFRESH_COUNT, LOG_TOTAL_COUNT )
              values 
              (my_row.info_schema_name, my_row.info_object_name, my_row.info_object_type , current_date, var_records_today, var_total_count);
            END;

  END LOOP;
END;
$$ LANGUAGE plpgsql
SECURITY INVOKER;

所有表的记录数每天都在增加,因此在任何特定日期插入的记录始终 >=0。 问题是,此过程有效,但仅适用于 info_schema_table 中的小记录,如果我对大约 1000 条记录运行它,即使在一小时内,该过程也不会在 Redshift 上完成。

请建议一种替代方法来执行它而不使用 for 循环。谢谢。

【问题讨论】:

我首先要将此循环重写为单个(希望是)INSERT 操作。似乎可以实现,给我一些时间。 未来你也可以考虑改变 diststyles 以避免在节点之间移动数据。 【参考方案1】:

好的,我们开始吧。

insert 正在工作,它显示了这如何在一个语句中工作:

insert into redshift_logging_table(log_schema_name, log_object_name, log_object_type, log_refresh_date, log_refresh_count, log_total_count)
select distinct 
    ist.info_schema_name, 
    ist.info_object_name, 
    ist.info_object_type, 
    current_date
    , sti.tbl_rows - last_value(log_total_count ignore nulls) over (
        partition by log_object_name order by log_refresh_date asc
        rows between unbounded preceding and unbounded following
    )
    , sti.tbl_rows
from svv_table_info sti
inner join info_schema_table ist on ist.info_schema_name = sti.schema and ist.info_object_name = sti.table
left join redshift_logging_table rlt on rlt.log_schema_name = ist.info_schema_name and rlt.log_object_name = ist.info_object_name;

您可以仅使用select 而不使用insert 来验证它是如何工作的,这很好,因为它会显示将插入的内容而不实际执行。


这里发生了什么: 从from开始:

info_schema_table 加入到 svv_table_info 以获取实际行数。它允许避免为每一行动态创建select count 语句; 接下来,对 redshift_logging_table 进行左连接 - 在某些对象尚不存在(新创建的等)的情况下使用左连接

select:

使用了last_value 窗口函数(参见https://docs.aws.amazon.com/redshift/latest/dg/r_WF_first_value.html),它允许从partition byorder by 描述的某些行中的特定列中查找值)。此值是为每个对象名称分别计算的日志表中的最后一次刷新计数。

您可能需要以一种或另一种方式对其进行调整,但我认为这是一个好的开始。此外,这可以只是移动到您的存储过程中。

享受吧!

【讨论】:

此查询运行良好。非常感谢@Adam Tokarski 帮助我。但是有什么方法可以通过动态获取列info_object_full_name 中不同表的计数来实现这一点,因为 svv_table_info tbl_rows 和实际数据存在一些差异。实际上,我想在不使用 svv 表的情况下执行此操作,而是使用 info_object_full_name 列。我问这个是因为我还有其他这样的程序要优化,它们使用 for 循环,并在它们的列上完成一些计算,然后插入到另一个表中。 在谈论计数时,您总是可以预料到这样的差异,尤其是在谈论 Redshit 时(参见 Max Ganz II (***.com/a/67778456/5172513) 的精彩回答)。但是如果想直接在每个表上做一些count(*),你需要构建动态sql,它可以在子查询中完成,但这又会很慢。可能不像循环那么慢,但仍然如此。 另外,我忘了提及 - 如果您在处理过程中出现漏洞,您的原始解决方案将失败 - 例如,如果有一天它因为 where REFRESH_DATE=current_date-1 而失败。在使用 Windows 功能的解决方案中 - 例如,参见我的 - 无论是昨天、上周还是去年,都有上次出现的使用值。所以应该更安全。 感谢@Adam Tokarski 的宝贵建议。我现在正在使用此查询进行日志记录,并且运行良好。除了有时不同表的 svv_table_info 表中的统计信息不会刷新,并且与实际计数存在一些差异。是否有任何查询可以包含在我的程序中,可以在我的实际程序开始之前刷新这些统计信息?非常感谢!!

以上是关于删除 Redshift 中的循环的主要内容,如果未能解决你的问题,请参考以下文章

Redshift 中的嵌套 While 循环

删除 Redshift 架构中的所有表 - 不删除权限

删除 Redshift 查询中的垂直制表 (vt)

Redshift:如何删除不可打印的字符

RedShift:删除语句不允许表别名?

AWS Redshift 可以删除包含在事务中的表吗?