14

我正在尝试选择一些字段,其中一个需要是一个数组,数组的每个元素都包含两个值。每个数组项都需要包含一个名称(不同的字符)和一个 ID(数字)。我知道如何返回单个值的数组(使用ARRAY关键字),但我不确定如何返回本身包含两个值的对象数组。

查询类似于

SELECT
    t.field1,
    t.field2,
    ARRAY(--with each element containing two values i.e. {'TheName', 1 })
FROM MyTable t

我读到这样做的一种方法是将值选择为一种类型,然后创建该类型的数组。问题是,函数的其余部分已经返回了一个类型(这意味着我会有嵌套类型 - 可以吗?如果是这样,你将如何在应用程序代码中读回这些数据 - 即使用像 NPGSQL 这样的 .Net 数据提供程序?)

任何帮助深表感谢。

4

2 回答 2

12

ARRAY 只能容纳相同类型的元素

您的示例显示 atext和一个integer值(周围没有单引号1)。通常不可能在数组中混合类型。要将这些值放入数组中,您必须创建一个composite type然后形成该复合类型的 ARRAY,就像您自己已经提到的那样。

或者,您可以使用 Postgres 9.2+、Postgres 9.4+ 中的数据类型jsonjsonb键值hstore对。


当然,您可以强制integer转换为text,并使用二维文本数组。考虑下面演示中数组输入的两种语法变体,并查阅有关数组输入的手册

有一个限制需要克服。如果您尝试将 ARRAY(从键和值构建)聚合到二维数组中,聚合函数array_agg()ARRAY构造函数会出错:

ERROR:  could not find array type for data type text[]

不过,有一些方法可以解决它。

将键值对聚合成二维数组

PostgreSQL 9.1 与standard_conforming_strings= on

CREATE TEMP TABLE tbl(
 id     int
,txt    text
,txtarr text[]
);

该列txtarr只是为了演示 INSERT 命令中的语法变体。第三行添加了元字符:

INSERT INTO tbl VALUES
 (1, 'foo', '{{1,foo1},{2,bar1},{3,baz1}}')
,(2, 'bar', ARRAY[['1','foo2'],['2','bar2'],['3','baz2']])
,(3, '}b",a{r''', '{{1,foo3},{2,bar3},{3,baz3}}'); -- txt has meta-characters

SELECT * FROM tbl;

简单案例:将两个整数(我用了两次相同)聚合成一个二维 int 数组:

更新:使用自定义聚合函数更好

使用多态类型anyarray,它适用于所有基本类型:

CREATE AGGREGATE array_agg_mult (anyarray)  (
    SFUNC     = array_cat
   ,STYPE     = anyarray
   ,INITCOND  = '{}'
);

称呼:

SELECT array_agg_mult(ARRAY[ARRAY[id,id]]) AS x        -- for int
      ,array_agg_mult(ARRAY[ARRAY[id::text,txt]]) AS y -- or text
FROM   tbl;

请注意附加ARRAY[]层以使其成为多维数组。

Postgres 9.5+ 的更新

Postgres 现在提供了一个array_agg()接受数组输入的变体,你可以从上面替换我的自定义函数:

手册:

array_agg(expression)
...
输入数组连接成一个更高维度的数组(输入必须都具有相同的维度,并且不能为空或 NULL)

于 2012-02-03T19:24:40.303 回答
5

我怀疑如果没有更多关于您的应用程序的知识,我将无法让您一直获得所需的结果。但我们可以走得很远。对于初学者,有以下ROW功能:

# SELECT 'foo', ROW(3, 'Bob');
 ?column? |   row   
----------+---------
 foo      | (3,Bob)
(1 row)

这样就可以让您将整行捆绑到一个单元格中。您还可以通过为其创建类型来使事情更加明确:

# CREATE TYPE person(id INTEGER, name VARCHAR);
CREATE TYPE
# SELECT now(), row(3, 'Bob')::person;
              now              |   row   
-------------------------------+---------
 2012-02-03 10:46:13.279512-07 | (3,Bob)
(1 row)

顺便说一句,每当你创建一个表时,PostgreSQL 都会创建一个同名的类型,所以如果你已经有一个这样的表,那么你也有一个类型。例如:

# DROP TYPE person;
DROP TYPE

# CREATE TABLE people (id SERIAL, name VARCHAR);
NOTICE:  CREATE TABLE will create implicit sequence "people_id_seq" for serial column "people.id"
CREATE TABLE

# SELECT 'foo', row(3, 'Bob')::people;
 ?column? |   row   
----------+---------
 foo      | (3,Bob)
(1 row)

在第三个查询中看到我people就像一个类型一样使用。

现在这可能没有您想象的那么大的帮助,原因有两个:

  1. 我找不到任何方便的语法来从嵌套行中提取数据。

    我可能遗漏了一些东西,但我只是没有看到很多人使用这种语法。我在文档中看到的唯一示例是将行值作为参数并对其进行处理的函数。我没有看到将行从单元格中拉出并查询其中一部分的示例。看起来你可以这样打包数据,但之后很难解构。您最终将不得不制作大量存储过程。

  2. 您的语言的 PostgreSQL 驱动程序可能无法处理嵌套在一行中的行值数据。

    我不能代表 NPGSQL,但由于这是一个非常特定于 PostgreSQL 的特性,你不会在支持其他数据库的库中找到对它的支持。例如,Hibernate 将无法处理获取存储为一行中单元格值的对象。我什至不确定 JDBC 是否能够有效地为 Hibernate 提供信息,所以问题可能会很深。

所以,你在这里做的事情是可行的,前提是你可以在没有很多细节的情况下生活。不过,我建议不要追求它,因为这将是一场艰苦的战斗,除非我真的被误导了。

于 2012-02-03T18:19:07.247 回答