การวางคอลัมน์ PostgreSQL 9.6 และผลข้างเคียงต่อฟังก์ชัน SQL ด้วย CTE


15

ถ้าฉันมีตารางที่มี 3 คอลัมน์ - พูด A, B และ D - และฉันต้องแนะนำใหม่ - พูด C เพื่อแทนที่ตำแหน่งปัจจุบันของ D ฉันจะใช้วิธีการต่อไปนี้:

  1. แนะนำ 2 คอลัมน์ใหม่เป็น C และ D2
  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 ได้อย่างไร บน Stack Overflow ที่อาจช่วยได้แม้ว่าส่วนใหญ่จะไม่แนะนำให้เรียงลำดับคอลัมน์ของคุณใหม่
joanolo

คำตอบ:


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');

ณ จุดนี้เรา DROP หนึ่งคอลัมน์:

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');

การปล่อยและสร้างฟังก์ชันใหม่ไม่สามารถแก้ปัญหาได้
สูญญากาศเต็มรูปแบบ (ตารางหรือฐานข้อมูลทั้งหมด) ไม่ได้แก้ปัญหา


รายงานข้อผิดพลาดถูกส่งผ่านไปยังรายชื่อผู้รับจดหมาย 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 นั่นคือข้อผิดพลาดดูเหมือนว่าจะได้รับการแก้ไขในรุ่นนี้

UPDATE

ตามความคิดเห็นของ @Jana: "ฉันสามารถยืนยันข้อผิดพลาดที่มีอยู่ใน 9.6.1 และได้รับการแก้ไขใน 9.6.2 การแก้ไขยังแสดงอยู่ในเว็บไซต์ของ postgres release : แบบสอบถาม Fix ปลอม" ให้ค่าสำหรับคอลัมน์ที่หายไป "ข้อผิดพลาดในระหว่าง INSERT หรือ UPDATE บนโต๊ะที่มีคอลัมน์ดร็อป "



4
ฉันใช้ 9.6.0 และ @joanolo ถูกต้องฉันสามารถทำซ้ำข้อผิดพลาดด้วยวิธีการของเขา ถ้าทอมไม่สามารถสร้างมันขึ้นมาใหม่ได้มันอาจเป็นข้อผิดพลาดที่แยกได้กับรุ่นเฉพาะนี้ (และ 9.6.1 ฉันถือว่า?) ตามที่ระบุไว้ในคำตอบที่ปัญหาจะปรากฏขึ้นเมื่อลดลงคอลัมน์ในตารางและใช้ฟังก์ชั่นที่มีผลกระทบต่อ CTE ที่โต๊ะ ดังนั้นปัญหาไม่ใช่เพียงแค่การจัดเรียงใหม่และนั่นคือสิ่งที่ฉันพยายามทำตามคำถามของฉัน ฉันจะแก้ไขชื่อเพื่อสะท้อนสิ่งนี้
แอนดี้

ฉันสามารถยืนยันข้อผิดพลาดที่มีอยู่ใน 9.6.1 และได้รับการแก้ไขใน 9.6.2 การแก้ไขยังแสดงรายการอยู่ในเว็บไซต์รุ่น postgres : การสืบค้น Fix ปลอมแปลงให้ข้อผิดพลาดระหว่างคอลัมน์ INSERT หรือ UPDATE บนตารางที่มีคอลัมน์ดร็อป
Jana

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 ตาราง temp ไปยังตาราง prod
  7. COMMIT

หากทั้งหมดนั้นฟังดูมากเกินไปโปรดจำไว้ว่าการอัปเดตแถวในฐานข้อมูลจำเป็นต้องเขียนแถวใหม่ (สมมติว่าไม่ใช่ TOASTคุณต้องแยกวิเคราะห์ข้อมูลและสร้างตารางสคีมาใหม่ แต่วิธีที่คุณต้องเขียนใหม่ แถวถ้าฉันต้องทำงานนี้นั่นเป็นวิธีที่ฉันจะทำ

แต่ทั้งหมดนี้พูดโดยทั่วไป ไม่มีใครทำซ้ำผลลัพธ์ของคุณ

คุณได้รับกรณีทดสอบที่เราไม่สามารถเรียกใช้

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

และคุณยังไม่ได้บอกรุ่นที่แน่นอนที่คุณใช้


ขอบคุณสำหรับคำอธิบายอย่างละเอียดฉันได้แก้ไขคำถามเพื่อให้รวมถึงรุ่นที่เฉพาะเจาะจง
Andy

4
ข้อผิดพลาดสามารถทำซ้ำได้ในรุ่น 9.6.0
ypercubeᵀᴹ

0

ฉันได้รับข้อผิดพลาดนี้โดยการสำรองและกู้คืนฐานข้อมูลของฉัน

ขั้นตอนสำหรับ Heroku

  • เปิดโหมดบำรุงรักษา: heroku maintenance:on
  • สำรองฐานข้อมูล: heroku pg:backups:capture
  • กู้คืนฐานข้อมูล: heroku pg:backups:restore
  • รีสตาร์ทแอพ: heroku restart
  • ปิดโหมดบำรุงรักษา: heroku maintenance:off

0

ฉันพบปัญหานี้เช่นกัน สำหรับผู้ที่ไม่ต้องการสำรองข้อมูล / คืนค่าฐานข้อมูลอย่างสมบูรณ์ รู้ว่าเพียงแค่คัดลอกตารางงาน ไม่มีวิธี "ขลัง" ในการคัดลอกตาราง ฉันทำได้โดยใช้:

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.