ฟังก์ชั่น PostgreSQL ไม่ได้ดำเนินการเมื่อเรียกจากภายใน CTE


14

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

ฉันมีฟังก์ชั่นที่กำหนดเป็น:

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

เมื่อฉันเรียกใช้ฟังก์ชันนี้จาก CTE คำสั่งจะเรียกใช้งานคำสั่ง SQL แต่ไม่เรียกใช้ฟังก์ชันตัวอย่างเช่น:

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

SELECT
1 -- Select 1 but update not performed

ในทางกลับกันถ้าฉันเรียกใช้ฟังก์ชั่นจาก CTE แล้วเลือกผลลัพธ์ของ CTE (หรือเรียกใช้ฟังก์ชันโดยตรงโดยไม่มี CTE) มันจะเรียกใช้งานคำสั่ง SQL และเรียกใช้ฟังก์ชันเช่น:

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

SELECT
*
FROM
test -- Select result and update performed

หรือ

SELECT * FROM __post_users_id_coin(10,1)

เนื่องจากฉันไม่สนใจเกี่ยวกับผลลัพธ์ของฟังก์ชั่น (เพียงต้องการใช้เพื่อทำการอัปเดต) มีวิธีใดบ้างที่จะใช้งานได้โดยไม่ต้องเลือกผลลัพธ์ของ CTE?

คำตอบ:


11

นั่นเป็นพฤติกรรมที่คาดหวัง CTE ถูกสร้างขึ้นมา แต่มีข้อยกเว้น

หาก CTE ไม่ได้ถูกอ้างอิงในแบบสอบถามหลักแล้วมันจะไม่ปรากฏเลย คุณสามารถลองได้เช่นนี้และมันจะทำงานได้ดี:

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

รหัสคัดลอกมาจากความคิดเห็นในโพสต์บล็อกเครกริงเกอร์:
CTEs PostgreSQL เป็นรั้วการเพิ่มประสิทธิภาพ


ก่อนที่จะลองใช้ข้อความค้นหานี้และข้อความค้นหาที่คล้ายกันหลายรายการฉันคิดว่าข้อยกเว้นคือ: "เมื่อ CTE ไม่ได้ถูกอ้างอิงในแบบสอบถามหลักหรือใน CTE อื่นและไม่ได้อ้างอิง CTE อื่น" ดังนั้นหากคุณต้องการให้ CTE ดำเนินการ แต่ผลลัพธ์ไม่ปรากฏในผลลัพธ์การค้นหาฉันคิดว่านี่จะเป็นการแก้ปัญหาได้ (อ้างอิงใน CTE อื่น)

แต่อนิจจามันไม่ทำงานอย่างที่ฉันคาดไว้:

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

ดังนั้น "กฎการยกเว้น" ของฉันจึงไม่ถูกต้อง เมื่อ CTE ถูกอ้างอิงโดย CTE อื่นและไม่มีการอ้างถึงโดยแบบสอบถามหลักสถานการณ์จะซับซ้อนมากขึ้นและฉันไม่แน่ใจว่าเกิดอะไรขึ้นและเมื่อ CTE ถูกทำให้เป็นรูปธรรม ฉันไม่พบการอ้างอิงใด ๆ สำหรับกรณีดังกล่าวในเอกสารประกอบ


ฉันไม่เห็นวิธีแก้ปัญหาที่ดีไปกว่าการใช้สิ่งที่คุณแนะนำไปแล้ว:

SELECT * FROM __post_users_id_coin(10, 1) ;

หรือ:

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

หากฟังก์ชั่นอัปเดตหลายแถวและคุณได้รับหลายแถว (พร้อม1) ในผลลัพธ์คุณสามารถรวมเพื่อรับแถวเดียว:

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

แต่ฉันต้องการให้ผลลัพธ์ของฟังก์ชันที่ส่งคืนการอัปเดตพร้อมSELECT *เป็นตัวอย่างของคุณดังนั้นสิ่งที่เรียกว่าการสืบค้นนี้รู้ว่ามีการอัพเดทหรือไม่และการเปลี่ยนแปลงในตารางเป็นอย่างไร


ขอให้เรายังคงอภิปรายนี้ในการแชท
ypercubeᵀᴹ

4

สิ่งนี้เป็นสิ่งที่คาดหวังพฤติกรรมของเอกสาร

ทอมเลนอธิบายไว้ที่นี่

เอกสารในคู่มือที่นี่:

