Как объединить две таблицы с возможными значениями NULL в индексе UNIQUE? - PullRequest
0 голосов
/ 01 марта 2019

Как объединить (удалить и удалить потерянные строки) в 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 ).

Прикоснитесь только к компаниям, существующим в tableB (в данном примере a & 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 для (a,g,e).

Вот скрипка: https://rextester.com/QUVC30763

Я думаю сначала удалить строку-сироту следующим образом:

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 );

Затемсогласен с этим:

 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;

Но проблема с функцией upsert заключается в том, что она не может обрабатывать пустые поля.Я должен установить -1 вместо null, иначе функция не сможет узнать, есть ли дубликаты или нет.Я чувствую, что установка -1 вместо null создаст много обходных путей в будущем, поэтому я хотел бы избежать этого, если смогу.

Примечание: Я обнаружил, чтоINSERT ... ON CONFLICT ... DO UPDATE - это, вероятно, правильный путь:

Но я не видел запроса, подходящего для моегодело.И я не уверен, возможно ли это с пустыми полями.Отсюда вопрос:
Есть ли чистый способ слияния с пустыми полями?

1 Ответ

0 голосов
/ 02 марта 2019

Я думаю, что вы на правильном пути.Но есть проблема дизайна с NULL против UNIQUE:

Столбцы option и category могут быть NULLNULL считается равным в этих случаях.Ваши текущие уникальные индексы не считают значения NULL равными, следовательно, не обеспечивают выполнение ваших требований.Это создает неоднозначности еще до того, как вы начнете сливаться.Значение NULL не годится для того, что вы пытаетесь реализовать.Обход этого создаст намного больше работы и дополнительные точки отказа.Попробуйте использовать специальное значение вместо NULL, и все станет на свои места.Вы рассматривали -1.Все, что естественно имеет смысл для вашего фактического типа данных и природы атрибута.

Тем не менее, DELETE имеет дополнительную, слегка скрытую проблему: он попытается удалить сиротустрок столько раз, сколько совпадений на company в tableB.Ничто не ломается, поскольку лишние попытки ничего не делают, но это без необходимости дорого .Вместо этого используйте дважды EXISTS:

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, разделите UPSERT на UPDATE, за которым следует INSERT ... ON CONFLICT DO NOTHING.Проще и дешевле, если у вас нет одновременных записей в таблицу.ON CONFLICT DO NOTHING работает без указания цели конфликта, поэтому вы можете реализовать свои требования с несколькими частичными индексами и заставить это работать. Руководство:

Для ON CONFLICT DO NOTHING необязательно указывать conflict_target;если опущено, обрабатываются конфликты со всеми используемыми ограничениями (и уникальными индексами).Для ON CONFLICT DO UPDATE необходимо указать conflict_target.

Но если вы исправите свою схему с работающим UNIQUE индексом или ограничением,UPSERT, который у вас уже есть, отлично работает.

И убедитесь, что нет одновременных записей в таблицу, или вы можете столкнуться с условиями гонки и / или тупиками, если не сделаете больше ...

...