将未知的任意行集转置为列

时间:2019-01-11 12:42:54

标签: postgresql plpgsql

我正在一个系统上工作,在该系统上,我们将任意传入数据存储到一个表结构中,该结构基本上是基于键值的存储。

现实中的表结构要复杂一些,但是为了解决问题,我将使用一个简化的示例:

CREATE TABLE records (id BIGINT, type TEXT, identifier TEXT);
CREATE TABLE record_items (id BIGINT, rec_id BIGINT, key TEXT, value TEXT);

INSERT INTO records VALUES (10, 'PERSON', 'P12823');
INSERT INTO record_items VALUES 
(11, 10, 'FIRST_NAME', 'James'),
(12, 10, 'LAST_NAME', 'Burwell'),
(13, 10, 'AGE', '76'),
(14, 10, 'HEIGHT', '5-9'),
(15, 10, 'WEIGHT', '149'),
(16, 10, 'BLOOD', 'A+');

INSERT INTO records VALUES (20, 'PERSON', 'P83494');
INSERT INTO record_items VALUES 
(21, 20, 'FIRST_NAME', 'David'),
(22, 20, 'LAST_NAME', 'McDonnell'),
(23, 20, 'AGE', '47'),
(24, 20, 'COMPANY', 'Super Saver Foods');

INSERT INTO records VALUES (30, 'PERSON', 'P81323');
INSERT INTO record_items VALUES 
(31, 30, 'FIRST_NAME', 'William'),
(32, 30, 'LAST_NAME', 'Nelson'),
(32, 30, 'HEIGHT', '5-6'),
(33, 30, 'VEHICLE', '2005 Dodge Charger');

如您所见,传入数据具有任意键。对于所有传入数据,都有一些键,然后有一些键在记录之间变化,而有些键则丢失:

SELECT r.id, r.type, r.identifier, ri.key, ri.value
  FROM records r
  JOIN record_items ri ON ri.rec_id = r.id
 ORDER BY r.id, ri.key

id | type   | ident  | key        | value
-------------------------------------------
10 | PERSON | P12823 | AGE        | 76
10 | PERSON | P12823 | BLOOD      | A+
10 | PERSON | P12823 | FIRST_NAME | James
10 | PERSON | P12823 | HEIGHT     | 5-9
10 | PERSON | P12823 | LAST_NAME  | Burwell
10 | PERSON | P12823 | WEIGHT     | 149
20 | PERSON | P83494 | AGE        | 47
20 | PERSON | P83494 | COMPANY    | Flix
20 | PERSON | P83494 | FIRST_NAME | David
20 | PERSON | P83494 | LAST_NAME  | Donnell
30 | PERSON | P81323 | FIRST_NAME | William
30 | PERSON | P81323 | HEIGHT     | 5-6
30 | PERSON | P81323 | LAST_NAME  | Nelson
30 | PERSON | P81323 | VEHICLE    | Dodge

但是,我们需要得到的是将所有这些任意行转置为列(列的顺序并不重要):

id | identifier | first_name | last_name | age | height | weight | company | vehicle
-----------------------------------------------------------------------------------
10 | P12823     | James      | Burwell   | 76  | 5-9    | 149    |         |
20 | P83494     | David      | Donnell   | 47  |        |        | Flix    |
30 | P81323     | William    | Nelson    |     | 5-6    |        |         | Dodge

我知道crosstab函数的存在,但是我无法使其按需运行。据我了解,主要问题在于,crosstab期望所有记录都具有相同顺序的完全相同的键,并且任何丢失或任意键都将结果集混在一起。 crosstab的另一个问题是,我需要显式定义结果集的列,但我们需要的是能够以类似于以下方式的方式调用函数:

SELECT * 
  FROM some_crosstab_like_function('PERSON') -- PERSON identifies record type

我正在考虑创建一种机制,该机制由函数和动态创建的临时表或视图组成,并返回必要的数据。但是,在进行此操作之前,我想知道是否还有其他更简单的解决方案来解决我的问题。

我们是PostgreSQL 10.5。

任何想法都会受到赞赏。

1 个答案:

答案 0 :(得分:0)

使用jsonb对象中的聚合数据创建临时表:

create temp table json_data as
select id, identifier, jsonb_object_agg(lower(key), value) as data
from (
    select r.id, r.type, r.identifier, ri.key, ri.value
    from records r
    join record_items ri on ri.rec_id = r.id
    ) s
group by 1, 2;

该表包含以下格式的数据:

select * 
from json_data;

 id | identifier |                                                     data                                                      
----+------------+---------------------------------------------------------------------------------------------------------------
 10 | P12823     | {"age": "76", "blood": "A+", "height": "5-9", "weight": "149", "last_name": "Burwell", "first_name": "James"}
 20 | P83494     | {"age": "47", "company": "Super Saver Foods", "last_name": "McDonnell", "first_name": "David"}
 30 | P81323     | {"height": "5-6", "vehicle": "2005 Dodge Charger", "last_name": "Nelson", "first_name": "William"}
(3 rows)

使用Flatten aggregated key/value pairs from a JSONB field

中描述的功能
select create_jsonb_flat_view('json_data', 'id, identifier', 'data');

select *
from json_data_view

 id | identifier | age | blood |      company      | first_name | height | last_name |      vehicle       | weight 
----+------------+-----+-------+-------------------+------------+--------+-----------+--------------------+--------
 10 | P12823     | 76  | A+    |                   | James      | 5-9    | Burwell   |                    | 149
 20 | P83494     | 47  |       | Super Saver Foods | David      |        | McDonnell |                    | 
 30 | P81323     |     |       |                   | William    | 5-6    | Nelson    | 2005 Dodge Charger | 
(3 rows)

注意:您可以(可能应该)修改此功能以满足您的需要,特别是您可能希望创建临时视图而不是常规视图。