web-dev-qa-db-ja.com

PostgreSQL 9.6の列の削除とCTEを使用するSQL関数への副作用

A、B、Dなどの3つの列を持つテーブルがあり、新しいテーブルを導入する必要がある場合、Dの現在の位置を置き換えるためにCと言います。次の方法を使用します。

  1. CおよびD2として2つの新しい列を導入します。
  2. Dの内容をD2にコピーします。
  3. Dを削除します。
  4. D2の名前をDに変更します。

新しい順序は、A、B、C、Dです。

これは(これまでのところ)問題を引き起こさなかったので、これは正当な慣行だと思いました。

しかし、今日、同じテーブルでステートメントを実行する関数が次のエラーを返したときに問題が発生しました。

table row type and query-specified row type do not match

そして次の詳細:

Query provides a value for a dropped column at ordinal position 13

PostgreSQLを再起動してVACUUM FULLを実行し、最後に提案された here および here のように関数を削除して再作成しましたが、これらの解決策は機能しませんでした(彼らは、システムテーブルが変更された状況に取り組みます。

非常に小さなデータベースで作業できるという贅沢があるので、それをエクスポートし、削除してから再インポートしました。その機能で問題を修正しました。


ここに見られるように、列の自然な順序をいじってはいけないという事実を知っていましたシステムテーブルを変更してpg_attributeなどで手を汚す):

Postgresの列の自然な順序を変更することは可能ですか?

私の関数によってスローされたエラーから判断すると、私のメソッドを使用して列の順序をシフトすることも禁止されていることに気づきました。なぜ私がしていることも間違っているのかについて誰かが光を当てることはできますか?


Postgresのバージョンは9.6.0です。

これが関数です:

CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '

-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),

-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),

-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)

-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER

facebook_idstripe_idの両方で名前の変更/並べ替えを実行しました(これらの前に新しい列が追加されました。これが名前の変更の理由ですが、このクエリには触れていません)。

列を特定の順序で並べることは、純粋に順序付けの対象外です。ただし、この質問をする理由は、列の単純な名前の変更と削除が、プロダクションモードで関数を使用している人に本当の問題を引き起こす可能性があることです(私自身に起こりました)。

15
Andy

9.6および9.6.1での可能性のあるバグ

これは完全に私にとってバグのように見えます...

なぜかはわかりませんが、発生していることは確認できました。これは、問題を再現する最も簡単な設定です(バージョン9.6.0および9.6.1)。

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    column_that_we_will_drop TEXT
) ;

-- Function that uses the previous table, and that has a CTE
CREATE OR REPLACE FUNCTION __post_users
    (_useremail text) 
RETURNS integer AS
$$
-- Need a CTE to produce the error. A 'constant' one suffices.
WITH something_even_if_useless(a) AS
(
    VALUES (1)
)
UPDATE
    users
SET
    id = id
WHERE 
    -- The CTE needs to be referenced, if the next
    -- condition were not in place, the problem is not reproduced
    EXISTS (SELECT * FROM something_even_if_useless)
    AND email = _useremail
RETURNING
    id
$$
LANGUAGE "sql" ;

このセットアップの後、次のステートメントが機能します

SELECT * FROM __post_users('[email protected]');

この時点で、1つの列を削除します。

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

この変更により、次のステートメントがエラーを生成します

SELECT * FROM __post_users('[email protected]');

これは、@ Andyによって言及されたものと同じです。

ERROR: table row type and query-specified row type do not match
SQL state: 42804
Detail: Query provides a value for a dropped column at ordinal position 3.
Context: SQL function "__post_users" statement 1
    SELECT * FROM __post_users('[email protected]');

関数を削除して再作成しても問題は解決しません。
VACUUM FULL(テーブルまたはデータベース全体)は問題を解決しません。


バグレポートは適切なPostgreSQLメーリングリストに渡され、 非常に速い応答 がありました:

これをHEADまたは9.6ブランチチップ)で再現することはできません。9.6.1以降のこのパッチですでに修正されていると思います。

https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f4d865f22

しかし、レポートをありがとう!

よろしく、トムレーン


バージョン9.6.2

2017-03-06に、バージョン9.6.2でこの動作を再現できないことを確認できます。つまり、このリリースではバグが修正されているようです。

