Postgresql 在SQL函数中虚拟化列

Postgresql 在SQL函数中虚拟化列,postgresql,Postgresql,我试图弄清楚是否有可能创建一个SQL函数,将参数行视为“duck-typed”。也就是说,我希望能够传递来自不同表或视图的行,这些表或视图具有某些公共列名,并在函数中对这些列进行操作 这里有一个非常简单的例子来描述这个问题: => CREATE TABLE tab1 ( id SERIAL PRIMARY KEY, has_desc BOOLEAN, x1 TEXT, description TEXT ); CREA

我试图弄清楚是否有可能创建一个SQL函数,将参数行视为“duck-typed”。也就是说,我希望能够传递来自不同表或视图的行,这些表或视图具有某些公共列名,并在函数中对这些列进行操作

这里有一个非常简单的例子来描述这个问题:

=> CREATE TABLE tab1 (
    id          SERIAL PRIMARY KEY,
    has_desc    BOOLEAN,
    x1          TEXT,
    description TEXT
);
CREATE TABLE
=> CREATE FUNCTION get_desc(tab tab1) RETURNS TEXT AS $$
    SELECT CASE tab.has_desc
        WHEN True THEN
            tab.description
        ELSE
            'Default Description'
    END;
$$ LANGUAGE SQL;

=> INSERT INTO tab1 (has_desc, x1, description) VALUES (True, 'Foo', 'FooDesc');
INSERT 0 1
=> INSERT INTO tab1 (has_desc, x1, description) VALUES (True, 'Bar', 'BarDesc');
INSERT 0 1
=> SELECT get_desc(tab1) FROM tab1;
 get_desc
----------
 BarDesc
 FooDesc
(2 rows)
这当然是非常人为的。实际上,我的表有更多的字段,而且函数要复杂得多

现在我想添加其他表/视图,并将它们传递给同一个函数。新的表/视图具有不同的列,但函数关心的列对所有这些表/视图都是通用的。为了添加到这个简单的示例中,我添加了以下两个表:

CREATE TABLE tab2 (
    id          SERIAL PRIMARY KEY,
    has_desc    BOOLEAN,
    x2          TEXT,
    description TEXT
);
CREATE TABLE tab3 (
    id          SERIAL PRIMARY KEY,
    has_desc    BOOLEAN,
    x3          TEXT,
    description TEXT
);
注:这三个字段都有
has_desc
description
字段,它们是
get_desc
中实际使用的唯一字段。当然,如果我尝试将现有函数与
tab2
一起使用,我会得到:

=> select get_desc(tab2) FROM tab2;
ERROR:  function get_desc(tab2) does not exist
LINE 1: select get_desc(tab2) FROM tab2;
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
我希望能够定义一个通用函数,它执行与
get_desc
相同的操作,但将三个表中的任何一个表中的一行作为参数。有办法吗

或者,是否有某种方法将整行强制转换为仅包含一组已定义字段的公共行类型


(我意识到我可以将函数参数更改为只接受
XX.has_desc
XX.description
,但我正在尝试隔离函数中使用的字段,而无需在调用函数的每个位置展开这些字段。)

Postgresql函数取决于参数的模式。如果不同的表具有不同的模式,则始终可以将它们投影到
get_desc
所需的公共子模式中。这可以通过在
get_desc
使用之前使用
with
子句快速临时完成

如果这个答案在细节上过于单薄,只需添加一条评论,我将充实一些示例

更多详情:

CREATE TABLE subschema_table ( has_desc boolean, description text ) ;

CREATE FUNCTION get_desc1(tab subschema_table) RETURNS TEXT AS $$
    SELECT CASE tab.has_desc
        WHEN True THEN
            tab.description
        ELSE
            'Default Description'
    END; $$ LANGUAGE SQL;
现在,以下内容将起作用(与其他表一起):

视图方法在我的测试中不起作用(视图似乎没有适当的模式)


