如何使用具有空值和UPSERT的聚合键?

时间:2016-10-03 19:54:42

标签: sql postgresql upsert postgresql-9.5

我在Postgresql 9.5中使用UPSERT时遇到问题。

我有一个包含50列的表,我的聚合键包含20个键,其中15个键可以为空。

所以这是我的表:

p = fftw_plan_r2r_1d(11025, audioData, spectrum, FFTW_REDFT00, FFTW_ESTIMATE);

我将使用我的聚合键创建部分索引。 但我之前需要创建一个唯一约束,因为所有键都不是CREATE TABLE public.test ( id serial NOT NULL, stamp timestamp without time zone, foo_id integer, bar_id integer, ... CONSTRAINT id_pk PRIMARY KEY (id), CONSTRAINT test_agg_key_unique UNIQUE (stamp, foo_id, bar_id, ...) );

NOT NULL

然后:

alter table public.test ADD CONSTRAINT test_agg_key_unique UNIQUE (stamp, foo_id, bar_id, ...);

现在我可以执行我的CREATE UNIQUE INDEX test_agg_key on lvl1_conversion.conversion (coalesce(stamp, '1980-01-01 01:01:01'), coalesce(foo_id, -1), coalesce(bar_id, -1), ...);

UPSERT

因此,如果聚合键已经存在,他将更新该行,如果它将插入一个新行。但是,当我使用相同的查询但具有一个或多个INSERT INTO public.test as t (id, stamp, foo_id, bar_id, ...) VALUES (RANDOM_ID, '2016-01-01 01:01:01', 1, 1, ...) ON CONFLICT (stamp, foo_id, bar_id, ...) do update set another_column = t.another_column + 1 where t.stamp = '2016-01-01 01:01:01' and t.foo_id = 1 and t.bar_id= 1 and ...; 值时,我会收到此异常:

null

由于此异常,它永远不会调用ERROR: duplicate key value violates unique constraint "test_agg_key_unique"

另一个很好的例子: https://dba.stackexchange.com/questions/151431/postgresql-upsert-issue-with-null-values

3 个答案:

答案 0 :(得分:2)

我能看到的唯一方法是使用触发器使列几乎不可为空,正式保持可为空。

测试表:

create table test
(
    id serial not null,
    stamp timestamp without time zone,
    foo_id integer,
    bar_id integer,
    another_column integer,
    constraint id_pk primary key (id),
    constraint test_agg_key_unique unique (stamp, foo_id, bar_id)
);

触发:

create or replace function before_insert_on_test()
returns trigger language plpgsql as $$
begin
    new.stamp:= coalesce(new.stamp, '1980-01-01 01:01:01');
    new.foo_id:= coalesce(new.foo_id, -1);
    new.bar_id:= coalesce(new.bar_id, -1);
    return new;
end $$;

create trigger before_insert_on_test
before insert on test
for each row
execute procedure before_insert_on_test();

您不需要额外的唯一索引:

insert into test values (default, null, 1, null, 0)
on conflict (stamp, foo_id, bar_id) do
    update set another_column = test.another_column+ 1
returning *;

 id |        stamp        | foo_id | bar_id | another_column 
----+---------------------+--------+--------+----------------
  1 | 1980-01-01 01:01:01 |      1 |     -1 |              0

insert into test values (default, null, 1, null, 0)
on conflict (stamp, foo_id, bar_id) do
    update set another_column = test.another_column+ 1
returning *;

 id |        stamp        | foo_id | bar_id | another_column 
----+---------------------+--------+--------+----------------
  1 | 1980-01-01 01:01:01 |      1 |     -1 |              1

请注意,您不需要where子句,因为update仅涉及有冲突的行。

更新:替代解决方案

问题源于这样一个事实,即包含可空元素的复合唯一索引通常不是一个好主意。你应该放弃这种方法并抵制触发器上的所有逻辑。

删除唯一索引并创建触发器:

create or replace function before_insert_on_test()
returns trigger language plpgsql as $$
declare
    found_id integer;
begin
    select id
    from test
    where
        coalesce(stamp, '1980-01-01 01:01:01') = coalesce(new.stamp, '1980-01-01 01:01:01')
        and coalesce(foo_id, -1) = coalesce(new.foo_id, -1)
        and coalesce(bar_id, -1) = coalesce(new.bar_id, -1)
    into found_id;
    if found then
        update test
        set another_column = another_column+ 1
        where id = found_id;
        return null;  -- abandon insert
    end if;
    return new;
end $$; 

create trigger before_insert_on_test
before insert on test
for each row
execute procedure before_insert_on_test();

仅使用insert,不使用on conflict

您可以尝试使用(非唯一)索引加速触发器:

create index on test(coalesce(stamp, '1980-01-01 01:01:01'), coalesce(foo_id, -1), coalesce(bar_id, -1));

答案 1 :(得分:1)

阅读完这个问题:here我找到了解决方案。

谢谢Erwin Brandstetter:https://dba.stackexchange.com/a/151438/107395

解决方案:

所以我需要创建一个包含所有键的索引,并为每列添加COALESCE可以为null。

因此,如果是文字COALESCE(test_field, '')或数字为COALESCE(test_field, -1)

CREATE UNIQUE INDEX test_upsert_solution_idx
    ON test_upsert (name, status, COALESCE(test_field, ''), COALESCE(test_field2, '')...);

并在UPSERT移除WHERE中的DO UPDATE,同时将COALESCE添加到ON CONFLICT

INSERT INTO test_upsert as tu(name, status, test_field, identifier, count) 
VALUES ('test', 1, null, 'ident', 11)
ON CONFLICT (name, status, COALESCE(test_field, '')) 
 DO UPDATE  -- match expr. index
  SET count = COALESCE(tu.count + EXCLUDED.count, EXCLUDED.count, tu.count);

答案 2 :(得分:0)

通过isnull函数将列赋值为空值并给出它们的默认值,如:

INSERT INTO public.test as t (id, stamp, foo_id, bar_id, ...)
VALUES (RANDOM_ID, '2016-01-01 01:01:01', 1, 1, ...)
ON CONFLICT (stamp, foo_id, bar_id, ...)
do update set another_column = isnull(t.another_column,0)  + 1
where t.stamp = '2016-01-01 01:01:01' and t.foo_id = 1 and t.bar_id= 1 and ...;