DO块工作正常,但函数循环太多了吗?

时间:2019-03-25 16:12:26

标签: sql postgresql function loops plpgsql

我有一个vehicle_data表:

CREATE TABLE public.vehicle_data (
  model_name text NOT NULL,
  record_date text NOT NULL,
  actual_inv_days real,
  forecasted_inv_days real[],
  CONSTRAINT vehicle_data_pkey PRIMARY KEY (model_name, record_date)
)

因此我的桌子看起来像这样:

model_name   record_date  actual_inv_days  forecasted_inv_days
car1          1-2015         33            {22,33,11,22,33,44}
car1          2-2015         22            {25,35,10,22,30}
car1          3-2015         30            {32,30,20,11}

我想为用户选择的月份 n 创建一个合并数组,该数组显示月份 1..n 中的actual_inv_days,然后一个数组中每月{em> n 的forecasted_inv_daysmerged_array('car1',2015,3)的结果将是:

{33,22,30,32,30,20,11}

下面的函数可以作为一个DO块正常工作,但是当我将其修改为一个函数时,它似乎比所需的循环了68次这是我编写的每个函数的事,我不知道该怎么办。

-- DROP FUNCTION public.merged_array(text, integer, integer);
CREATE OR REPLACE FUNCTION public.merged_array(model text, yr integer, pivot integer)
  RETURNS real[] AS
$BODY$
DECLARE actual_arr real[];
    element real;
    final_arr real[];
BEGIN
    FOR i IN 1..pivot 
      LOOP
    element:= (
          SELECT actual_inv_days
          FROM   vehicle_data
          WHERE  model_name = model
          AND split_part(record_date,'-',2)::int = yr
          AND split_part(record_date,'-',1)::int = i);
        -- append new element to existing array
        actual_arr:= actual_arr||element;
    END LOOP;
    final_arr:= actual_arr||(
         SELECT forecasted_inv_days
         FROM   vehicle_data
         WHERE  model_name= model
         AND split_part(record_date,'-',2)::int = yr
         AND split_part(record_date,'-',1)::int = pivot);
    RETURN final_arr;
END$BODY$
  LANGUAGE plpgsql;

我想做的是:

  1. 创建actual_array,在其中添加actual_inv_days作为元素,针对特定年份(model)的特定yr,将所选月份作为{ {1}}。

  2. 一个月一个月地走,直到达到“枢轴月”,其中满足模型和年份。

  3. 创建一个pivot,将final_arrayactual_array数组合并。

1 个答案:

答案 0 :(得分:1)

我认为您的代码中没有理由“循环68次超过必要次数”

尽管如此,就像a_horse所说的那样,所有这些都应该是一个查询。您的数据模型可以从重新设计中受益-尤其是record_date列。字符串1-2015的字符串是存储该信息的最不理想的方法之一。甚至不允许简单的范围查询。 (2015-01那样会更有用,但仍然不是最佳选择。)

两个integer列会更好。
我将使用单个date,将其截断为月份,因为它最适合要存储的信息。占用4个字节。

然后您的表可能如下所示(其他所有内容不变):

CREATE TABLE vehicle_data (
  model_name  text NOT NULL
, record_date date NOT NULL
, actual_inv_days real
, forecasted_inv_days real[]
, CONSTRAINT record_date_truncated_to_month CHECK (date_trunc('month', record_date) = record_date)
, CONSTRAINT vehicle_data_pkey PRIMARY KEY (model_name, record_date)
);

然后,您可以将查询包装到此简单的SQL函数中,该函数可以替代旧的函数:

CREATE OR REPLACE FUNCTION public.merged_array(model text, yr integer, pivot integer)
  RETURNS real[] AS
$func$
   SELECT forecasted_inv_days
       || ARRAY (
            SELECT actual_inv_days
            FROM   vehicle_data
            WHERE  model_name = $1
            AND    record_date BETWEEN to_date(yr::text, 'YYYY')
                               AND     to_date(yr||'-'||pivot, 'YYYY-MM')
            ORDER  BY record_date
            )
   FROM   vehicle_data
   WHERE  model_name = model
   AND    record_date = to_date(yr||'-'||pivot, 'YYYY-MM')
$func$  LANGUAGE sql;

当然,您现在也可以传递一个实际日期(截断为月份)...

关于to_date()date_trunc()的手册。

关于上面的相关子查询中使用的ARRAY构造函数: