web-dev-qa-db-ja.com

未知のキーセットの列にhstoreキーを動的に変換します

hstoreを使用して一連のカスタムフィールドを格納するデータベースがあります。 hstoreをサポートしない別のデータベースにマージするために、キーを追加の列に分割したいと思います。

ユーザーは新しいカスタムフィールドを追加できるため、事前にキーの知識に依存することはできません。 "hstore-columnからの属性がビューの個別の列として表示されますか?" の答えは、私の問題には当てはまりません。

レコードのキーが他のレコードに存在しない場合は、null値の同じ列を取得する必要があります。

どうすればよいですか?

8
Fred Stark

これも非常に効率的に行うことができます。ただし、SQLは呼び出し時に戻りの型を知る必要があるため、単一のステートメントではありません。したがって、2つのステップが必要です。ソリューションには多数の高度なテクニック...

と同じテーブルを仮定すると、@ Denverの回答では

_CREATE TABLE hstore_test (
  id serial PRIMARY KEY
, hstore_col hstore
);
_

ソリューション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;
_

サブクエリ_(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;
_

結果:

_ id | key1  | key2  | key3
----+-------+-------+-------
  1 | val11 | val12 | val13
  2 | val21 | val22 |
  3 |       |       |        -- for a row where hstore_col IS NULL
_


解決策2:crosstab()

キーのlotsの場合、これはmayの方が優れています。おそらく違います。テストする必要があります。結果はソリューション1と同じです。

crosstab()関数を提供する追加の拡張tablefuncが必要です。 これを最初に読んでください慣れていない場合:

ステップ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;
_

ドル引用符 のネストされたレベルに注意してください。

空またはNULLのhstore値を持つ行を保持するために、補助クエリで短い_CROSS JOIN_の代わりにメインクエリでこの明示的な形式を使用します。

_LEFT   JOIN LATERAL each(hstore_col) kv ON TRUE
_

関連:

ステップ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);
_

初めて実行する前に、妥当性を検査することをお勧めします。これにより、パフォーマンスが最適化されます。

ノート

6

私は少し遅れていることに気づきました-そして、あなたは確かにそれを理解しました-しかし、あなたがデンバー・ティモシーの答えに残したコメントを見て、私は他の皆に答えを残したいと思いました:

select (each(hstore_col)).key from hstore_test;

これにより、hstore_colに含まれるkeyごとに行が作成されるため、事前にキーが何であるかを知る必要はありません。

4
s.m.

列で->演算子を使用する必要があります( ここ を参照)。

他のレコードに同じキーがないレコードは、NULLとして表示されます。

create table hstore_test (id serial, hstore_col hstore);
insert into hstore_test (hstore_col) values ('key1=>val11, key2=>val12, key3=>val13'), ('key1=>val21, key2=>val22');
select hstore_col->'key1' as key1, hstore_col->'key2' as key2, hstore_col->'key3' as key3 from hstore_test;
┌───────┬───────┬───────┐
│ key1  │ key2  │ key3  │
├───────┼───────┼───────┤
│ val11 │ val12 │ val13 │
│ val21 │ val22 │ NULL  │
└───────┴───────┴───────┘
(2 rows)

ここ も同様の答えです。

2
Ian Timothy