Apenas esperando para confirmar minha observação e obter uma explicação sobre por que isso está acontecendo.
Eu tenho uma função definida como:
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
Quando eu chamo essa função de uma CTE, ela executa o comando SQL mas não aciona a função, por exemplo:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))
SELECT
1 -- Select 1 but update not performed
Por outro lado, se eu chamar a função de um CTE e depois selecionar o resultado do CTE (ou chamar a função diretamente sem CTE) ele executa o comando SQL e aciona a função, por exemplo:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))
SELECT
*
FROM
test -- Select result and update performed
ou
SELECT * FROM __post_users_id_coin(10,1)
Como não me importo muito com o resultado da função (só preciso dela para realizar a atualização), existe alguma maneira de fazer isso funcionar sem selecionar o resultado do CTE?
Esse é o tipo de comportamento esperado. CTEs são materializados, mas há uma exceção.
Se um CTE não for referenciado na consulta pai, ele não será materializado. Você pode tentar isso, por exemplo, e funcionará bem:
Código copiado de um comentário na postagem do blog de Craig Ringer:
CTEs do PostgreSQL são cercas de otimização .
Antes de tentar esta e várias consultas semelhantes, pensei que a exceção era: "quando um CTE não é referenciado na consulta pai ou em outro CTE e não se referencia a outro CTE". Então, se você queria que o CTE fosse executado, mas os resultados não aparecem no resultado da consulta, pensei que isso seria uma solução alternativa (referenciando-o em outro CTE).
Mas, infelizmente, não funciona como eu esperava:
e, portanto, minha "regra de exceção" não está correta. Quando uma CTE é referenciada por outra CTE e nenhuma delas é referenciada pela consulta pai, a situação fica mais complicada e não sei exatamente o que acontece e quando as CTEs são materializadas. Também não consigo encontrar nenhuma referência para esses casos na documentação.
Não vejo solução melhor do que usar o que você já sugeriu:
ou:
Se a função atualizar várias linhas e você obtiver muitas linhas (com
1
) no resultado, poderá agregar para obter uma única linha:mas eu preferiria ter o resultado da função que faz uma atualização retornado,
SELECT *
como seu exemplo, então o que quer que chame esta consulta saiba se houve atualizações e quais foram as alterações na tabela.Esse é um comportamento esperado e documentado.
Tom Lane explica aqui.
Documentado no manual aqui:
Ênfase em negrito minha. "Modificação de dados" são
INSERT
,UPDATE
eDELETE
consultas. (Em oposição aSELECT
.). O manual mais uma vez:função adequada
Abandonei as cláusulas padrão (ruÃdo) e
STRICT
é o sinônimo curto paraRETURNS NULL ON NULL INPUT
.Certifique-se de alguma forma que os nomes dos parâmetros não entrem em conflito com os nomes das colunas. Prefixei com
_
, mas essa é apenas minha preferência pessoal.Se
coin
puderNULL
, sugiro:Se
users.id
for a chave primária, entãoRETURNS TABLE
nemROWs 1000
faz sentido. Apenas uma única linha pode ser atualizada/retornada. Mas isso é tudo fora do ponto principal.chamada adequada
Não faz sentido usar a
RETURNING
cláusula e retornar valores de sua função se você for ignorar os valores retornados na chamada de qualquer maneira. Também não faz sentido decompor as linhas retornadasSELECT * FROM ...
se você as ignorar de qualquer maneira.Basta retornar uma constante escalar (
RETURNING 1
), definir a função comoRETURNS int
(ou descartarRETURNING
completamente e torná-laRETURNS void
) e chamá-la comSELECT my_function(...)
Solução
Desde que você ...
.. apenas
SELECT
uma constante do CTE. É garantido que será executado desde que seja referenciado no exteriorSELECT
(direta ou indiretamente).Se você realmente tem uma função de retorno definido e ainda não se importa com a saÃda:
Não há necessidade de retornar mais de 1 linha. A função ainda é chamada.
Finalmente, não está claro por que você precisa do CTE para começar. Provavelmente apenas uma prova de conceito.
Intimamente relacionado:
Resposta relacionada no SO:
E considere: