问题
I have a table like this:
Table: survey
| formula | var1 | var2 | var3 |
|------------------------|-------|--------|--------|
| var1 + var2 | 12 | 9 | 2 |
| var3 * (var1 * var2) | 20 | 10 | 1 |
The trick is that the values inside column formula can be used as the SELECT clause against its row to get the final score, for example:
SELECT var1 + var2 FROM (VALUE ('var1 + var2', 12, 9, 2) AS t(formula, var1, var2, var3)
I am working on a sql procedure to dynamically compute the score for each row.
My current solution is to loop over each row and use EXECUTE to evaluate each formula. Here is the code:
CREATE FUNCTION cpt_scores() RETURNS SETOF scores as
$$
DECLARE
score numeric;
in_rec record;
BEGIN
FOR in_rec IN
SELECT * FROM survey
LOOP
EXECUTE format('SELECT %s FROM (VALUES %s) AS t(formula, var1, var2, var3)', in_rec.formula, in_rec)
INTO score
RETURN NEXT ROW(score);
END LOOP;
END;
$$ language plpgsql;
I would like to know if there is any better way to handle this task. I think that the hard-coded column names in that FROM clause can cause trouble when there are too many metrics to type manually.
回答1:
You can utilize query_to_xml function and synthetize its argument using your formula column. See example (dbfiddle here):
create table t (formula text,var1 int,var2 int,var3 int);
insert into t
values (' var1 + var2 ', 12 , 9 , 2 )
, (' var3 * (var1 * var2) ', 20 , 10 , 1 );
select *
, (xpath('/row/f/text()',
query_to_xml(
'select ' || formula || ' as f from t where t.ctid = ''' || ctid || '''::tid'
, false, true, ''
)
))[1]::text::int as result
from t
Explanation: Synthetized query works on single row of original table. Since it has from t clause, it has access to any needed column. To pass proper row from outer query, my example uses ctid system column. I hope you actually have id column in your table which is more appropriate.
回答2:
I don't think your function works, because values %s will expand to e.g. (values ("var1 + var2",12,9,2) which is invalid SQL.
However, if the number of "variables" is fixed, you can pass a record to the function that is then evaluate that as part of another query:
CREATE or replace FUNCTION cpt_scores(p_row survey)
RETURNS int
AS
$$
DECLARE
score numeric;
in_rec record;
l_sql text;
BEGIN
l_sql := format('SELECT %s FROM (values (%s, %s, %s)) AS t(var1, var2, var3)',
p_row.formula, p_row.var1, p_row.var2, p_row.var3);
execute l_sql INTO score;
return score;
END;
$$
language plpgsql;
Note that the formula is not included in the "expansion" of the values.
Then you can use it like this:
select s.*, cpt_scores(s) as score
from survey s;
Online example
来源:https://stackoverflow.com/questions/62260703/evaluate-select-clause-embedded-in-each-row