创建视图时关闭表名的自动限定

vol*_*ron 4 postgresql database-design execution-plan view

问题定义

我正在尝试修改共享开发数据库,​​供开发团队用于应用程序开发/测试。

大多数集体工作(表名、视图等)都存储在公共环境中模式中,但我已经为每个用户设置了模式以用作临时空间。然而,真正的目标是用户使用其架构中的对象(如果存在),然后依赖其他对象,就像今天使用 search_path 的方式一样。

例子

用一个例子可能会更好地描述这一点。假设团队正在开发一个汽车维修应用程序的数据库,其中包含一些表和视图:

public.automobiles
public.parts
public.inventory
public.mechanics
public.schedule
public.v_repairs  -- view that joins fields from all tables above
Run Code Online (Sandbox Code Playgroud)

这非常有效,但假设开发人员(例如 Sally)想要测试一项新功能,用她自己的数据集来检查视觉反馈或阈值测试。她在自己的架构中创建了一个表sally.schedule。因为默认的search_path类似于"$user",public,所以她创建的任何简单查询都会在公开之前首先检查她的架构。例如:

SELECT * FROM schedule LEFT JOIN mechanics USING(mechanic_id);
Run Code Online (Sandbox Code Playgroud)

当 sally 连接到数据库时,这将使用 public.mechanics 和 sally.schedule。这正是预期的用途,但是在保存视图时,它通过插入架构来完全限定表名称。因此,如果将上面相同的查询创建为公共模式中的视图,它将如下所示:

SELECT * FROM public.schedule LEFT JOIN public.mechanics USING(mechanic_id);
Run Code Online (Sandbox Code Playgroud)

search_path 的魔力被否定了。当Sally连接到数据库调用视图时(SELECT * FROM v_mechanics_schedule ) 时,它会忽略她创建的 sally.schedule 表,而只使用公共表。

有没有办法在保存视图时不让 Postgres 存储表/视图对象的架构名称?


注意:这是我正在研究的新事物,但我从未真正需要过它,因为开发人员通常可以克隆应用程序、复制数据库并在自己的沙箱环境中工作。不需要一些巧妙的协作模式设置

Erw*_*ter 5

不,没有办法。我认为人们对事情的运作方式存在轻微的误解。

与将函数体存储为字符串后期绑定!)的函数不同,视图解析查询并且根本不存储原始文本。更新:从 Postgres 14 开始,还有 SQL 标准函数(带有),它们也会在创建时解析主体!看:BEGIN ATOMIC

所有标识符都根据当前解析search_path,并且仅保存其内部 OID(早期绑定!)。这也是为什么SELECT *总是在该时间点解析为列列表的原因VIEW

当查看 a 的定义时,您在 pgAdmin 或其他客户端中看到的VIEW是重新设计的 SQL 字符串。永远public.schedule是并且永远是public.schedulesally.schedulesally.schedule(除非您重命名表或架构选择显示的(模式限定的)名称,以便标识符对于当前搜索路径是明确的。

这也会显示您何时更改列、表或架构的名称。该视图一直有效,因为它不依赖于这些属性。定义的显示是动态调整的。

可能的解决方案

如果您想要后期绑定,可以使用函数而不是视图,并默认为当前搜索路径(可以SET函数范围的搜索路径来精确避免这种影响。)

所以而不是:

CREATE VIEW v_mechanics_schedule AS
SELECT *
FROM   schedule s  -- use aliases for short qualified column names in display
LEFT   JOIN mechanics m USING (mechanic_id);
Run Code Online (Sandbox Code Playgroud)

如果没有架构限定,表名称将根据创建时search_path的当前名称进行解析当前名称进行解析。

可以

CREATE FUNCTION f_mechanics_schedule()
  RETURNS TABLE (...)  -- spell out columns
  LANGUAGE SQL AS
$func$
SELECT * FROM schedule s  -- typically error prone
LEFT     JOIN mechanics m USING(mechanic_id);
$func$
Run Code Online (Sandbox Code Playgroud)

现在,表名是根据search_path执行时间解析的。请注意,这不适用于返回类型 ( RETURNS TABLE (...)),它是在创建时确定的。因此,这个技巧仅适用于兼容的表 - 查询必须返回相同的列列表,只有数据类型和列很重要,列被忽略。只有子句中定义的列名称RETURNS在函数外部可见。

这也是为什么SELECT *在函数体中返回值通常是不可靠的。如果更改基础表的定义,该函数会中断:定义的返回类型保持不变,但SELECT *解析为不同的列列表...

OTOH,如果您将函数声明为RETURNS SETOF some_table(不同的语法变体),则会注册函数依赖项,并且函数的返回类型绑定到表定义,因此SELECT * FROM some_table更有意义。

SECURITY DEFINER这也是为什么在不修复以下问题的情况下拥有函数是不安全的原因search_path:任何具有TEMP权限的用户都可以创建一个隐藏其他表的临时表,因为pg_temp默认情况下在搜索路径中排在第一位...
相关:

您确实需要了解底层机制才能发挥作用。