我正在 PostgreSQL 9.3.10 中编写一个 PL/pgSQL 函数来返回谁参加了下表中的某些类(class)/类(class):
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 |
+-------+---------+---------+
我想做的是,给定一组类/部分 ID 对 (int[][]
),返回所有属于这些类/部分的人。例如 my_func(ARRAY[[1,1],[2,2]])
返回:
+-------+---------+---------+
| 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
子句然后调用 执行
。有没有更好的方法来获得我的结果?
编辑: 我实现了@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, 1) as i
INNER JOIN attendance ON attendance.class = $1[i][1]
AND attendance.section = $1[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)
问题在于查询正在扫描出勤表中的所有出勤,而没有在加入之前对其进行过滤。有什么办法可以解决这个问题吗?
最佳答案
Postgres 9.4 或更高版本
现在有一种更优雅的方式。为 class
和 section
的值传递单独的一维数组,并并行取消嵌套。相同的数组位置匹配:
CREATE OR REPLACE FUNCTION f_attendance(_class_arr int[], _section_arr int[])
RETURNS SETOF attendance
LANGUAGE sql ROWS 10 STABLE AS
$func$
SELECT a.*
FROM unnest(_class_arr, _section_arr) a(class, section)
JOIN attendance a USING (class, section);
$func$;
调用:
SELECT * FROM f_attendance('{1,2}', '{1,2}');
参见:
Postgres 9.3(原始答案)
您可以使用简单的 SQL 函数来实现。关键特征是函数 generate_subscripts()
:
CREATE OR REPLACE FUNCTION f_attendance(_arr2d int[])
RETURNS SETOF attendance
LANGUAGE sql ROWS 10 STABLE AS
$func$
SELECT a.*
FROM generate_subscripts($1, 1) i
JOIN attendance a ON a.class = $1[i][1]
AND a.section = $1[i][2];
$func$;
调用:
SELECT * FROM f_attendance(ARRAY[[1,1],[2,2]]);
或者数组 literal 也是如此——这在某些情况下更方便,尤其是对于准备好的语句:
SELECT * FROM f_attendance('{{1,1},{2,2}}');
函数总是需要一个二维数组。即使你传递一对,嵌套它:
SELECT * FROM f_attendance('{{1,1}}');
实现审核
您创建了函数
VOLATILE
,但它可以是STABLE
。 The manual:Because of this snapshotting behavior, a function containing only
SELECT
commands can safely be markedSTABLE
.相关:
您还使用
LANGUAGE plpgsql
而不是sql
,如果您在同一个 session 中多次执行该函数,这是有意义的。但是您还必须使其STABLE
否则您将失去潜在的性能优势。 The manual once more:STABLE
andIMMUTABLE
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.您的
EXPLAIN
输出显示仅索引扫描,而不是您在评论中怀疑的顺序扫描。EXPLAIN
输出中还有一个排序步骤与您显示的代码不匹配。您确定复制了正确的EXPLAIN
输出吗?你是怎么得到它的? PL/pgSQL 函数是EXPLAIN
的黑盒。您是否使用了auto_explain
?详情:Postgres 查询规划器不知道传递的参数将包含多少数组元素,因此很难规划查询,它可能默认为顺序扫描(取决于更多因素)。您可以通过声明预期的行数来提供帮助。如果您通常没有超过 10 个项目,请像我在上面所做的那样添加
ROWS 10
。并再次测试。
关于sql - 将多个值集或数组传递给一个函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34600567/