2

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

我读过从 select 语句中调用函数可能是性能杀手,这在我的情况中有所反映,查询花费的时间增加了三倍多,我认为函数不会花费这么长时间。该函数以线性时间运行,但确实使用 SQL 循环。为了了解数据库的大小,这个特定的查询返回了大约 220,000 行。不调用函数与运行调用函数相比,查询的运行时间从 < 3 秒变为 > 9 秒。需要格式化的列未在连接条件或 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$
4

2 回答 2

1

If you really must perform the formatting in the database then modify your table to include a field to store the formatted number.

A trigger can call your function to generate the formatted number when the value changes, then you only (slightly) increase the time taken to INSERT or UPDATE a few rows at a time, rather than all of them.

Your query returning all 220k rows then becomes a simple SELECT of the formatted value and should be nice and quick.

于 2012-09-11T22:52:26.193 回答
1

It depends on the specifics of the function. To find out how much a bare function call will cost, create dummy functions like:

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

And try your query again.
If then you wonder why your function is slow, add it to your question.


Solution for updated question

Your function could be improved in many places, but there is a more radical solution:

SELECT to_char(12345678901, '00000"."00"."00"."00')

Many times faster, obviously. More about to_char() in the manual.
Consider the following demo:

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() is only prepared for up to 11 decimal digits, as you can see.
Can easily be extended, if need should be.

于 2012-09-11T22:52:34.840 回答