为什么 WHERE 子句没有在视图查询中下推?

Num*_*our 4 postgresql performance optimization view query-performance

使用 Postgres 9.4,我经常执行以下查询:

SELECT DISTINCT ON(recipient) * FROM messages
LEFT JOIN identities ON messages.recipient = identities.name
WHERE timestamp BETWEEN timeA AND timeB
ORDER BY recipient, timestamp DESC;
Run Code Online (Sandbox Code Playgroud)

所以我决定创建一个视图:

CREATE VIEW myView AS SELECT DISTINCT ON(recipient) * FROM messages
LEFT JOIN identities ON messages.recipient = identities.name
ORDER BY recipient, timestamp DESC;
Run Code Online (Sandbox Code Playgroud)

我刚刚意识到,如果我查询我的观点,就像SELECT * FROM myView WHERE timestamp BETWEEN timeA AND timeB我的表现要差很多一样。

这样EXPLAIN ANALYZE两个问题,我发现了原因是,在第二种情况下,数据库带来了所有记录,请问左连接,然后应用WHERE条款。换句话说,WHERE子句不会被下推到视图的查询中。我还尝试ORDER BY从视图中删除,但数据库仍然LEFT JOIN对完整数据而不是过滤集执行。

这种行为的原因是什么?有没有办法在使用视图时获得可比较的性能?

ype*_*eᵀᴹ 13

这个查询:

SELECT DISTINCT ON(recipient) * FROM messages
LEFT JOIN identities ON messages.recipient = identities.name
WHERE messages.timestamp BETWEEN timeA AND timeB
ORDER BY recipient, timestamp DESC;
Run Code Online (Sandbox Code Playgroud)

说:

对于timeA 和 timeB 之间的所有消息,找到收件人,并为每个收件人找到一条消息(timeA 和 timeB 之间的最新消息)。


这个查询(如果你使用视图,你会得到):

SELECT *
FROM 
  ( SELECT DISTINCT ON(recipient) * FROM messages
    LEFT JOIN identities ON messages.recipient = identities.name
    ORDER BY recipient, timestamp DESC
  ) AS myView
WHERE timestamp BETWEEN timeA AND timeB;
Run Code Online (Sandbox Code Playgroud)

说:

对于所有消息,查找收件人,并为每个收件人查找一条消息(所有时间中的最新消息),然后显示时间 A 和时间 B 之间的消息。


因此,第一个查询将显示时间 A 和 B 之间的消息,而第二个查询不会显示(因为可能有一个或多个消息发送给同一收件人,晚于时间 B)。

因此,查询在逻辑上是不同的,并且不能(也不应该)为您的视图推送条件。

如果您想将参数传递给您的视图,请查看此问题中的两个答案(@a_horse_with_no_name@Erwin Brandstetter),了解如何使用集合返回函数:将“WHERE”参数传递给 PostgreSQL 视图?


Sah*_*sci 7

您可以创建这样的函数;

CREATE OR REPLACE FUNCTION public.get_messages_by_timestamp (
  time_a timestamp,
  time_b timestamp
)
RETURNS TABLE (
  recipient varchar,
  "timestamp" timestamp
) AS
$$
BEGIN
  RETURN QUERY
    SELECT DISTINCT ON (m.recipient) 
        m.recipient,
        m."timestamp"
      FROM messages m
      LEFT JOIN identities i ON m.recipient = i.name
      WHERE 
        m."timestamp" BETWEEN time_a AND time_b
      ORDER BY 
        m.recipient,
        m."timestamp" DESC;
END;
$$
LANGUAGE 'plpgsql';
Run Code Online (Sandbox Code Playgroud)

然后你可以像使用表格一样使用该功能

  SELECT *
         FROM get_messages_by_timestamp('2015-01-01', '2015-01-02')
Run Code Online (Sandbox Code Playgroud)