coa*_*ano 8 postgresql pivot dynamic-sql hstore
我有一个数据库,它使用hstore
. 为了将其合并到另一个不支持 的数据库中hstore
,我想将键拆分为额外的列。
用户可以添加新的自定义字段,因此我不能提前依赖键的知识。这在“来自 hstore 列的属性作为视图中的单独列”给出了答案?不适用于我的问题。
如果一条记录在其他记录中没有键,则它应该获得具有空值的同一列。
我该怎么做呢?
这也可以非常有效地完成。但是,不是在单个语句中,因为 SQL 要求在调用时知道返回类型。所以你需要两个步骤。该解决方案涉及许多先进的技术......
假设与@Denver 在他的回答中使用相同的表:
CREATE TABLE hstore_test (
id serial PRIMARY KEY
, hstore_col hstore
);
Run Code Online (Sandbox Code Playgroud)
SELECT
在我写下下面的交叉表解决方案后,我发现一个简单的“蛮力”解决方案可能更快。基本上,查询@Denver 已经发布,动态构建:
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
列的列别名。
这将生成以下形式的查询:
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)
crosstab()
对于许多键,这可能会表现得更好。可能不是。你必须测试。结果与解决方案 1 相同。
您需要tablefunc
提供该crosstab()
功能的附加扩展。如果您不熟悉,请先阅读以下内容:
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)
有关的:
这将生成以下形式的查询:
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)
您可能想在第一次运行之前检查它的合理性。这应该提供优化的性能。
这两种解决方案都适用于任意数量的键,最多可达Postgres 表中约 1600 列的物理限制。
两者都适用于任何形状或形式的键,最长可达标识符的最大长度,默认为 63 字节。
除了hstore功能each()
这是已经由SM提到的,我也使用了相关功能skeys()
,以确定键。
请务必正确引用列名,以避免可能通过恶意形成的键名进行SQL 注入攻击。我用quote_literal()
和 来处理quote_ident()
。
我意识到我有点晚了\xe2\x80\x94现在你肯定已经弄清楚了\xe2\x80\x94但是,看到你在丹佛蒂莫西的答案上留下的评论,我想我会给其他人留下一个答案:
\n\nselect (each(hstore_col)).key from hstore_test;\n
Run Code Online (Sandbox Code Playgroud)\n\nkey
这将为中包含的每个内容创建一行hstore_col
,因此您无需事先知道键是什么。