【问题标题】:How to use PL/pgSQL to construct a table with dynamic columns如何使用 PL/pgSQL 构造具有动态列的表
【发布时间】:2018-02-10 02:15:42
【问题描述】:

我有一个名为 locations 的 Postgres 表。它有几百万行包含以下格式的数据

 id |  location_a  |  location_b
----+--------------+--------------
 36 | Sydney       | London
 37 | Atlanta      | London
 38 | New York     | Tokyo
 39 | Tokyo        | Sydney
 40 | Tokyo        | Sydney
.....

我希望能够生成以下形式的数据透视表/计数 -

问题在于列数是可变的,因此必须以编程方式/动态确定,而不是使用静态 SELECT 查询。

我理解 PL/pgSQL 的基本概念,因为它是一种脚本语言,可以让我做这样的动态事情。

但是我在开始时遇到了很多麻烦。有没有一种简单的方法来计算上述内容?

【问题讨论】:

  • 考虑使用cross tabs,但即使在这种情况下,您仍然需要在查询中列出城市名称。
  • 据我所知,postgres 无法达到您想要的结果。如果您选择函数来创建动态表,那么您必须再次设置返回类型表和要返回的所有列。但是您不想这样做,因为您需要一个动态列。 Postgres 在执行查询和生成表方面不像 MySQL。
  • 如果"用动态列构建表"你的意思是CREATE TABLE,那么就可以了。如果您的目标是直接返回动态列数的表函数,那么您就不走运了:那是做不到的。不过,您可以在数组或 json 等中返回聚合数据。相关:stackoverflow.com/a/15514334/939860。首先阐明您确切需要什么,披露您的 Postgres 版本并提供源表的准确定义 (CREATE TABLE ...)。

标签: sql postgresql plpgsql dynamic-sql crosstab


【解决方案1】:

您可以动态创建视图。 in this answer. 描述了比您更简单的案例的想法和解决方案@ 请在继续之前阅读它。

我们将使用以下查询来创建视图:

with all_locations(location) as (
    select distinct location_a
    from locations
    union
    select distinct location_b
    from locations
)

select location_a as location, json_object_agg(location_b, count order by location_b) as data
from (
    select a.location as location_a, b.location as location_b, count(l.*)
    from all_locations a
    cross join all_locations b
    left join locations l on location_a = a.location and location_b = b.location
    group by 1, 2
    ) s
group by 1
order by 1;

结果:

 location |                                    data                                    
----------+----------------------------------------------------------------------------
 Atlanta  | { "Atlanta" : 0, "London" : 1, "New York" : 0, "Sydney" : 0, "Tokyo" : 0 }
 London   | { "Atlanta" : 0, "London" : 0, "New York" : 0, "Sydney" : 0, "Tokyo" : 0 }
 New York | { "Atlanta" : 0, "London" : 0, "New York" : 0, "Sydney" : 0, "Tokyo" : 1 }
 Sydney   | { "Atlanta" : 0, "London" : 1, "New York" : 0, "Sydney" : 0, "Tokyo" : 0 }
 Tokyo    | { "Atlanta" : 0, "London" : 0, "New York" : 0, "Sydney" : 2, "Tokyo" : 0 }
(5 rows)

城市列表将在函数内部使用两次,因此存储在数组cities中。请注意,您可以将函数中的第一个查询替换为更简单的查询(它只是不同城市的有序列表)。

create or replace function create_locations_view()
returns void language plpgsql as $$
declare
    cities text[];
    list text;
begin
--  fill array with all cities in alphabetical order
    select array_agg(location_a order by location_a)
    from (
        select distinct location_a
        from locations
        union
        select distinct location_b
        from locations
        ) s
    into cities;

--  construct list of columns to use in select list
    select string_agg(format($s$data->>'%1$s' "%1$s"$s$, city), ', ')
    from unnest(cities) city
    into list;

--  create view from select based on the above list
    execute format($ex$
        drop view if exists locations_view;
        create view locations_view as 
        select location, %1$s
        from (
            select location_a as location, json_object_agg(location_b, count order by location_b) as data
            from (
                select a.location as location_a, b.location as location_b, count(l.*)
                from unnest(%2$L::text[]) a(location)
                cross join unnest(%2$L::text[]) b(location)
                left join locations l on location_a = a.location and location_b = b.location
                group by 1, 2
                ) s
            group by 1
        ) s
        order by 1
        $ex$, list, cities);
end $$;

使用该函数并从创建的视图中选择数据:

select create_locations_view();
select * from locations_view;

 location | Atlanta | London | New York | Sydney | Tokyo 
----------+---------+--------+----------+--------+-------
 Atlanta  | 0       | 1      | 0        | 0      | 0
 London   | 0       | 0      | 0        | 0      | 0
 New York | 0       | 0      | 0        | 0      | 1
 Sydney   | 0       | 1      | 0        | 0      | 0
 Tokyo    | 0       | 0      | 0        | 2      | 0
(5 rows)

这种方法我用过几次,但我没有处理过大数据的经验,所以我不能保证它是有效的。

【讨论】:

  • 谢谢,这是一个非常有创意的解决方案!它比我希望的要长,但看起来通常没有太多支持来做这样的事情。感谢您的帮助。
  • 某些限制和缺乏现成工具源于数据库理论的基础。基本上,此类数据转换属于表示层,应在客户端执行。请注意,通过转换第一个查询的结果(使用 jsonb 列),在您的应用程序中获得所需的效果相对容易。另一方面,答案中描述的功能确实包含的代码不多,也不是特别复杂,但需要一些高级知识。
猜你喜欢
  • 2014-07-18
  • 1970-01-01
  • 2015-02-26
  • 1970-01-01
  • 1970-01-01
  • 2013-08-07
  • 1970-01-01
  • 1970-01-01
  • 2018-02-10
相关资源
最近更新 更多