也许另一个答案给出了更好的方法。

Postgresql函数取决于参数的模式。如果不同的表有不同的模式,那么您可以始终将它们投影到
get\u desc
所需的公共子模式中。这可以通过在之前使用
with
子句快速临时完成e> 获取描述使用

如果这个答案在细节上过于单薄,只需添加一条评论,我将充实一些示例

更多详情:

CREATE TABLE subschema_table ( has_desc boolean, description text ) ;

CREATE FUNCTION get_desc1(tab subschema_table) RETURNS TEXT AS $$
    SELECT CASE tab.has_desc
        WHEN True THEN
            tab.description
        ELSE
            'Default Description'
    END; $$ LANGUAGE SQL;
现在,以下内容将起作用(与其他表一起):

视图方法在我的测试中不起作用(视图似乎没有适当的模式)

也许另一个答案给出了更好的方法。

你可以

你可以


我正在添加一个答案,以展示我为后代解决这一问题的完整方法。但感谢@klin让我指出了正确的方向。(@klin的裸体转换的一个问题是,当两个表的公共列在各自的列列表中不在相同的相对位置时,它不会生成正确的行类型。)

我的解决方案添加了一个新的自定义类型(
gdtab
),其中包含公共字段,然后是一个可以从每个源表的行类型转换为
gdtab
类型的函数,然后添加一个强制转换,使每个转换隐式化

-- Common type for get_desc function
CREATE TYPE gdtab AS (
    id          INTEGER,
    has_desc    BOOLEAN,
    description TEXT
);

CREATE FUNCTION get_desc(tab gdtab) RETURNS TEXT AS $$
    SELECT CASE tab.has_desc
        WHEN True THEN
            tab.description
        ELSE
            'Default Description'
    END;
$$ LANGUAGE SQL;

CREATE TABLE tab1 (
    id          SERIAL PRIMARY KEY,
    has_desc    BOOLEAN,
    x1          TEXT,
    description TEXT
);

-- Convert tab1 rowtype to gdtab type
CREATE FUNCTION tab1_as_gdtab(t tab1) RETURNS gdtab AS $$
    SELECT CAST(ROW(t.id, t.has_desc, t.description) AS gdtab);
$$ LANGUAGE SQL;

-- Implicitly cast from tab1 to gdtab as needed for get_desc
CREATE CAST (tab1 AS gdtab) WITH FUNCTION tab1_as_gdtab(tab1) AS IMPLICIT;

CREATE TABLE tab2 (
    id          SERIAL PRIMARY KEY,
    x2          TEXT,
    x2x         TEXT,
    has_desc    BOOLEAN,
    description TEXT
);

CREATE FUNCTION tab2_as_gdtab(t tab2) RETURNS gdtab AS $$
    SELECT CAST(ROW(t.id, t.has_desc, t.description) AS gdtab);
$$ LANGUAGE SQL;

CREATE CAST (tab2 AS gdtab) WITH FUNCTION tab2_as_gdtab(tab2) AS IMPLICIT;
测试用途:

INSERT INTO tab1 (has_desc, x1, description) VALUES (True, 'FooBlah', 'FooDesc'),
                                                    (False, 'BazBlah', 'BazDesc'),
                                                    (True, 'BarBlah', 'BarDesc');

INSERT INTO tab2 (has_desc, x2, x2x, description) VALUES (True, 'FooBlah', 'x2x', 'FooDesc'),
                                                         (False, 'BazBlah', 'x2x', 'BazDesc'),
                                                         (True, 'BarBlah', 'x2x', 'BarDesc');

SELECT get_desc(tab1) FROM tab1;
SELECT get_desc(tab2) FROM tab2;

我正在添加一个答案,以展示我为后代解决这一问题的完整方法。但感谢@klin让我指出了正确的方向。(@klin的裸体转换的一个问题是,当两个表的公共列在各自的列列表中不在相同的相对位置时,它不会生成正确的行类型。)

