回答

收藏

PostgreSQL在表函数中参数化了“排序依据/限制”

技术问答 技术问答 317 人阅读 | 0 人回复 | 2023-09-14

我有一个执行简单的sql select语句的sql函数:( _: C2 H3 D2 Z$ B
CREATE OR REPLACE FUNCTION getStuff(param character varying): ]# K8 k; V3 z) \' m
  RETURNS SETOF stuff AS" {; q' u1 D  q  f
$BODY$4 N9 R9 A% D' S* e2 k- \
    select ** b7 y/ a- W' {! T# n% w* ^6 \3 O  s; G
    from stuff
. I% B( v$ S# Z8 l  q    where col = $1$ H9 R/ e1 M+ Y! x  r+ j! k- t
$BODY$
8 i3 I* M+ ~" d+ s  LANGUAGE sql;. ~4 Z8 u( m$ y& i: }: o7 L5 ]
2 h! f8 n9 B4 h* [6 E+ ]& M
现在,我正在像这样调用此函数:4 e9 E, S2 \! }5 r: Y; J1 A1 {, S6 v: X' d
select * from getStuff('hello');
$ M" r: ^* q+ t. \4 c$ ^* M+ I) r# ?" f; o$ j
如果我需要使用order byandlimit子句对结果进行排序和限制,我有哪些选择?5 K9 D% X: f; d/ i! Y
我猜这样的查询:
) W: e/ k  @* P4 d+ Y- x' K9 sselect * from getStuff('hello') order by col2 limit 100;" s! B& q- ^5 w/ b
( v* v1 a. G  J' v2 F% q
效率不是很高,因为表中的所有行都stuff将由函数返回,getStuff然后才按限制排序和切片。
' g# q( G% t( b8 E! n( |: ]4 s但是,即使我是对的,也没有简单的方法来通过sql语言函数的参数传递命令。只能传递值,不能传递sql语句的一部分。' Z8 h- r& w7 Y# j* q5 `
另一个选择是用plpgsql语言创建函数,可以在其中构造查询并通过执行它EXECUTE。但这也不是一个很好的方法。) T# {- b6 q$ \7 E- C
那么,还有其他方法可以实现这一目标吗?还是您会选择什么选项?在函数之外还是plpgsql进行排序/限制?
2 d9 `0 x5 Z+ a+ ~; Y* A0 @- z我正在使用PostgreSQL 9.1。# w% y7 D& d0 v3 G! o
编辑
( T! O2 x* ]- C* K) r我修改了CREATE FUNCTION语句,如下所示:
* s. G# H( C' c8 g+ s  iCREATE OR REPLACE FUNCTION getStuff(param character varying, orderby character varying)
0 I9 Q: q4 _0 j  RETURNS SETOF stuff AS, ]3 _; ~' ^: B7 q
$BODY$
: D( ~/ D6 b& x    select t.*
# k, L8 m: i" l    from stuff t
: n% F8 f4 v( }9 m    where col = $1. ]$ n% [/ A' B% r7 l
    ORDER BY
9 [  ~$ G2 Q: \3 L/ E( N: y        CASE WHEN $2 = 'parent' THEN t.parent END,% n+ L2 i3 M" T; i  [
        CASE WHEN $2 = 'type' THEN t."type" END,
8 ]4 }) L2 }9 L. s* i' t        CASE WHEN $2 = 'title' THEN t.title END
8 Q/ ?* t1 o& {% j0 N7 r3 ]+ x$BODY$
. o- g9 E, s6 D7 U2 [  LANGUAGE sql;
0 N( g0 q% ]2 X$ B2 \  S0 P$ ?; A. y$ f& |( A2 _
该stuff表如下所示:
5 y7 g, v2 O3 JCREATE TABLE stuff
" u" G& d% g- g: J) S) }* \    ($ K' C* u$ E4 L: P, R0 z7 ~8 `
      id integer serial,8 L/ t: S( n0 E( \
      "type" integer NOT NULL,4 @: M  q! V, E7 U# q6 E
      parent integer,
( J" `# W( @# o      title character varying(100) NOT NULL,5 p  f) f' v/ C9 w; r/ e3 D
      description text,
5 a; P  j$ e2 z      CONSTRAINT "pkId" PRIMARY KEY (id),
; l' G( ^# z% W( e    )
3 }& J: t& S5 [. j% h: |
. ], f% v+ p/ O# C0 j' o编辑2' O2 b, ~! e6 j2 t* n2 K4 e: ?
我已经严重阅读了Dems代码。我已将其更正为问题。这段代码对我有用。
' G8 x6 _) q, i; w8 i                * T; r0 L+ ~: Q
解决方案:
3 W1 J/ Y6 _+ t* {8 f( r                # U/ }. M- T  v6 B% V" W3 @  e

+ s  E2 B' y/ ]
7 ?8 p8 Y2 \" F1 p0 Z, O$ E                plpgsql函数对于更复杂的东西没有任何问题。可能会影响性能的唯一情况是嵌套plpgsql函数时,因为查询计划程序无法在外部查询的上下文中进一步优化所包含的代码,这可能会使速度变慢,也可能不会使其变慢。7 T( z6 _# N. h& O) Z. q
在此稍后的答案中有更多详细信息:, @0 K5 r. A7 Z+ v
PostgreSQL函数中的语言sql和语言plpgsql之间的区别" ?5 L( I. D+ n+ N
在这种情况下,它比CASE查询中的许多子句简单得多:
5 N7 v' X& P1 O/ M. QCREATE OR REPLACE FUNCTION get_stuff(_param text, _orderby text, _limit int)3 ]/ B/ D. [9 l) ]2 s8 f
  RETURNS SETOF stuff AS
( _; C; K& i+ U. S( v% @$func$  Y, |8 y! R: ^1 W
BEGIN
/ n  {5 Q1 [1 @2 t' c: |   RETURN QUERY EXECUTE '
4 p4 O( v) {) R! O( x8 c8 h$ q      SELECT *
- @' W/ N7 t: W      FROM   stuff
. m) p. M: g( h: z; B7 o      WHERE  col = $1
+ [5 `0 X, |. z" P5 w) D" w) _      ORDER  BY ' || quote_ident(_orderby) || ' ASC
9 Q; m5 [6 h7 P- r      LIMIT  $2'* X% @5 O& O0 B2 C# ^
   USING _param, _limit;, h& p; z  q7 K6 a  {1 R* _3 s+ D
END6 t% ^8 ], Z: g# y& x2 r0 O
$func$  LANGUAGE plpgsql;! w& z5 s, Y% D% T

& K: `: N; k$ N( Z称呼:( `8 u" {! m: ~/ Y) R, R
SELECT * FROM get_stuff('hello', 'col2', 100);
( h# T" f7 m9 G% ]$ }5 t  f- I" E4 n1 p) \& A& ?6 R$ {( ]
笔记
1 Q5 M. I( m/ O% H( T  a- A用于RETURN QUERY EXECUTE一次性返回查询结果。
- e. Q" J: M5 `; O使用quote_ident()标识符反对SQLI保障。" ]8 b+ E+ y0 |% N: B  n
或format()更复杂的事情。看:2 x1 _6 T' i  u  u" j
表名作为PostgreSQL函数参数" A3 O& Y) t5 i0 x  s
将参数值与USING子句一起传递,以避免再次进行强制转换,引号和SQLi。
1 L5 Y4 z5 v9 X! X" R注意不要在参数和列名之间创建命名冲突。_在示例中,我在参数名称前添加了下划线()。只是我个人的喜好。
! |1 d: i* v: z8 J编辑后的第二个函数无法工作,因为您只能parent在声明返回类型时返回SETOF stuff。您可以声明自己喜欢的任何返回类型,但是实际的返回值必须与声明匹配。您可能想要使用RETURNS TABLE它。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则