列表与子列表 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 |
| } |
+----------+
这给出一行结果,文本输出除以换行符:
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 个表:
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 |
| } |
+----------+
这给出一行结果,文本输出除以换行符:
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)