在一个SQL函数调用中有多个ALTER TABLE ADD COLUMN

时间:2018-06-11 15:09:05

标签: sql postgresql plpgsql

我遇到了一些我想要了解的奇怪行为。

我创建了一个plpgsql函数,除了ALTER TABLE ADD COLUMN之外什么都不做。我在同一张桌子上称它为2次:

  • A)单个SELECT
  • B)在与A)相同的SELECT的SQL函数中

结果不同:A)创建两列,而B)只创建一列。为什么呢?

代码:

CREATE FUNCTION add_text_column(table_name text, column_name text) RETURNS VOID
LANGUAGE plpgsql
AS $fff$
BEGIN
    EXECUTE '
        ALTER TABLE ' || table_name  || '
        ADD COLUMN  ' || column_name || ' text;
    ';
END;
$fff$
;
--  this function is called only in B
CREATE FUNCTION add_many_text_columns(table_name text) RETURNS VOID
LANGUAGE SQL
AS $fff$
    WITH
    col_names (col_name) AS (
        VALUES
            ( 'col_1' ),
            ( 'col_2' )
    )
    SELECT  add_text_column(table_name, col_name)
    FROM    col_names
    ;
$fff$
;

-- A)
CREATE TABLE a (id integer);

WITH
col_names (col_name) AS (
    VALUES
        ( 'col_1' ),
        ( 'col_2' )
)
SELECT  add_text_column('a', col_name)
FROM    col_names
;
SELECT * FROM a;

-- B)
CREATE TABLE b (id integer);
SELECT add_many_text_columns('b');
SELECT * FROM b;

结果:

CREATE FUNCTION
CREATE FUNCTION
CREATE TABLE
 add_text_column
-----------------


(2 rows)

 id | col_1 | col_2
----+-------+-------
(0 rows)

CREATE TABLE
 add_many_text_columns
-----------------------

(1 row)

 id | col_1
----+-------
(0 rows)

我正在使用PostgreSQL 10.4。请注意,这只是一个最小的工作示例,而不是我需要的全部功能。

2 个答案:

答案 0 :(得分:2)

SELECT t(4)

当我运行g()时,您认为会发生什么?从g called with 1打印的唯一声明是add_many_text_columns

原因是你的SELECT函数返回一个结果(void)。因为它是SQL而且只是返回CREATE OR REPLACE FUNCTION t(i INTEGER) RETURNS SETOF VOID AS $$ SELECT g(id) FROM generate_series(1, i) id; $$ LANGUAGE SQL; 语句的结果,它似乎在获得第一个结果后停止执行,如果你想到它就有意义 - 它毕竟只能返回一个结果。

现在将功能更改为:

SELECT t(4)

再次运行g called with 1 g called with 2 g called with 3 g called with 4 ,现在打印出来:

SETOF VOID

因为函数现在返回SETOF VOID,所以它不会在第一个结果后停止并完全执行。

回到你的函数,你可以更改你的SQL函数以返回plpgsql,但它确实没有多大意义 - 我想更好地将其更改为{{1并让它做PERFORM

CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS $$
BEGIN
        PERFORM g(id)
        FROM generate_series(1, i) id;
END
$$ LANGUAGE plpgsql;

这将完全执行该语句,它仍然返回一个VOID

答案 1 :(得分:1)

eurotrash provided a good explanation.

替代解决方案1 ​​

CREATE OR REPLACE FUNCTION t(i INTEGER)
  RETURNS VOID AS
$func$
   SELECT g(id)
   FROM generate_series(1, i) id;

   SELECT null::void;
$func$  LANGUAGE sql;

因为,quoting the manual:

  

SQL函数执行任意SQL语句列表,返回   列表中最后一个查询的结果。在简单(非设定)   case,将返回最后一个查询结果的第一行。

通过在末尾添加一个虚拟SELECT,我们避免在处理具有多行的查询的第一行后停止Postgres。

替代解决方案2

CREATE OR REPLACE FUNCTION t(i INTEGER)
  RETURNS VOID AS
$func$
   SELECT count(g(id))
   FROM generate_series(1, i) id;
$func$  LANGUAGE sql;

通过使用聚合函数,在任何情况下都会处理所有基础行。该函数返回bigintcount()返回的内容),因此我们得到行数。

替代解决方案3

如果您需要以某种未知原因返回void,您可以投票:

CREATE OR REPLACE FUNCTION t(i INTEGER)
  RETURNS VOID AS
$func$
   SELECT count(g(id))::text::void
   FROM generate_series(1, i) id;
$func$  LANGUAGE sql;

text的演员阵容是踏脚石,因为bigintvoid的演员阵容未定义。