Giảm cột PostgreQuery 9.6 và tác dụng phụ đối với các hàm SQL với CTE


15

Nếu tôi có một bảng có 3 cột - nói A, B và D - và tôi phải giới thiệu một bảng mới - nói C để thay thế vị trí hiện tại của D. Tôi sẽ sử dụng phương pháp sau:

  1. Giới thiệu 2 cột mới là C và D2.
  2. Sao chép nội dung của D sang D2.
  3. Xóa D.
  4. Đổi tên thành D.

Thứ tự mới sẽ là A, B, C và D.

Tôi nghĩ rằng đây là một thực tiễn hợp pháp vì (cho đến nay) nó không tạo ra vấn đề gì.

Tuy nhiên, hôm nay tôi đã gặp một vấn đề khi một hàm thực hiện một câu lệnh trên cùng một bảng trả về lỗi sau:

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

Và các chi tiết sau:

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

Tôi đã thử khởi động lại PostgreSQL, thực hiện VACUUM FULLvà cuối cùng xóa và tạo lại chức năng như được đề xuất ở đâyở đây nhưng các giải pháp này không hoạt động (ngoài thực tế là họ đã cố gắng giải quyết tình huống trong đó bảng hệ thống đã bị thay đổi).

Có được sự sang trọng khi làm việc với một cơ sở dữ liệu rất nhỏ, tôi đã xuất nó, xóa nó và sau đó nhập lại nó và điều đó đã khắc phục vấn đề với chức năng của tôi.


Tôi đã nhận thức được thực tế rằng người ta không nên loay hoay với thứ tự tự nhiên của các cột bằng cách sửa đổi các bảng hệ thống (bị bẩn tay pg_attribute, v.v.) như đã thấy ở đây:

Có thể thay đổi thứ tự tự nhiên của các cột trong Postgres không?

Đánh giá lỗi do chức năng của tôi bây giờ tôi nhận ra rằng việc thay đổi thứ tự các cột bằng phương thức của tôi cũng là không. Bất cứ ai cũng có thể tỏa sáng như tại sao những gì tôi đang làm cũng sai?


Phiên bản Postgres là 9.6.0.

Đây là chức năng:

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

Tôi đã thực hiện đổi tên / sắp xếp lại trên cả hai cột facebook_idstripe_id(một cột mới đã được thêm vào trước đó, đó là lý do cho việc đổi tên, nhưng không bị truy vấn này chạm vào).

Có các cột theo một thứ tự nhất định hoàn toàn không được quan tâm cho đơn hàng. Tuy nhiên, lý do để đặt câu hỏi này là do lo ngại rằng việc đổi tên và xóa một cột đơn giản có thể gây ra vấn đề thực sự cho ai đó sử dụng các chức năng trong chế độ sản xuất (như đã xảy ra với chính tôi).


Hãy xem Làm thế nào để tôi thay đổi vị trí của một cột trong bảng cơ sở dữ liệu PostgreQuery? trên Stack Overflow có thể hữu ích, mặc dù họ chủ yếu đề nghị KHÔNG sắp xếp lại các cột của bạn.
joanolo

Câu trả lời:


16

Lỗi có thể xảy ra vào ngày 9.6 và 9.6.1

Điều này hoàn toàn giống như một lỗi với tôi ...

