与子列表分组的列表postgresql

时间:2017-01-21 11:20:57

标签: postgresql postgresql-9.1

这是一个问题,我不知道如何搜索甚至描述。但我会尝试一下。

首先,我有两张桌子:

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

4 个答案:

答案 0 :(得分:2)

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

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)(换行符)(例如,适用于Windows的chr(13), chr(10)

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

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)

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

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)

答案 2 :(得分:1)

您的问题并不完全清楚您期望的结果。 但是,我想这里有一个可能的解决方案,可以产生类似于你想要的东西。

您可以使用array_agg()函数和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"}

答案 3 :(得分:1)

另一种可能的解决方案:

1-构建一个由所有记录的昏迷分隔的字符串 2-从字符串中创建一个数组 3-最后使用unexst展开数组。

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