CTE 내부에서 호출 될 때 PostgreSQL 함수가 실행되지 않습니다


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 ;

Craig Ringer의 블로그 게시물의 주석에서 복사 된 코드 :
PostgreSQL의 CTE는 최적화 펜스 입니다.


이 쿼리와 몇 가지 유사한 쿼리를 시도하기 전에 예외는 "부모 쿼리 나 다른 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 *예제 와 같이 업데이트를 수행하는 함수의 결과가 반환되도록하고 싶습니다. 따라서이 쿼리를 호출하면 업데이트가 있는지 테이블의 변경 사항을 알 수 있습니다.



4

이것은 예상되는 문서화 된 동작입니다.

Tom Lane이 여기에 설명합니다.

여기 매뉴얼에 문서화되어 있습니다.

문 데이터-수정WITH정확히 한 번 실행을하고, 항상 완료 , 독립적 여부의 차 조회는 출력의 (실제로 나) 모든 읽습니다. 공지 사항이에 대한 규칙 다르다는 점 SELECT에서 WITH: 이전 섹션에 명시된 같이의 실행 SELECT까지만 차 조회의 출력을 요구로 수행됩니다 .

대담한 강조 광산. "데이터는-수정"입니다 INSERT, UPDATEDELETE쿼리. (와 반대 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;

기본 (노이즈) 절을 삭제했으며 STRICT에 대한 짧은 동의어입니다RETURNS NULL ON NULL INPUT .

매개 변수 이름이 열 이름과 충돌하지 않는지 확인하십시오. 나는 앞에 붙었 _지만, 그것은 나의 개인적인 취향 일 뿐이다.

내가 제안 coin할 수 있다면 NULL:

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

경우 users.id다음 기본 키도 RETURNS TABLEROWs 1000말이. 단일 행만 업데이트 / 반환 할 수 있습니다. 그러나 그것은 요점 옆에 있습니다.

적절한 전화

RETURNING어쨌든 호출에서 반환 된 값을 무시하려면 절 을 사용하고 함수에서 값을 반환 하는 것은 의미가 없습니다 . SELECT * FROM ...어쨌든 무시한 경우 반환 된 행을 분해하는 것은 의미가 없습니다 .

스칼라 상수 ( RETURNING 1)를 반환 하고 함수를 다음과 같이 정의 RETURNS int하거나 (또는 RETURNING완전히 삭제하고 RETURNS void) 호출하십시오.SELECT my_function(...)

해결책

너가 ~ 한 뒤로 ...

실제로 결과에 신경 쓰지 마십시오

.. SELECTCTE의 상수입니다. 외부에서 SELECT(직접 또는 간접적으로) 참조되는 한 실행되도록 보장됩니다 .

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

실제로 리턴 리턴 기능이 있고 여전히 출력에 신경 쓰지 않는 경우 :

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

하나 이상의 행을 반환 할 필요가 없습니다. 이 함수는 여전히 호출됩니다.

마지막으로 왜 CTE가 필요한지 불분명합니다. 아마도 개념 증명 일뿐입니다.

밀접하게 관련:

SO에 대한 관련 답변 :

그리고 다음을 고려하십시오.


대단한 팬 이여 Erwin에게도 답변 해 주셔서 감사합니다. 동일한 래핑 기능을 사용 INSERT하기 전에 CTE를 사용하고 UPDATE있습니다. 거래가 없습니다.
Andy

좋은. 그냥 aq : 수정 CTE testWITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;간주됩니까?
ypercubeᵀᴹ

@ ypercubeᵀᴹ : A SELECT는 CTE 용어에 따라 "데이터 수정"이 아닙니다. 위의 설명을 추가했습니다. 커튼 뒤의 데이터를 수정하는 함수에 코드를 추가하는 경우 사용자의 책임입니다.
Erwin Brandstetter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.