更新

@Janaのコメントごと:「バグが9.6.1に存在し、9.6.2で修正されたことを確認できます。この修正は postgres release Webサイト にも記載されています:偽のクエリを修正しますドロップされた列の場合「ドロップされた列を持つテーブルでのINSERTまたはUPDATE中のエラー」


16
joanolo

聞いたことがあると思いますが、これは恐ろしい考えです。

  • 論理的な順序はSELECT *のようなものにのみ影響します
  • 論理的な順序付けの効果は、外観に限定されます。

したがって、まったく重要ではないことがあなたを納得させず、私たちはPhotoshopを行構造で再生し、ディスプレイにこだわっていることを認めている場合は、いくつか説明しますより多くの事。

  • PostgreSQLには論理的な順序付けはありません
  • 物理的な注文には実際のメリットがあります(テーブルパッキング)
  • PostgreSQLはCREATE TABLEの後の物理的な順序を制御することもできません(ただし、優先度ははるかに高くなります

したがって、PostgreSQLは悪い表示層です。それに加えて、ALTERは正常に機能しますが、ドラゴンを誘惑するべきではありません。 ALTER TABLECAVEATセクション の両方がこれについて言及しています。

一部のDDLコマンドは、現在TRUNCATEおよび_ALTER TABLEのテーブル書き換え形式のみであり、MVCCセーフではありません。つまり、DDLコマンドがコミットされる前に作成されたスナップショットを使用している場合、トランケーションまたはリライトコミットの後、同時トランザクションに対してテーブルは空のように見えます。これは、DDLコマンドが開始する前に問題のテーブルにアクセスしなかったトランザクションの場合にのみ問題になります。アクセスしたトランザクションは、少なくともACCESS SHAREテーブルロックを保持し、そのトランザクションが完了するまでDDLコマンドをブロックします。したがって、これらのコマンドは、ターゲットテーブルに対する連続したクエリのテーブルコンテンツに明らかな不整合を引き起こすことはありませんが、ターゲットテーブルのコンテンツとデータベース内の他のテーブルとの間に目に見える不整合を引き起こす可能性があります。

そして、それだけでは不十分であり、これが恐ろしいアイデアではなく、良いアイデアであると偽りたい場合。次にこれを試してください、

  1. pg_dump -tでテーブルをダンプします
  2. ダンプ内の列を手動で並べ替えます。
  3. TEMPテーブルにデータを読み込みます。
  4. BEGINトランザクション
  5. DROP古いテーブル全体、
  6. RENAME tempテーブルからprodテーブルへ。
  7. COMMIT

これらすべてが過剰に聞こえる場合は、データベースの行を更新するために行を書き換える必要があることに注意してください(行がでないと仮定して[〜#〜] toast [〜 #〜] 。データを解析してテーブルスキーマを再構築する必要がありますが、どちらの方法でも行を書き換える必要があります。これを行うにはhadの場合タスク、それは私がそれをする方法です。

しかし、これはすべて一般的に言えます。誰もあなたの結果を再現していません。

実行できないテストケースを提供しました

ERROR:  column users.authentication_code does not exist
LINE 24: users.authentication_code,

そして、あなたは私たちにあなたが使っているexactバージョンを教えていません。

0
Evan Carroll

私はデータベースをバックアップして復元することで、このバグを回避しました。

Herokuの手順

  • メンテナンスモードをオンにします:heroku maintenance:on
  • データベースのバックアップ:heroku pg:backups:capture
  • データベースの復元:heroku pg:backups:restore
  • アプリを再起動:heroku restart
  • メンテナンスモードをオフにします:heroku maintenance:off
0
ma11hew28

私もこのバグに遭遇しました。 DBを完全にバックアップ/復元したくない人のために。テーブルをコピーするだけでうまくいくことを知ってください。ただし、テーブルをコピーする「魔法の」方法はありません。私はそれを使ってそれをしました:

SELECT * INTO mytable_copy FROM mytable;
ALTER TABLE mytable RENAME TO mytable_backup; -- just in case. you never know
ALTER TABLE mytable_copy RENAME TO mytable;

その後も、手動でインデックス、外部キー、デフォルトを再作成する必要があります。このようにテーブルを再作成すると、バグがなくなりました。

0
Thibauld