【问题标题】:Postgres - find min of arrayPostgres - 查找数组的最小值
【发布时间】:2015-04-09 21:43:42
【问题描述】:

假设我有这样一张桌子:

  link_ids  |  length
------------+-----------
 {1,4}      | {1,2}
 {2,5}      | {0,1}

如何找到每个link_ids 的最小长度?

所以最终输出看起来像:

  link_ids  |  length
------------+-----------
 {1,4}      | 1
 {2,5}      | 0

【问题讨论】:

  • 为什么你的列是数组?这看起来是一个非常糟糕的架构设计。
  • 扩大@Falmarri 的评论 - 有一组正式的规则(称为范式)描述了关系数据库的模式应该是什么样子,以防止出现很多问题 - 它被认为是至少符合前三个是明智的——你的模式不符合第一个,因为你的表的单元格不存储原子值。您应该使用表格来存储列表。
  • 我熟悉范式。这是递归查询的结果,我想进一步处理。
  • 您应该编写递归查询,以便它为您提供行而不是数组。
  • 您的 Postgres 版本对于这个问题至关重要。另外:列可以为NULL吗?数组可以为空吗? link_ids 是唯一的吗?数组是否像您的示例所暗示的那样按升序排列?如果您使用的是实际表,请发布表定义。否则,最好发布您的递归查询:可能有更好的解决方案。 (加上基础表的表定义。)

标签: sql arrays postgresql min unnest


【解决方案1】:

假设一个表格如下:

CREATE TABLE tbl (
  link_ids int[] PRIMARY KEY     -- which is odd for a PK
, length int[]
, CHECK (length <> '{}'::int[])  -- rules out null and empty in length
);

查询 Postgres 9.3 或更高版本:

SELECT link_ids, min(len) AS min_length
FROM   tbl t, unnest(t.length) len  -- implicit LATERAL join
GROUP  BY 1;

或者创建一个小函数(Postgres 8.4+):

CREATE OR REPLACE FUNCTION arr_min(anyarray)
  RETURNS anyelement LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT min(i) FROM unnest($1) i';

仅在 Postgres 9.6 或更高版本中添加 PARALLEL SAFE。那么:

SELECT link_ids, arr_min(length) AS min_length FROM t;

函数可以内联,并且快速

或者,对于平凡长度integer数组,使用附加模块intarray及其内置的sort() function (Postgres 8.3+):

SELECT link_ids, (sort(length))[1] AS min_length FROM t;

【讨论】:

  • 不错! LATERAL JOIN 对我帮助很大。在我的情况下避免大量子查询。谢谢。
【解决方案2】:

对于数组的最小值:

SELECT max(x) from unnest(array_name) as x;

【讨论】:

    【解决方案3】:

    对 Erwin 的回答的一个小补充 - 有时使用 unnest 的子查询可能比横向连接更便宜。

    我使用了 Erwin 回答中的表定义并填写了它:

    t=# insert into t select '{1}'::int[]||g,'{1}'::int[]||g from generate_series(1,9999,1) g;
    INSERT 0 9999
    t=# select * from t order by ctid desc limit 1;
     link_ids |  length
    ----------+----------
     {1,9999} | {1,9999}
    (1 row)
    

    然后分析 LATERAL JOIN:

    t=# explain analyze select link_ids,max(r) from t, unnest(length) r where link_ids = '{1,9999}' group by 1;
                                                          QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------------
     GroupAggregate  (cost=0.29..4.81 rows=1 width=33) (actual time=0.030..0.030 rows=1 loops=1)
       ->  Nested Loop  (cost=0.29..4.30 rows=100 width=33) (actual time=0.025..0.027 rows=2 loops=1)
             ->  Index Scan using t_pkey on t  (cost=0.29..2.30 rows=1 width=58) (actual time=0.015..0.016 rows=1 loops=1)
                   Index Cond: (link_ids = '{1,9999}'::integer[])
             ->  Function Scan on unnest r  (cost=0.00..1.00 rows=100 width=4) (actual time=0.007..0.007 rows=2 loops=1)
     Total runtime: 0.059 ms
    (6 rows)
    

    并尝试子查询:

    t=# explain analyze select link_ids, (select max(r) from unnest(length) r) from t where link_ids = '{1,9999}';
                                                          QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------------
     Index Scan using t_pkey on t  (cost=0.29..3.56 rows=1 width=58) (actual time=0.030..0.031 rows=1 loops=1)
       Index Cond: (link_ids = '{1,9999}'::integer[])
       SubPlan 1
         ->  Aggregate  (cost=1.25..1.26 rows=1 width=4) (actual time=0.011..0.011 rows=1 loops=1)
               ->  Function Scan on unnest r  (cost=0.00..1.00 rows=100 width=4) (actual time=0.008..0.008 rows=2 loops=1)
     Total runtime: 0.060 ms
    (6 rows)
    

    最后确保结果相同:

    t=# select link_ids, (select max(r) from unnest(length) r) 
    from t 
    where link_ids = '{1,9999}';
     link_ids | max
    ----------+------
     {1,9999} | 9999
    (1 row)
    
    t=# select link_ids,max(r) 
    from t, unnest(length) r 
    where link_ids = '{1,9999}' 
    group by 1;
     link_ids | max
    ----------+------
     {1,9999} | 9999
    (1 row)
    

    【讨论】:

      【解决方案4】:

      假设表名是tlink_ids的每个值都是唯一的。

      select link_ids, min(len)
      from (select link_ids, unnest(length) as len from t) as t
      group by link_ids;
      
       link_ids | min
      ----------+-----
       {2,5}    |   0
       {1,4}    |   1
      

      【讨论】:

        【解决方案5】:

        (我假设 link_ids 可以有双打,因为没有 id 列,我们将即兴创作)。

        WITH r AS
        (SELECT row_number() OVER() as id,
               link_ids,
               length from Table1)
        SELECT DISTINCT ON (id) link_ids,
               unnest(length) 
        FROM r 
        ORDER BY id, length;
        

        fiddle

        【讨论】:

          猜你喜欢
          • 2021-09-15
          • 2018-09-24
          • 1970-01-01
          • 2016-06-06
          • 2021-12-28
          • 1970-01-01
          • 1970-01-01
          • 2014-06-11
          相关资源
          最近更新 更多