如何在 Postgresql 函数中使用临时序列
How to use a temp sequence within a Postgresql function
我有一些 SQL 行,它们将从同一个 GROUP_ID 中获取一组不连续的 ID(例如,如果某些行被删除),并使它们再次连续。为了可重用性目的,我想把它变成一个函数。如果单独执行,这些行会起作用,但是当我尝试创建函数时,出现错误
ERROR: relation "id_seq_temp" does not exist
LINE 10: UPDATE THINGS SET ID=nextval('id_se...
如果我在函数外部创建序列并在函数中使用该序列,则函数创建成功(架构合格或不合格)。但是,我觉得在函数内部创建临时序列而不是将其留在架构中是一个更干净的解决方案。
我看到这个问题:Function shows error "relation my_table does not exist"
但是,我使用的是 public 架构,并且用 public.
限定序列的架构似乎没有帮助。
我也看到了这个问题:How to create a sql function using temp sequences and a SELECT on PostgreSQL8。我可能可以使用 generate_series
但这增加了很多 SERIES
解决的复杂性,例如需要知道要生成多大的系列。
这是我的功能,我匿名化了一些名称 - 以防万一出现打字错误。
CREATE OR REPLACE FUNCTION reindex_ids(IN BIGINT) RETURNS VOID
LANGUAGE SQL
AS $$
CREATE TEMPORARY SEQUENCE id_seq_temp
MINVALUE 1
START WITH 1
INCREMENT BY 1;
ALTER SEQUENCE id_seq_temp RESTART;
UPDATE THINGS SET ID=ID+2000 WHERE GROUP_ID=;
UPDATE THINGS SET ID=nextval('id_seq_temp') WHERE GROUP_ID=;
$$;
是否可以在函数后面的函数中使用您在函数中创建的序列?
问题的答案
原因是SQL个函数(LANGUAGE sql
)被解析规划为一个。在函数运行之前必须存在所有使用的对象。
您可以切换到PL/pgSQL, (LANGUAGE plpgsql
),它按需计划每个语句。您可以在那里创建对象并在下一个命令中使用它们。
参见:
由于您不返回任何东西,请考虑 PROCEDURE
。 (FUNCTION
也有效。)
CREATE OR REPLACE PROCEDURE reindex_ids(IN bigint)
LANGUAGE plpgsql AS
$proc$
BEGIN
IF EXISTS ( SELECT FROM pg_catalog.pg_class
WHERE relname = 'id_seq_temp'
AND relnamespace = pg_my_temp_schema()
AND relkind = 'S') THEN
ALTER SEQUENCE id_seq_temp RESTART;
ELSE
CREATE TEMP SEQUENCE id_seq_temp;
END IF;
UPDATE things SET id = id + 2000 WHERE group_id = ;
UPDATE things SET id = nextval('id_seq_temp') WHERE group_id = ;
END
$proc$;
致电:
CALL reindex_ids(123);
这将创建您的临时序列(如果它尚不存在)。
如果序列存在,则将其重置。 (请记住,临时对象在会话期间存在。)
万一其他对象占用了名称,则会引发异常。
替代解决方案
解决方案 1
这通常有效:
UPDATE things t
SET id = t1.new_id
FROM (
SELECT pk_id, row_number() OVER (ORDER BY id) AS new_id
FROM things
WHERE group_id = -- your input here
) t1
WHERE t.pk_id = t1.pk_id;
并且每一行只更新一次一次,所以成本减半。
将 pk_id
替换为您的 PRIMARY KEY
列或任何 UNIQUE NOT NULL
列(组合)。
诀窍是 UPDATE
通常根据 FROM
子句中子查询的排序顺序处理行。按升序更新永远不会违反重复键。
window function row_number()
的 ORDER BY
子句将排序顺序强加于结果集。这是一个未记录的实现细节,因此您可能希望向子查询添加显式 ORDER BY
。但是由于 UPDATE
的行为无论如何都没有记录,它仍然取决于实现细节。
您可以将 that 包装到一个普通的 SQL 函数中。
解决方案 2
请考虑不要 做您正在做的事情。序列号中的差距通常是预料之中的,这不是问题。只是忍受它。参见:
- Serial numbers per group of rows for compound key
我有一些 SQL 行,它们将从同一个 GROUP_ID 中获取一组不连续的 ID(例如,如果某些行被删除),并使它们再次连续。为了可重用性目的,我想把它变成一个函数。如果单独执行,这些行会起作用,但是当我尝试创建函数时,出现错误
ERROR: relation "id_seq_temp" does not exist
LINE 10: UPDATE THINGS SET ID=nextval('id_se...
如果我在函数外部创建序列并在函数中使用该序列,则函数创建成功(架构合格或不合格)。但是,我觉得在函数内部创建临时序列而不是将其留在架构中是一个更干净的解决方案。
我看到这个问题:Function shows error "relation my_table does not exist"
但是,我使用的是 public 架构,并且用 public.
限定序列的架构似乎没有帮助。
我也看到了这个问题:How to create a sql function using temp sequences and a SELECT on PostgreSQL8。我可能可以使用 generate_series
但这增加了很多 SERIES
解决的复杂性,例如需要知道要生成多大的系列。
这是我的功能,我匿名化了一些名称 - 以防万一出现打字错误。
CREATE OR REPLACE FUNCTION reindex_ids(IN BIGINT) RETURNS VOID
LANGUAGE SQL
AS $$
CREATE TEMPORARY SEQUENCE id_seq_temp
MINVALUE 1
START WITH 1
INCREMENT BY 1;
ALTER SEQUENCE id_seq_temp RESTART;
UPDATE THINGS SET ID=ID+2000 WHERE GROUP_ID=;
UPDATE THINGS SET ID=nextval('id_seq_temp') WHERE GROUP_ID=;
$$;
是否可以在函数后面的函数中使用您在函数中创建的序列?
问题的答案
原因是SQL个函数(LANGUAGE sql
)被解析规划为一个。在函数运行之前必须存在所有使用的对象。
您可以切换到PL/pgSQL, (LANGUAGE plpgsql
),它按需计划每个语句。您可以在那里创建对象并在下一个命令中使用它们。
参见:
由于您不返回任何东西,请考虑 PROCEDURE
。 (FUNCTION
也有效。)
CREATE OR REPLACE PROCEDURE reindex_ids(IN bigint)
LANGUAGE plpgsql AS
$proc$
BEGIN
IF EXISTS ( SELECT FROM pg_catalog.pg_class
WHERE relname = 'id_seq_temp'
AND relnamespace = pg_my_temp_schema()
AND relkind = 'S') THEN
ALTER SEQUENCE id_seq_temp RESTART;
ELSE
CREATE TEMP SEQUENCE id_seq_temp;
END IF;
UPDATE things SET id = id + 2000 WHERE group_id = ;
UPDATE things SET id = nextval('id_seq_temp') WHERE group_id = ;
END
$proc$;
致电:
CALL reindex_ids(123);
这将创建您的临时序列(如果它尚不存在)。
如果序列存在,则将其重置。 (请记住,临时对象在会话期间存在。)
万一其他对象占用了名称,则会引发异常。
替代解决方案
解决方案 1
这通常有效:
UPDATE things t
SET id = t1.new_id
FROM (
SELECT pk_id, row_number() OVER (ORDER BY id) AS new_id
FROM things
WHERE group_id = -- your input here
) t1
WHERE t.pk_id = t1.pk_id;
并且每一行只更新一次一次,所以成本减半。
将 pk_id
替换为您的 PRIMARY KEY
列或任何 UNIQUE NOT NULL
列(组合)。
诀窍是 UPDATE
通常根据 FROM
子句中子查询的排序顺序处理行。按升序更新永远不会违反重复键。
window function row_number()
的 ORDER BY
子句将排序顺序强加于结果集。这是一个未记录的实现细节,因此您可能希望向子查询添加显式 ORDER BY
。但是由于 UPDATE
的行为无论如何都没有记录,它仍然取决于实现细节。
您可以将 that 包装到一个普通的 SQL 函数中。
解决方案 2
请考虑不要 做您正在做的事情。序列号中的差距通常是预料之中的,这不是问题。只是忍受它。参见:
- Serial numbers per group of rows for compound key