CTgre içinden çağrıldığında PostgreSQL işlevi yürütülmüyor


16

Sadece gözlemimi onaylamayı ve bunun neden olduğunu açıklamayı umuyorum.

Ben olarak tanımlanan bir işlevi var:

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

Bu işlevi bir CTE'den çağırdığımda, SQL komutunu çalıştırır ancak işlevi tetiklemez , örneğin:

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

SELECT
1 -- Select 1 but update not performed

Öte yandan, işlevi bir CTE'den çağırıp CTE'nin sonucunu seçersem (veya işlevi doğrudan CTE'siz çağırırsanız) SQL komutunu çalıştırır ve işlevi tetikler , örneğin:

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

SELECT
*
FROM
test -- Select result and update performed

veya

SELECT * FROM __post_users_id_coin(10,1)

Fonksiyonun sonucunu gerçekten umursamadığım için (sadece güncellemeyi gerçekleştirmek için ihtiyacım var) CTE'nin sonucunu seçmeden çalışmanın herhangi bir yolu var mı?

Yanıtlar:


12

Bu bir tür beklenen davranış. CTE'ler gerçekleşir, ancak bir istisna vardır.

Ana sorguda bir CTE'ye başvurulmazsa, o zaman hiç gerçekleşmez. Örneğin bunu deneyebilirsiniz ve iyi çalışır:

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

Craig Ringer blog gönderisindeki bir yorumdan kopyalanan kod:
PostgreSQL'in CTE'leri optimizasyon çitleri .


Bu ve birkaç benzer sorgu denemeden önce, istisna olduğunu düşündüm: "bir CTE üst sorgu veya başka bir CTE başvuruda bulunmaz ve kendini başka bir CTE başvuru yok". CTE yürütülmesini istiyordu ama sonuçları sonuçta gösterilmez, Yani, bu bir çözüm olacağını düşündüm (başka bir CTE referans).

Ama ne yazık ki, beklediğim gibi çalışmıyor :

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

ve bu nedenle, "istisna kuralı" doğru değil. Bir CTE'ye başka bir CTE tarafından başvurulduğunda ve hiçbirine üst sorgu tarafından başvurulmadığında, durum daha karmaşıktır ve ne olduğunu ve CTE'lerin ne zaman gerçekleştiğini tam olarak bilmiyorum. Belgelerde bu gibi durumlar için de referans bulamıyorum.


Zaten önerdiklerinizi kullanmaktan daha iyi bir çözüm görmüyorum:

SELECT * FROM __post_users_id_coin(10, 1) ;

veya:

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

İşlev birden çok satırı güncellerse 1ve sonuçta çok sayıda satır (ile ) alırsanız, tek bir satır almak için toplayabilirsiniz:

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

ancak bir güncelleştirme yapan işlevin sonuçlarının SELECT *örnek olarak döndürülmesini tercih ederim , bu nedenle bu sorguyu çağıran ne olursa olsun güncellemeler olup olmadığını ve tablodaki değişikliklerin ne olduğunu bilir.



5

Bu beklenen, belgelenmiş davranış.

Tom Lane bunu burada açıklıyor.

Buradaki kılavuzda belgelenmiştir:

İfadeleri Veri değiştirici içinde WITHtam olarak bir kez yürütülür ve her zaman tamamlanma , bağımsız bir şekilde, ister primer sorgu kendi çıkış (aslında ya da herhangi bir) bütün okur. Uyarı Bunun kuraldan farklı olduğunu SELECTiçinde WITH: Bir önceki bölümde belirtildiği gibi, bir yürütülmesi SELECTsadece primer sorgu çıkışını talep olarak yapılır .

Cesur vurgu benim. "Veri modifiye" dir INSERT, UPDATEve DELETEsorgular. (Aksine SELECT.). Kılavuz bir kez daha:

Sen (veri modifiye ifadeleri kullanabilir INSERT, UPDATEya da DELETE) içinde WITH.

Uygun fonksiyon

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;

Ben varsayılan (gürültü) yan tümceleri düştü ve STRICTkısa eşanlamlıRETURNS NULL ON NULL INPUT .

Parametre adlarının bir şekilde sütun adlarıyla çakışmadığından emin olun. Ben _tercih ettim, ama bu sadece benim kişisel tercihim.

Eğer coinolabilir NULLben öneririz:

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

Eğer users.ido zaman, birincil anahtar ne RETURNS TABLEde ROWs 1000herhangi bir anlam ifade etmiyor. Yalnızca tek bir satır güncellenebilir / döndürülebilir. Ama hepsi ana noktanın yanında.

Uygun çağrı

Yine RETURNINGde çağrıda döndürülen değerleri yoksayarsanız, fıkra ve işlevinizden döndürülen değerleri kullanmak mantıklı değildir . Ayrıca, SELECT * FROM ...yine de yoksayarsanız , döndürülen satırları ayrıştırmanın bir anlamı yoktur .

Sadece bir skaler sabit ( RETURNING 1) döndürün, işlevi olarak tanımlayın RETURNS int(veya RETURNINGtamamen bırakın ve yapın RETURNS void) veSELECT my_function(...)

Çözüm

Senden beri ...

sonucu gerçekten umursamıyorum

.. sadece SELECTCTE'nin sabit bir formudur. Dışta SELECT(doğrudan veya dolaylı olarak) belirtildiği sürece yürütülmesi garanti edilir .

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

Aslında bir set-döndürme fonksiyonunuz varsa ve yine de çıktıyı umursamıyorsanız:

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

1'den fazla satır döndürmeye gerek yok. İşlev hala çağrılır.

Son olarak, başlamak için neden CTE'ye ihtiyacınız olduğu belirsizdir. Muhtemelen sadece bir kavram kanıtı.

Yakından alakalı:

SO ile ilgili cevap:

Ve düşünün:


Müthiş, büyük bir fan ve cevabını almaktan onur duydum Erwin. Ben aynı sarma fonksiyonu içinde bir INSERTönceki yaptığım gibi CTE kullanıyorum UPDATE- hiçbir işlem kullanılabilir.
Andy

Güzel. Sadece aq: testiçinde WITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;değiştirici bir CTE var mı değil mi?
ypercubeᵀᴹ

@ ypercubeᵀᴹ: A SELECT, CTE terminolojisine göre "veri değiştirici" değildir. Yukarıda biraz açıklama ekledim. Perde arkasındaki verileri değiştiren bir fonksiyona kod ekleyip eklememesi kullanıcının sorumluluğundadır.
Erwin Brandstetter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.