And*_*sen 4 postgresql null update change-tracking upsert
我们维护一个用 PostgreSQL 和 python 实现的大型数据仓库。我们所做的一种非常常见的模式是进行更新插入,并在更新时进行记录。我们有一些独特的键my_key
和值,例如my_uuid, my_text, my_int, my_date
。如果给定的任何这些值发生变化,my_key
我们希望更新该行。没关系,我们有一个运行良好的模式:
insert into my_table (
my_key,
my_uuid,
my_text,
my_int,
my_date
)
select
some_key,
some_uuid,
some_text,
some_int,
some_date
from some_table
on conflict (my_key) do update set
some_uuid = excluded.some_uuid,
some_text = excluded.some_text,
some_int = excluded.some_int,
some_date = excluded.some_date,
update_timestamp = now()
where
coalesce(my_table.some_uuid, uuid_nil()) <> coalesce(excluded.some_uuid, uuid_nil())
or coalesce(my_table.some_text, '') <> coalesce(excluded.some_text, '')
or coalesce(my_table.some_int, -1) <> coalesce(excluded.some_int, -1)
or coalesce(my_table.some_date, '3000-01-01'::date) <> coalesce(excluded.some_date, '3000-01-01'::date)
Run Code Online (Sandbox Code Playgroud)
最后一个on conflict ... where
子句很重要,因为它确保update_timestamp
仅在发生更改时更新。它还确保我们不会更新不必要的行,从而提高性能。
无论如何,我们经常遇到逻辑问题coalesce()
。它以这种模式存在的原因是为了支持传入和传出的值null
。让我们看下面的例子:
coalesce(my_table.some_text, '') <> coalesce(excluded.some_text, '')
Run Code Online (Sandbox Code Playgroud)
这工作得很好,并为测试用例的完整列表生成以下结果:
select coalesce('a', '') <> coalesce('a', '') --> false
union all
select coalesce(null, '') <> coalesce(null, '') --> false
union all
select coalesce('a', '') <> coalesce('b', '') --> true
union all
select coalesce(null, '') <> coalesce('b', '') --> true
union all
select coalesce('a', '') <> coalesce(null, '') --> true
Run Code Online (Sandbox Code Playgroud)
也就是说,只有当值实际发生变化时才是正确的。但是,如果一个值确实是空字符串会发生什么''
?那么就不会更新了。
这意味着我们需要创造性地选择虚拟值,''
使其不是自然出现的值。我们可以发明一个在生产中不太可能出现的关键字。但我宁愿找到另一种没有这个缺点的模式。
存在哪些选项可以实现我上面显示的相同更改“真值表”?我们总是可以使用case when ...
,但它变得非常冗长。我们需要一些易于编写和阅读的东西。一行通常可以包含 5-15 个值列
是否有任何替代方案可以在不存在我们今天使用的模式的缺点的情况下进行更新插入?
以下内容可用作寻找合适模式的测试平台:
select
v1, v2, expected,
COALESCE(v1, '') <> COALESCE(v2, '') as current_version,
COALESCE(v1 <> v2, true) as candidate_version
from (
select 'a' as v1, 'a' as v2, false as expected
union all
select null as v1, null as v2, false as expected
union all
select '' as v1, null as v2, true as expected
union all
select null as v1, '' as v2, true as expected
union all
select 'a' as v1, null as v2, true as expected
union all
select null as v1, 'b' as v2, true as expected
union all
select 'a' as v1, 'b' as v2, true as expected
) q
Run Code Online (Sandbox Code Playgroud)
返回:
v1 v2 expected current_version candidate_version
a a false false false
null null false false true
'' null true false true
null '' true false true
a null true true true
null b true true true
a b true true true
Run Code Online (Sandbox Code Playgroud)
小智 9
您可以使用is distinct from
gsiems 提到的 null 安全“不等于”运算符。null is distinct from null
是假的,也是42 is distinct from null
真的。
您的测试平台:
select
v1, v2, expected,
v1 is distinct from v2 as is_different
from (
values
('a', 'a', false),
(null, null, false),
('', null, true),
(null, '', true),
('a', null, true),
(null, 'b', true),
('a', 'b', true)
) q (v1, v2, expected)
Run Code Online (Sandbox Code Playgroud)
回报
v1 | v2 | expected | is_different
---+----+----------+-------------
a | a | false | false
| | false | false
| | true | true
| | true | true
a | | true | true
| b | true | true
a | b | true | true
Run Code Online (Sandbox Code Playgroud)
您可以通过比较完整的记录来缩短此过程,这也消除了对OR
where
(my_table.some_uuid, my_table.some_text, my_table.some_int, my_table.some_date)
is distinct from
(excluded.some_uuid, excluded.some_text, excluded.some_int, excluded.some_date)
Run Code Online (Sandbox Code Playgroud)