在Postgres中有效遍历VARIADIC

时间:2020-01-22 09:52:19

标签: postgresql union variadic-functions

我在Postgres中有下表

通常会像下面这样填充

id    day       visits      passes
1   Monday     {11,13,19}   {13,17}
2   Tuesday    {7,9}        {11,13,19}
3   Wednesday  {2,5,21}     {21,27}
4   Thursday   {3,11,39}    {21,19}` 

为了在几天内获得visitpasses的ID,我编写了以下函数

CREATE OR REPLACE FUNCTION day_entries(p_column TEXT,VARIADIC ids int[]) RETURNS bigint[] AS
$$
DECLARE result bigint[];
DECLARE hold bigint[];
BEGIN
  FOR i IN 1 .. array_upper(ids,1) LOOP
    execute format('SELECT %I FROM days WHERE id = $1',p_column) USING ids[i] INTO hold;
    result := unnest(result) UNION unnest(hold);
  END LOOP;
  RETURN result;
END;
$$
LANGUAGE 'plpgsql';

可与随后对day_entries('visits',1,2,3)返回的调用一起使用

{11,9,19,21,5,13,​​2,7}

尽管它能完成工作,但我仍然担心,基于我对Postgres函数编写的一天的知识,我在处理过程中遇到了一个或多个效率低下的问题。可以通过某种方式简化功能吗?

另一个问题更是好奇心而不是问题:结果中元素的顺序似乎与所触摸的三行中visits条目的顺序无关。尽管就我而言这不是问题,但我很好奇它为什么会发生。

1 个答案:

答案 0 :(得分:2)

您可以在单个语句中进行嵌套和聚合,而无需循环。您可以对数组使用ANY运算符来选择所有匹配的行。

CREATE OR REPLACE FUNCTION day_entries(p_column TEXT, variadic p_ids int[]) 
RETURNS bigint[] AS
$$
DECLARE 
   result bigint[];
BEGIN
  execute 
    format('SELECT array(select unnest(%I) from days WHERE id = any($1))', p_column) 
    USING p_ids -- pass the whole array as a parameter
    INTO result;

  RETURN result;
END;
$$
LANGUAGE plpgsql;

与您的问题无关,但我认为您在设计上走错了路。虽然阵列一开始可能对初学者来说很有趣,但是应该很少使用它们。

并且,如果您发现自己来回嵌套和汇总事情,则有力地表明可以改进某些地方。

我会将您的表分为两个表,一个表存储“日”信息,另一个表中存储访问量 ,并在同一表中用一列来区分这两个表。然后,找到访问就像添加一个... = 'visit'一样简单,而不必处理(缓慢且容易出错的)动态SQL。

不知道更多细节,我可能会创建这样的表:

create table days
(
  id integer not null primary key,
  day character varying(9) not null
);

create table event
(
  day_id integer not null references days,
  event_id integer not null,
  event_type varchar(10) not null check (event_type in ('visit', 'pass'))
);

event_id甚至可能对您未向我们显示的另一个表进行键控是很陌生的-同样,对于非规范化表,这实际上是做不到的。

获取特定日期的所有访问量非常简单:_

select event_id
from event
where day_id in (1,2)
  and event_type = 'visit';

或者如果您确实需要将其作为数组:

select array_agg(event_id)
from event
where day_id in (1,2)
  and event_type = 'visit';

Online example