Pass array of tags to a plpgsql function and use i

2019-08-12 00:31发布

I'd like to create a function that returns items based on their tags. However, I do not know how to format an array in the IN() clause. I believe that is why I get no result.

Here is what I got:

CREATE OR REPLACE FUNCTION getItemsByTag(tags text[])
  RETURNS TABLE (id bigint, title text, tag text[]) AS $$
BEGIN
    IF array_length(tags, 1) > 0

    THEN
            EXECUTE format('
            SELECT d.id, d.title, array_agg(t.title)
            FROM items d
            INNER JOIN item_tags dt
            ON dt.item_id = d.id
            INNER JOIN tags t
            ON t.id = dt.tag_id
            AND t.title IN (%L)
            GROUP BY d.id, d.title
            ', array_to_string(tags, ','));
    -- ELSE ...
    END IF;

END;
$$ LANGUAGE plpgsql;

Then when I call:

select getItemsByTag('{"gaming", "sport"}');

I get no result even though there are items tagged with "gaming".

Test case

CREATE TABLE items(
id serial primary key,
title text);

CREATE TABLE tags(
id serial primary key,
title text);

CREATE TABLE item_tags(
item_id int references items(id),
tag_id int references tags(id),
primary key(item_id, tag_id));

insert into items (title) values ('my title 1'), ('my title 2');
insert into tags (title) values ('tag1'), ('tag2');
insert into item_tags (item_id, tag_id) values (1,1), (1, 2);

Function:

CREATE OR REPLACE FUNCTION getItemsByTag(tags text[])
  RETURNS TABLE (id bigint, title text, tag text[]) AS $$
BEGIN

    IF array_length(tags, 1) > 0

    THEN
            EXECUTE format('
            SELECT d.id, d.title, array_agg(t.title)
            FROM items d
            INNER JOIN item_tags dt
            ON dt.item_id = d.id
            INNER JOIN tags t
            ON t.id = dt.tag_id
            AND t.title IN (%L)
            GROUP BY d.id, d.title
            ', array_to_string(tags, ','));
    -- ELSE ...
    END IF;
END;
$$ LANGUAGE plpgsql;

Call:

 select getItemsByTag('{"tag1", "tag2"}');

3条回答
Juvenile、少年°
2楼-- · 2019-08-12 00:58

Try to use in place of tags in format statement this:
'''' || array_to_string(tags, "','") || ''''
the result in the IN clause will be like
IN ('gaming','sport').

查看更多
太酷不给撩
3楼-- · 2019-08-12 01:13

You are not actually returning the result. You would use RETURN QUERY EXECUTE for that. Example:

But you don't need dynamic SQL here to begin with ...

CREATE OR REPLACE FUNCTION get_items_by_tag(VARIADIC tags text[])
  RETURNS TABLE (id int, title text, tag text[]) AS
$func$
BEGIN
   IF array_length(tags, 1) > 0 THEN
      -- NO need for EXECUTE
      RETURN QUERY
      SELECT d.id, d.title, array_agg(t.title)
      FROM   items d
      JOIN   item_tags dt ON dt.item_id = d.id
      JOIN   tags t       ON t.id = dt.tag_id
      AND    t.title = ANY ($1)     -- use ANY construct
      GROUP  BY d.id;               -- PK covers whole table
      -- array_to_string(tags, ',') -- no need to convert array with ANY
-- ELSE ...
   END IF;
END
$func$  LANGUAGE plpgsql;

Call with actual array:

SELECT * FROM get_items_by_tag(VARIADIC '{tag1,tag2}'::text[]);

Or call with list of items ("dictionary"):

SELECT * FROM get_items_by_tag('tag1', 'tag2');

Major points

Not sure why you have IF array_length(tags, 1) > 0 THEN, but can probably be replaced with IF tags IS NOT NULL THEN or no IF at all and follow up with IF NOT FOUND THEN. More:

查看更多
唯我独甜
4楼-- · 2019-08-12 01:13

It's because I'm not returning anything.

        return query EXECUTE format('
        SELECT d.id, d.title, array_agg(t.title)
        FROM items d
        INNER JOIN item_tags dt
        ON dt.item_id = d.id
        INNER JOIN tags t
        ON t.id = dt.tag_id
        AND t.title = ANY(%L)
        GROUP BY d.id, d.title
        ', tags) ;
查看更多
登录 后发表回答