Можно ли кешировать переменную в функции PostgreSQL? - PullRequest
0 голосов
/ 02 ноября 2018

Context

Я создаю расширение Postgres, которое добавляет возможность задавать нечеткие запросы с лингвистическими переменными. Например:

SELECT age~=('age'|>'adult') FROM people;

вернет достоверность того факта, что человек является взрослым (взрослый определяется как трапециевидная функция 30/40~60\65.

Задача

Я сделал функцию, которая позволяет возвращать лингвистическое имя для указанного значения и лингвистической переменной:

SELECT age, age~>'age' FROM people;

возвращает

 age | age~>'age'
-----+--------------
 20  | young adult
 10  | child
 45  | adult
 60  | old

Источник этой функции и оператор выглядит так:

CREATE FUNCTION get_fuzzy_name(
  input FLOAT8,
  type_name VARCHAR(64)
) RETURNS VARCHAR(64) AS $$
DECLARE
  type_id fuzzy.types.id%TYPE;
  deg FLOAT8;
  result_name VARCHAR(64);
BEGIN
  type_id := get_fuzzy_type(type_name); -- returns type's id based on it's name
  SELECT
      degree(input, fun) AS d, name
    INTO
      deg, result_name
    FROM fuzzy.functions
    WHERE type=type_id
    ORDER BY d DESC LIMIT 1;
  IF deg=0 THEN
    RETURN NULL;
  END IF;
  RETURN result_name;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

CREATE OPERATOR ~> (
  PROCEDURE = get_fuzzy_name,
  LEFTARG = FLOAT8,
  RIGHTARG = VARCHAR(64)
);

Проблема в том, что для каждой строки указанная выше функция запрашивает нечеткий тип и выполняет функции снова и снова. Итак, я придумал это как основу для улучшений (нечеткие функции хранятся в переменной):

CREATE TYPE FUZZY_TYPE_FUNCTION AS (
  func  TRAPEZOIDAL_FUNCTION,
  range_name VARCHAR(64)
);
CREATE FUNCTION get_fuzzy_name(
  input FLOAT8,
  type_name VARCHAR(64)
) RETURNS VARCHAR(64) AS $$
DECLARE
  f FUZZY_TYPE_FUNCTION;
  _type_functions FUZZY_TYPE_FUNCTION[] := array(SELECT (fun, name) FROM fuzzy.functions WHERE fuzzy.functions.type=type_name);
  _deg_tmp FLOAT8;
  _deg FLOAT8;
  _result_name VARCHAR(64);
BEGIN
  _deg := 0;
  FOREACH f IN array(_type_functions) LOOP
    _deg_tmp := degree(input, f.func);
    RAISE NOTICE '% && % = %', f, input, _deg_tmp;
    IF _deg<_deg_tmp THEN
      _deg := _deg_tmp;
      _result_name := f.range_name;
      EXIT WHEN _deg=1;
    END IF;
  END LOOP;
  IF _deg=0 THEN
    RETURN NULL;
  END IF;
  RETURN _result_name;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

Есть ли способ получить значение таблицы функций только один раз для каждого запроса и кэшировать его, чтобы его можно было повторно использовать и значительно ускорить весь запрос?

Дополнительная информация

По запросу это таблицы:

CREATE SCHEMA IF NOT EXISTS fuzzy;

CREATE TABLE IF NOT EXISTS fuzzy.types (
  id   SERIAL PRIMARY KEY,
  name VARCHAR(64) UNIQUE
);

CREATE TABLE IF NOT EXISTS fuzzy.functions (
  type INT                  NOT NULL,
  fun  TRAPEZOIDAL_FUNCTION NOT NULL,
  name VARCHAR(64),
  FOREIGN KEY (type) REFERENCES fuzzy.types (id) ON DELETE CASCADE,
  UNIQUE (type, name)
);

fuzzy.types, вероятно, будет содержать несколько строк, которые являются парами id-name, fuzzy.functions, скорее всего, будет содержать 3-10 строк для каждого типа, что в тяжелых случаях может составлять около 500 строк (я полагаю).

1 Ответ

0 голосов
/ 03 ноября 2018

Возможно, вы опираетесь на несколько вводящих в заблуждение предположений о производительности функций.

Попробуйте вместо этого использовать эту упрощенную функцию SQL:

CREATE FUNCTION get_fuzzy_name(_input FLOAT8, _type_name VARCHAR(64))
  RETURNS VARCHAR(64) AS
$func$
   SELECT f.name
   FROM   fuzzy.functions f
   JOIN   fuzzy.types     t ON t.id = f.type
   WHERE  t.name = _type_name
   AND    degree(_input, f.fun) > 0
   ORDER  BY degree(_input, f.fun) DESC
   LIMIT  1;
$func$  LANGUAGE sql STABLE;
  • LANGUAGE sql. Нет переменных, присваиваний, IF конструкций, ... 1 простой запрос. Полная перезапись, но должна быть эквивалентной.

  • STABLE, а не IMMUTABLE.

  • Нет вызова вложенной функции вообще. Заменено на соединение. Должно быть дешевле.

  • Возможно, будет еще дешевле встроить нераскрытую функцию degree(). Может даже сгореть до гораздо более быстрого запроса "ближайшего соседа". Недостаточно информации.

  • Эта функция может быть встроенной , в отличие от вашего оригинала. Я удалил STRICT, что может быть на пути. Не могу сказать, недостаточно информации.

См. Postgres Wiki о включении скалярных функций .
И:

Связанный:

...