2

クエリ内の特定の列をフォーマットするために SQL 関数を呼び出す必要があるクエリがあります。必要なフォーマットは、電話番号のフォーマットと非常によく似ています。に変わり1234567890ます(123)456-7890

selectステートメントから関数を呼び出すとパフォーマンスが低下する可能性があることを読んだことがありますが、それは私の状況に反映されており、クエリにかかる時間は3倍以上になり、関数にこれほど時間がかかるとは思いませんでした. 関数は線形時間で実行されますが、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

データベースで書式設定を実行する必要がある場合は、テーブルを変更して、書式設定された数値を格納するフィールドを含めます。

トリガーは、値が変更されたときに関数を呼び出して、フォーマットされた数値を生成することができます。その後、すべてではなく、一度に数行ずつ、INSERTまたは数行にかかる時間を (わずかに) 増やします。UPDATE

22 万行すべてを返すクエリSELECTは、書式設定された値の単純なものになり、すばらしく速くなるはずです。

于 2012-09-11T22:52:26.193 に答える
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()は、マニュアルを参照してください。
次のデモを検討してください。

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()ご覧のとおり、10 進数 11 桁までしか用意されていません。
必要に応じて、簡単に拡張できます。

于 2012-09-11T22:52:34.840 に答える