【问题标题】:Append results from a query to the same result row in PostgreSQL - Redshift将查询结果附加到 PostgreSQL 中的同一结果行 - Redshift
【发布时间】:2015-01-23 05:13:45
【问题描述】:

我有一个表,有 3 列 A, B , C - 其中 A 不是主键。我们需要为每个不同的 A(按 A 分组)选择 B、C 对,并将结果附加到最终结果集的末尾。这在sql中可以吗?

A | B | C
a1| b1| c1
a1| b2| c2
a1| b3| c3
a2| b1| c2
a2| b2| c5

我需要得到

a1 | (c1,b1) ; (c2,b2);(c3;b3) 
a2 | (c2,b1) ; (c5,b2) 

作为末尾附加的行。 我通常通过 sqlalchemy 执行此操作,然后最终在 Python 中转换数据,有没有一种方法可以直接在 SQL 中执行此操作?

编辑并提出问题: 什么是红移(Postgres 8.0.2)中 string_agg() 的替代方法 - 有关上述用例的更多信息。

使用 string_agg 我得到ERROR: function string_agg(text, "unknown") does not exist Hint: No function matches the given name and argument types. You may need to add explicit type casts

编辑2:使用自定义聚合函数添加错误

An error occurred when executing the SQL command:
CREATE FUNCTION cut_semicolon(text) RETURNS text AS $$
BEGIN
  RETURN SUBSTRING($1 FROM 4)

ERROR: unterminated dollar-quoted string at or near "$$
BEGIN
  RETURN SUBSTRING($1 FROM 4)"
  Position: 53

CREATE FUNCTION cut_semicolon(text) RETURNS text AS $$
                                                    ^

Execution time: 0.24s
(Statement 1 of 7 finished)

0 rows affected
END executed successfully

Execution time: 0.22s
(Statement 2 of 7 finished)

An error occurred when executing the SQL command:
$$ LANGUAGE 'plpgsql' IMMUTABLE

ERROR: unterminated dollar-quoted string at or near "$$ LANGUAGE 'plpgsql' IMMUTABLE"
  Position: 1

$$ LANGUAGE 'plpgsql' IMMUTABLE
^

Execution time: 0.22s
(Statement 3 of 7 finished)

An error occurred when executing the SQL command:
CREATE FUNCTION concat_semicolon(text, text) RETURNS text AS $$
BEGIN
  RETURN $1 || ' ; ' || $2

ERROR: unterminated dollar-quoted string at or near "$$
BEGIN
  RETURN $1 || ' ; ' || $2"
  Position: 62

CREATE FUNCTION concat_semicolon(text, text) RETURNS text AS $$
                                                             ^

Execution time: 0.22s
(Statement 4 of 7 finished)

0 rows affected
END executed successfully

Execution time: 0.22s
(Statement 5 of 7 finished)

An error occurred when executing the SQL command:
$$ LANGUAGE 'plpgsql' IMMUTABLE

ERROR: unterminated dollar-quoted string at or near "$$ LANGUAGE 'plpgsql' IMMUTABLE"
  Position: 1

$$ LANGUAGE 'plpgsql' IMMUTABLE
^

Execution time: 0.22s
(Statement 6 of 7 finished)

An error occurred when executing the SQL command:
CREATE AGGREGATE concat_semicolon(
  BASETYPE=text,
  SFUNC=concat_semicolon,
  STYPE=text,
  FINALFUNC=cut_semicolon,
  INITCOND=''
)

ERROR: SQL command "CREATE AGGREGATE concat_semicolon(
  BASETYPE=text,
  SFUNC=concat_semicolon,
  STYPE=text,
  FINALFUNC=cut_semicolon,
  INITCOND=''
)" not supported.

Execution time: 0.23s
(Statement 7 of 7 finished)


5 statements failed.
Script execution finished
Total script execution time: 1.55s

还浏览了 Google 群组中的相关答案,&看起来像是替换了分隔符“;”可能有帮助? - 虽然我不确定,哪个;在这个函数定义中替换。 参考:https://groups.google.com/forum/#!topic/sql-workbench/5LHVUXTm3BI

编辑 3: 也许,Redshift 不支持创建函数本身? “错误:不支持 CREATE FUNCTION”一个 2013 年的帖子这样说 forums.aws.amazon.com/thread.jspa?threadID=121137

编辑 4:

select A, concat(concat(concat(C, ',' ) , cast(B as varchar)), ',')
from  my_table
group by A,B,C


-- Is it ok to group by all A,B, C - since I can't group by A alone, which removes the related "C" columns-- 

