如何“简洁”地检测 PostgreSQL 的 upsert(冲突时)where 子句中的更改值,同时支持 null 更改?

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 fromgsiems 提到的 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)

  • 我们现在已经对此进行了测试,除了使我们的代码更加干净之外,我们注意到性能提高了 50%。 (2认同)