Postgres GROUP BY id 直接适用于表,但不适用于相同的视图

Ave*_*ery 4 postgresql view

假设我有两张桌子。其中一个以一对多关系引用另一个:

CREATE TABLE t (
  id SERIAL PRIMARY KEY
, name text
);

INSERT INTO t (name) VALUES ('one'), ('two'), ('three');

CREATE TABLE y (
  id SERIAL PRIMARY KEY
, tid INTEGER REFERENCES t
, amount INTEGER
);

INSERT INTO y (tid, amount) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(3, 1);
Run Code Online (Sandbox Code Playgroud)

我可以从这些表中查询数据,以获得与 和t上设置的特定条件匹配的行:ty

SELECT row_to_json(t.*) as "t_nogroup" FROM t
JOIN y ON t.id = y.tid
WHERE t.id = 1
AND y.amount > 1;
Run Code Online (Sandbox Code Playgroud)

这产生

t_nogroup
=========
{"id":1,"name":"one"}
{"id":1,"name":"one"}
Run Code Online (Sandbox Code Playgroud)

是的,它有效,但包含重复项。我可以使用以下方法过滤掉重复项GROUP BY

SELECT row_to_json(t.*) as "t_group" FROM t
JOIN y ON t.id = y.tid
WHERE t.id = 1
AND y.amount > 1
GROUP BY t.id;
Run Code Online (Sandbox Code Playgroud)

制作:

t_group
=======
{"id":1,"name":"one"}
Run Code Online (Sandbox Code Playgroud)

伟大的!但是,当我尝试交换ty为非物化VIEWs 时:

CREATE VIEW vt AS SELECT t.* FROM t;
CREATE VIEW vy AS SELECT y.* FROM y;
Run Code Online (Sandbox Code Playgroud)

VIEW并使用新的s运行相同的查询

SELECT row_to_json(vt.*) as "view_t_nogroup" FROM vt
JOIN vy ON vt.id = vy.tid
WHERE vt.id = 1
AND vy.amount > 1;

SELECT row_to_json(vt.*) as "view_t_group" FROM vt
JOIN vy ON vt.id = vy.tid
WHERE vt.id = 1
AND vy.amount > 1
GROUP BY vt.id;
Run Code Online (Sandbox Code Playgroud)

然后我得到第二个查询的错误:

ERROR: column "vt.*" must appear in the GROUP BY clause or be used in an aggregate function
Run Code Online (Sandbox Code Playgroud)

为什么会出现这种情况?使用表时*看到的任何逻辑都应该仍然适用于视图,对吗?idtvt

小提琴链接:http://sqlfiddle.com/#!17/26b0e /5

我正在运行 PostgreSQL 9.6。我也知道这是一个人为的示例,但是我在一个非常相似的一对多关系中遇到了这个错误,其中GROUP BY需要多个语句,因此仅仅使用DISTINCT不是一个选项,就像这个示例一样。

ype*_*eᵀᴹ 6

出现这个问题是因为 Postgres 知道该表t具有(id)主键 - 因此我们可以GROUP BY t.id在列表中使用该表中的更多列,SELECT而无需聚合它们 - 但它无法为 view 推断出相同的情况vt

对于提供的示例,您可以将它们重写为GROUP BY根本不使用。由于您不需要选择列表中第二个连接表中的任何列,因此您可以将其转换JOIN为子EXISTS查询,即将连接转换为半连接。现在表和视图半连接都可以正常工作,不会出现错误:

SELECT row_to_json(t.*) as "t_group" FROM t
WHERE t.id = 1 
  AND EXISTS 
      ( SELECT 1 
        FROM y 
        WHERE t.id = y.tid
          AND y.amount > 1 
      ) ;

SELECT row_to_json(vt.*) as "t_group" FROM vt
WHERE vt.id = 1 
  AND EXISTS 
      ( SELECT 1 
        FROM vy 
        WHERE vt.id = vy.tid
          AND vy.amount > 1 
      ) ;
Run Code Online (Sandbox Code Playgroud)