gives -:
a1 c1b1b2b3
a2 c2b1b2

但不是 C 的所有条目(以及分号)

a1 c1,b1;c2,b2;c2,b3
a2 c2,b1;c5,b2

但我想要中间的逗号 & 还需要知道 A、B、C 的分组是否可以?

【问题讨论】:

  • 在发出简单的select A, B, C from table 之后,您不想在应用程序级别计算这类东西的任何特殊原因?
  • @Denis 在工作中也被多次问过这个问题。我将此任务用作数据准备的一个步骤,我要做的最后一件事是在 redshift 中进行部分数据准备 - 处理它python,将其加入红移表中,然后进行其余的数据准备。看起来这将是最好的镜头?此外,在 Python 中,当我获取记录时,所有“相关”记录都必须适合内存,这就是为什么我的整个数据必须适合 - 让我能够处理要转换为单行的相关行(草率的原因),这一直促使我在红移中解决它。我使用数据框来存储 A、B 和 c 列
  • 我昨天添加了一个更长的答案,这解释了为什么您无法在 Redshift 中做您想要的事情。顺便说一句,不要忘记在处理大型集合时可以使用游标(和临时表)来减少内存使用量。而且,描述更完整的问题(在一个单独的问题中,以避免使当前答案无关紧要)很可能会产生一个您甚至没有考虑过的解决方案。

标签: python mysql postgresql sqlalchemy amazon-redshift


【解决方案1】:

这在 PostgreSQL 中可能是可以实现的。特别是如果 B 和 C 属于同一类型。您可以使用 ARRAY 生成两列结果并在第二列中聚合数据,否则使用 JSON。我不确定如何在 MySQL 中生成它,但可能需要序列化为字符串,并在 Python 中反转它。

无论哪种方式,我认为正确的答案是:不要这样做。你会得到更少的可读性、hacky、不可移植的解决方案,这可能不一定是一个更快的解决方案。在 Python 中对数据进行一些后处理以赋予它们最终形式并没有错,实际上这是一种很常见的做法。特别是如果它纯粹是重新格式化输出而不用于生成汇总结果。

