如何将两个表与UNIQUE索引中可能的NULL值合并?

时间:2019-03-01 20:52:11

标签: postgresql null upsert postgresql-10 unique-index

如何将(合并和删除孤立行)合并到@Controller @RequestMapping(path="/demo") public class HomeController { @Autowired private JobListRepository jobListRepository; @GetMapping(path="/add") public @ResponseBody String addNewJobs () { JobList jobList = new JobList(); jobList.setLimit(50); JobListRepository.save(jobList); return "Saved"; } @GetMapping(path="/all") public @ResponseBody Iterable<JobList> getAllJobs() { return JobListRepository.findAll(); } }

tableA

tableA

+---------+--------+----------+-------+ | company | option | category | rates | +---------+--------+----------+-------+ | a | f | null | 2.5 | +---------+--------+----------+-------+ | a | f | d | 2 | * +---------+--------+----------+-------+ | a | g | e | 3 | ** +---------+--------+----------+-------+ | c | g | e | 4 | +---------+--------+----------+-------+ | d | f | d | 1 | +---------+--------+----------+-------+ 表示孤立行*。
*表示要更改的值( 3 -> 4 )。

仅触摸**中现有的公司(在示例中为tableBa,不理会c)。

d

tableB

两个表中+---------+--------+----------+-------+ | company | option | category | rates | +---------+--------+----------+-------+ | a | f | null | 2.5 | +---------+--------+----------+-------+ | a | g | e | 4 | +---------+--------+----------+-------+ | c | g | e | 4 | +---------+--------+----------+-------+ 上都有一个唯一索引。

所需的结果(company, option, category)

tableA

+---------+--------+----------+-------+ | company | option | category | rates | +---------+--------+----------+-------+ | a | f | null | 2.5 | +---------+--------+----------+-------+ | a | g | e | 4 | <- +---------+--------+----------+-------+ | c | g | e | 4 | +---------+--------+----------+-------+ | d | f | d | 1 | +---------+--------+----------+-------+ 的第二行(a,f,d,2)被删除,rates 3 更改为 4

这里是一个小提琴:https://rextester.com/QUVC30763

我正在考虑首先使用以下方法删除孤立行:

(a,g,e)

然后使用以下方法进行修饰:

DELETE from tableA
 USING tableB
 WHERE 
   -- ignore rows with IDs that don't exist in tableB
   tableA.company = tableB.company
   -- ignore rows that have an exact all-column match in tableB
   AND NOT EXISTS 
      (select * from tableB 
      where tableB.company is not distinct from tableA.company 
      AND tableB.option is not distinct from tableA.option 
      AND tableB.category is not distinct from tableA.category );

但是upsert函数的问题在于它无法处理可为空的字段。我必须将 INSERT INTO tableA (company, option, category, rates) SELECT company, option, category, rates FROM tableB ON CONFLICT (company, option, category) DO update set rates= EXCLUDED.rates WHERE tableA.rates IS DISTINCT FROM EXCLUDED.rates; 设置为-1的位置,否则该函数将无法知道是否存在重复项。我觉得将null设置为-1会在将来创建许多变通办法,所以我想避免这种情况。

注意:我发现null可能是解决方法:

但是我没有看到适合我的情况的查询。而且我不确定是否可以使用可空字段。因此,问题是
有没有一种干净的方法可以合并可为空的字段?

1 个答案:

答案 0 :(得分:1)

我认为您在正确的道路上。但是NULLUNIQUE的设计存在问题:

optioncategory可以是NULL。在这些情况下,NULL被认为是相等的。您当前的唯一索引不会 认为NULL的值相等,因此不会执行您的要求。这甚至在您开始合并之前就产生了歧义。 NULL的值对您尝试实现的内容不利。解决此问题将产生更多的工作和更多的故障点。考虑使用特殊值代替NULL,一切都准备就绪。您正在考虑-1。对于您的实际数据类型和属性的性质而言,任何自然有意义的事情。

也就是说, DELETE 还有一个细微的隐藏问题:它将尝试删除孤立行,其次数与{{ 1}}。一切都没有中断,因为过多的尝试无济于事,但是它不必要地昂贵。两次使用company

tableB

如果您坚持使用EXISTS值,则将UPSERT分为DELETE FROM tableA a WHERE EXISTS ( SELECT FROM tableB b WHERE a.company = b.company ) AND NOT EXISTS ( SELECT FROM tableB b WHERE (a.company, a.option, a.category) IS NOT DISTINCT FROM (b.company, b.option, b.category) ); ,然后是NULL。如果您没有并发写入表,则更简单,更便宜。 UPDATE可以在不指定冲突目标的情况下工作,因此您可以使用多个部分索引来实现您的需求并使它生效。 The manual:

  

对于INSERT ... ON CONFLICT DO NOTHING,可以选择指定一个    ON CONFLICT DO NOTHING ;如果省略,则与所有可用约束冲突   (和唯一索引)得到处理。对于ON CONFLICT DO NOTHING,a   必须提供 conflict_target

但是,如果您使用 working ON CONFLICT DO UPDATE索引或约束来修复架构,那么您已经拥有的UPSERT会很好地发挥作用。

并确保没有对表的并发写入,否则您可能会面临竞争条件和/或死锁,除非您执行更多操作...