将多个值集或数组传递给一个函数
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}}');
实施审核
你制作了函数VOLATILE
,但它可以是STABLE
。 Per documentation:
Because of this snapshotting behavior, a function containing only SELECT
commands can safely be marked STABLE
.
相关:
您还使用 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.
您的 EXPLAIN
输出显示 仅索引扫描,而不是您在评论中怀疑的顺序扫描。
您的 EXPLAIN
输出中还有一个排序步骤与您显示的代码不匹配。你确定你复制了正确的 EXPLAIN
输出吗?你是怎么得到它的? PL/pgSQL 函数是 EXPLAIN
的黑盒。你用过auto_explain
吗?详情:
Postgres 查询规划器不知道传递的参数将有多少个数组元素,因此很难规划查询,它可能默认为顺序扫描(取决于更多因素)。您可以通过声明预期的行数来提供帮助。如果您通常没有超过 10 个项目,请像我上面那样添加 ROWS 10
。并再次测试。
我正在 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}}');
实施审核
你制作了函数
VOLATILE
,但它可以是STABLE
。 Per documentation:Because of this snapshotting behavior, a function containing only
SELECT
commands can safely be markedSTABLE
.相关:
您还使用
LANGUAGE plpgsql
而不是sql
,如果您在同一个会话中多次执行该函数,这是有意义的。但是你也必须做到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
。并再次测试。