Обсуждение: You might be able to move the set-returning function into a LATERALFROM item.

Поиск
Список
Период
Сортировка

You might be able to move the set-returning function into a LATERALFROM item.

От
Alexander Farber
Дата:
Good morning,

for a word game in PostgreSQL 10.3 I have a table with jsonb column "tiles".

The column holds either a JSON array of objects (word tiles played) or a string (of swapped letters).

I am trying to fetch a history/protocol of a game with:


CREATE OR REPLACE FUNCTION words_get_moves(
                in_gid    integer
        ) RETURNS TABLE (
                out_action text,
                out_letters text,
                out_words text
        ) AS
$func$
        WITH cte1 AS (
        SELECT 
            mid,
            action,
            STRING_AGG(x->>'letter', '') AS tiles
        FROM (
                SELECT 
                    mid,
                    action,
                    CASE WHEN JSONB_TYPEOF(tiles) = 'array' THEN JSONB_ARRAY_ELEMENTS(tiles) ELSE NULL END AS x
                    --JSONB_ARRAY_ELEMENTS(tiles) AS x
                FROM words_moves
                WHERE gid = in_gid
                --AND JSONB_TYPEOF(tiles) = 'array'
        ) AS p
        GROUP BY mid, action),
        cte2 AS (
                SELECT 
                mid,
                STRING_AGG(y, ', ') AS words
            FROM (
                SELECT 
                    mid,
                    FORMAT('%s (%s)', word, score) AS y
                FROM words_scores
                WHERE gid = in_gid
        ) AS q
        GROUP BY mid)
        SELECT
                action,
                tiles,
                words 
        FROM cte1 
        LEFT JOIN cte2 using (mid) 
        ORDER BY mid ASC;
$func$ LANGUAGE sql;

However calling this stored function gives the error:

ERROR:  0A000: set-returning functions are not allowed in CASE
LINE 18: ...     CASE WHEN JSONB_TYPEOF(tiles) = 'array' THEN JSONB_ARRA...
                                                              ^
HINT:  You might be able to move the set-returning function into a LATERAL FROM item.

I have read that PostgreSQL 10 handles SRF more strictly, but what does it want me to do here, to add 1 more table to the LEFT JOIN?

Thank you
Alex

Re: You might be able to move the set-returning function into aLATERAL FROM item.

От
Laurenz Albe
Дата:
Alexander Farber wrote:
> for a word game in PostgreSQL 10.3 I have a table with jsonb column "tiles".
> 
> The column holds either a JSON array of objects (word tiles played) or a string (of swapped letters).
> 
> I am trying to fetch a history/protocol of a game with:
> 
> 
> CREATE OR REPLACE FUNCTION words_get_moves(
> [...] AS
> $func$
> [...]
>                     CASE WHEN JSONB_TYPEOF(tiles) = 'array' THEN JSONB_ARRAY_ELEMENTS(tiles) ELSE NULL END AS x
> [...]
> $func$ LANGUAGE sql;
> 
> However calling this stored function gives the error:
> 
> ERROR:  0A000: set-returning functions are not allowed in CASE
> LINE 18: ...     CASE WHEN JSONB_TYPEOF(tiles) = 'array' THEN JSONB_ARRA...
>                                                               ^
> HINT:  You might be able to move the set-returning function into a LATERAL FROM item.
> 
> I have read that PostgreSQL 10 handles SRF more strictly, but what does it want me to do here, to add 1 more table to
theLEFT JOIN?
 

The problem is that "jsonb_array_elements" returns several rows, which does not
make sense in this context.  Which of the rows do you want?

If you know that it will always return at most one row, you could use:

 ... THEN (SELECT jae FROM jsonb_array_elements(tiles) jae LIMIT 1)

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com


Re: You might be able to move the set-returning function into a LATERAL FROM item.

От
Tom Lane
Дата:
Alexander Farber <alexander.farber@gmail.com> writes:
> I am trying to fetch a history/protocol of a game with:

>                 SELECT
>                     CASE WHEN JSONB_TYPEOF(tiles) = 'array' THEN
> JSONB_ARRAY_ELEMENTS(tiles) ELSE NULL END AS x

I think you could push the conditionality into a plpgsql function,
something like (untested)

create function jsonb_elements_if_array(j jsonb) returns setof jsonb as $$
begin
  if jsonb_typeof(j) = 'array' then
    return query select jsonb_array_elements(j);
  end if;
end$$
strict immutable language plpgsql;

Note that this gives *no* elements, rather than a single NULL value,
if the input isn't an array --- but that seems to me to make more sense
than your existing code anyhow.  If you disagree, add "else return next
null::jsonb".

            regards, tom lane


Re: You might be able to move the set-returning function into aLATERAL FROM item.

От
Alexander Farber
Дата:
Thank you -

On Tue, Mar 20, 2018 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think you could push the conditionality into a plpgsql function,
something like (untested)

create function jsonb_elements_if_array(j jsonb) returns setof jsonb as $$
begin
  if jsonb_typeof(j) = 'array' then
    return query select jsonb_array_elements(j);
  end if;
end$$
strict immutable language plpgsql;

Note that this gives *no* elements, rather than a single NULL value,
if the input isn't an array --- but that seems to me to make more sense
than your existing code anyhow.  If you disagree, add "else return next
null::jsonb".

I think I will just introduce a separate column (until now I was trying to squeeze 2 different kinds of data - JSON array of objects and a string - into the one column)... I believe Adrian had suggested it before :-)