如何使用动态SQL设置复合变量字段的值
给定这种类型:
-- Just for testing purposes:
CREATE TYPE testType as (name text)
我可以使用此函数动态获取字段的值:
CREATE OR REPLACE FUNCTION get_field(object anyelement, field text) RETURNS text as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1."' || field || '"'
USING object
INTO value;
return value;
END;
$BODY$
LANGUAGE plpgsql
调用 get_field('(david)'::testType, 'name')
按预期返回“david”。
但是如何设置复合类型字段的值呢?我已经尝试过这些功能:
CREATE OR REPLACE FUNCTION set_field_try1(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE '$1."' || field || '" := $2'
USING object, value;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try2(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1 INTO $2."' || field || '"'
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try3(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'BEGIN $1."' || field || '" := $2; SELECT $1; END;'
INTO object
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
以及一些变体。 调用 set_field_tryX
不起作用。我总是收到“错误:语法错误位于或附近...”。 我怎样才能做到这一点?
注意:
- 参数为
anyelement
,字段可以是复合类型中的任意字段。我不能只使用 object.name。 - 我担心 SQL 注入。任何这方面的建议将不胜感激,但这不是我的问题。
Given this type:
-- Just for testing purposes:
CREATE TYPE testType as (name text)
I can get the value of a field dynamically with this function:
CREATE OR REPLACE FUNCTION get_field(object anyelement, field text) RETURNS text as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1."' || field || '"'
USING object
INTO value;
return value;
END;
$BODY$
LANGUAGE plpgsql
Calling get_field('(david)'::testType, 'name')
works as expected returning "david".
But how can I set a value of a field in a composite type? I've tried these functions:
CREATE OR REPLACE FUNCTION set_field_try1(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE '$1."' || field || '" := $2'
USING object, value;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try2(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'SELECT $1 INTO $2."' || field || '"'
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
CREATE OR REPLACE FUNCTION set_field_try3(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
value text;
BEGIN
EXECUTE 'BEGIN $1."' || field || '" := $2; SELECT $1; END;'
INTO object
USING value, object;
return object;
END;
$BODY$
LANGUAGE plpgsql
and some variations.
Calling set_field_tryX
doesn't work. I always get "ERROR: syntax error at or near...".
How can I accomplish this?
Notes:
- The parameter is
anyelement
and the field can be any field in the composite type. I can't just use object.name. - I'm concerned about SQL injection. Any advice in this would be appreciated but it is not my question.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
使用
hstore
更快从 Postgres 9.0 开始,使用 附加模块
hstore
安装在您的数据库中,有一个非常简单且快速的解决方案,使用#=
运算符 ...要安装模块:
示例:
显然,值必须转换为
text
并返回。包含更多详细信息的示例 plpgsql 函数:
现在也可以使用
json
/jsonb
!json
(pg 9.3+) 或jsonb
(pg 9.4+) 有类似的解决方案。该功能未记录,但从 Postgres 13 开始是官方的。手册:
因此,您可以获取任何现有行并填充任意字段(覆盖其中的内容)。
json
与hstore
的主要优点:小缺点:有点慢。
有关详细信息,请参阅 @Geir 添加的答案。
没有
hstore
和json
如果您使用的是旧版本,或者无法安装附加模块
hstore
或无法假设它已安装,这里是我之前发布的改进版本。不过,仍然比hstore
运算符慢:调用:
注释
不需要将值
_val
显式转换为目标数据类型,可以使用动态查询将自动强制,从而避免pg_type
上的子查询。但我更进一步:将
quote_literal(_val)
替换为通过USING
子句直接插入值。节省一次函数调用和两次强制转换,而且更安全。在现代 PostgreSQL 中,text
自动被强制转换为目标类型。 (9.1之前的版本未测试。)array_to_string(ARRAY())
比string_agg()
更快。不需要变量,不需要
DECLARE
。更少的作业。动态 SQL 中没有子查询。
($1).field
更快。pg_typeof(_comp_val)::text::regclass
与
相同
(从 pg_catalog.pg_type 中选择 oid = pg_typeof($1)::oid)
对于有效的复合类型,速度更快。
最后的修改是基于这样的假设:对于已注册的复合类型,
pg_type.typname
始终与关联的pg_class.relname
相同,并且双重强制转换可以替换子查询。我在一个大数据库中运行此测试进行验证,结果如预期的那样为空:INOUT
参数消除了对显式RETURN
的需要。这只是一个符号快捷方式。 Pavel 不会喜欢它,他更喜欢显式的RETURN
语句...所有内容放在一起,速度都是之前版本的两倍。
原始(过时)答案:
结果是快了 2.25 倍的版本。但如果不以帕维尔的第二个版本为基础,我可能无法做到这一点。
此外,此版本通过在单个查询中执行所有操作,避免了大部分转换到文本并返回,因此应该更不容易出错。
使用 PostgreSQL 9.0 和 9.1 进行测试。
Faster with
hstore
Since Postgres 9.0, with the additional module
hstore
installed in your database there is a very simple and fast solution with the#=
operator that ...To install the module:
Examples:
Values have to be cast to
text
and back, obviously.Example plpgsql functions with more details:
Now works with
json
/jsonb
, too!There are similar solutions with
json
(pg 9.3+) orjsonb
(pg 9.4+)The functionality was undocumented, but it's official since Postgres 13. The manual:
So you can take any existing row and fill arbitrary fields (overwriting what's in it).
Major advantages of
json
vshstore
:Minor disadvantage: a bit slower.
See @Geir's added answer for details.
Without
hstore
andjson
If you are on an older version or cannot install the additional module
hstore
or cannot assume it's installed, here is an improved version of what I posted previously. Still slower than thehstore
operator, though:Call:
Notes
An explicit cast of the value
_val
to the target data type is not necessary, a string literal in the dynamic query would be coerced automatically, obviating the subquery onpg_type
. But I took it one step further:Replace
quote_literal(_val)
with direct value insertion via theUSING
clause. Saves one function call and two casts, and is safer anyway.text
is coerced to the target type automatically in modern PostgreSQL. (Did not test with versions before 9.1.)array_to_string(ARRAY())
is faster thanstring_agg()
.No variables needed, no
DECLARE
. Fewer assignments.No subquery in the dynamic SQL.
($1).field
is faster.pg_typeof(_comp_val)::text::regclass
does the same as
(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
for valid composite types, just faster.
This last modification is built on the assumption that
pg_type.typname
is always identical to the associatedpg_class.relname
for registered composite types, and the double cast can replace the subquery. I ran this test in a big database to verify, and it came up empty as expected:INOUT
parameter obviates the need for an explicitRETURN
. This is just a notational shortcut. Pavel won't like it, he prefers an explicitRETURN
statement ...Everything put together this is twice as fast as the previous version.
Original (outdated) answer:
The result is a version that's ~ 2.25 times faster. But I probably couldn't have done it without building on Pavel's second version.
In addition, this version avoids most of the casting to text and back by doing everything within a single query, so it should be much less error prone.
Tested with PostgreSQL 9.0 and 9.1.
我编写了 setfield 函数的第二个版本。它适用于 postgres 9.1 我没有在旧版本上测试它。这并不是一个奇迹(从性能角度来看),但它更强大,并且比以前的速度快了大约 8 倍。
I wrote a second version of setfield function. It work on postgres 9.1 I didn't test it on older versions. It's not a miracle (from performance view), but it is more robust and about 8 times faster than the previous.
更新/警告:埃尔文指出这是目前未记录,以及 manual 表示不可能以这种方式更改记录。
请改用 hstore 或 Pavel 的解决方案。
这个简单的基于 json 的解决方案几乎与 hstore 一样快,并且只需要 Postgres 9.3 或更高版本。如果您无法使用 hstore 扩展,这应该是一个不错的选择,并且性能差异应该可以忽略不计。基准: https://stackoverflow.com/a/28673542/1914376
a) 我们可以通过强制转换/内联执行此操作连接。 Json 函数需要 Postgres 9.3:
b) 或使用 Postgres 9.4 中的函数内联。
注意:我选择 json_object(ARRAY[key,value]) 因为它比 json_build_object(key,value) 快一点:
要隐藏转换详细信息,您可以在函数中使用 a),只需很少的操作即可开销。
UPDATE/caution: Erwin points out that this is currently undocumented, and the manual indicates it should not be possible to alter records this way.
Use hstore or Pavel's solution instead.
This simple json based solution is almost as fast as hstore, and requires only Postgres 9.3 or newer. This should be a good option if you can't use the hstore extension, and the performance difference should be negligible. Benchmarks: https://stackoverflow.com/a/28673542/1914376
a) We can either do it inline by cast/concat. Json function requires Postgres 9.3:
b) or inline by using functions from Postgres 9.4.
Note: I chose json_object(ARRAY[key,value]) since it was a bit faster than json_build_object(key,value):
To hide the casting details you can use a) in a function, with little overhead.
plpgsql 外部(在动态 SQL 上下文中)的“SELECT INTO”具有与您预期不同的含义 - 它将查询结果存储到表中。
任何字段的修改都是可能的,但并不简单
,但是这段代码不是很有效 - 不可能在 plpgsql 中写得很好。你可以找到一些 C 库,这应该可以做到。
"SELECT INTO" outside plpgsql (in dynamic SQL context) has different sense than you expect - it store a result of query to table.
Modification of any field is possible, but not simple
But this code is not very effective - is not possible to write this well in plpgsql. You can find some C library, that should to do.
测试设置和基准测试 v2
Erwin 鼓励在此线程中重现他的基准测试 (https://stackoverflow.com/a/7782839/1914376),所以我用综合测试数据修改了他的代码,并添加了 hstore 解决方案和我的答案中的 json 解决方案(以及 Pavel 在另一个中找到的 json 解决方案)线程)基准测试现在作为一个查询运行,从而更容易捕获结果。
9.4.1、win32、i5-4300U 上的测试结果
Test setup and benchmarks v2
Erwin encouraged to reproduce his benchmark in this thread (https://stackoverflow.com/a/7782839/1914376), so I modified his code with synthetic test data and added both the hstore solution and the json-solution from my answer (and a json solution by Pavel found in another thread) The benchmark is now run as one query, making it easier to capture the results.
Test results on 9.4.1, win32, i5-4300U
2015 年 3 月更新:
现在基本上已经过时了。考虑@Geir 的新基准以及更快的变体。
测试设置和基准测试
我采用了所提供的三个解决方案(截至 2011 年 10 月 16 日)并在 PostgreSQL 9.0 上运行了测试。
您可以在下面找到完整的设置。只有测试数据不包括在内,因为我使用了现实生活中的数据库(不是合成数据)。它全部封装在自己的模式中,以便非侵入式使用。
我想鼓励任何想要重现测试的人。也许与 postgres 9.1 一起使用?并在此处添加您的结果? :)
基准测试
我运行了几次查询来填充缓存。所呈现的结果是使用
EXPLAIN ANALYZE
进行的五个总运行时间中最好的结果。第一轮包含 1000 行
Pavel 的第一个原型通过更多行使共享内存达到最大。
Pavel 1:2445.112 ms
Pavel 2:263.753 ms
Erwin 1:120.671 ms
另一项测试有 5183 行。
帕维尔 2:1327.429 毫秒
埃尔文 1:588.691 毫秒
Update March 2015:
Largely outdated now. Consider the new benchmark by @Geir with faster variants.
Test setup and benchmarks
I took the three solutions presented (by Oct. 16th, 2011) and ran a test on PostgreSQL 9.0.
You find the complete setup below. Only test data are not included as I used a real life database (not synthetic data). It's all encapsulated in its own schema for non-intrusive use.
I would like to encourage anybody who wants to reproduce the test. Maybe with postgres 9.1? And add your results here? :)
Benchmarks
I ran the queries a couple of times to populate the cache. The presented results are the best of five total runtimes with
EXPLAIN ANALYZE
.Rirst round with 1000 rows
Pavel's first prototype maxes out shared memory with more rows.
Pavel 1: 2445.112 ms
Pavel 2: 263.753 ms
Erwin 1: 120.671 ms
Another test with 5183 rows.
Pavel 2: 1327.429 ms
Erwin1: 588.691 ms