Search code examples
postgresqlpostgraphile

PostgreSQL -- JOIN UNNEST output with CTE INSERT ID -- INSERT many to many


In a PostgreSQL function, is it possible to join the result of UNNEST, which is an integer array from function input, with an ID returned from a CTE INSERT?

I have PostgreSQL tables like:

CREATE TABLE public.message (
    id SERIAL PRIMARY KEY,
    content TEXT
);

CREATE TABLE public.message_tag (
    id SERIAL PRIMARY KEY,
    message_id INTEGER NOT NULL CONSTRAINT message_tag_message_id_fkey REFERENCES public.message(id) ON DELETE CASCADE,
    tag_id INTEGER NOT NULL CONSTRAINT message_tag_tag_id_fkey REFERENCES public.tag(id) ON DELETE CASCADE
);

I want to create a PostgreSQL function which takes input of content and an array of tag_id. This is for graphile. I want to do it all in one function, so I get a mutation.

Here's what I got so far. I don't know how to join an UNNEST across an id returned from a CTE.

CREATE FUNCTION public.create_message(content text, tags Int[])
RETURNS public.message
AS $$

-- insert to get primary key of message, for many to many message_id
WITH moved_rows AS (
  INSERT INTO public.message (content)
  RETURNING *;
)

-- many to many relation
INSERT INTO public.message_tag
SELECT moved_rows.id as message_id, tagInput.tag_id  FROM moved_rows, UNNEST(tags) as tagInput;
RETURNING *

$$ LANGUAGE sql VOLATILE STRICT;

Solution

  • You're not that far from your goal:

    • the semicolon placement in the CTE is wrong
    • the first INSERT statement lacks a SELECT or VALUES clause to specify what should be inserted
    • the INSERT into tag_message should specify the columns in which to insert (especially if you have that unnecessary serial id)
    • you specified a relation alias for the UNNEST call already, but none for the column tag_id
    • your function was RETURNING a set of message_tag rows but was specified to return a single message row

    To fix these:

    CREATE FUNCTION public.create_message(content text, tags Int[])
    RETURNS public.message
    AS $$
    
    -- insert to get primary key of message, for many to many message_id
    WITH moved_rows AS (
      INSERT INTO public.message (content)
      VALUES ($1)
      RETURNING *
    ),
    -- many to many relation
    _ AS (
    INSERT INTO public.message_tag (message_id, tag_id)
    SELECT moved_rows.id, tagInput.tag_id
    FROM moved_rows, UNNEST($2) as tagInput(tag_id)
    )
    TABLE moved_rows;
    
    $$ LANGUAGE sql VOLATILE STRICT;
    

    (Online demo)