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


15

3列(A、B、Dなど)のテーブルがあり、新しい列を導入しなければならなかった場合、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提案さているように最後に関数を削除して再作成しようとしましたが、これらの解決策は機能しませんでした(システムテーブルが変更された状況に取り組むことを除いて)。

非常に小さなデータベースで作業する余裕があったので、エクスポートし、削除してから再インポートしました。これにより、機能に関する問題修正されました


ここに見られるように、システムテーブルを変更する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_idおよびstripe_id(新しい列が改名の理由ですが、このクエリで触れていないされ、これらの前に追加されました)。

列を特定の順序にすることは、純粋に順序の対象外です。ただし、この質問をする理由は、列の単純な名前変更と削除が、実稼働モードで関数を使用している誰かに実際の問題を引き起こす可能性があるという懸念からです(自分自身に起こりました)。


見てください、私はPostgreSQLデータベーステーブル内の列の位置を変更するにはどうすればよいですか?スタックオーバーフローについては、列の順序を変更しないことをお勧めしますが、役に立つかもしれません。
ジョアノロ16

回答:


16

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('a@b.com');

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

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

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

SELECT * FROM __post_users('a@b.com');

@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('a@b.com');

関数をドロップして再作成しても、問題は解決しません。
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のリリースのウェブサイトを:修正スプリアス『問合せは、中に列』エラーを落としたために価値を提供します列が削除されたテーブルでのINSERTまたはUPDATE」



4
9.6.0を使用していて、@ joanoloが正しい場合、彼の方法でバグを再現できます。トムがそれを再現できない場合、それはおそらくこの特定のバージョンの孤立したバグでした(そして9.6.1と思いますか?)。その答えで述べたように、問題は、ときに表示されるドロップテーブルの列をし、 CTEが影響して機能を使用しているテーブルを。したがって、問題は並べ替えだけではなく、それが私の質問で得ようとしていたことです。これを反映してタイトルを編集します。
アンディ

バグは9.6.1に存在し、9.6.2で修正されたことを確認できます。修正はまた、上で記載されてpostgresのウェブサイトをリリース修正スプリアス削除された列を持つテーブルにINSERTまたはUPDATE中にエラー「クエリが列を落としたため値を提供」
ジャナ

0

これを聞いたことがあるかもしれませんが、これは恐ろしいアイデアです。

  • 論理的な順序付けは、 SELECT *
  • 論理的な順序の効果は、外観に限定されます。

まったく問題にならないからといってあなたを思いとどまらせず、行構造でPhotoshopをプレイし、ディスプレイに夢中になっていることを認めるなら、もう少し説明しましょう。

  • PostgreSQLには論理的な順序が存在しません
  • 物理的な注文には本当の利点があります(テーブルパッキング)
  • PostgreSQLでは、CREATE TABLEどちらの後にも物理的な順序を制御することはできません(ただし、優先順位ははるかに高くなります)

そのため、PostgreSQLは不適切な表示レイヤーです。その上で、ALTERうまく動作している間は、ドラゴンを誘惑するべきではありません。ALTER TABLE、およびCAVEATセクションの両方でこれについて言及していますが、

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

そして、それだけでは十分でなく、これが恐ろしいアイデアではなく、良いアイデアであるふりをしたいのであれば。それからこれを試して、

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

これらのすべてが過度に聞こえる場合は、データベース内の行を更新するには行を書き換える必要があることに注意してください(TOASTではないと 仮定します。データを解析し、テーブルスキーマを再構築する必要がありますが、行。私は場合は持っていた、この作業を行うには、それは私がそれを行うだろう方法です。

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

実行できないテストケースを指定しました

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

そして、あなたは私たちにあなたがいる正確なバージョンを教えていない。


徹底的な説明をありがとう、特定のバージョンを含めるように質問を編集しました。
アンディ

4
バグはバージョン9.6.0での再現性がある
ypercubeᵀᴹ

0

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

Herokuの手順

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

0

私もこのバグに遭遇しました。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;

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.