Chức năng PostgreSQL không được thực thi khi được gọi từ bên trong CTE


14

Chỉ hy vọng xác nhận quan sát của tôi và nhận được một lời giải thích về lý do tại sao điều này đang xảy ra.

Tôi có một chức năng được định nghĩa là:

CREATE OR REPLACE FUNCTION "public"."__post_users_id_coin" ("coins" integer, "userid" integer) RETURNS TABLE (id integer) AS '
UPDATE
users
SET
coin = coin + coins
WHERE
userid = users.id
RETURNING
users.id' LANGUAGE "sql" COST 100 ROWS 1000
VOLATILE
RETURNS NULL ON NULL INPUT
SECURITY INVOKER

Khi tôi gọi hàm này từ CTE, nó thực thi lệnh SQL nhưng không kích hoạt hàm, ví dụ:

WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))

SELECT
1 -- Select 1 but update not performed

Mặt khác, nếu tôi gọi hàm từ CTE và sau đó chọn kết quả của CTE (hoặc gọi hàm trực tiếp mà không có CTE), nó sẽ thực thi lệnh SQL và kích hoạt chức năng, ví dụ:

WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))

SELECT
*
FROM
test -- Select result and update performed

hoặc là

SELECT * FROM __post_users_id_coin(10,1)

Vì tôi không thực sự quan tâm đến kết quả của chức năng (chỉ cần nó để thực hiện cập nhật), có cách nào để làm việc này mà không chọn kết quả của CTE không?

Câu trả lời:


11

Đó là loại hành vi dự kiến. CTE được vật chất hóa nhưng có một ngoại lệ.

Nếu một CTE không được tham chiếu trong truy vấn cha thì nó hoàn toàn không được cụ thể hóa. Bạn có thể thử điều này chẳng hạn và nó sẽ chạy tốt:

WITH not_executed AS (SELECT 1/0),
     executed AS (SELECT 1)
SELECT * FROM executed ;

Mã được sao chép từ một nhận xét trong bài đăng trên blog của Craig Ringer:
CTE của PostgreQuery là hàng rào tối ưu hóa .


Trước khi thử điều này và một số truy vấn tương tự, tôi đã nghĩ rằng ngoại lệ là: "khi CTE không được tham chiếu trong truy vấn chính hoặc trong CTE khác và không tham chiếu CTE khác". Vì vậy, nếu bạn muốn CTE được thực thi nhưng kết quả không được hiển thị trong kết quả truy vấn, tôi nghĩ đây sẽ là một cách giải quyết (tham chiếu nó trong CTE khác).

Nhưng than ôi, nó không hoạt động như tôi mong đợi:

WITH test AS
    (SELECT * FROM __post_users_id_coin(10, 1)),
  execute_test AS 
    (TABLE test)
SELECT 1 ;     -- no, it doesn't do the update

và do đó, "quy tắc ngoại lệ" của tôi là không chính xác. Khi một CTE được tham chiếu bởi một CTE khác và không ai trong số chúng được tham chiếu bởi truy vấn chính, tình huống phức tạp hơn và tôi không chắc chắn chính xác điều gì xảy ra và khi CTE được cụ thể hóa. Tôi không thể tìm thấy bất kỳ tài liệu tham khảo cho các trường hợp như vậy trong tài liệu.


Tôi không thấy giải pháp nào tốt hơn việc sử dụng những gì bạn đã đề xuất:

SELECT * FROM __post_users_id_coin(10, 1) ;

hoặc là:

WITH test AS
    (SELECT * FROM __post_users_id_coin(10, 1))
SELECT *
FROM test ;

Nếu hàm cập nhật nhiều hàng và bạn nhận được nhiều hàng (có 1) trong kết quả, bạn có thể tổng hợp để có được một hàng:

SELECT MAX(1) AS result FROM __post_users_id_coin(10, 1) ;

nhưng tôi muốn có kết quả của hàm thực hiện cập nhật, với SELECT *ví dụ của bạn, vì vậy, bất kỳ cuộc gọi nào truy vấn này đều biết nếu có cập nhật và những thay đổi trong bảng là gì.



4

Đây là dự kiến, hành vi tài liệu.

Tom Lane giải thích nó ở đây.

Tài liệu hướng dẫn ở đây:

