PostgreSQL交叉表查询

sch*_*one 180 sql postgresql pivot case crosstab

有没有人知道如何在PostgreSQL中创建交叉表查询?
例如,我有下表:

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5
Run Code Online (Sandbox Code Playgroud)

我想查询返回以下交叉表:

Section    Active    Inactive
A          1         2
B          4         5
Run Code Online (Sandbox Code Playgroud)

这可能吗?

Erw*_*ter 299

每个数据库安装一次附加模块tablefunc ,提供该功能.自Postgres 9.1以来,您可以使用:crosstab()CREATE EXTENSION

CREATE EXTENSION IF NOT EXISTS tablefunc;
Run Code Online (Sandbox Code Playgroud)

改进了测试用例

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing
Run Code Online (Sandbox Code Playgroud)

简单形式 - 不适合缺少属性

crosstab(text)1个输入参数:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);
Run Code Online (Sandbox Code Playgroud)

返回:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |      7 |           -- !!
  • 无需进行转换和重命名.
  • 请注意错误的结果C:7为第一列填写值.有时,这种行为是可取的,但不适用于此用例.
  • 在简单的形式也被限制到恰好在所提供的输入查询三列:ROW_NAME,类别,.如下面的2参数替代方案中没有额外列的空间.

安全的形式

crosstab(text, text)2个输入参数:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);
Run Code Online (Sandbox Code Playgroud)

返回:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |        |        7  -- !!
  • 请注意正确的结果C.

  • 所述第二参数可以是返回一个任意查询每属性匹配在端列定义的顺序.通常,您需要查询基础表中的不同属性,如下所示:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
    
    Run Code Online (Sandbox Code Playgroud)

    这是在手册中.

    由于您必须拼写列定义列表中的所有列(预定义变体除外),因此在表达式中提供简短列表通常更为有效:crosstabN()VALUES

    $$VALUES ('Active'::text), ('Inactive')$$)
    
    Run Code Online (Sandbox Code Playgroud)

    或(不在手册中):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
    
    Run Code Online (Sandbox Code Playgroud)
  • 我使用美元报价使报价更容易.

  • 你甚至可以输出列有不同的数据类型crosstab(text, text)-只要值列文表示是目标类型的有效输入.这样,你可能有不同的种类和输出的属性text,date,numeric对各自的属性等.本手册章节crosstab(text, text)末尾有一个代码示例.

db <> 在这里小提琴

高级示例


\crosstabview 在psql中

Postgres 9.6将此元命令添加到其默认的交互式终端psql中.您可以运行您将用作第一个crosstab()参数的查询并将其提供给\crosstabview(立即或在下一步中).喜欢:

db=> SELECT section, status, ct FROM tbl \crosstabview
Run Code Online (Sandbox Code Playgroud)

与上面类似的结果,但它只是客户端表示功能.输入行的处理方式略有不同,因此ORDER BY不是必需的.\crosstabview手册中的详细信息.该页面底部有更多代码示例.

关于dba.SE的相关答案,DanielVérité(psql特性的作者):



以前接受的答案是过时的.

  • 该功能的变体crosstab(text, integer)已过时.第二个integer参数被忽略.我引用当前的手册:

    crosstab(text sql, int N) ...

    已过时的版本crosstab(text).N现在忽略该参数,因为值列的数量始终由调用查询确定

  • 不必要的铸造和重命名.

  • 如果某行没有所有属性,则会失败.请参阅上面两个输入参数的安全变体,以正确处理缺少的属性.

  • ORDER BY在单参数形式中是必需的crosstab().手册:

    实际上,SQL查询应始终指定ORDER BY 1,2以确保输入行的顺序正确

  • @ErwinBrandstetter 这是您以一种非常有能力、深思熟虑且易于掌握的方式解释复杂事物的另一个例子。如果有一个诺贝尔价格来帮助解决堆栈溢出,你应该得到它 (4认同)
  • +1,好写,感谢注意`在实践中,SQL查询应始终指定ORDER BY 1,2以确保输入行正确排序 (2认同)
  • @Ashish:请开始一个新问题.评论不是这个地方.您始终可以链接到此上下文. (2认同)
  • 非常感谢您指出 $$ 引用可用于通过 dbeaver 等工具保持内部 sql“独立可执行”(通过仅选择内部 sql 文本);更不用说保留编辑器为 sql 提供的任何颜色编码。 (2认同)

Jer*_*hka 29

您可以使用附加模块tablefunccrosstab()功能- 您必须为每个数据库安装一次.从PostgreSQL 9.1开始,你可以使用它:CREATE EXTENSION

CREATE EXTENSION tablefunc;
Run Code Online (Sandbox Code Playgroud)

在你的情况下,我相信它看起来像这样:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);
Run Code Online (Sandbox Code Playgroud)


ara*_*nid 24

SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section
Run Code Online (Sandbox Code Playgroud)

  • @JohnBarça:这样一个简单的案例可以通过CASE语句轻松解决.但是,使用更多属性和/或其他数据类型而不仅仅是整数,这会很快变得难以处理.顺便说一句:这个表单使用聚合函数`sum()`,最好使用`min()`或`max()`而不是'ELSE`也适用于`text`.但这与`corosstab()`的效果略有不同,它只使用每个属性的"第一"值.只要只有一个就没关系.最后,表现也是相关的.`crosstab()`是用C语言编写的,并针对任务进行了优化. (4认同)
  • 有人可以解释一下 tablefunc 模块中的 crosstab 函数添加到这个答案中的内容吗?它既完成了手头的工作,而且在我看来更容易理解? (2认同)
  • 考虑添加说明,而不只是代码块 (2认同)
  • 在我的 postgresql 中,由于某种原因未定义 tablefunc 和 crosstab,并且不允许我定义它们。这个直观的解决方案对我有用,所以竖起大拇指! (2认同)

Mil*_*los 5

JSON聚合解决方案:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X
Run Code Online (Sandbox Code Playgroud)