将 hstore 键动态转换为一组未知键的列

coa*_*ano 8 postgresql pivot dynamic-sql hstore

我有一个数据库,它使用hstore. 为了将其合并到另一个不支持 的数据库中hstore,我想将键拆分为额外的列。

用户可以添加新的自定义字段,因此我不能提前依赖键的知识。这在“来自 hstore 列的属性作为视图中的单独列”给出了答案不适用于我的问题。

如果一条记录在其他记录中没有键,则它应该获得具有空值的同一列。

我该怎么做呢?

Erw*_*ter 6

这也可以非常有效地完成。但是,不是在单个语句中,因为 SQL 要求在调用时知道返回类型。所以你需要两个步骤。该解决方案涉及许多先进的技术......

假设与@Denver 在他的回答中使用相同的表:

CREATE TABLE hstore_test (
  id serial PRIMARY KEY
, hstore_col hstore
);
Run Code Online (Sandbox Code Playgroud)

解决方案 1:简单 SELECT

在我写下下面的交叉表解决方案后,我发现一个简单的“蛮力”解决方案可能更快。基本上,查询@Denver 已经发布,动态构建:

步骤 1a:生成查询

SELECT format(
     'SELECT id, h->%s
      FROM  (SELECT id, hstore_col AS h FROM hstore_test) t;'
    , string_agg(quote_literal(key) || ' AS ' || quote_ident(key), ', h->')
   ) AS sql 
FROM  (
   SELECT DISTINCT key
   FROM   hstore_test, skeys(hstore_col) key
   ORDER  BY 1
   ) sub;
Run Code Online (Sandbox Code Playgroud)

子查询(SELECT id, hstore_col AS h FROM hstore_test)只是为了获取h您的hstore列的列别名。

步骤 1b:执行查询

这将生成以下形式的查询:

SELECT id, h->'key1' AS key1, h->'key2' AS key2, h->'key3' AS key3
FROM  (SELECT id, hstore_col AS h FROM hstore_test) t;
Run Code Online (Sandbox Code Playgroud)

结果:

 id | key1  | key2  | key3
----+-------+-------+-------
  1 | val11 | val12 | val13
  2 | val21 | val22 |
  3 |       |       |        -- for a row where hstore_col IS NULL
Run Code Online (Sandbox Code Playgroud)


解决方案2: crosstab()

对于许多键,这可能会表现得更好。可能不是。你必须测试。结果与解决方案 1 相同。

您需要tablefunc提供该crosstab()功能的附加扩展。如果您不熟悉,请先阅读以下内容

步骤 2a:生成查询

SELECT format(
   $s$SELECT * FROM crosstab(
     $$SELECT h.id, kv.*
       FROM   hstore_test h, each(hstore_col) kv
       ORDER  BY 1, 2$$
   , $$SELECT unnest(%L::text[])$$
   ) AS t(id int, %s text);
   $s$
 , array_agg(key)  -- escapes strings automatically
 , string_agg(quote_ident(key), ' text, ')  -- needs escaping!
   ) AS sql 
FROM  (
   SELECT DISTINCT key
   FROM   hstore_test, skeys(hstore_col) key
   ORDER  BY 1
   ) sub;
Run Code Online (Sandbox Code Playgroud)

注意Dollar-quoting的嵌套级别。

我在主查询中使用这种显式形式而不是CROSS JOIN辅助查询中的短形式来保留具有空hstore值或 NULL值的行:

LEFT   JOIN LATERAL each(hstore_col) kv ON TRUE
Run Code Online (Sandbox Code Playgroud)

有关的:

步骤 2b:执行查询

这将生成以下形式的查询:

SELECT * FROM crosstab(
     $$SELECT h.id, kv.*
       FROM   hstore_test h
       LEFT   JOIN LATERAL each(hstore_col) kv ON TRUE
       ORDER  BY 1, 2$$
   , $$SELECT unnest('{key1,key2,key3}'::text[])$$
   ) AS t(id int, key1 text, key2 text, key3 text);
Run Code Online (Sandbox Code Playgroud)

您可能想在第一次运行之前检查它的合理性。这应该提供优化的性能。

笔记


s.m*_*.m. 5

我意识到我有点晚了\xe2\x80\x94现在你肯定已经弄清楚了\xe2\x80\x94但是,看到你在丹佛蒂莫西的答案上留下的评论,我想我会给其他人留下一个答案:

\n\n
select (each(hstore_col)).key from hstore_test;\n
Run Code Online (Sandbox Code Playgroud)\n\n

key这将为中包含的每个内容创建一行hstore_col,因此您无需事先知道键是什么。

\n