Tôi không biết tại sao nó lại xảy ra, nhưng tôi có thể xác nhận rằng nó xảy ra. Đây là thiết lập được tìm thấy đơn giản nhất tái tạo sự cố (trong phiên bản 9.6.0 và 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" ;

Sau khi thiết lập này, câu lệnh tiếp theo chỉ hoạt động

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

Tại thời điểm này, chúng tôi DROP một cột:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Thay đổi này làm cho câu lệnh tiếp theo tạo ra lỗi

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

tương tự như được đề cập bởi @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');

Thả và tái tạo chức năng KHÔNG giải quyết vấn đề.
VACUUM FULL (bảng hoặc toàn bộ cơ sở dữ liệu) không giải quyết được vấn đề.


Báo cáo lỗi đã được chuyển đến danh sách gửi thư PostgreSQL thích hợp và chúng tôi đã có phản hồi rất nhanh :

Tôi không thể tái tạo điều này trong đầu chi nhánh hoặc 9,6. Tôi tin rằng nó đã được sửa bởi bản vá này, nó đã xuất hiện sau một chút sau 9.6.1:

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

Nhưng cảm ơn vì báo cáo!

liên quan, tom lane


Phiên bản 9.6.2

Vào ngày 2017 / 03-06, tôi có thể xác nhận rằng tôi không thể tái tạo hành vi này trên phiên bản 9.6.2. Đó là, lỗi dường như đã được sửa chữa trong bản phát hành này.

CẬP NHẬT

Mỗi bình luận của @Jana: "Tôi có thể xác nhận lỗi có trong phiên bản 9.6.1 và đã được sửa trong 9.6.2. Bản sửa lỗi cũng được liệt kê trên trang web phát hành postgres : Khắc phục lỗi" cung cấp giá trị cho lỗi cột bị rớt "trong khi XÁC NHẬN hoặc CẬP NHẬT trên một bảng có cột bị rớt "



4
Tôi đang sử dụng 9.6.0 và @joanolo là chính xác, tôi có thể tái tạo lỗi bằng phương pháp của mình. Nếu Tom không thể sao chép nó, đó có thể là một lỗi bị cô lập với phiên bản cụ thể này (và tôi cho rằng 9.6.1?). Như đã đề cập trong câu trả lời, vấn đề xuất hiện khi thả một cột vào bảng sử dụng hàm có CTE ảnh hưởng đến bảng đó . Do đó, vấn đề không chỉ xảy ra với việc sắp xếp lại và đó là điều tôi đang cố gắng giải quyết với câu hỏi của mình. Tôi sẽ chỉnh sửa tiêu đề để phản ánh điều này.
Andy

Tôi có thể xác nhận lỗi hiện diện trong 9.6.1 và đã được sửa trong 9.6.2. Bản sửa lỗi cũng được liệt kê trên trang web phát hành postgres : Khắc phục lỗi "truy vấn cung cấp giá trị cho cột bị rớt" trong khi CHỌN hoặc CẬP NHẬT trên một bảng có cột bị rớt
Jana

0

Tôi biết có lẽ bạn đã nghe điều này trước đây, nhưng đây là một ý tưởng khủng khiếp.

  • Đặt hàng hợp lý chỉ có tác dụng những thứ như SELECT *
  • Hiệu quả của trật tự logic được giới hạn ở ngoại hình.

Vì vậy, nếu không quan trọng chút nào không làm bạn thất vọng và chúng tôi thừa nhận rằng chúng tôi chỉ chơi Photoshop với cấu trúc hàng và ám ảnh trên màn hình, hãy giải thích thêm một số điều.

  • Đặt hàng hợp lý không tồn tại trong PostgreSQL
  • Đặt hàng vật lý có lợi ích thực sự (đóng gói bảng)
  • PostgreSQL không cung cấp cho bạn bất kỳ quyền kiểm soát nào đối với Đặt hàng vật lý sau CREATE TABLEđó (mặc dù đó sẽ là ưu tiên cao hơn nhiều )

Vì vậy, PostgreSQL là một lớp hiển thị xấu. Trong tất cả những điều đó, trong khiALTER hoạt động tốt, bạn không nên cám dỗ con rồng. Cả hai ALTER TABLEphần CAVEAT đều đề cập đến điều này,

Một số lệnh DDL, hiện chỉ TRUNCATEcác hình thức viết lại bảng ALTER TABLE, không an toàn MVCC. Điều này có nghĩa là sau khi cắt bớt hoặc viết lại các cam kết, bảng sẽ xuất hiện trống đối với các giao dịch đồng thời, nếu chúng đang sử dụng một ảnh chụp nhanh được thực hiện trước khi lệnh DDL được cam kết. Đây sẽ chỉ là một vấn đề đối với một giao dịch không truy cập vào bảng được đề cập trước khi lệnh DDL bắt đầu - bất kỳ giao dịch nào đã thực hiện sẽ giữ ít nhất một khóa bảng ACCESS SHARE, sẽ chặn lệnh DDL cho đến khi giao dịch đó hoàn thành. Vì vậy, các lệnh này sẽ không gây ra bất kỳ sự không nhất quán rõ ràng nào trong nội dung bảng cho các truy vấn liên tiếp trên bảng đích, nhưng chúng có thể gây ra sự không thống nhất có thể nhìn thấy giữa nội dung của bảng đích và các bảng khác trong cơ sở dữ liệu.

Và, nếu tất cả những điều đó là không đủ, và bạn vẫn muốn giả vờ rằng đây là một ý tưởng tốt chứ không phải là một ý tưởng khủng khiếp. Sau đó thử cái này

  1. Đổ bàn với pg_dump -t
  2. Sắp xếp lại các cột bằng tay trong bãi chứa.
  3. Tải các công cụ vào một bảng TEMP.
  4. BEGIN Một giao dịch
  5. DROP cái bàn cũ hoàn toàn,
  6. RENAME bảng tạm thời để bảng prod.
  7. COMMIT

Nếu tất cả điều đó nghe có vẻ quá mức, hãy nhớ rằng việc cập nhật các hàng trong cơ sở dữ liệu yêu cầu viết lại các hàng (giả sử chúng không phải là TOAST . Bạn phải phân tích dữ liệu và xây dựng lại lược đồ bảng, nhưng bằng cách nào đó bạn phải viết lại Nếu tôi phải thực hiện nhiệm vụ này, đó là cách tôi sẽ thực hiện.

Nhưng, tất cả điều này đang nói chung. Không ai đã sao chép kết quả của bạn.

Bạn đã đưa ra một trường hợp thử nghiệm mà chúng tôi không thể chạy

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

Và, bạn đã không cho chúng tôi biết phiên bản chính xác mà bạn đang sử dụng.


Cảm ơn bạn đã giải thích kỹ lưỡng, tôi đã chỉnh sửa câu hỏi để bao gồm phiên bản cụ thể.
Andy

4
Lỗi này có thể tái tạo trong phiên bản 9.6.0
ypercubeᵀᴹ

0

Tôi đã khắc phục lỗi này bằng cách sao lưu và khôi phục cơ sở dữ liệu của mình.

Các bước cho Heroku

  • Bật chế độ bảo trì: heroku maintenance:on
  • Sao lưu cơ sở dữ liệu: heroku pg:backups:capture
  • Khôi phục cơ sở dữ liệu: heroku pg:backups:restore
  • Khởi động lại ứng dụng: heroku restart
  • Tắt chế độ bảo trì: heroku maintenance:off

0

Tôi cũng gặp phải lỗi này. Đối với những người không muốn sao lưu / khôi phục hoàn toàn DB của họ. Biết rằng chỉ cần sao chép bảng hoạt động. Không có cách "kỳ diệu" để sao chép một bảng mặc dù. Tôi đã làm điều đó bằng cách sử dụng:

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;

Sau đó, bạn vẫn sẽ cần phải tự tạo lại các chỉ mục, khóa ngoại và mặc định của mình. Tái tạo bảng như thế này làm cho lỗi biến mất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.