列表与子列表 postgresql 分组

List grouped with sublists postgresql

这是一个我不怎么搜索甚至描述的问题。但我会试一试。

首先我有 2 个表:

CREATE TABLE vch
    (vchid int4, subject text);    
INSERT INTO vch
    (vchid, subject)
VALUES
    (1, 'Volvo'),
    (2, 'Ford'),
    (3, 'Jeep'),
    (4, 'Toyota');

CREATE TABLE rec
    (recid int4, recvch int4, recname text);    
INSERT INTO rec
    (recid, recvch, recname)
VALUES
    (1000, 1,'xxx'),
    (2000, 1,'yyy'),
    (3000, 3,'zzz'),
    (4000, 4,'aaa');

我的目标是创建如下所示的输出:

1 Volvo
{
1000 xxx
2000 yyy
}
3 Jeep
{
3000 zzz 
}
4 Toyota
{
4000 aaa
}

它看起来与 json 相关,但格式如上。

我最接近的是这个:

SELECT vchid, 0 as id, subject FROM vch
UNION ALL
SELECT recvch, recid, recname FROM rec
ORDER BY 1 

sqlfiddle: http://sqlfiddle.com/#!15/d3a61d/12

从你的问题中不能完全清楚你期望得到什么结果。 但这是一种可能的解决方案,我想它会产生类似于您想要的结果。

您可以使用 array_agg() function and string concatenation 生成字符串数组

SELECT vchid, subject, array_agg(rec.recid || ' ' || rec.recname) AS rec
FROM vch JOIN rec ON vch.vchid = rec.recvch
GROUP BY vch.vchid, vch.subject;

结果

vchid | subject |          rec
------+---------+------------------------
    1 | Volvo   | {"1000 xxx","2000 yyy"}
    3 | Jeep    | {"3000 zzz"}
    4 | Toyota  | {"4000 aaa"}

如果您可以在输出中嵌入换行符,则可以使用以下方法:

with all_rows (id, recid, name, label, src) as (
  SELECT vchid, null as id, subject, concat(vchid, ' ', subject), 1
  FROM vch
  where exists (select 1 from rec where rec.recvch = vch.vchid)
  UNION ALL
  SELECT recvch, recid, recname, concat(recid, ' ', recname), 2
  FROM rec
)    
select case
         when recid is not null and count(*) over (partition by id) <= 2 then concat('{', chr(10), label, chr(10), '}')
         when recid is not null and row_number() over (partition by id order by recid) = 1 then concat('{', chr(10), label)
         when recid is not null 
              and row_number() over (partition by id order by src, recid) = count(*) over (partition by id) then concat(label, chr(10), '}')
         else label
       end as label      
from all_rows
order by id, src, recid nulls first;

这输出:

1 Volvo 
{       
1000 xxx
2000 yyy
}
3 Jeep  
{       
3000 zzz
}
4 Toyota
{       
4000 aaa
}

但是,例如

{
4000 aaa
}

单行(和列)但包含嵌入的换行符。

这取决于您导出数据的方式(如果适合您的话)(例如,psql 将显示 + 以指示输出中的换行)

当然,您需要将 chr(10)(换行符)替换为适合您平台的换行符(例如 chr(13), chr(10) 替换 Windows)


另一种选择是创建一个执行此操作的函数。可能更容易处理:

create or replace function get_result() 
  returns setof text
as
$$
declare
  v_rec record;
  r_rec record;
begin
  for v_rec in SELECT vchid, subject
               FROM vch
               where exists (select 1 from rec where rec.recvch = vch.vchid)
               order by vchid
  loop
     return next concat(v_rec.vchid, ' ', v_rec.subject);
     return next '{';
     for r_rec in select recid, recname
                  from rec
                  where recvch = v_rec.vchid
                  order by recid
     loop
       return next concat(r_rec.recid, ' ', r_rec.recname);
     end loop;
     return next '}';
  end loop;
end;
$$
language plpgsql;

然后简单地 运行:

select *
from get_result();

另一种可能的解决方案:

1- 构建一个由所有记录的逗号分隔的字符串。
2- 从字符串创建一个数组。
3- 最后使用 unnest 扩展数组。

select unnest(string_to_array(
                  (vchid::text || ' ' || subject
                  || ',{'::text
                  || ',' 
                  || (select string_agg(format(e'%s %s', rec.recid, rec.recname), ',')
                     from rec where vch.vchid = rec.recvch)::text                     
                  || ',}'::text)::text
                  , ','))
from vch;

最终结果:

+----------+
| result   |
+----------+
| 1 Volvo  |
+----------+
| {        |
+----------+
| 1000 xxx |
+----------+
| 2000 yyy |
+----------+
| }        |
+----------+
| 3 Jeep   |
+----------+
| {        |
+----------+
| 3000 zzz |
+----------+
| }        |
+----------+
| 4 Toyota |
+----------+
| {        |
+----------+
| 4000 aaa |
+----------+
| }        |
+----------+

这是一组行,如果您要查找一个文本列,@a_horse_with_no_name 已提供正确答案。

事实上,您可以使用提供的查询通过向每一行添加一个 LF 来获取一条记录。

select string_agg(f, chr(10))
from (select unnest(string_to_array(
                  (vchid::text || ' ' || subject
                  || ',{'::text
                  || ',' 
                  || (select string_agg(format(e'%s %s', rec.recid, rec.recname), ',')
                     from rec where vch.vchid = rec.recvch)::text                     
                  || ',}'::text)::text
                  , ',')) f
from vch) a
;

就是这样:

+----------+
| result   |
+----------+
| 1 Volvo  |
| {        |
| 1000 xxx |
| 2000 yyy |
| }        |
| 3 Jeep   |
| {        |
| 3000 zzz |
| }        |
| 4 Toyota |
| {        |
| 4000 aaa |
| }        |
+----------+

在这里查看:http://rextester.com/JKEG32613

这给出一行结果,文本输出除以换行符:

select
    string_agg(
        format(
            e'%s %s\n{\n%s\n}',
            vchid, subject, rec
        ),
        e'\n' order by 1
    ) as result
from (
    select
        vchid,
        subject,
        string_agg(
            format(
                e'%s %s ',
                recid, recname
            ),
            e'\n'
        ) as rec
    from vch
    join rec on vchid = recvch
    group by 1, 2
    ) s;

结果:

  result   
-----------
 1 Volvo  +
 {        +
 1000 xxx +
 2000 yyy +
 }        +
 3 Jeep   +
 {        +
 3000 zzz +
 }        +
 4 Toyota +
 {        +
 4000 aaa +
 }
(1 row)

您可以将 unnest(string_to_array()) 添加到上述查询中,使每一行都成为一行:

select 
    unnest(string_to_array(result, e'\n')) as result
from (  
    select
        string_agg(
            format(
                e'%s %s\n{\n%s\n}',
                vchid, subject, rec
            ),
            e'\n' order by 1
        ) as result
    from (
        select
            vchid,
            subject,
            string_agg(
                format(
                    e'%s %s ',
                    recid, recname
                ),
                e'\n'
            ) as rec
        from vch
        join rec on vchid = recvch
        group by 1, 2
        ) s
    ) s;

结果:

  result   
-----------
 1 Volvo
 {
 1000 xxx 
 2000 yyy 
 }
 3 Jeep
 {
 3000 zzz 
 }
 4 Toyota
 {
 4000 aaa 
 }
(13 rows)