Вот основная таблица с данными:
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, потому что он уже работает более эффективно, чем прежняя обычная таблица.
Нет необходимости представлять эту вертикальную структуру в виде горизонтальных строк данных, за исключением экспорта для устаревших систем. И это как раз тема моего вопроса.
Используемый вами (анти) шаблон дизайна известен как «Значение атрибута объекта», и хотя он выглядит простым, он становится кошмаром, когда вам нужно делать отчеты или любые другие нетривиальные запросы по нему. Некоторые люди даже называют его «шаблоном только для записи», потому что по нему очень сложно делать запросы.
Возвращая текст, ваш окончательный 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; ОДНАКО, ваш дизайн данных выглядит так, как будто его нужно переработать с нуля, не так ли?
Эта модель - кошмар, откажитесь от нее, пока еще есть время.
Упростив хранение произвольных значений («универсальное»), вы усложнили их запросы. У каждого решения есть свои недостатки. Обратной стороной подхода EAV является то, что вы не можете запросить его структурированным способом, потому что вы действительно не знаете, что именно вы сохранили.
В настоящее время люди обычно используют столбец JSON для «расширения» таблицы «динамическими атрибутами». Вы по-прежнему теряете правильную проверку типов данных, но, по крайней мере, запросы намного проще и эффективнее, чем с подходом EAV. См., Например, здесь: coussej.github.io/2016/01/14/…





Используйте пример полиморфная функция,:
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 в качестве аргумента для определения возвращаемого типа.
Большинство типов можно преобразовывать в текст и обратно. Вернуть текст и привести к типу?