Функция PostgreSQL с произвольным типом возвращаемых данных

Вот основная таблица с данными:

CREATE TABLE my_report
(
  id               serial                      NOT NULL primary key,
  report_timestamp timestamp without time zone NOT NULL,
  value_id         integer                     NOT NULL,
  text_value       character varying(255),
  numeric_value    double precision,
  bool_value       boolean,
  dt_value         timestamp with time zone,
  CONSTRAINT my_report_fkey_valdef FOREIGN KEY (value_id)
      REFERENCES public.my_value_defs (value_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE RESTRICT
);

Он содержит несколько столбцов для каждого значения, поскольку значения могут иметь разные типы данных.

Какой столбец будет выбран, зависит от столбца data_type второй таблицы, связанной с внешним ключом value_id:

CREATE TABLE my_value_defs
(
  value_id     serial                NOT NULL primary key,
  value_name   character varying(50) NOT NULL,
  data_type    integer               NOT NULL,
  CONSTRAINT my_value_defs_pkey PRIMARY KEY (value_id),
  CONSTRAINT my_value_defs_value_name_key UNIQUE (value_name)
);

Теперь я пытаюсь сократить огромный SQL-запрос, создав функцию, которая может возвращать либо text_value, либо numeric_value, либо bool_value, либо dt_value, но обнаружил, что я должен явно определить возвращаемый тип данных:

CREATE OR REPLACE FUNCTION public.rep_dta_val(
    val_id integer,
    dt timestamp with time zone,
    timeout integer)
  RETURNS timestamp with time zone AS -- ********** HERE **********
$BODY$SELECT
   r.dt_value
FROM
   my_report r
WHERE
   r.value_id = val_id
   AND r.report_timestamp BETWEEN
      dt - make_interval(secs := timeout)
      AND dt
ORDER BY
   r.report_timestamp desc
LIMIT 1;$BODY$
  LANGUAGE sql VOLATILE
  COST 100;

Эта функция еще не завершена, поэтому, пожалуйста, не жалуйтесь на нее здесь.

Я не люблю все переводить в текст. Типы данных следует сохранить.

Когда невозможно создать функцию с переменным типом выходных данных, я должен сгенерировать в своем приложении полный SQL-запрос полной длины следующим образом:

SELECT
(SELECT r.text_value    as acc_right     FROM my_report r WHERE r.value_id =  3 AND r.report_timestamp BETWEEN now() - INTERVAL '60 seconds' AND now() ORDER BY r.report_timestamp desc LIMIT 1),
(SELECT r.numeric_value as h_angle       FROM my_report r WHERE r.value_id =  4 AND r.report_timestamp BETWEEN now() - INTERVAL '60 seconds' AND now() ORDER BY r.report_timestamp desc LIMIT 1),
(SELECT r.text_value    as vol_flow      FROM my_report r WHERE r.value_id = 25 AND r.report_timestamp BETWEEN now() - INTERVAL '60 seconds' AND now() ORDER BY r.report_timestamp desc LIMIT 1),
(SELECT r.numeric_value as draft_mid     FROM my_report r WHERE r.value_id = 57 AND r.report_timestamp BETWEEN now() - INTERVAL '60 seconds' AND now() ORDER BY r.report_timestamp desc LIMIT 1),
(SELECT r.dt_value      as eta_timestamp FROM my_report r WHERE r.value_id = 58 AND r.report_timestamp BETWEEN now() - INTERVAL '60 seconds' AND now() ORDER BY r.report_timestamp desc LIMIT 1);

Вышеупомянутый запрос делает именно то, что мне нужно, но он слишком длинный, поэтому я создал несколько функций, по одной для каждого типа данных, и использую их следующим образом:

SELECT
rep_txt_val( 3, now(), 60) as acc_right,
rep_num_val( 4, now(), 60) as h_angle,
rep_txt_val(25, now(), 60) as vol_flow,
rep_num_val(57, now(), 60) as draft_mid,
rep_dta_val(58, now(), 60) as eta_timestamp;

Теперь хотелось бы иметь универсальную функцию для всех типов данных.

P.S. Зачем использовать EAV?

Потому что раньше это была обычная таблица, и ЭТО было кошмаром, потому что время от времени нужно было автоматически создавать столбцы. Иногда обычный пользователь не имеет прав на выполнение команд DDL.

У меня есть полностью динамически настраиваемое программное обеспечение, и количество значений, которые необходимо сохранить в таблице, зависит от файла конфигурации этого программного обеспечения. Я выбрал эту модель, чтобы упростить обслуживание базы данных и передачу данных между системами.

Кроме того, я мог бы сказать, что различные значения должны сохраняться через определенные промежутки времени.

Так что я не откажусь от EAV, потому что он уже работает более эффективно, чем прежняя обычная таблица.

Нет необходимости представлять эту вертикальную структуру в виде горизонтальных строк данных, за исключением экспорта для устаревших систем. И это как раз тема моего вопроса.

Большинство типов можно преобразовывать в текст и обратно. Вернуть текст и привести к типу?

Cetin Basoz 02.10.2018 10:27

Используемый вами (анти) шаблон дизайна известен как «Значение атрибута объекта», и хотя он выглядит простым, он становится кошмаром, когда вам нужно делать отчеты или любые другие нетривиальные запросы по нему. Некоторые люди даже называют его «шаблоном только для записи», потому что по нему очень сложно делать запросы.

a_horse_with_no_name 02.10.2018 10:55

Возвращая текст, ваш окончательный SQL может выглядеть так: SELECT rep_gen_val (3, now (), 60) как acc_right, CAST (rep_gen_val (4, now (), 60) AS decimal) как h_angle, rep_gen_val (25, now (), 60) как vol_flow, CAST (rep_gen_val (57, now (), 60) AS decimal) как draft_mid, CAST (rep_gen_val (58, now (), 60) AS TIMESTAMP) как eta_timestamp; ОДНАКО, ваш дизайн данных выглядит так, как будто его нужно переработать с нуля, не так ли?

Cetin Basoz 02.10.2018 10:56

Эта модель - кошмар, откажитесь от нее, пока еще есть время.

klin 02.10.2018 11:07

Упростив хранение произвольных значений («универсальное»), вы усложнили их запросы. У каждого решения есть свои недостатки. Обратной стороной подхода EAV является то, что вы не можете запросить его структурированным способом, потому что вы действительно не знаете, что именно вы сохранили.

a_horse_with_no_name 02.10.2018 13:59

В настоящее время люди обычно используют столбец JSON для «расширения» таблицы «динамическими атрибутами». Вы по-прежнему теряете правильную проверку типов данных, но, по крайней мере, запросы намного проще и эффективнее, чем с подходом EAV. См., Например, здесь: coussej.github.io/2016/01/14/…

a_horse_with_no_name 02.10.2018 14:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
987
1

Ответы 1

Используйте пример полиморфная функция,:

create or replace function func(anyelement)
returns anyelement language plpgsql as $$
begin
    raise notice '%', pg_typeof($1)::text;
    case pg_typeof($1)::text
        when 'text' then return 'some text';
        when 'numeric' then return 1.23;
        when 'timestamp without time zone' then return now();
        else return $1;
    end case;
end $$;

select func(null::text), func(null::numeric), func(null::timestamp)

NOTICE:  text
NOTICE:  numeric
NOTICE:  timestamp without time zone

   func    | func |            func            
-----------+------+----------------------------
 some text | 1.23 | 2018-10-02 14:51:51.407031
(1 row)

Обратите внимание, что вы должны использовать anyelement в качестве аргумента для определения возвращаемого типа.

Другие вопросы по теме