【问题标题】:PostgreSQL - best way to return an array of key-value pairsPostgreSQL - 返回键值对数组的最佳方法
【发布时间】:2012-02-26 03:24:28
【问题描述】:

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

查询类似于

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

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

非常感谢任何帮助。

【问题讨论】:

    标签: sql database postgresql npgsql


    【解决方案1】:

    我怀疑如果没有更多关于您的应用程序的知识,我将无法为您提供所需的结果。但我们可以走得很远。对于初学者,有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 提供有用的信息,所以问题可能会很深。

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

    【讨论】:

    • 感谢您的输入,同时检索数据现在是可行的,就像您说的那样,我将努力在 C# 端解构该数据。不知道下一步该去哪里……但谢谢!
    【解决方案2】:

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

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

    或者您可以在 Postgres 9.2+ 中使用数据类型 json,在 Postgres 9.4+ 中使用 jsonbhstore 用于键值对。


    当然,您可以将integer 转换为text,并使用二维文本数组。考虑下面演示中数组输入的两种语法变体,并咨询the manual on array input

    有一个限制需要克服。如果您尝试将 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数组:

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

    使用polymorphic type 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() 的变体,您可以将上面的自定义函数替换为:

    The manual:

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

    【讨论】:

      猜你喜欢
      • 2013-05-12
      • 2019-05-10
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      • 2013-08-16
      • 1970-01-01
      相关资源
      最近更新 更多