将标签数组传递给 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
我想根据他们的 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