查询 Postgres 数组列类型

Query against a Postgres array column type

TL;DR 我想知道 @> {as_champion, whatever} 和使用 IN ('as_champion', 'whatever') 之间的优缺点是什么(或者它们是否相等)。详情如下:

我正在使用 Rails 并使用 Postgres 的数组列类型,但由于 Rails 查找器方法运行不佳,我的查询必须使用原始 sql用它。我找到了一种可行的方法,但想知道首选方法是什么:

Memberships table 上的 roles 列是我的数组列。它是通过 rails 添加的:

add_column :memberships, :roles, :text, array: true

当我检查 table 时,它显示类型为:text[](不确定这是否真的是 Postgres 表示数组列的方式,或者这是否是 Rails 恶作剧。

为了查询它,我做了类似的事情:

Membership.where("roles @> ?", '{as_champion, whatever}')

从精Array Operators manual:

Operator: @>
Description: contains
Example: ARRAY[1,4,3] @> ARRAY[3,1]
Result: t (AKA true)

因此@>将其操作数数组视为集合并检查右侧是否是左侧的子集。

IN 有点不同,与 subqueries:

一起使用

9.22.2. IN

expression IN (subquery)

The right-hand side is a parenthesized subquery, which must return exactly one column. The left-hand expression is evaluated and compared to each row of the subquery result. The result of IN is "true" if any equal subquery row is found. The result is "false" if no equal row is found (including the case where the subquery returns no rows).

literal lists:

9.23.1. IN

expression IN (value [, ...])

The right-hand side is a parenthesized list of scalar expressions. The result is "true" if the left-hand expression's result is equal to any of the right-hand expressions. This is a shorthand notation for

expression = value1
OR
expression = value2
OR
...

所以 a IN b 或多或少意味着:

Is the value a equal to any of the values in the list b (which can be a query producing single element rows or a literal list).

当然,你可以这样说:

array[1] in (select some_array from ...)
array[1] in (array[1], array[2,3])

但这些情况下的数组仍被视为单个值(恰好具有一些内部结构)。


如果您想检查一个数组是否包含任何值列表,那么 @> 不是您想要的。考虑一下:

array[1,2] @> array[2,4]

4 不在 array[1,2] 中,因此 array[2,4] 不是 array[1,2].

的子集

如果您想检查某人是否具有两个角色,那么:

roles @> array['as_champion', 'whatever']

是正确的表达式,但是如果你想检查 roles 是否是这些值的 any 那么你需要重叠运算符(&&):

roles && array['as_champion', 'whatever']

请注意,我对所有数组都使用 "array constructor" 语法,这是因为使用知道将数组扩展为逗号分隔的工具(例如 ActiveRecord)会更方便替换占位符但不完全理解 SQL 数组时列出。

考虑到所有这些,我们可以这样说:

Membership.where('roles @> array[?]', %w[as_champion whatever])
Membership.where('roles @> array[:roles]', :roles => some_ruby_array_of_strings)

一切都会按预期进行。您仍在使用很少的 SQL 片段(因为 ActiveRecord 没有完全理解 SQL 数组或表示 @> 运算符的任何方式)但至少您不会不必担心引用问题。您可能可以通过 AREL 手动添加 @> 支持,但我发现 AREL 很快就会变成一个难以理解和不可读的混乱,除了最琐碎的用途之外。