使用动态 SQL 重置串行 PK 的顺序

Joh*_*ohn 3 postgresql auto-increment dynamic-sql plpgsql

如何使用字符串和DECLARE变量的组合动态引用序列名称(如下所示)?下面的代码是否正确或者是否有其他方法可以做到这一点?

DO我的目标是出于性能原因在/块中执行此操作BEGIN,而且它将通过做一些有用的事情来帮助我理解 plpgsql,因此除非确实必须,否则我不会将其混合到 PHP 中。

DO $$
 DECLARE PKEY VARCHAR;
BEGIN

SELECT pg_attribute.attname INTO PKEY 
FROM pg_index, pg_class, pg_attribute 
WHERE pg_class.oid = 'parts1'::regclass 
AND indrelid = pg_class.oid 
AND pg_attribute.attrelid = pg_class.oid 
AND pg_attribute.attnum = any(pg_index.indkey) 
AND indisprimary;

--SELECT setval('parts1_id_seq', (SELECT MAX(pkey) + 1 FROM parts));
SELECT setval('parts1_' || PKEY || '_seq', (SELECT MAX(pkey) + 1 FROM parts));

  END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 5

一般建议

你自己提到过,你刚刚开始使用 Postgres。然而,您正在立即处理极其高级的任务,处理系统目录并使用高级动态 SQL 进行操作以实现自动化。

虽然您的目标看起来很合理,但您仍然需要从基础开始这里需要解释的东西太多了。从优秀手册(的相关部分)开始。我在下面提供了一些深层链接。

回答

要获取主键中涉及的列的名称(和数据类型),请使用以下更简单的查询:

SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
FROM   pg_index i
JOIN   pg_attribute a ON a.attrelid = i.indrelid
                     AND a.attnum = ANY(i.indkey)
WHERE  i.indrelid = 'tbl'::regclass
AND    i.indisprimary;
Run Code Online (Sandbox Code Playgroud)

我更新了Postgres Wiki 页面,您的原始查询似乎源自该页面。

但是,这可以返回多行,而您只分配一个值。假设您已经确定我们正在处理serial类型(单列)主键。否则,请使用我之前的答案中列出的类似技术来确定。

然后使用专用的系统信息函数pg_get_serial_sequence()来确定所使用序列的名称,如我之前的答案中所示
根据文档:

serial获取,smallserialbigserial列使用的序列名称

DO
$do$
BEGIN
   EXECUTE (
   SELECT format($$SELECT setval('%s'::regclass, max(%I)) FROM %s$$
                 , pg_get_serial_sequence(a.attrelid::regclass::text, a.attname)
                 , a.attname
                 , a.attrelid::regclass
                )
   FROM   pg_index i
   JOIN   pg_attribute a ON a.attrelid = i.indrelid
                        AND a.attnum = i.indkey[0]
   WHERE  i.indrelid = 'tbl'::regclass
   AND    i.indisprimary
   );
END
$do$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

这将构建并执行以下形式的查询:

SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;
Run Code Online (Sandbox Code Playgroud)

解释/建议

  • 这是高级的东西,不太适合初学者。如果您不确切知道自己在做什么,那么搞乱系统目录可能很快就会失败。

  • 在 Postgres 和 plpgsql 中坚持使用合法的全小写标识符,让您的生活更轻松。但永远不要在动态 SQL 中依赖它,因为动态 SQL 中您还需要始终防御 SQL 注入。

  • 大量使用来format()方便、安全地构建查询字符串。

  • 在 plpgsql 函数中,SELECT如果不分配结果就无法调用。你会用PERFORM它来代替。手册中有详细说明。
    我完全删除了它,因为我将所有内容都简化为一个EXECUTE.

  • 由于整个操作仅对单列主键有意义,因此我将JOIN条件简化为a.attnum = i.indkey[0]
    Note that pg_index.indkeyhas thespecial (internal) type int2vector。与 Postgres 数组不同,它的索引从0开始,而不是 1。

  • 养成在 plpgsql 代码(包括DO语句)周围使用带有标记的美元引号的习惯。这允许像我在示例中所做的那样嵌套简单的美元引号。细节:

  • 你在这里只需要一个SELECT

    SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;
    
    Run Code Online (Sandbox Code Playgroud)

    代替:

    SELECT setval('tbl_tbl_id_seq'::regclass, (SELECT max(tbl_id) FROM tbl));
    
    Run Code Online (Sandbox Code Playgroud)

SQL Fiddle演示了一些事情。