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

标签 sql postgresql stored-procedures multidimensional-array plpgsql

我正在 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 或更高版本

现在有一种更优雅的方式。为 classsection 的值传递单独的一维数组,并并行取消嵌套。相同的数组位置匹配:

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}}');

实现审核

  1. 您创建了函数VOLATILE,但它可以是STABLEThe manual:

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

    相关:

  1. 您还使用 LANGUAGE plpgsql 而不是 sql,如果您在同一个 session 中多次执行该函数,这是有意义的。但是您还必须使其 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.

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

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

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

关于sql - 将多个值集或数组传递给一个函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34600567/

相关文章:

sql - 使用 WITH 绑定(bind)的值插入数据

sql - SQL Server Money 数据类型需要更高的精度

windows - 无法启动 PostgreSQL 11.2。尝试删除所有内容,现在 PostgreSQL 11.2 拒绝安装并出现错误

postgresql - Psycopg2 SQL 语句在查询生成器中工作,而不是在 Python 中工作

sql - 在 postgresql 的单个查询中使用 WITH + DELETE 子句

sql-server - 为什么MSSQL中的某些存储过程必须在前面加上 `master..`来调用,而其他存储过程则可以不加前缀来调用?

php - Laravel - 选择列值第一次出现的行

sql - 使用CTE获取emp的层次结构,无论职位如何

mysql - 将所有值设置为零(单行中的一个值除外)

postgresql - Postgres pl/pgsql 错误 : column "column_name" does not exist