数组1包含在数组2中,元素以相同的顺序包含

时间:2017-03-01 13:33:06

标签: arrays postgresql

PostgreSQL有没有办法让我找到一个数组是否包含在另一个数组中,但具有相同的顺序?
例如,我想知道array1是否在array2中,匹配元素的顺序相同。

array1[1, 3, 6, 8]
array2[3, 8, 2, 9, 10, 1, 6]

显然不是示例中的情况,但在PostgreSQL中是否有内置的方法,还是应该创建自己的函数?

PostgreSQL的版本是9.6。查询将运行的实际数字是bigints。

1 个答案:

答案 0 :(得分:2)

一般情况

第二个数组的所有元素也在第一个元素中。按相同的顺序,但允许有差距。

我建议使用这种多态PL / pgSQL函数:

CREATE OR REPLACE FUNCTION array_contains_array_in_order(arr1 ANYARRAY
                                                       , arr2 ANYARRAY
                                                       , elem ANYELEMENT = NULL)
  RETURNS bool AS
$func$
DECLARE
   pos int := 1;
BEGIN
   FOREACH elem in ARRAY arr2
   LOOP
      pos := pos + array_position(arr1[pos:], elem);  -- see below
      IF pos IS NULL THEN
         RETURN FALSE;
      END IF;
   END LOOP; 

   RETURN true; --  all elements found in order
END
$func$ LANGUAGE plpgsql IMMUTABLE COST 3000;

作为@a_horse commented,我们可以省略数组下标中的上限,表示“无界”(arr1[pos:])。在9.6之前的旧版本中,替换为arr1[pos:2147483647] - 2147483647 = 2 ^ 31 - 1 是理论上的最大数组索引,是最大的有符号int4数。

这适用于......

关于ANYELEMENT技巧:

性能

我进行了快速性能测试,将此功能与the one @a_horse supplied进行了比较。这个速度提高了约5倍。

如果您使用此过滤器来表示大表,我强烈建议您将其与(逻辑上冗余的)可搜索过滤器结合使用,如:

SELECT *
FROM   tbl
WHERE  arr @> '{2,160,134,58,149,111}'::int[] 
AND    array_contains_array_in_order(arr, '{2,160,134,58,149,111}')  

这将在数组列上使用GIN索引,如:

CREATE INDEX ON tbl USING gin (arr);

仅过滤剩余的(通常非常少数!)阵列,这些数组共享所有元素。通常 很多 更快。

使用intarray模块的注意事项

注意:仅适用于integer[],不适用于smallint[]bigint[]或任何其他数组类型!

小心,如果您安装了intarray extension@>int[]提供了CREATE INDEX ON intarr USING gin (arr gin__int_ops); 运算符的变体。您可以使用其特殊的运算符类创建(附加)GIN索引(在适用的情况下稍快一些):

WHERE  arr OPERATOR(pg_catalog.@>) '{2,160,134,58,149,111}'::int[] 

,虽然您只有一个带有默认运算符类的GIN索引,但您必须明确表示标准运算符与索引合作:

CREATE OR REPLACE FUNCTION array_contains_array_exactly(arr1 ANYARRAY, arr2 ANYARRAY)
  RETURNS bool AS
$func$
DECLARE
   len int := array_length(arr2, 1) - 1;  -- length of arr2 - 1 to fix off-by-1
   pos int;                               -- for current search postition in arr1
BEGIN
   /*                                     -- OPTIONAL, if invalid input possible
   CASE array_length(arr1, 1) >  len      -- array_length(arr2, 1) - 1 
   WHEN TRUE  THEN                        -- valid arrays
      -- do nothing, proceed
   WHEN FALSE THEN                        -- arr1 shorter than arr2
      RETURN FALSE;                       -- or raise exception?
   ELSE                                   -- at least one array empty or NULL
      RETURN NULL;
   END CASE;
   */

   pos := array_position(arr1, arr2[1]);  -- pos of arr2's 1st elem in arr1

   WHILE pos IS NOT NULL
   LOOP
      IF arr1[pos:pos+len] = arr2 THEN    -- array slice matches arr2 *exactly*
         RETURN TRUE;                     -- arr2 is part of arr1
      END IF;

      pos := pos + array_position(arr1[(pos+1):], arr2[1]);
   END LOOP; 

   RETURN FALSE;
END
$func$ LANGUAGE plpgsql IMMUTABLE COST 1000;

详细说明:

简单案例

如评论所述,您的案例更简单:

完整的第二个数组包含在第一个数组中(相同顺序,无间隙!)。

@If(@Adjust(ServiceDate; 0; 6; 0; 0; 0; 0) >= @Today; "Editable"; "ReadOnly")

对于更长的阵列,比上述速度快得多。所有其他考虑因素仍然适用。