我如何编写一个将这种形式的数据转换为所需结果的数据透视表?
How can I write a pivot that takes this form of data to the result desired?
我有一个 table 看起来像这样
CREATE TABLE foo (id, name, category)
AS VALUES
( 1, 'name1.1', 'cat1.1.1'),
( 1, 'name1.2', 'cat1.1.1'),
( 1, 'name1.3', 'cat1.2.1'),
( 2, 'name2.1', 'cat2.1.1'),
( 2, 'name2.2', 'cat2.1.1'),
( 3, 'name3.1', 'cat3.1.1')
;
我正在尝试获得如下所示的结果,
Id
name1
name2
name3
1
name1.1
name1.2
name1.3
2
name2.1
name2.2
3
name3.1
首先你需要添加the tablefunc
module which provides this ability.
CREATE EXTENSION tablefunc;
现在您需要这样的查询,
SELECT *
FROM crosstab(
-- here we normalize this into `id | cat | value`
-- where the category is [1-3]
$$SELECT id, RIGHT(name,1)::int AS cat, name AS value FROM foo ORDER BY 1,2;$$,
-- For just the cat 1,2,3
$$VALUES (1),(2),(3);$$
) AS ct(id text, name1 text, name2 text, name3 text);
哪个会return这个,
id | name1 | name2 | name3
----+---------+---------+---------
1 | name1.1 | name1.2 | name1.3
2 | name2.1 | name2.2 |
3 | name3.1 | |
(3 rows)
注意这里的大问题,您的类别实际上是数据的函数 RIGHT(name,1)::int
。那可能是你的阻碍。相反,您在 category
中提供的实际数据似乎可以完全忽略,除非我遗漏了什么。
另请注意,我在两个地方对类别名称进行了硬编码,
$$VALUES (1),(2),(3);$$
并且,
ct(id text, name1 text, name2 text, name3 text);
这是必需的,因为 PostgreSQL 不允许您在 运行 命令时 return 结果集未知的查询。这将仅支持 [name1 - name3]
如果您希望它真正动态,则程序的范围会扩大很多,并且它与这个问题无关。
我会将所有名称聚合到一个数组中,然后将数组元素提取为列:
select id,
names[1] as name1,
names[2] as name2,
names[3] as name3
from (
select id,
array_agg(name order by name) as names
from foo
group by id
) t
如果名称可以包含 name10.11
之类的内容,则列的顺序将不是数字,因为字符串 '10'
低于字符串 '2'
。如果你想让顺序反映数字,排序就有点复杂了:
array_agg(name order by string_to_array(replace(name, 'name', ''), '.')::int[]) as names
这会删除 name
前缀并将数字转换为整数数组,然后正确排序。另一种选择是删除不是数字或点的所有内容:
array_agg(name order by string_to_array(regexp_replace(name, '[^0-9.]', '', 'g'), '.')::int[]) as names
我有一个 table 看起来像这样
CREATE TABLE foo (id, name, category)
AS VALUES
( 1, 'name1.1', 'cat1.1.1'),
( 1, 'name1.2', 'cat1.1.1'),
( 1, 'name1.3', 'cat1.2.1'),
( 2, 'name2.1', 'cat2.1.1'),
( 2, 'name2.2', 'cat2.1.1'),
( 3, 'name3.1', 'cat3.1.1')
;
我正在尝试获得如下所示的结果,
Id | name1 | name2 | name3 |
---|---|---|---|
1 | name1.1 | name1.2 | name1.3 |
2 | name2.1 | name2.2 | |
3 | name3.1 |
首先你需要添加the tablefunc
module which provides this ability.
CREATE EXTENSION tablefunc;
现在您需要这样的查询,
SELECT *
FROM crosstab(
-- here we normalize this into `id | cat | value`
-- where the category is [1-3]
$$SELECT id, RIGHT(name,1)::int AS cat, name AS value FROM foo ORDER BY 1,2;$$,
-- For just the cat 1,2,3
$$VALUES (1),(2),(3);$$
) AS ct(id text, name1 text, name2 text, name3 text);
哪个会return这个,
id | name1 | name2 | name3
----+---------+---------+---------
1 | name1.1 | name1.2 | name1.3
2 | name2.1 | name2.2 |
3 | name3.1 | |
(3 rows)
注意这里的大问题,您的类别实际上是数据的函数 RIGHT(name,1)::int
。那可能是你的阻碍。相反,您在 category
中提供的实际数据似乎可以完全忽略,除非我遗漏了什么。
另请注意,我在两个地方对类别名称进行了硬编码,
$$VALUES (1),(2),(3);$$
并且,
ct(id text, name1 text, name2 text, name3 text);
这是必需的,因为 PostgreSQL 不允许您在 运行 命令时 return 结果集未知的查询。这将仅支持 [name1 - name3]
如果您希望它真正动态,则程序的范围会扩大很多,并且它与这个问题无关。
我会将所有名称聚合到一个数组中,然后将数组元素提取为列:
select id,
names[1] as name1,
names[2] as name2,
names[3] as name3
from (
select id,
array_agg(name order by name) as names
from foo
group by id
) t
如果名称可以包含 name10.11
之类的内容,则列的顺序将不是数字,因为字符串 '10'
低于字符串 '2'
。如果你想让顺序反映数字,排序就有点复杂了:
array_agg(name order by string_to_array(replace(name, 'name', ''), '.')::int[]) as names
这会删除 name
前缀并将数字转换为整数数组,然后正确排序。另一种选择是删除不是数字或点的所有内容:
array_agg(name order by string_to_array(regexp_replace(name, '[^0-9.]', '', 'g'), '.')::int[]) as names