在SELECT语句中调用函数时的性能

时间:2012-09-11 22:42:03

标签: sql performance postgresql string-formatting plpgsql

我有一个查询,我需要调用SQL函数来格式化查询中的特定列。所需的格式与格式化电话号码非常相似,即。将1234567890更改为(123)456-7890

我已经读过从select语句中调用一个函数可能是一个性能杀手,它在我的情况下有所体现,查询占用的时间超过三倍,我认为函数不会占用这么多更长的时间。该函数以线性时间运行,但确实使用SQL循环。为了了解数据库的大小,此特定查询返回大约220,000行。查询的运行时间来自< 3s到> 9s运行时没有调用函数而不是运行调用函数。需要格式化的列未在连接条件或where子句中编制索引或使用。

此处的性能是否下降,或者我可以采取哪些措施来改善它?

这是有问题的功能:

CREATE OR REPLACE FUNCTION fn(bigint)
  RETURNS character varying LANGUAGE plpgsql AS
$BODY$
DECLARE
   v_chars varchar[];
   v_ret   varchar;
   v_length int4;
   v_count  int4;
BEGIN
   if ($1 isnull or $1 = 0) then
      return null;
   end if;

   v_chars  := regexp_split_to_array($1::varchar,'');
   v_ret    := '';
   v_length := array_upper (v_chars,1);
   v_count  := 0;

   for v_index in 1..11   loop
      v_count := v_count + 1;

      if (v_index <= v_length) then
         v_ret := v_chars[v_length - (v_index - 1)] || v_ret;
      else
         v_ret := '0' || v_ret;
      end if;

      if (v_count <= 6 and (v_count % 2) = 0) then
         v_ret := '.' || v_ret;
      end if;
   end loop;

   return v_ret;
END
$BODY$

2 个答案:

答案 0 :(得分:1)

如果你真的必须在数据库中执行格式化,那么修改你的表格以包含一个字段来存储格式化的数字。

触发器可以调用您的函数在值更改时生成格式化的数字,然后您(稍微)一次增加INSERTUPDATE几行所花费的时间,而不是所有这些。

您的查询返回所有220k行,然后变为格式化值的简单SELECT,并且应该很好而且快速。

答案 1 :(得分:1)

这取决于功能的细节。要了解裸函数调用将花费多少,请创建虚拟函数,如:

CREATE FUNCTION f_bare_plpgsql(text)
  RETURNS text LANGUAGE plpgsql IMMUTABLE AS
$BODY$
BEGIN
   RETURN $1;
END
$BODY$;

CREATE FUNCTION f_bare_sql(text)
  RETURNS text LANGUAGE sql IMMUTABLE AS
$BODY$
   SELECT $1;
$BODY$;

再次尝试查询。
如果那时你想知道为什么你的功能很慢,请将它添加到你的问题中。


更新问题的解决方案

你的功能可以在许多地方得到改善,但有一个更激进的解决方案:

SELECT to_char(12345678901, '00000"."00"."00"."00')
显然,快了很多倍。有关to_char() in the manual的更多信息 请考虑以下演示:

WITH x(n) AS (
   VALUES  (1::bigint), (12), (123), (1234), (12345), (123456), (1234567)
          ,(12345678), (123456789), (1234567890), (12345678901), (123456789012)
   )
SELECT n, x.fn(n), to_char(n, '00000"."00"."00"."00')
FROM x

      n       |       fn       |     to_char
--------------+----------------+-----------------
            1 | 00000.00.00.01 |  00000.00.00.01
           12 | 00000.00.00.12 |  00000.00.00.12
          123 | 00000.00.01.23 |  00000.00.01.23
         1234 | 00000.00.12.34 |  00000.00.12.34
        12345 | 00000.01.23.45 |  00000.01.23.45
       123456 | 00000.12.34.56 |  00000.12.34.56
      1234567 | 00001.23.45.67 |  00001.23.45.67
     12345678 | 00012.34.56.78 |  00012.34.56.78
    123456789 | 00123.45.67.89 |  00123.45.67.89
   1234567890 | 01234.56.78.90 |  01234.56.78.90
  12345678901 | 12345.67.89.01 |  12345.67.89.01
 123456789012 | 23456.78.90.12 |  #####.##.##.##
正如您所见,

to_char()仅准备最多11位小数 如果需要,可以轻松扩展。

相关问题