红移枢轴函数

Redshift Pivot Function

我有一个类似的 table,我正试图在 Redshift 中进行调整:

UUID Key Value
a123 Key1 Val1
b123 Key2 Val2
c123 Key3 Val3

目前我正在使用下面的代码来旋转它并且它工作正常。但是,当我用子查询替换 IN 部分时,它会抛出错误。

select * 
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
'Key1',
'Key2',
'Key3
))

问题:用从 Key 列获取不同值的子查询替换 IN 部分的最佳方法是什么?

我想要达到的目标;

select * 
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
select distinct "keys" from tbl
))

来自 Redshift 文档 - “PIVOT IN 列表值不能是列引用或 sub-queries。每个值的类型必须与 FOR 列引用兼容。”参见:https://docs.aws.amazon.com/redshift/latest/dg/r_FROM_clause-pivot-unpivot-examples.html

所以我认为这需要作为 2 个查询的序列来完成。如果您需要它作为单个命令,您可能可以在存储过程中执行此操作。

更新了请求的存储过程,结果为游标示例:

为了得到您的支持table,我将添加一些背景信息和有关其工作原理的说明。首先,存储过程不能直接在您的工作台上产生结果。它可以将结果存储在 (temp) table 或命名游标中。游标只是将查询结果存储在等待获取的领导节点上。游标的生命周期是当前事务,因此提交或回滚将删除游标。

这是您希望作为单个 SQL 语句发生的情况,但首先让我们设置测试数据:

create table test (UUID varchar(16), Key varchar(16), Value varchar(16));

insert into test values
('a123', 'Key1', 'Val1'),
('b123', 'Key2', 'Val2'),
('c123', 'Key3', 'Val3');

您要执行的操作是首先为 PIVOT 子句 IN 列表创建一个字符串,如下所示:

select '\'' || listagg(distinct "key",'\',\'') || '\'' from test;

然后你想把这个字符串插入到你的 PIVOT 查询中,它应该如下所示:

select * 
from (select UUID, "Key", value from test) 
PIVOT (max(value) for "key" in ( 'Key1', 'Key2', 'Key3')
);

但是在工作台上执行此操作意味着将一个查询的结果和 copy/paste-ing 放入第二个查询中,并且您希望它自动发生。不幸的是,由于上述原因,Redshift 确实允许在 PIVOT 语句中使用 sub-queries。

我们可以获取一个查询的结果,并用它来构造 运行 存储过程中的另一个查询。这是这样一个存储过程:

CREATE OR REPLACE procedure pivot_on_all_keys(curs1 INOUT refcursor)
AS
$$
DECLARE 
  row record;
BEGIN
  select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
  OPEN curs1 for EXECUTE 'select *
    from (select UUID, "Key", value from test) 
      PIVOT (max(value) for "key" in ( ' || row.keys || ' )
  );';
END;
$$ LANGUAGE plpgsql;

此过程的作用是使用生成 IN 列表的查询结果定义并填充名为“行”的“记录”(1 行数据)。接下来它打开一个游标,其名称由调用命令提供,其中包含使用记录“行”中的 IN 列表的 PIVOT 查询的内容。完成。

执行时(通过 运行ning 调用)此函数将在包含 PIVOT 查询结果的领导节点上生成游标。在此存储过程中,要创建的游标的名称作为字符串传递给函数。

call pivot_on_all_keys('mycursor');

此时需要做的就是从命名游标中“获取”数据。这是通过 FETCH 命令完成的。

fetch all from mycursor;

我在单节点 Redshift 集群上对此进行了原型设计,但此配置不支持“FETCH ALL”,因此我不得不使用“FETCH 1000”。因此,如果您也在单节点集群上,则需要使用:

fetch 1000 from mycursor;

最后一点要注意的是,游标“mycursor”现在存在,如果您尝试重新运行 存储过程,它将失败。您可以将不同的名称传递给该过程(创建另一个游标),或者您可以结束事务(END、COMMIT 或 ROLLBACK),或者您可以使用 CLOSE 关闭游标。销毁游标后,您可以为新游标使用相同的名称。如果你想让它成为 repeatable 你可以 运行 这批命令:

call pivot_on_all_keys('mycursor'); fetch all from mycursor; close mycursor;

请记住,游标具有当前事务的生命周期,因此任何结束事务的操作都将销毁游标。如果您在工作台中启用了 AUTOCOMMIT,这将插入破坏游标的 COMMIT(您可以 运行 批处理中的 CALL 和 FETCH 以防止在许多工作台中出现这种情况)。还有一些命令执行隐式 COMMIT 并且也会破坏游标(如 TRUNCATE)。

出于这些原因,并根据您需要围绕 PIVOT 查询执行的其他操作,您可能希望将存储过程写入临时 table 而不是游标。然后可以查询temptable得到结果。 temp table 具有会话的生命周期,因此有点粘性但效率稍低,因为需要创建 table,需要将 PIVOT 查询的结果写入计算节点,然后必须将结果发送到领导节点以产生所需的输出。只需要为工作选择合适的工具。

===================================

要在存储过程中填充 table,您只需执行命令即可。整个事情看起来像:

CREATE OR REPLACE procedure pivot_on_all_keys()
AS
$$
DECLARE 
  row record;
BEGIN
  select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
  EXECUTE 'drop table if exists test_stage;';
  EXECUTE 'create table test_stage AS select *
    from (select UUID, "Key", value from test) 
      PIVOT (max(value) for "key" in ( ' || row.keys || ' )
  );';
END;
$$ LANGUAGE plpgsql;

call pivot_on_all_keys();
select * from test_stage;

如果您希望这个新的 table 具有用于优化下游查询的键,您需要在一个语句中创建 table 然后插入其中,但这是快速路径。

有点off-topic,但我想知道为什么亚马逊不能为 pivot 引入更简单的语法。 IMO,如果 GROUP BY 被 PIVOT BY 取代,它可以给解释器足够的提示来将行转换为列。例如:

SELECT partname, avg(price) as avg_price FROM Part GROUP BY partname;

可以写成:

SELECT partname, avg(price) as avg_price FROM Part PIVOT BY partname;

甚至 multi-level 旋转也可以用相同的语法处理。

SELECT year, partname, avg(price) as avg_price FROM Part PIVOT BY year, partname;