将多个值集或数组传递给一个函数

Pass multiple sets or arrays of values to a function

我正在 PostgreSQL 9.3.10 中编写一个 PL/pgSQL 函数给 return 参加了以下 table 中的某些 classes/sessions:

Attendance
+-------+---------+---------+
| Class | Section |  Name   |
+-------+---------+---------+
|    1  |      1  | Amy     |
|    1  |      1  | Bill    |
|    1  |      2  | Charlie |
|    1  |      2  | Dan     |
|    2  |      1  | Emily   |
|    2  |      1  | Fred    |
|    2  |      2  | George  |
+-------+---------+---------+

我想要做的是,给定一个 class/section id 对 (int[][]) 的数组,return 那些 classes/sections 中的所有人。例如 my_func(ARRAY[[1,1],[2,2]]) returns:

+-------+---------+---------+
| Class | Section |  Name   |
+-------+---------+---------+
|    1  |      1  | Amy     |
|    1  |      1  | Bill    |
|    2  |      2  | George  |
+-------+---------+---------+

如果我事先知道这些对,那就很简单了:

SELECT * FROM attendance 
WHERE ((class = 1 AND section = 1) OR (class = 2 AND section = 2));

相反,这些对将成为函数的参数。

现在,我能想到的唯一方法是让函数通过在查询末尾附加一堆 WHERE 子句来构建一个 SQL 查询字符串,并且然后调用 EXECUTE。有没有更好的方法来获得我的结果?

编辑: 我实施了@Erwin 的建议,目前能够得到我想要的结果。不幸的是,它似乎相当慢。这是函数我是 运行:

CREATE OR REPLACE FUNCTION public.get_attendance(int[])
  RETURNS  TABLE(
    class_c int,
    section_c int
  )
AS
$BODY$
  BEGIN
    RETURN QUERY
      SELECT class, section
      FROM generate_subscripts(, 1) as i 
      INNER JOIN attendance ON attendance.class = [i][1]
                            AND  attendance.section = [i][2];

  END;
$BODY$
LANGUAGE plpgsql VOLATILE;

这样查询:

SELECT *  FROM get_attendance(ARRAY[[1,15],[2,15],[3,8]]);

我得到以下 EXPLAIN ANALYZE output

Merge Join  (cost=60.26..50139.72 rows=30840 width=8) (actual time=44.174..142.100 rows=25290 loops=1)
  Merge Cond: ((attendance.class = (('{{1,15},{2,15},{3,8}}'::integer[])[i.i][1])) AND (attendance.section = (('{{1,15},{2,15},{3,8}}'::integer[])[i.i][2])))
  ->  Index Only Scan using class_section_idx on attendance  (cost=0.43..43372.25 rows=1233588 width=8) (actual time=0.009..86.625 rows=1145046 loops=1)
        Heap Fetches: 0
  ->  Sort  (cost=59.83..62.33 rows=1000 width=4) (actual time=0.010..0.757 rows=10031 loops=1)
        Sort Key: (('{{1,15},{2,15},{3,8}}'::integer[])[i.i][1]), (('{{1,15},{2,15},{3,8}}'::integer[])[i.i][2])
        Sort Method: quicksort  Memory: 25kB
        ->  Function Scan on generate_subscripts i  (cost=0.00..10.00 rows=1000 width=4) (actual time=0.006..0.007 rows=3 loops=1)

问题是查询正在扫描出勤 table 中的所有出勤,而没有在加入之前对其进行过滤。有什么办法可以解决这个问题吗?

如果将记录数组传递给函数,则很简单:

with attendance (class, section, name) as(values
    (1, 1, 'Amy'),
    (1, 1, 'Bill'),
    (1, 2, 'Charlie'),
    (1, 2, 'Dan'),
    (2, 1, 'Emily'),
    (2, 1, 'Fred'),
    (2, 2, 'George')
)
select *
from attendance
where (class, section) = any(array[(1,1),(2,2)])
;
 class | section |  name  
-------+---------+--------
     1 |       1 | Amy
     1 |       1 | Bill
     2 |       2 | George

您可以使用简单的 SQL 函数来实现。关键特征是函数 generate_subscripts():

CREATE OR REPLACE FUNCTION f_attendance(_arr2d int[])
  RETURNS SETOF attendance AS
$func$
<b>   SELECT a.*
   FROM   generate_subscripts(, 1) i
   JOIN   attendance a ON a.class   = [i][1]
                      AND a.section = [i][2]</b>
$func$  LANGUAGE ROWS 10 sql STABLE;

致电:

SELECT * FROM f_attendance(ARRAY[[1,1],[2,2]]);

或者与数组相同 literal - 这在某些情况下更方便,尤其是对于准备好的语句:

SELECT * FROM f_attendance('{{1,1},{2,2}}');

函数总是需要一个二维数组。即使你传递一对,嵌套它:

SELECT * FROM f_attendance('{{1,1}}');

实施审核

  1. 你制作了函数VOLATILE,但它可以是STABLEPer documentation:

    Because of this snapshotting behavior, a function containing only SELECT commands can safely be marked STABLE.

    相关:

  2. 您还使用 LANGUAGE plpgsql 而不是 sql,如果您在同一个会话中多次执行该函数,这是有意义的。但是你也必须做到 STABLE 否则你将失去潜在的性能优势。 The manual once more:

    STABLE and IMMUTABLE functions use a snapshot established as of the start of the calling query, whereas VOLATILE functions obtain a fresh snapshot at the start of each query they execute.

  3. 您的 EXPLAIN 输出显示 仅索引扫描,而不是您在评论中怀疑的顺序扫描。

  4. 您的 EXPLAIN 输出中还有一个排序步骤与您显示的代码不匹配。你确定你复制了正确的 EXPLAIN 输出吗?你是怎么得到它的? PL/pgSQL 函数是 EXPLAIN 的黑盒。你用过auto_explain吗?详情:

  5. Postgres 查询规划器不知道传递的参数将有多少个数组元素,因此很难规划查询,它可能默认为顺序扫描(取决于更多因素)。您可以通过声明预期的行数来提供帮助。如果您通常没有超过 10 个项目,请像我上面那样添加 ROWS 10。并再次测试。