Postgres 9.5では、簡単な例を使用しています。私がこのデータを持っているとすると:
テーブルeverything
:
| fruit | country |
|--------|---------|
| Banana | USA |
| Banana | Panama |
これらのテーブルをどのように作成しますか?:
テーブルfruit
:
| fruit_id | name |
|----------|--------|
| 1 | Banana |
テーブルcountry
:
| country_id | name |
|------------|--------|
| 1 | USA |
| 2 | Panama |
テーブル fruit_country
:
| fruit_id | country_id |
|----------|------------|
| 1 | 1 |
| 1 | 2 |
最初のパスとして、私は次のようなことができることを望みました:
WITH fruit_ids AS (
INSERT INTO fruit(name) (
SELECT (fruit) FROM everything
)
ON CONFLICT DO NOTHING
RETURNING fruit_id
),
country_ids AS (
INSERT INTO country(name) (
SELECT(country) FROM everything
)
RETURNING country_id
)
INSERT INTO country_fruit(fruit_id, country_id) (
SELECT
fruit_id,
(SELECT country_id FROM country_ids)
FROM fruit_ids
);
しかし、サイコロはありません:
ERROR: more than one row returned by a subquery used as an expression
エラーメッセージの直接の原因は、括弧の乱用です。しかし、もっとあります:
データ変更CTEは、ユースケースに適したツールではありません。 [〜#〜] upsert [〜#〜](ON CONFLICT DO NOTHING
)は、果物の一部がテーブルfruit
にすでに存在している可能性があることを示します(またはその使用は意味がありません)。したがって、一部の果物はRETURNING
で返されるセットに含まれていない可能性があります。
代わりに、単純な個別のINSERT
コマンドを使用してください。操作全体をアトミック(すべてまたはなし)にする必要がある場合は、それをトランザクションにラップします。
また、 は列に適した名前ではありません。代わりに、果物にはname
fruit
を、国にはcountry
を使用します。名前の競合を回避し、結合条件に(完全にオプションの)USING
句を許可することで、クエリを少し単純化します。
テーブルは次のようになります:(それが問題の一部だった場合でも?)
CREATE TABLE fruit (
fruit_id serial PRIMARY KEY
, fruit text UNIQUE
);
CREATE TABLE country (
country_id serial PRIMARY KEY
, country text UNIQUE
);
CREATE TABLE fruit_country (
fruit_id int REFERENCES fruit
, country_id int REFERENCES country
, PRIMARY KEY (fruit_id, country_id)
);
詳細な手順:
INSERT
からeverything
データへ:
BEGIN;
INSERT INTO fruit (fruit)
SELECT DISTINCT fruit -- must be distinct!
FROM everything
ON CONFLICT DO NOTHING;
INSERT INTO country (country)
SELECT DISTINCT country -- must be distinct!
FROM everything
ON CONFLICT DO NOTHING; -- UPSERT here as well
INSERT INTO fruit_country(fruit_id, country_id)
SELECT f.fruit_id, c.country_id
FROM everything
JOIN fruit f USING (fruit) -- matching row guaranteed now
JOIN country c USING (country); -- matching row guaranteed now
COMMIT;