我的解决方案添加了一个新的自定义类型(
gdtab
),其中包含公共字段,然后是一个可以从每个源表的行类型转换为
gdtab
类型的函数,然后添加一个强制转换,使每个转换隐式化

-- Common type for get_desc function
CREATE TYPE gdtab AS (
    id          INTEGER,
    has_desc    BOOLEAN,
    description TEXT
);

CREATE FUNCTION get_desc(tab gdtab) RETURNS TEXT AS $$
    SELECT CASE tab.has_desc
        WHEN True THEN
            tab.description
        ELSE
            'Default Description'
    END;
$$ LANGUAGE SQL;

CREATE TABLE tab1 (
    id          SERIAL PRIMARY KEY,
    has_desc    BOOLEAN,
    x1          TEXT,
    description TEXT
);

-- Convert tab1 rowtype to gdtab type
CREATE FUNCTION tab1_as_gdtab(t tab1) RETURNS gdtab AS $$
    SELECT CAST(ROW(t.id, t.has_desc, t.description) AS gdtab);
$$ LANGUAGE SQL;

-- Implicitly cast from tab1 to gdtab as needed for get_desc
CREATE CAST (tab1 AS gdtab) WITH FUNCTION tab1_as_gdtab(tab1) AS IMPLICIT;

CREATE TABLE tab2 (
    id          SERIAL PRIMARY KEY,
    x2          TEXT,
    x2x         TEXT,
    has_desc    BOOLEAN,
    description TEXT
);

CREATE FUNCTION tab2_as_gdtab(t tab2) RETURNS gdtab AS $$
    SELECT CAST(ROW(t.id, t.has_desc, t.description) AS gdtab);
$$ LANGUAGE SQL;

CREATE CAST (tab2 AS gdtab) WITH FUNCTION tab2_as_gdtab(tab2) AS IMPLICIT;
测试用途:

INSERT INTO tab1 (has_desc, x1, description) VALUES (True, 'FooBlah', 'FooDesc'),
                                                    (False, 'BazBlah', 'BazDesc'),
                                                    (True, 'BarBlah', 'BarDesc');

INSERT INTO tab2 (has_desc, x2, x2x, description) VALUES (True, 'FooBlah', 'x2x', 'FooDesc'),
                                                         (False, 'BazBlah', 'x2x', 'BazDesc'),
                                                         (True, 'BarBlah', 'x2x', 'BarDesc');

SELECT get_desc(tab1) FROM tab1;
SELECT get_desc(tab2) FROM tab2;

我很欣赏这个例子。这似乎相当于扩展函数参数以包含函数将在其上运行的所有字段——如果我必须在每次调用之前添加一个WITH子句。是的,但就代码而言,混乱将与主查询分开。此外,创建每个表的视图一个通用的模式也将摆脱
WITH
子句。视图可以在函数附近定义。我很欣赏这个例子。这似乎相当于扩展函数参数以包含函数将要操作的所有字段——如果我必须在每次调用之前加一个WITH子句。True,但就代码而言,混乱将从主查询中分离出来。此外,使用公共架构创建每个表的视图也将摆脱
with
子句。视图可以在函数附近定义。请参阅我答案中的警告,但CAST idea为+1。问题是关于函数冗余的兼容类型se,自定义强制转换是一个强大的工具。通过使用强制转换功能,您可以执行几乎任何完全不同类型的转换。呃,问题是“新表/视图有不同的列,但函数关心的列对它们来说都是通用的。”我试图实现的只是将不同的行类型强制转换为一种形式,我可以将其用作函数的参数。我承认,可能不清楚我的表/视图是否有很大的不同,列的数量不同,顺序也不同。请参阅我回答中的警告,但CAST idea的+1。问题是关于当然,自定义强制转换是一个强大的工具。通过使用强制转换功能,您几乎可以执行任何操作