将标签数组传递给 plpgsql 函数并在 WHERE 条件下使用它

Pass array of tags to a plpgsql function and use it in WHERE condition

我想根据他们的 tags 创建一个 returns items 的函数。但是,我不知道如何在 IN() 子句中格式化数组。我相信这就是我没有结果的原因。

这是我得到的:

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;

然后当我打电话时:

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

即使有标有“游戏”的项目,我也没有得到任何结果。

测试用例

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);

函数:

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;

通话:

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

尝试在 format 语句中使用 tags 代替
'''' || array_to_string(tags, "','") || ''''
IN 子句中的结果将类似于
IN ('gaming','sport').

因为我没有退货。

        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) ;

你实际上 returning 结果。您将为此使用 RETURN QUERY EXECUTE。示例:

  • PostgreSQL parameterized Order By / Limit in table function

但是你不需要动态 SQL 在这里开始...

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 ()     -- 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;

用实际数组调用:

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

或调用项目列表 ("dictionary"):

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

要点

  • 使用 RETURN QUERY 实际 return 结果行。

    • PostgreSQL function returning multiple result sets
  • 除非需要,否则不要使用动态 SQL。 (这里没有EXECUTE。)

  • 使用 ANY 构造而不是 IN。为什么?

  • 为了方便起见,我建议使用 VARIADIC 函数。通过这种方式,您可以根据自己的选择传递数组或项目列表。参见:

    • Pass multiple values in single parameter
  • 尽可能避免在 Postgres 中使用大小写混合的标识符。

    • Are PostgreSQL column names case-sensitive?

不确定你为什么有 IF array_length(tags, 1) > 0 THEN,但可能可以用 IF tags IS NOT NULL THEN 代替,或者根本没有 IF,然后用 IF NOT FOUND THEN 代替。更多:

  • Dynamic SQL (EXECUTE) as condition for IF statement