Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/86.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用PostgreSQL 9.3在CTE UPSERT中生成默认值_Sql_Postgresql_Merge_Sql Insert_Upsert - Fatal编程技术网

使用PostgreSQL 9.3在CTE UPSERT中生成默认值

使用PostgreSQL 9.3在CTE UPSERT中生成默认值,sql,postgresql,merge,sql-insert,upsert,Sql,Postgresql,Merge,Sql Insert,Upsert,我发现使用可写CTE模拟PostgreSQL中的upsert是一个非常优雅的解决方案,直到我们在Postgres中得到实际的upsert/merge。(见:) 但是,有一个问题:如何插入默认值?使用NULL当然没有帮助,因为NULL被显式地作为NULL插入,这与MySQL不同。例如: WITH new_values (id, playlist, item, group_name, duration, sort, legacy) AS ( VALUES (651, 21, 30012, '

我发现使用可写CTE模拟PostgreSQL中的upsert是一个非常优雅的解决方案,直到我们在Postgres中得到实际的upsert/merge。(见:)

但是,有一个问题:如何插入默认值?使用
NULL
当然没有帮助,因为
NULL
被显式地作为
NULL
插入,这与MySQL不同。例如:

WITH new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
    VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
    ,      (NULL::int, 21, 1, 'b', 34, 2, NULL::boolean)
    ,      (668, 21, 30012, 'c', 30, 3, FALSE)
    ,      (7428, 21, 23068, 'd', 0, 4, FALSE)
), upsert AS (
    UPDATE playlist_items m
    SET    (playlist, item, group_name, duration, sort, legacy)
       = (nv.playlist, nv.item, nv.group_name, nv.duration, nv.sort, nv.legacy)
    FROM   new_values nv
    WHERE  nv.id = m.id
    RETURNING m.id
)
INSERT INTO playlist_items (playlist, item, group_name, duration, sort, legacy)
SELECT playlist, item, group_name, duration, sort, legacy
FROM   new_values nv
WHERE  NOT EXISTS (SELECT 1
                   FROM   upsert m
                   WHERE  nv.id = m.id)
RETURNING id
例如,我希望
legacy
列采用第二行
VALUES
的默认值

我尝试了一些方法,比如在值列表中显式使用
DEFAULT
,但这不起作用,因为CTE不知道它插入了什么。我还在insert语句中尝试了
coalesce(col,DEFAULT)
,但似乎也不起作用。那么,有可能实现我想要的吗?

Postgres 9.5实现了
UPSERT
。见下文

博士后9.4或以上 这是一个棘手的问题。您遇到了以下限制():

插入
的顶层出现的
列表中 表达式可以替换为
DEFAULT
,以指示目标 应插入列的默认值
默认值
在以下情况下不能使用
出现在其他上下文中。

我的。如果没有要插入的表,则不会定义默认值。因此,您的问题没有直接的解决方案,但根据具体要求,有许多可能的替代路线

是否从系统目录中获取默认值? 您可以从系统目录或从中获取它们。请在此完成说明:

但是,您仍然只有一个行列表,其中包含要使用默认值的表达式的文本表示形式。您必须动态地构建和执行语句,以获得要使用的值。乏味而混乱。相反,我们可以让内置的Postgres功能为我们做到这一点:

简单捷径 插入一个虚拟行并将其返回以使用生成的默认值:

INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
问题/解决方案的范围
  • 这只保证对你有用。大多数VOLATILE函数也能正常工作,但没有保证。
    current\u timestamp
    函数系列符合稳定条件,因为它们的值在事务中不会更改。
    特别是,这对序列列(或序列中的任何其他默认图形)有副作用。但这不应该是个问题,因为您通常不会直接写入
    serial
    列。这些内容根本不应该列在
    INSERT
    语句中。
    serial
    列的剩余缺陷:单次调用获取默认行仍会使序列提前,从而在编号中产生间隙。同样,这不应该是一个问题,因为在
    serial
    列中通常会出现间隙
还有两个问题可以解决:

  • 如果定义了列
    非NULL
    ,则必须在结果中插入伪值并替换为
    NULL

  • 我们实际上不想插入伪行。我们可以稍后删除(在同一事务中),但这可能会有更多的副作用,比如删除时的触发器
    。有一个更好的方法:

避免虚拟行 克隆包含列默认值的临时表,并插入其中:

同样的结果,副作用更少。由于默认表达式是逐字复制的,因此克隆将从相同的序列(如果有)中提取。但是完全避免了不需要的行或触发器的其他副作用

这一想法归功于Igor:

删除
非空
约束 您必须为
notnull
列提供伪值,因为():

NOTNULL约束总是复制到新表中

要么适应
INSERT
语句中的约束,要么(更好)消除约束:

ALTER TABLE tmp_playlist_items
   ALTER COLUMN foo DROP NOT NULL
 , ALTER COLUMN bar DROP NOT NULL;
有一种快速且肮脏的方式具有超级用户权限:

UPDATE pg_attribute
SET    attnotnull = FALSE
WHERE  attrelid = 'tmp_playlist_items'::regclass
AND    attnotnull
AND    attnum > 0;
它只是一个临时表,没有数据,也没有其他用途,在事务结束时被删除。因此,捷径很诱人。不过,基本规则是:永远不要直接篡改系统目录

那么,让我们看看一个干净的方法: 在
DO
语句中使用动态SQL进行自动化。您只需要保证拥有的常规特权,因为相同的角色创建了临时表

DO $$BEGIN
EXECUTE (
   SELECT 'ALTER TABLE tmp_playlist_items ALTER '
       || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
       || ' DROP NOT NULL'
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = 'tmp_playlist_items'::regclass
   AND    attnotnull
   AND    attnum > 0
   );
END$$
更干净,速度也更快。使用动态命令谨慎执行,并警惕SQL注入。这份声明是安全的。我已经发了

一般解决方案(9.4及以上版本) 我们可以将
VALUES
子句直接附加到
INSERT
,这允许使用
DEFAULT
关键字。在
(id)
上出现唯一违规的情况下,Postgres将更新。我们可以在
更新中使用排除的行

关于冲突的
中的
SET
WHERE
子句可以访问 使用表名称(或别名)的现有行,以及 建议使用特殊的
排除表插入

以及:

请注意,插入前所有每行
触发器的效果如下
反映在排除的值中,因为这些影响可能是原因之一
添加到要从插入中排除的行

剩余角盒 您有多种更新选项:您可以

  • 。。。根本不更新:将
    WHERE
    子句添加到
    update
    以仅写入选定行
  • 。。。仅更新选定列
  • 。。。仅当列当前为空时更新:
    COALESCE(
    
    DO $$BEGIN
    EXECUTE (
       SELECT 'ALTER TABLE tmp_playlist_items ALTER '
           || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
           || ' DROP NOT NULL'
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'tmp_playlist_items'::regclass
       AND    attnotnull
       AND    attnum > 0
       );
    END$$
    
    BEGIN;
    
    CREATE TEMP TABLE tmp_playlist_items
       (LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
    
    DO $$BEGIN
    EXECUTE (
       SELECT 'ALTER TABLE tmp_playlist_items ALTER '
           || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
           || ' DROP NOT NULL'
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'tmp_playlist_items'::regclass
       AND    attnotnull
       AND    attnum > 0
       );
    END$$;
    
    LOCK TABLE playlist_items IN EXCLUSIVE MODE;  -- forbid concurrent writes
    
    WITH default_row AS (
       INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
       )
    , new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
       VALUES
          (651, 21, 30012, 'a', 30, 1, FALSE)
        , (NULL, 21, 1, 'b', 34, 2, NULL)
        , (668, 21, 30012, 'c', 30, 3, FALSE)
        , (7428, 21, 23068, 'd', 0, 4, FALSE)
       )
    , upsert AS (  -- *not* replacing existing values in UPDATE (?)
       UPDATE playlist_items m
       SET   (  playlist,   item,   group_name,   duration,   sort,   legacy)
           = (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
       --                                   ..., COALESCE(n.legacy, m.legacy)  -- see below
       FROM   new_values n
       WHERE  n.id = m.id
       RETURNING m.id
       )
    INSERT INTO playlist_items
            (playlist,   item,   group_name,   duration,   sort, legacy)
    SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
                                       , COALESCE(n.legacy, d.legacy)
    FROM   new_values n, default_row d   -- single row can be cross-joined
    WHERE  NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
    RETURNING id;
    
    COMMIT;
    INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
    VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
    ,      (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT)  -- !
    ,      (668, 21, 30012, 'c', 30, 3, FALSE)
    ,      (7428, 21, 23068, 'd', 0, 4, FALSE)
    ON CONFLICT (id) DO UPDATE
    SET (playlist, item, group_name, duration, sort, legacy)
     = (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
      , EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
    -- (...,  COALESCE(l.legacy, EXCLUDED.legacy))  -- see below
    RETURNING m.id;