【讨论】:

    【解决方案2】:

    PostgreSQL

    SELECT
      a,
      STRING_AGG('(' || c || ',' || b || ')', ' ; ')
    FROM
      tbl
    GROUP BY
      a;
    

    编辑: 对于 9.0 之前(引入 STRING_AGG 时)甚至 8.4 之前(添加 ARRAY_AGG 时)的 PostgreSQL 版本,您可以创建自己的 custom aggregate function

    编辑 2:对于 8.0 之前的版本(可能 Amazon Redshift 不知何故基于 PostgreSQL 7.4),不支持 $$ 语法,因此函数体需要用引号括起来,并且引号内身体需要逃脱。

    CREATE FUNCTION cut_semicolon(text) RETURNS text AS '
    BEGIN
      RETURN SUBSTRING($1 FROM 4);
    END;
    ' LANGUAGE 'plpgsql' IMMUTABLE;
    
    
    CREATE FUNCTION concat_semicolon(text, text) RETURNS text AS '
    BEGIN
      RETURN $1 || '' ; '' || $2;
    END;
    ' LANGUAGE 'plpgsql' IMMUTABLE;
    
    CREATE AGGREGATE concat_semicolon(
      BASETYPE=text,
      SFUNC=concat_semicolon,
      STYPE=text,
      FINALFUNC=cut_semicolon,
      INITCOND=''
    );
    

    然后改用那个聚合。

    SELECT
      a,
      CONCAT_SEMICOLON('(' || c || ',' || b || ')')
    FROM
      tbl
    GROUP BY
      a;
    

    MySQL

    SELECT
      a,
      GROUP_CONCAT(CONCAT('(', c, ',', b, ')') SEPARATOR ' ; ')
    FROM
      tbl
    GROUP BY
      a;
    

    【讨论】:

    • 我得到“错误:函数字符串_agg(文本,“未知”)不存在提示:没有函数匹配给定的名称和参数类型。您可能需要添加显式类型转换。“我有为此升级 Postgres sql 吗?选择版本(); i686-pc-linux-gnu 上的 PostgreSQL 8.0.2 版本,由 GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)、Redshift 1.0.860 编译
    • 问这个问题,因为 select string_agg('abc', ':') 显示了与上面评论中相同的错误 - 所以假设 string_agg 不受支持,尽管它是 突出显示.另一个观察:Redshift指向的我的jar版本是postgresql-9.3-1102.jdbc3.jar,为什么我看到的版本是8.0.2。
    • STRING_AGG 是在 9.0 版中添加的。你肯定必须升级你的 PostgreSQL(如果可以的话)。您使用的版本 - 8.0.2 非常旧,不再受支持,它于 2005 年 4 月 7 日发布,九年前
    • STRING_AGG 是一个聚合函数。 Amazon RedShift PostgreSQL 似乎不支持它。
    • 好吧,也许 Amazon Redshift 在语法上基于 PostgreSQL 7.4,所以我编辑了答案以支持 那个旧 语法。
    【解决方案3】:

    除非您有非常具体的理由在数据库本身内执行此类操作,否则应该在您的应用程序内完成。否则,您最终会得到返回复杂文本字段的集合,您可能需要对其进行解析以进行后处理等。

    换句话说:

    select A, B, C from table
    

    然后,类似 (Ruby):

    res = {}
    rows.each do |row|
      res[row['a']] ||= []
      res[row['a']][] = [row['b'], row['c']]
    end
    

    如果您坚持在 Postgres 中执行此操作,那么您的选择并不多——如果有的话,在 Redshift 中。

    array_agg()string_agg() 聚合函数都可能有用,但它们分别在 8.4 和 9.0 中引入,而 Redshift 显然都不支持。

    据我所知,Redshift doesn't support array constructors,所以使用ARRAY((select ...)) 构造,可能 已经奏效了。

    返回使用ROW() 构造的东西也是不可能的。即使是这样,它也会像罪恶一样丑陋,并且不可能在 Python 内部进行操作。

    如果其他答案以及它让您遵循的线索是可以接受的,那么自定义聚合函数似乎是不可能的。这并不令人惊讶:文档似乎很清楚您无法创建用户定义的函数,更不用说创建一个 pl/语言开始了。

    换句话说,据我所知,您唯一的选择是在您的应用程序中进行这种类型的聚合。顺便说一句,这就是你应该做这种事情的地方。

    【讨论】:

      【解决方案4】:

      试试这个来获得

      a1 | (c1,b1) ; (c2,b2);(c3;b3) 
      a2 | (c2,b1) ; (c5,b2)
      

      这是代码:

      1. 制作临时表,带有运行ID,以SQL Server为例,你可以尝试另一个查询

        Select identity(int, 1, 1) as ID, A, '('+C+';'+B+')' as aa
        Into #table2
        From #table
        Order BY A, aa
        
      2. 用循环查询

        Declare @sSql as Varchar(1000), @A as Varchar(2), @A2 as Varchar(2), @aa as Varchar(10)
        Declare @iRec as int, @iL as int
        Set @iRec  = (Select Count(*) From #table2)
        Set @iL = 1
        Set @sSql = ''
        
        While @iL <= @iRec
        Begin
            Set @A  = (Select A  From #table2 Where ID = @iL)
            Set @aa = (Select aa From #table2 Where ID = @iL)
        
            if @A = @A2
                Begin
                    Set @sSql = Left(@sSql, Len(@sSql)-1)+';'+@aa+'`'
                End
            Else
                BEGIN
                    Set @sSql = @sSql + ' Union Select `'+ @A+'`,`'+@aa+'`'
                END
        
            Set @A2 = @A
            Set @iL = @iL + 1
        End
        Set @sSql = Right(@sSql, Len(@sSql)-7)
        Set @sSql = Replace(@sSql, '`', '''')
        Exec(@sSql)
        

      有效吗?

      【讨论】:

      • 这个问题是关于让它在 Redshift 中工作的。它对我不起作用,它在 Redshift 中检查了吗?
      • 对不起,这只是逻辑。我从不使用或尝试 Redshift。对不起,我不能给你主意
      【解决方案5】:

      得出的结论是在 postgres+ Redshift 堆栈中无法解决。 这就是我解决它的方法。

      import pandas as pd
      df =pd.DataFrame({'A':[1,1,1,2,2,3,3,3],'B':['aaa','bbb','cc','gg','aaa','bbb','cc','gg']})
      
      def f(x):
          return [x['B'].values]
      
      #s=df.groupby('A').apply(f)
      series =df.groupby('A').apply(f)
      series.name = 'metric'
      s=pd.DataFrame(series.reset_index())
      print s
      
         A            metric
      0  1  [[aaa, bbb, cc]]
      1  2       [[gg, aaa]]
      2  3   [[bbb, cc, gg]]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-29
        • 2017-12-04
        • 1970-01-01
        • 2016-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多