【问题标题】:Dynamic transpose for unknown row value into column name on postgres将未知行值动态转置为 postgres 上的列名
【发布时间】:2021-03-22 09:22:04
【问题描述】:

我有这样的表:

customer_number label value
1 address St. John 1A
1 phone 111111111
1 email john@cena.com
2 address St. Marry 231A
2 phone 222222222
2 email please@marry.me

我想要新的表格或视图,所以它变成了:

customer_number address phone email
1 St. John 1A 111111111 john@cena.com
2 St. Marry 231A 222222222 please@marry.me

但将来可能会添加不同的标签,例如可能会有一个名为occupation的新标签。

重要提示,我不知道标签列的值,所以它应该迭代到该列中的任何值。

有什么办法吗?

【问题讨论】:

    标签: sql postgresql pivot


    【解决方案1】:

    你不能有一个“动态”的数据透视,因为查询的所有列的数量、名称和数据类型必须在查询实际执行之前被数据库知道(即在解析时)时间)。

    我发现将内容聚合成 JSON 更容易处理。

    select customer_number,
           jsonb_object_agg(label, value) as props
    from the_table
    group by customer_number
    

    如果你的前端可以直接处理 JSON 值,你可以到此为止。

    如果您确实需要每个属性一列的视图,您可以从 JSON 值中获取它们:

    select customer_number, 
           props ->> 'address' as address,
           props ->> 'phone' as phone,
           props ->> 'email' as email
    from (       
      select customer_number,
             jsonb_object_agg(label, value) as props
      from the_table
      group by customer_number
    ) t
    

    我发现添加新属性时这更容易管理。


    如果您需要一个带有所有标签的视图,您可以创建一个存储过程来动态创建它。如果不同标签的数量不会经常变化,这可能是一个解决方案:

    create procedure create_customer_view() 
    as
    $$
    declare
      l_sql text;
      l_columns text;
    begin
      select string_agg(distinct format('(props ->> %L) as %I', label, label), ', ')
        into l_columns
      from the_table;
      
      l_sql := 
        'create view customer_properties as 
         select customer_number, '||l_columns||' 
         from (
          select customer_number, jsonb_object_agg(label, value) as props
           from the_table 
           group by customer_number 
         ) t';
      execute l_sql;
    end;
    $$
    language plpgsql;
    

    然后使用以下命令创建视图:

    call create_customer_view();  
    

    在你的代码中使用:

    select *
    from customer_properties;
    

    您可以安排该过程定期运行(例如,通过 Linux 上的 cron 作业)

    【讨论】:

    • 所以我必须知道所有可能的label 值?
    • @F.Suyuti:是的,但是您可以创建一个过程来动态创建具有当前标签的视图。查看我的编辑
    • 你的程序就像一个魅力,谢谢
    【解决方案2】:

    一般的 SQL 不擅长动态透视。

    这是一个查询,它将为您透视数据。但是,它不是动态的,即如果添加了未来的 occupation 标签,那么您将不得不更改查询。不确定这是否可以接受:

    select customer_number,
    max(value) filter (where label='address') as address,
    max(value) filter (where label='phone') as phone,
    max(value) filter (where label='email') as email
    from your_customer_table
    group by customer_number
    

    假设您在此处运行 Postgres 9.4 或更高版本,以便支持 filter 函数。如果没有,那么可以使用case 语句重新处理它:

    select customer_number,
    max(case when label='address' then value else null end) as address,
    max(case when label='phone' then value else null end) as phone,
    max(case when label='email' then value else null end) as email
    from your_customer_table
    group by customer_number
    

    【讨论】:

      【解决方案3】:

      我使用 cross apply 来解决这个问题.. 这是我的查询

      select distinct tb9.customer_number, tb9_2.*
      from Table_9 tb9 cross apply
           (select max(case when tb9_2.[label] like '%address%' then [value] end) as [address],
                   max(case when tb9_2.[label] like '%phone%' then [value] end) as [phone],
                   max(case when tb9_2.[label] like '%email%' then [value] end) as [email]
            from Table_9 tb9_2
            where tb9.customer_number = tb9_2.customer_number
           ) tb9_2;
      

      【讨论】:

      • 这对 Postgres(或标准 SQL)无效
      • 我不关注 Postgres,但它适用于 MS SQL @a_horse_with_no_name ,它可能是上述答案之后的第二种方式。谢谢你
      • 对于 Postgres,您需要将非标准的 cross apply 替换为符合标准的 cross join lateral,并且您需要删除列名周围的非标准方括号。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 2013-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多