Funkcja PostgreSQL nie jest wykonywana po wywołaniu z wnętrza CTE


16

Mam nadzieję potwierdzić moją obserwację i uzyskać wyjaśnienie, dlaczego tak się dzieje.

Mam funkcję zdefiniowaną jako:

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

Kiedy wywołuję tę funkcję z CTE, wykonuje polecenie SQL, ale nie wyzwala funkcji, na przykład:

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

SELECT
1 -- Select 1 but update not performed

Z drugiej strony, jeśli wywołam funkcję z CTE, a następnie wybiorę wynik CTE (lub wywołam funkcję bezpośrednio bez CTE), wykona polecenie SQL i wyzwoli funkcję, na przykład:

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

SELECT
*
FROM
test -- Select result and update performed

lub

SELECT * FROM __post_users_id_coin(10,1)

Ponieważ tak naprawdę nie dbam o wynik funkcji (wystarczy, że przeprowadzi aktualizację), czy jest jakiś sposób, aby to zadziałało bez wybierania wyniku CTE?

Odpowiedzi:


12

To rodzaj oczekiwanego zachowania. CTE są zmaterializowane, ale istnieje wyjątek.

Jeśli w zapytaniu nadrzędnym nie ma odniesienia do CTE, wówczas w ogóle się nie zmaterializuje. Możesz spróbować na przykład i będzie działał dobrze:

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

Kod skopiowany z komentarza na blogu Craiga Ringera:
CTE PostgreSQL to ogrodzenia optymalizacyjne .


Przed wypróbowaniem tego i kilku podobnych zapytań pomyślałem, że wyjątkiem jest: „gdy do CTE nie ma odniesienia w zapytaniu nadrzędnym lub w innym CTE i nie odwołuje się do innego CTE”. Tak więc, jeśli chcesz, aby CTE zostało wykonane, ale wyniki nie zostały pokazane w wyniku zapytania, pomyślałem, że będzie to obejście problemu (odwołując się do niego w innym CTE).

Ale niestety nie działa tak, jak się spodziewałem:

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

i dlatego moja „reguła wyjątku” jest nieprawidłowa. Kiedy do CTE odwołuje się inny CTE, a żadne z nich nie odwołuje się do zapytania nadrzędnego, sytuacja jest bardziej skomplikowana i nie jestem pewien, co dokładnie się stanie i kiedy materializują się CTE. W dokumentacji nie mogę znaleźć żadnego odniesienia do takich przypadków.


Nie widzę lepszego rozwiązania niż użycie tego, co już zasugerowałeś:

SELECT * FROM __post_users_id_coin(10, 1) ;

lub:

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

Jeśli funkcja aktualizuje wiele wierszy i otrzymujesz w wyniku wiele wierszy (z 1), możesz agregować, aby uzyskać pojedynczy wiersz:

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

ale wolałbym, aby zwrócono wyniki funkcji, która wykonuje aktualizację, z SELECT *twoim przykładem, więc niezależnie od wywołań to zapytanie wie, czy były aktualizacje i jakie były zmiany w tabeli.



5

Jest to oczekiwane, udokumentowane zachowanie.

Tom Lane wyjaśnia to tutaj.

Udokumentowane w instrukcji tutaj:

Dane modyfikujące oświadczenia w WITHwykonywane są tylko raz i zawsze do końca , niezależnie od tego, czy zapytanie podstawowej czyta wszystko (lub nawet każdy) ich produkcji. Zauważ, że różni się to od reguły dla SELECTin WITH: jak podano w poprzedniej sekcji, wykonanie a SELECTjest przeprowadzane tylko tak długo, jak podstawowe zapytanie wymaga jego wyniku .

Odważny nacisk moje. „Dane modyfikujący” są INSERT, UPDATEi DELETEzapytań. (W przeciwieństwie do SELECT.). Instrukcja jeszcze raz:

Można użyć stwierdzenia danych modyfikujące ( INSERT, UPDATElub DELETE) w WITH.

Prawidłowa funkcja

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;

Porzuciłem domyślne klauzule (szum) i STRICTjest to krótki synonimRETURNS NULL ON NULL INPUT .

Upewnij się, że nazwy parametrów nie powodują konfliktu z nazwami kolumn. Byłem zajęty _, ale to tylko moje osobiste preferencje.

Jeśli coinmogę NULL, sugeruję:

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

Jeśli users.idjest kluczem podstawowym, to ani RETURNS TABLEnie ROWs 1000ma sensu. Tylko jeden wiersz może być aktualizowany / zwracany. Ale to wszystko poza głównym punktem.

Prawidłowe połączenie

Nie ma sensu używać RETURNINGklauzuli i zwracać wartości z funkcji, jeśli i tak chcesz zignorować zwrócone wartości w wywołaniu. Nie ma również sensu rozkładanie zwróconych wierszy, SELECT * FROM ...jeśli i tak je zignorujesz.

Po prostu zwróć stałą skalarną ( RETURNING 1), zdefiniuj funkcję jako RETURNS int(lub upuść RETURNINGi zrób to RETURNS void) i wywołaj jąSELECT my_function(...)

Rozwiązanie

Ponieważ ty ...

tak naprawdę nie dbam o wynik

.. po prostu SELECTstała z CTE. Gwarantuje się, że będzie wykonywany tak długo, jak długo będzie się do niego odwoływał SELECT(bezpośrednio lub pośrednio).

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

Jeśli faktycznie masz funkcję zwracania zestawu i nadal nie obchodzi Cię wynik:

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

Nie musisz zwracać więcej niż 1 wiersz. Funkcja jest nadal wywoływana.

Wreszcie, nie jest jasne, dlaczego potrzebujesz CTE na początek. Prawdopodobnie tylko dowód koncepcji.

Blisko związane:

Powiązana odpowiedź na temat SO:

I zastanów się:


Wspaniały, wielki fan i zaszczyt mieć odpowiedź również Erwin. Korzystam z CTE, podobnie jak INSERTprzed UPDATEtą samą funkcją owijania - brak dostępnych transakcji.
Andy,

Ładny. Tylko aq: czy testin jest WITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;uważany za modyfikujący CTE, czy nie?
ypercubeᵀᴹ

@ ypercubeᵀᴹ: A SELECTnie oznacza „modyfikowania danych” zgodnie z terminologią CTE. Dodałem wyjaśnienie powyżej. Obowiązkiem użytkownika jest dodanie kodu do funkcji modyfikującej dane za zasłonami.
Erwin Brandstetter,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.