คำสั่งในการปรับเปลี่ยนข้อมูลWITHจะถูกดำเนินการเพียงครั้งเดียวและ ดำเนินการให้เสร็จสิ้นเสมอโดยไม่ขึ้นอยู่กับว่าข้อความค้นหาหลักอ่านผลลัพธ์ทั้งหมดของพวกเขาหรือไม่ แจ้งให้ทราบว่าเรื่องนี้จะแตกต่างจากกฎสำหรับSELECTในWITH: ตามที่ระบุไว้ในส่วนก่อนหน้านี้การดำเนินการจะดำเนินการเพียงเท่าที่แบบสอบถามหลักเรียกร้องเอาท์พุทSELECT

เหมืองเน้นหนัก "ข้อมูลการปรับเปลี่ยน" เป็นINSERT, UPDATEและDELETEคำสั่ง (ตรงข้ามกับSELECT.) คู่มืออีกครั้ง:

คุณสามารถใช้งบข้อมูลการปรับเปลี่ยน ( INSERT, UPDATEหรือDELETE) WITHใน

ฟังก์ชั่นที่เหมาะสม

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;

ฉันลดลงเริ่มต้น (เสียง) และคำสั่ง เป็นคำพ้องความหมายสั้นSTRICTRETURNS NULL ON NULL INPUT

ตรวจสอบให้แน่ใจว่าชื่อพารามิเตอร์นั้นไม่ขัดแย้งกับชื่อคอลัมน์ ฉันเติมด้วย_แต่นั่นเป็นเพียงความชอบส่วนตัวของฉัน

ถ้าฉันcoinสามารถNULLแนะนำ:

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

ถ้าusers.idเป็นคีย์หลักแล้วค่าRETURNS TABLEมิได้ROWs 1000ทำให้รู้สึกใด ๆ สามารถอัพเดต / ส่งคืนได้เพียงแถวเดียว แต่นั่นคือทั้งหมดที่อยู่ข้างจุดหลัก

โทรเหมาะสม

ไม่มีเหตุผลที่จะใช้ส่วนRETURNINGคำสั่งและค่าส่งคืนจากฟังก์ชันของคุณหากคุณจะเพิกเฉยกับค่าที่ส่งคืนในการโทรอยู่ดี นอกจากนี้ยังไม่มีเหตุผลที่จะแยกย่อยแถวที่ส่งคืนด้วยSELECT * FROM ...หากคุณไม่สนใจ

เพียงแค่คืนค่าสเกลาร์สเกลาร์ ( RETURNING 1) กำหนดฟังก์ชันเป็นRETURNS int(หรือปล่อยRETURNINGพร้อมกันแล้วทำRETURNS void) และเรียกมันด้วยSELECT my_function(...)

วิธีการแก้

ตั้งแต่คุณ ...

ไม่สนใจผลลัพธ์

.. เพียงแค่SELECTรูปแบบคงที่ CTE มันรับประกันว่าจะถูกดำเนินการตราบใดที่มีการอ้างอิงในด้านนอกSELECT(โดยตรงหรือโดยอ้อม)

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

หากคุณมีฟังก์ชั่นset-returnและยังไม่สนใจเอาต์พุต:

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

ไม่จำเป็นต้องส่งคืนมากกว่า 1 แถว ฟังก์ชั่นยังคงเรียกว่า

ในที่สุดก็ไม่มีความชัดเจนว่าทำไมคุณต้องเริ่มต้น CTE อาจเป็นเพียงการพิสูจน์แนวคิด

ที่เกี่ยวข้องอย่างใกล้ชิด:

คำตอบที่เกี่ยวข้องกับ SO:

และพิจารณา:


ยอดเยี่ยมแฟนตัวยงและรู้สึกเป็นเกียรติที่ได้รับคำตอบจากคุณเออร์วิน ฉันใช้ CTE ขณะที่ฉันกำลังทำINSERTก่อนหน้านี้UPDATEภายในฟังก์ชันการตัดคำเดียวกัน - ไม่มีธุรกรรม
Andy

ดี เพียงแค่ AQ: เป็นtestในWITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;การพิจารณาการปรับเปลี่ยน CTE หรือไม่?
ypercubeᵀᴹ

@ ypercubeᵀᴹ: A SELECTไม่ใช่ "data-modifying" ตามคำศัพท์ของ CTE ฉันได้เพิ่มความกระจ่างบางอย่างข้างต้น มันเป็นความรับผิดชอบของผู้ใช้ถ้าเขาเพิ่มรหัสลงในฟังก์ชั่นที่แก้ไขข้อมูลหลังม่าน
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.