Báo cáo dữ liệu thay đổi trong WITHđược thực hiện đúng một lần, và luôn hoàn thành , độc lập với việc truy vấn chính đọc tất cả (hoặc thực sự có) của sản lượng của họ. Lưu ý rằng điều này khác với quy tắc SELECTtrong WITH: như đã nêu trong phần trước, việc thực hiện a SELECTchỉ được thực hiện khi có truy vấn chính yêu cầu đầu ra của nó .

Nhấn mạnh đậm của tôi. "Dữ liệu sửa đổi" là INSERT, UPDATEDELETEtruy vấn. (Trái ngược với SELECT.). Hướng dẫn một lần nữa:

Bạn có thể sử dụng báo cáo dữ liệu thay đổi ( INSERT, UPDATEhoặc DELETE) trong WITH.

Chức năng đúng

CREATE OR REPLACE FUNCTION public.__post_users_id_coin (_coins integer, _userid integer)
  RETURNS TABLE (id integer) AS
$func$
UPDATE users u
SET    coin = u.coin + _coins  -- see below
WHERE  u.id = _userid
RETURNING u.id
$func$ LANGUAGE sql COST 100 ROWS 1000 STRICT;

Tôi bỏ các mệnh đề (tiếng ồn) mặc định và STRICTlà từ đồng nghĩa ngắn choRETURNS NULL ON NULL INPUT .

Hãy chắc chắn rằng bằng cách nào đó tên tham số không xung đột với tên cột. Tôi đã chuẩn bị trước _, nhưng đó chỉ là sở thích cá nhân của tôi.

Nếu coincó thể NULLtôi đề nghị:

SET    coin = CASE WHEN coin IS NULL THEN _coins ELSE coin + _coins END

Nếu users.idlà khóa chính, thì RETURNS TABLEcũng không ROWs 1000có nghĩa gì cả. Chỉ một hàng duy nhất có thể được cập nhật / trả lại. Nhưng đó là tất cả bên cạnh điểm chính.

Gọi đúng

Sẽ không có nghĩa gì khi sử dụng RETURNINGmệnh đề và trả về các giá trị từ hàm của bạn nếu bạn sẽ bỏ qua các giá trị được trả về trong cuộc gọi. Nó cũng không có ý nghĩa để phân tách các hàng trả lại với SELECT * FROM ...nếu bạn bỏ qua chúng.

Chỉ cần trả về hằng số vô hướng ( RETURNING 1), xác định hàm là RETURNS int(hoặc thả RETURNINGhoàn toàn và tạo nó RETURNS void) và gọi nó bằngSELECT my_function(...)

Giải pháp

Vì bạn ...

không thực sự quan tâm đến kết quả

.. chỉ là SELECTmột dạng không đổi của CTE. Nó được đảm bảo được thực hiện miễn là nó được tham chiếu ở bên ngoài SELECT(trực tiếp hoặc gián tiếp).

WITH test AS (SELECT __post_users_id_coin(10, 1))
SELECT 1 FROM test;

Nếu bạn thực sự có chức năng trả về thiết lập và vẫn không quan tâm đến đầu ra:

WITH test AS (SELECT * FROM __post_users_id_coin(10, 1))
SELECT 1 FROM test LIMIT 1;

Không cần phải trả lại hơn 1 hàng. Hàm vẫn được gọi.

Cuối cùng, không rõ lý do tại sao bạn cần CTE để bắt đầu. Có lẽ chỉ là một bằng chứng về khái niệm.

Quan hệ gần gũi:

Câu trả lời liên quan về SO:

Và xem xét:


Tuyệt vời, fan hâm mộ lớn và vinh dự có câu trả lời của bạn quá Erwin. Tôi đang sử dụng CTE vì tôi đang thực hiện INSERTtrước UPDATEchức năng gói giống nhau - không có giao dịch nào khả dụng.
Andy

Đẹp. Chỉ cần aq: là testtrong WITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;coi là một sửa đổi CTE hay không?
ypercubeᵀᴹ

@ ypercubeᵀᴹ: A SELECTkhông "sửa đổi dữ liệu" theo thuật ngữ CTE. Tôi đã thêm một số làm rõ ở trên. Đó là trách nhiệm của người dùng nếu (các) anh ta thêm mã vào một chức năng sửa đổi dữ liệu đằng sau màn cửa.
Erwin Brandstetter
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.